--- jsr166/src/test/tck/Collection8Test.java 2016/12/07 22:03:41 1.38 +++ jsr166/src/test/tck/Collection8Test.java 2018/03/24 14:46:18 1.48 @@ -12,12 +12,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Queue; +import java.util.Set; import java.util.Spliterator; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; @@ -58,9 +60,10 @@ public class Collection8Test extends JSR Object bomb() { return new Object() { - public boolean equals(Object x) { throw new AssertionError(); } - public int hashCode() { throw new AssertionError(); } - }; + @Override public boolean equals(Object x) { throw new AssertionError(); } + @Override public int hashCode() { throw new AssertionError(); } + @Override public String toString() { throw new AssertionError(); } + }; } /** Checks properties of empty collections. */ @@ -91,6 +94,19 @@ public class Collection8Test extends JSR assertTrue(c.isEmpty()); assertEquals(0, c.size()); assertEquals("[]", c.toString()); + if (c instanceof List) { + List x = (List) c; + assertEquals(1, x.hashCode()); + assertEquals(x, Collections.emptyList()); + assertEquals(Collections.emptyList(), x); + assertEquals(-1, x.indexOf(impl.makeElement(86))); + assertEquals(-1, x.lastIndexOf(impl.makeElement(99))); + } + else if (c instanceof Set) { + assertEquals(0, c.hashCode()); + assertEquals(c, Collections.emptySet()); + assertEquals(Collections.emptySet(), c); + } { Object[] a = c.toArray(); assertEquals(0, a.length); @@ -139,12 +155,12 @@ public class Collection8Test extends JSR } if (c instanceof BlockingQueue) { BlockingQueue q = (BlockingQueue) c; - assertNull(q.poll(0L, MILLISECONDS)); + assertNull(q.poll(randomExpiredTimeout(), randomTimeUnit())); } if (c instanceof BlockingDeque) { BlockingDeque q = (BlockingDeque) c; - assertNull(q.pollFirst(0L, MILLISECONDS)); - assertNull(q.pollLast(0L, MILLISECONDS)); + assertNull(q.pollFirst(randomExpiredTimeout(), randomTimeUnit())); + assertNull(q.pollLast(randomExpiredTimeout(), randomTimeUnit())); } } @@ -251,6 +267,16 @@ public class Collection8Test extends JSR () -> d.pop(), () -> d.descendingIterator().next()); } + if (c instanceof List) { + List x = (List) c; + assertThrows( + NoSuchElementException.class, + () -> x.iterator().next(), + () -> x.listIterator().next(), + () -> x.listIterator(0).next(), + () -> x.listIterator().previous(), + () -> x.listIterator(0).previous()); + } } public void testRemoveIf() { @@ -342,9 +368,112 @@ public class Collection8Test extends JSR } /** + * All elements removed in the middle of CONCURRENT traversal. + */ + public void testElementRemovalDuringTraversal() { + Collection c = impl.emptyCollection(); + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + int n = rnd.nextInt(6); + ArrayList copy = new ArrayList(); + for (int i = 0; i < n; i++) { + Object x = impl.makeElement(i); + copy.add(x); + c.add(x); + } + ArrayList iterated = new ArrayList(); + ArrayList spliterated = new ArrayList(); + Spliterator s = c.spliterator(); + Iterator it = c.iterator(); + for (int i = rnd.nextInt(n + 1); --i >= 0; ) { + assertTrue(s.tryAdvance(spliterated::add)); + if (rnd.nextBoolean()) assertTrue(it.hasNext()); + iterated.add(it.next()); + } + Consumer alwaysThrows = e -> { throw new AssertionError(); }; + if (s.hasCharacteristics(Spliterator.CONCURRENT)) { + c.clear(); // TODO: many more removal methods + if (testImplementationDetails + && !(c instanceof java.util.concurrent.ArrayBlockingQueue)) { + if (rnd.nextBoolean()) + assertFalse(s.tryAdvance(alwaysThrows)); + else + s.forEachRemaining(alwaysThrows); + } + if (it.hasNext()) iterated.add(it.next()); + if (rnd.nextBoolean()) assertIteratorExhausted(it); + } + assertTrue(copy.containsAll(iterated)); + assertTrue(copy.containsAll(spliterated)); + } + + /** + * Some elements randomly disappear in the middle of traversal. + */ + public void testRandomElementRemovalDuringTraversal() { + Collection c = impl.emptyCollection(); + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + int n = rnd.nextInt(6); + ArrayList copy = new ArrayList(); + for (int i = 0; i < n; i++) { + Object x = impl.makeElement(i); + copy.add(x); + c.add(x); + } + ArrayList iterated = new ArrayList(); + ArrayList spliterated = new ArrayList(); + ArrayList removed = new ArrayList(); + Spliterator s = c.spliterator(); + Iterator it = c.iterator(); + if (! (s.hasCharacteristics(Spliterator.CONCURRENT) || + s.hasCharacteristics(Spliterator.IMMUTABLE))) + return; + for (int i = rnd.nextInt(n + 1); --i >= 0; ) { + assertTrue(s.tryAdvance(e -> {})); + if (rnd.nextBoolean()) assertTrue(it.hasNext()); + it.next(); + } + Consumer alwaysThrows = e -> { throw new AssertionError(); }; + // TODO: many more removal methods + if (rnd.nextBoolean()) { + for (Iterator z = c.iterator(); z.hasNext(); ) { + Object e = z.next(); + if (rnd.nextBoolean()) { + try { + z.remove(); + } catch (UnsupportedOperationException ok) { return; } + removed.add(e); + } + } + } else { + Predicate randomlyRemove = e -> { + if (rnd.nextBoolean()) { removed.add(e); return true; } + else return false; + }; + c.removeIf(randomlyRemove); + } + s.forEachRemaining(spliterated::add); + while (it.hasNext()) + iterated.add(it.next()); + assertTrue(copy.containsAll(iterated)); + assertTrue(copy.containsAll(spliterated)); + assertTrue(copy.containsAll(removed)); + if (s.hasCharacteristics(Spliterator.CONCURRENT)) { + ArrayList iteratedAndRemoved = new ArrayList(iterated); + ArrayList spliteratedAndRemoved = new ArrayList(spliterated); + iteratedAndRemoved.retainAll(removed); + spliteratedAndRemoved.retainAll(removed); + assertTrue(iteratedAndRemoved.size() <= 1); + assertTrue(spliteratedAndRemoved.size() <= 1); + if (testImplementationDetails + && !(c instanceof java.util.concurrent.ArrayBlockingQueue)) + assertTrue(spliteratedAndRemoved.isEmpty()); + } + } + + /** * Various ways of traversing a collection yield same elements */ - public void testIteratorEquivalence() { + public void testTraversalEquivalence() { Collection c = impl.emptyCollection(); ThreadLocalRandom rnd = ThreadLocalRandom.current(); int n = rnd.nextInt(6); @@ -412,6 +541,43 @@ public class Collection8Test extends JSR } /** + * Iterator.forEachRemaining has same behavior as Iterator's + * default implementation. + */ + public void testForEachRemainingConsistentWithDefaultImplementation() { + Collection c = impl.emptyCollection(); + if (!testImplementationDetails + || c.getClass() == java.util.LinkedList.class) + return; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + int n = 1 + rnd.nextInt(3); + for (int i = 0; i < n; i++) c.add(impl.makeElement(i)); + ArrayList iterated = new ArrayList(); + ArrayList iteratedForEachRemaining = new ArrayList(); + Iterator it1 = c.iterator(); + Iterator it2 = c.iterator(); + assertTrue(it1.hasNext()); + assertTrue(it2.hasNext()); + c.clear(); + Object r1, r2; + try { + while (it1.hasNext()) iterated.add(it1.next()); + r1 = iterated; + } catch (ConcurrentModificationException ex) { + r1 = ConcurrentModificationException.class; + assertFalse(impl.isConcurrent()); + } + try { + it2.forEachRemaining(iteratedForEachRemaining::add); + r2 = iteratedForEachRemaining; + } catch (ConcurrentModificationException ex) { + r2 = ConcurrentModificationException.class; + assertFalse(impl.isConcurrent()); + } + assertEquals(r1, r2); + } + + /** * Calling Iterator#remove() after Iterator#forEachRemaining * should (maybe) remove last element */ @@ -586,6 +752,31 @@ public class Collection8Test extends JSR } /** + * Concurrent Spliterators, once exhausted, stay exhausted. + */ + public void testStickySpliteratorExhaustion() throws Throwable { + if (!impl.isConcurrent()) return; + if (!testImplementationDetails) return; + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final Consumer alwaysThrows = e -> { throw new AssertionError(); }; + final Collection c = impl.emptyCollection(); + final Spliterator s = c.spliterator(); + if (rnd.nextBoolean()) { + assertFalse(s.tryAdvance(alwaysThrows)); + } else { + s.forEachRemaining(alwaysThrows); + } + final Object one = impl.makeElement(1); + // Spliterator should not notice added element + c.add(one); + if (rnd.nextBoolean()) { + assertFalse(s.tryAdvance(alwaysThrows)); + } else { + s.forEachRemaining(alwaysThrows); + } + } + + /** * Motley crew of threads concurrently randomly hammer the collection. */ public void testDetectRaces() throws Throwable { @@ -695,6 +886,22 @@ public class Collection8Test extends JSR } } + /** + * Spliterator.getComparator throws IllegalStateException iff the + * spliterator does not report SORTED. + */ + public void testGetComparator_IllegalStateException() { + Collection c = impl.emptyCollection(); + Spliterator s = c.spliterator(); + boolean reportsSorted = s.hasCharacteristics(Spliterator.SORTED); + try { + s.getComparator(); + assertTrue(reportsSorted); + } catch (IllegalStateException ex) { + assertFalse(reportsSorted); + } + } + // public void testCollection8DebugFail() { // fail(impl.klazz().getSimpleName()); // }