--- jsr166/src/test/tck/Collection8Test.java 2016/11/15 22:59:09 1.26 +++ jsr166/src/test/tck/Collection8Test.java 2016/12/11 19:59:51 1.39 @@ -21,6 +21,7 @@ import java.util.Queue; import java.util.Spliterator; import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; @@ -304,7 +305,7 @@ public class Collection8Test extends JSR switch (rnd.nextInt(4)) { case 0: survivors.addAll(c); break; case 1: survivors.addAll(Arrays.asList(c.toArray())); break; - case 2: c.forEach(e -> survivors.add(e)); break; + case 2: c.forEach(survivors::add); break; case 3: for (Object e : c) survivors.add(e); break; } assertTrue(orig.containsAll(accepts)); @@ -341,6 +342,109 @@ 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() { @@ -352,32 +456,47 @@ public class Collection8Test extends JSR ArrayList iteratedForEachRemaining = new ArrayList(); ArrayList tryAdvanced = new ArrayList(); ArrayList spliterated = new ArrayList(); + ArrayList splitonced = new ArrayList(); ArrayList forEached = new ArrayList(); + ArrayList streamForEached = new ArrayList(); + ConcurrentLinkedQueue parallelStreamForEached = new ConcurrentLinkedQueue(); ArrayList removeIfed = new ArrayList(); for (Object x : c) iterated.add(x); - c.iterator().forEachRemaining(e -> iteratedForEachRemaining.add(e)); + c.iterator().forEachRemaining(iteratedForEachRemaining::add); for (Spliterator s = c.spliterator(); - s.tryAdvance(e -> tryAdvanced.add(e)); ) {} - c.spliterator().forEachRemaining(e -> spliterated.add(e)); - c.forEach(e -> forEached.add(e)); + s.tryAdvance(tryAdvanced::add); ) {} + c.spliterator().forEachRemaining(spliterated::add); + { // trySplit returns "strict prefix" + Spliterator s1 = c.spliterator(), s2 = s1.trySplit(); + if (s2 != null) s2.forEachRemaining(splitonced::add); + s1.forEachRemaining(splitonced::add); + } + c.forEach(forEached::add); + c.stream().forEach(streamForEached::add); + c.parallelStream().forEach(parallelStreamForEached::add); c.removeIf(e -> { removeIfed.add(e); return false; }); boolean ordered = c.spliterator().hasCharacteristics(Spliterator.ORDERED); if (c instanceof List || c instanceof Deque) assertTrue(ordered); + HashSet cset = new HashSet(c); + assertEquals(cset, new HashSet(parallelStreamForEached)); if (ordered) { assertEquals(iterated, iteratedForEachRemaining); assertEquals(iterated, tryAdvanced); assertEquals(iterated, spliterated); + assertEquals(iterated, splitonced); assertEquals(iterated, forEached); + assertEquals(iterated, streamForEached); assertEquals(iterated, removeIfed); } else { - HashSet cset = new HashSet(c); assertEquals(cset, new HashSet(iterated)); assertEquals(cset, new HashSet(iteratedForEachRemaining)); assertEquals(cset, new HashSet(tryAdvanced)); assertEquals(cset, new HashSet(spliterated)); + assertEquals(cset, new HashSet(splitonced)); assertEquals(cset, new HashSet(forEached)); + assertEquals(cset, new HashSet(streamForEached)); assertEquals(cset, new HashSet(removeIfed)); } if (c instanceof Deque) { @@ -534,6 +653,41 @@ public class Collection8Test extends JSR assertTrue(found.isEmpty()); } + /** TODO: promote to a common utility */ + static T chooseOne(T ... ts) { + return ts[ThreadLocalRandom.current().nextInt(ts.length)]; + } + + /** TODO: more random adders and removers */ + static Runnable adderRemover(Collection c, E e) { + return chooseOne( + () -> { + assertTrue(c.add(e)); + assertTrue(c.contains(e)); + assertTrue(c.remove(e)); + assertFalse(c.contains(e)); + }, + () -> { + assertTrue(c.add(e)); + assertTrue(c.contains(e)); + assertTrue(c.removeIf(x -> x == e)); + assertFalse(c.contains(e)); + }, + () -> { + assertTrue(c.add(e)); + assertTrue(c.contains(e)); + for (Iterator it = c.iterator();; ) + if (it.next() == e) { + try { it.remove(); } + catch (UnsupportedOperationException ok) { + c.remove(e); + } + break; + } + assertFalse(c.contains(e)); + }); + } + /** * Motley crew of threads concurrently randomly hammer the collection. */ @@ -541,41 +695,55 @@ public class Collection8Test extends JSR if (!impl.isConcurrent()) return; final ThreadLocalRandom rnd = ThreadLocalRandom.current(); final Collection c = impl.emptyCollection(); - final long testDurationMillis = timeoutMillis(); + final long testDurationMillis + = expensiveTests ? LONG_DELAY_MS : timeoutMillis(); final AtomicBoolean done = new AtomicBoolean(false); final Object one = impl.makeElement(1); final Object two = impl.makeElement(2); + final Consumer checkSanity = x -> assertTrue(x == one || x == two); + final Consumer checkArraySanity = array -> { + // assertTrue(array.length <= 2); // duplicates are permitted + for (Object x : array) assertTrue(x == one || x == two); + }; + final Object[] emptyArray = + (Object[]) java.lang.reflect.Array.newInstance(one.getClass(), 0); final List> futures; final Phaser threadsStarted = new Phaser(1); // register this thread - final List tasks = List.of( - () -> c.forEach(x -> assertTrue(x == one || x == two)), - () -> c.stream().forEach(x -> assertTrue(x == one || x == two)), + final Runnable[] frobbers = { + () -> c.forEach(checkSanity), + () -> c.stream().forEach(checkSanity), + () -> c.parallelStream().forEach(checkSanity), () -> c.spliterator().trySplit(), () -> { Spliterator s = c.spliterator(); - s.tryAdvance(x -> assertTrue(x == one || x == two)); + s.tryAdvance(checkSanity); s.trySplit(); }, () -> { Spliterator s = c.spliterator(); - do {} while (s.tryAdvance(x -> assertTrue(x == one || x == two))); + do {} while (s.tryAdvance(checkSanity)); }, + () -> { for (Object x : c) checkSanity.accept(x); }, + () -> checkArraySanity.accept(c.toArray()), + () -> checkArraySanity.accept(c.toArray(emptyArray)), () -> { - for (Object x : c) assertTrue(x == one || x == two); - }, - () -> { - assertTrue(c.add(one)); - assertTrue(c.contains(one)); - assertTrue(c.remove(one)); - assertFalse(c.contains(one)); - }, - () -> { - assertTrue(c.add(two)); - assertTrue(c.contains(two)); - assertTrue(c.remove(two)); - assertFalse(c.contains(two)); - }) - .stream() + Object[] a = new Object[5]; + Object three = impl.makeElement(3); + Arrays.fill(a, 0, a.length, three); + Object[] x = c.toArray(a); + if (x == a) + for (int i = 0; i < a.length && a[i] != null; i++) + checkSanity.accept(a[i]); + // A careful reading of the spec does not support: + // for (i++; i < a.length; i++) assertSame(three, a[i]); + else + checkArraySanity.accept(x); + }, + adderRemover(c, one), + adderRemover(c, two), + }; + final List tasks = + Arrays.stream(frobbers) .filter(task -> rnd.nextBoolean()) // random subset .map(task -> (Runnable) () -> { threadsStarted.arriveAndAwaitAdvance(); @@ -587,7 +755,7 @@ public class Collection8Test extends JSR try (PoolCleaner cleaner = cleaner(pool, done)) { threadsStarted.bulkRegister(tasks.size()); futures = tasks.stream() - .map(task -> pool.submit(task)) + .map(pool::submit) .collect(Collectors.toList()); threadsStarted.arriveAndDeregister(); Thread.sleep(testDurationMillis); @@ -596,6 +764,40 @@ public class Collection8Test extends JSR assertNull(future.get(0L, MILLISECONDS)); } + /** + * Spliterators are either IMMUTABLE or truly late-binding or, if + * concurrent, use the same "late-binding style" of returning + * elements added between creation and first use. + */ + public void testLateBindingStyle() { + if (!testImplementationDetails) return; + if (impl.klazz() == ArrayList.class) return; // for jdk8 + // Immutable (snapshot) spliterators are exempt + if (impl.emptyCollection().spliterator() + .hasCharacteristics(Spliterator.IMMUTABLE)) + return; + final Object one = impl.makeElement(1); + { + final Collection c = impl.emptyCollection(); + final Spliterator split = c.spliterator(); + c.add(one); + assertTrue(split.tryAdvance(e -> { assertSame(e, one); })); + assertFalse(split.tryAdvance(e -> { throw new AssertionError(); })); + assertTrue(c.contains(one)); + } + { + final AtomicLong count = new AtomicLong(0); + final Collection c = impl.emptyCollection(); + final Spliterator split = c.spliterator(); + c.add(one); + split.forEachRemaining( + e -> { assertSame(e, one); count.getAndIncrement(); }); + assertEquals(1L, count.get()); + assertFalse(split.tryAdvance(e -> { throw new AssertionError(); })); + assertTrue(c.contains(one)); + } + } + // public void testCollection8DebugFail() { // fail(impl.klazz().getSimpleName()); // }