--- jsr166/src/test/tck/Collection8Test.java 2016/11/06 18:51:53 1.17 +++ jsr166/src/test/tck/Collection8Test.java 2016/11/28 17:59:47 1.36 @@ -9,6 +9,7 @@ import static java.util.concurrent.TimeU import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; @@ -20,16 +21,19 @@ 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; import java.util.concurrent.Future; +import java.util.concurrent.Phaser; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; import junit.framework.Test; @@ -60,12 +64,23 @@ public class Collection8Test extends JSR } /** Checks properties of empty collections. */ - public void testEmptyMeansEmpty() throws InterruptedException { + public void testEmptyMeansEmpty() throws Throwable { Collection c = impl.emptyCollection(); emptyMeansEmpty(c); - if (c instanceof java.io.Serializable) - emptyMeansEmpty(serialClone(c)); + if (c instanceof java.io.Serializable) { + try { + emptyMeansEmpty(serialClonePossiblyFailing(c)); + } catch (java.io.NotSerializableException ex) { + // excusable when we have a serializable wrapper around + // a non-serializable collection, as can happen with: + // Vector.subList() => wrapped AbstractList$RandomAccessSubList + if (testImplementationDetails + && (! c.getClass().getName().matches( + "java.util.Collections.*"))) + throw ex; + } + } Collection clone = cloneableClone(c); if (clone != null) @@ -97,7 +112,7 @@ public class Collection8Test extends JSR assertSame(3, a[2]); } assertIteratorExhausted(c.iterator()); - Consumer alwaysThrows = (e) -> { throw new AssertionError(); }; + Consumer alwaysThrows = e -> { throw new AssertionError(); }; c.forEach(alwaysThrows); c.iterator().forEachRemaining(alwaysThrows); c.spliterator().forEachRemaining(alwaysThrows); @@ -240,14 +255,24 @@ public class Collection8Test extends JSR public void testRemoveIf() { Collection c = impl.emptyCollection(); + boolean ordered = + c.spliterator().hasCharacteristics(Spliterator.ORDERED); ThreadLocalRandom rnd = ThreadLocalRandom.current(); int n = rnd.nextInt(6); for (int i = 0; i < n; i++) c.add(impl.makeElement(i)); AtomicReference threwAt = new AtomicReference(null); - ArrayList survivors = new ArrayList(c); + List orig = rnd.nextBoolean() + ? new ArrayList(c) + : Arrays.asList(c.toArray()); + + // Merely creating an iterator can change ArrayBlockingQueue behavior + Iterator it = rnd.nextBoolean() ? c.iterator() : null; + + ArrayList survivors = new ArrayList(); ArrayList accepts = new ArrayList(); ArrayList rejects = new ArrayList(); - Predicate randomPredicate = (e) -> { + + Predicate randomPredicate = e -> { assertNull(threwAt.get()); switch (rnd.nextInt(3)) { case 0: accepts.add(e); return true; @@ -259,27 +284,59 @@ public class Collection8Test extends JSR try { try { boolean modified = c.removeIf(randomPredicate); - if (!modified) { - assertNull(threwAt.get()); - assertEquals(n, rejects.size()); - assertEquals(0, accepts.size()); + assertNull(threwAt.get()); + assertEquals(modified, accepts.size() > 0); + assertEquals(modified, rejects.size() != n); + assertEquals(accepts.size() + rejects.size(), n); + if (ordered) { + assertEquals(rejects, + Arrays.asList(c.toArray())); + } else { + assertEquals(new HashSet(rejects), + new HashSet(Arrays.asList(c.toArray()))); } - } catch (ArithmeticException ok) {} - survivors.removeAll(accepts); - assertEquals(n - accepts.size(), c.size()); + } catch (ArithmeticException ok) { + assertNotNull(threwAt.get()); + assertTrue(c.contains(threwAt.get())); + } + if (it != null && impl.isConcurrent()) + // check for weakly consistent iterator + while (it.hasNext()) assertTrue(orig.contains(it.next())); + switch (rnd.nextInt(4)) { + case 0: survivors.addAll(c); break; + case 1: survivors.addAll(Arrays.asList(c.toArray())); break; + case 2: c.forEach(survivors::add); break; + case 3: for (Object e : c) survivors.add(e); break; + } + assertTrue(orig.containsAll(accepts)); + assertTrue(orig.containsAll(rejects)); + assertTrue(orig.containsAll(survivors)); + assertTrue(orig.containsAll(c)); + assertTrue(c.containsAll(rejects)); assertTrue(c.containsAll(survivors)); assertTrue(survivors.containsAll(rejects)); - for (Object x : accepts) assertFalse(c.contains(x)); - if (threwAt.get() == null) - assertEquals(accepts.size() + rejects.size(), n); + if (threwAt.get() == null) { + assertEquals(n - accepts.size(), c.size()); + for (Object x : accepts) assertFalse(c.contains(x)); + } else { + // Two acceptable behaviors: entire removeIf call is one + // transaction, or each element processed is one transaction. + assertTrue(n == c.size() || n == c.size() + accepts.size()); + int k = 0; + for (Object x : accepts) if (c.contains(x)) k++; + assertTrue(k == accepts.size() || k == 0); + } } catch (Throwable ex) { System.err.println(impl.klazz()); - System.err.printf("c=%s%n", c); + // c is at risk of corruption if we got here, so be lenient + try { System.err.printf("c=%s%n", c); } + catch (Throwable t) { t.printStackTrace(); } System.err.printf("n=%d%n", n); + System.err.printf("orig=%s%n", orig); System.err.printf("accepts=%s%n", accepts); System.err.printf("rejects=%s%n", rejects); System.err.printf("survivors=%s%n", survivors); - System.err.printf("threw=%s%n", threwAt.get()); + System.err.printf("threwAt=%s%n", threwAt.get()); throw ex; } } @@ -296,32 +353,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) { @@ -341,12 +413,12 @@ public class Collection8Test extends JSR /** * Calling Iterator#remove() after Iterator#forEachRemaining - * should remove last element + * should (maybe) remove last element */ public void testRemoveAfterForEachRemaining() { Collection c = impl.emptyCollection(); ThreadLocalRandom rnd = ThreadLocalRandom.current(); - { + testCollection: { int n = 3 + rnd.nextInt(2); for (int i = 0; i < n; i++) c.add(impl.makeElement(i)); Iterator it = c.iterator(); @@ -354,12 +426,21 @@ public class Collection8Test extends JSR assertEquals(impl.makeElement(0), it.next()); assertTrue(it.hasNext()); assertEquals(impl.makeElement(1), it.next()); - it.forEachRemaining((e) -> {}); - it.remove(); - assertEquals(n - 1, c.size()); - for (int i = 0; i < n - 1; i++) - assertTrue(c.contains(impl.makeElement(i))); - assertFalse(c.contains(impl.makeElement(n - 1))); + it.forEachRemaining(e -> assertTrue(c.contains(e))); + if (testImplementationDetails) { + if (c instanceof java.util.concurrent.ArrayBlockingQueue) { + assertIteratorExhausted(it); + } else { + try { it.remove(); } + catch (UnsupportedOperationException ok) { + break testCollection; + } + assertEquals(n - 1, c.size()); + for (int i = 0; i < n - 1; i++) + assertTrue(c.contains(impl.makeElement(i))); + assertFalse(c.contains(impl.makeElement(n - 1))); + } + } } if (c instanceof Deque) { Deque d = (Deque) impl.emptyCollection(); @@ -370,12 +451,14 @@ public class Collection8Test extends JSR assertEquals(impl.makeElement(n - 1), it.next()); assertTrue(it.hasNext()); assertEquals(impl.makeElement(n - 2), it.next()); - it.forEachRemaining((e) -> {}); - it.remove(); - assertEquals(n - 1, d.size()); - for (int i = 1; i < n; i++) - assertTrue(d.contains(impl.makeElement(i))); - assertFalse(d.contains(impl.makeElement(0))); + it.forEachRemaining(e -> assertTrue(c.contains(e))); + if (testImplementationDetails) { + it.remove(); + assertEquals(n - 1, d.size()); + for (int i = 1; i < n; i++) + assertTrue(d.contains(impl.makeElement(i))); + assertFalse(d.contains(impl.makeElement(0))); + } } } @@ -388,7 +471,7 @@ public class Collection8Test extends JSR final Object x = impl.makeElement(1); final Object y = impl.makeElement(2); final ArrayList found = new ArrayList(); - Consumer spy = (o) -> { found.add(o); }; + Consumer spy = o -> found.add(o); c.stream().forEach(spy); assertTrue(found.isEmpty()); @@ -422,7 +505,7 @@ public class Collection8Test extends JSR Runnable checkElt = () -> { threadsStarted.countDown(); while (!done.get()) - c.stream().forEach((x) -> { assertSame(x, elt); }); }; + c.stream().forEach(x -> assertSame(x, elt)); }; Runnable addRemove = () -> { threadsStarted.countDown(); while (!done.get()) { @@ -446,7 +529,7 @@ public class Collection8Test extends JSR final Object x = impl.makeElement(1); final Object y = impl.makeElement(2); final ArrayList found = new ArrayList(); - Consumer spy = (o) -> { found.add(o); }; + Consumer spy = o -> found.add(o); c.forEach(spy); assertTrue(found.isEmpty()); @@ -467,32 +550,111 @@ public class Collection8Test extends JSR assertTrue(found.isEmpty()); } - public void testForEachConcurrentStressTest() throws Throwable { + /** + * Motley crew of threads concurrently randomly hammer the collection. + */ + public void testDetectRaces() throws Throwable { 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 elt = impl.makeElement(1); - final Future f1, f2; + 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); + 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 Runnable[] frobbers = { + () -> c.forEach(checkSanity), + () -> c.stream().forEach(checkSanity), + () -> c.parallelStream().forEach(checkSanity), + () -> c.spliterator().trySplit(), + () -> { + Spliterator s = c.spliterator(); + s.tryAdvance(checkSanity); + s.trySplit(); + }, + () -> { + Spliterator s = c.spliterator(); + do {} while (s.tryAdvance(checkSanity)); + }, + () -> { for (Object x : c) checkSanity.accept(x); }, + () -> checkArraySanity.accept(c.toArray()), + () -> checkArraySanity.accept(c.toArray(emptyArray)), + () -> { + 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)); + }, + }; + final List tasks = + Arrays.stream(frobbers) + .filter(task -> rnd.nextBoolean()) // random subset + .map(task -> (Runnable) () -> { + threadsStarted.arriveAndAwaitAdvance(); + while (!done.get()) + task.run(); + }) + .collect(Collectors.toList()); final ExecutorService pool = Executors.newCachedThreadPool(); try (PoolCleaner cleaner = cleaner(pool, done)) { - final CountDownLatch threadsStarted = new CountDownLatch(2); - Runnable checkElt = () -> { - threadsStarted.countDown(); - while (!done.get()) - c.forEach((x) -> { assertSame(x, elt); }); }; - Runnable addRemove = () -> { - threadsStarted.countDown(); - while (!done.get()) { - assertTrue(c.add(elt)); - assertTrue(c.remove(elt)); - }}; - f1 = pool.submit(checkElt); - f2 = pool.submit(addRemove); + threadsStarted.bulkRegister(tasks.size()); + futures = tasks.stream() + .map(pool::submit) + .collect(Collectors.toList()); + threadsStarted.arriveAndDeregister(); Thread.sleep(testDurationMillis); } - assertNull(f1.get(0L, MILLISECONDS)); - assertNull(f2.get(0L, MILLISECONDS)); + for (Future future : futures) + 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() {