--- jsr166/src/test/tck/Collection8Test.java 2015/06/14 20:58:14 1.1 +++ jsr166/src/test/tck/Collection8Test.java 2016/10/29 22:47:55 1.6 @@ -10,12 +10,23 @@ import static java.util.concurrent.TimeU import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +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.Spliterator; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +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 junit.framework.Test; @@ -38,10 +49,253 @@ public class Collection8Test extends JSR impl); } + /** Checks properties of empty collections. */ + public void testEmptyMeansEmpty() { + Collection c = impl.emptyCollection(); + assertTrue(c.isEmpty()); + assertEquals(0, c.size()); + assertEquals("[]", c.toString()); + { + Object[] a = c.toArray(); + assertEquals(0, a.length); + assertSame(Object[].class, a.getClass()); + } + { + Object[] a = new Object[0]; + assertSame(a, c.toArray(a)); + } + { + Integer[] a = new Integer[0]; + assertSame(a, c.toArray(a)); + } + { + Integer[] a = { 1, 2, 3}; + assertSame(a, c.toArray(a)); + assertNull(a[0]); + assertSame(2, a[1]); + assertSame(3, a[2]); + } + assertIteratorExhausted(c.iterator()); + Consumer alwaysThrows = (e) -> { throw new AssertionError(); }; + c.forEach(alwaysThrows); + c.iterator().forEachRemaining(alwaysThrows); + c.spliterator().forEachRemaining(alwaysThrows); + assertFalse(c.spliterator().tryAdvance(alwaysThrows)); + if (Queue.class.isAssignableFrom(impl.klazz())) { + Queue q = (Queue) c; + assertNull(q.peek()); + assertNull(q.poll()); + } + if (Deque.class.isAssignableFrom(impl.klazz())) { + Deque d = (Deque) c; + assertNull(d.peekFirst()); + assertNull(d.peekLast()); + assertNull(d.pollFirst()); + assertNull(d.pollLast()); + assertIteratorExhausted(d.descendingIterator()); + } + } + + public void testNullPointerExceptions() { + Collection c = impl.emptyCollection(); + assertThrows( + NullPointerException.class, + () -> c.addAll(null), + () -> c.containsAll(null), + () -> c.retainAll(null), + () -> c.removeAll(null), + () -> c.removeIf(null), + () -> c.forEach(null), + () -> c.iterator().forEachRemaining(null), + () -> c.spliterator().forEachRemaining(null), + () -> c.spliterator().tryAdvance(null), + () -> c.toArray(null)); + + if (!impl.permitsNulls()) { + assertThrows( + NullPointerException.class, + () -> c.add(null)); + } + if (!impl.permitsNulls() + && Queue.class.isAssignableFrom(impl.klazz())) { + Queue q = (Queue) c; + assertThrows( + NullPointerException.class, + () -> q.offer(null)); + } + if (!impl.permitsNulls() + && Deque.class.isAssignableFrom(impl.klazz())) { + Deque d = (Deque) c; + assertThrows( + NullPointerException.class, + () -> d.addFirst(null), + () -> d.addLast(null), + () -> d.offerFirst(null), + () -> d.offerLast(null), + () -> d.push(null), + () -> d.descendingIterator().forEachRemaining(null)); + } + } + + public void testNoSuchElementExceptions() { + Collection c = impl.emptyCollection(); + assertThrows( + NoSuchElementException.class, + () -> c.iterator().next()); + + if (Queue.class.isAssignableFrom(impl.klazz())) { + Queue q = (Queue) c; + assertThrows( + NoSuchElementException.class, + () -> q.element(), + () -> q.remove()); + } + if (Deque.class.isAssignableFrom(impl.klazz())) { + Deque d = (Deque) c; + assertThrows( + NoSuchElementException.class, + () -> d.getFirst(), + () -> d.getLast(), + () -> d.removeFirst(), + () -> d.removeLast(), + () -> d.pop(), + () -> d.descendingIterator().next()); + } + } + + public void testRemoveIf() { + Collection c = impl.emptyCollection(); + 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); + ArrayList accepts = new ArrayList(); + ArrayList rejects = new ArrayList(); + Predicate randomPredicate = (e) -> { + assertNull(threwAt.get()); + switch (rnd.nextInt(3)) { + case 0: accepts.add(e); return true; + case 1: rejects.add(e); return false; + case 2: threwAt.set(e); throw new ArithmeticException(); + default: throw new AssertionError(); + } + }; + try { + boolean modified = c.removeIf(randomPredicate); + if (!modified) { + assertNull(threwAt.get()); + assertEquals(n, rejects.size()); + assertEquals(0, accepts.size()); + } + } catch (ArithmeticException ok) {} + survivors.removeAll(accepts); + if (n - accepts.size() != c.size()) { + System.err.println(impl.klazz()); + System.err.println(c); + System.err.println(accepts); + System.err.println(rejects); + System.err.println(survivors); + System.err.println(threwAt.get()); + } + assertEquals(n - accepts.size(), c.size()); + 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); + } + + /** + * Various ways of traversing a collection yield same elements + */ + public void testIteratorEquivalence() { + Collection c = impl.emptyCollection(); + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + int n = rnd.nextInt(6); + for (int i = 0; i < n; i++) c.add(impl.makeElement(i)); + ArrayList iterated = new ArrayList(); + ArrayList iteratedForEachRemaining = new ArrayList(); + ArrayList spliterated = new ArrayList(); + ArrayList foreached = new ArrayList(); + for (Object x : c) iterated.add(x); + c.iterator().forEachRemaining(e -> iteratedForEachRemaining.add(e)); + c.spliterator().forEachRemaining(e -> spliterated.add(e)); + c.forEach(e -> foreached.add(e)); + boolean ordered = + c.spliterator().hasCharacteristics(Spliterator.ORDERED); + if (c instanceof List || c instanceof Deque) + assertTrue(ordered); + if (ordered) { + assertEquals(iterated, iteratedForEachRemaining); + assertEquals(iterated, spliterated); + assertEquals(iterated, foreached); + } else { + HashSet cset = new HashSet(c); + assertEquals(cset, new HashSet(iterated)); + assertEquals(cset, new HashSet(iteratedForEachRemaining)); + assertEquals(cset, new HashSet(spliterated)); + assertEquals(cset, new HashSet(foreached)); + } + if (c instanceof Deque) { + Deque d = (Deque) c; + ArrayList descending = new ArrayList(); + ArrayList descendingForEachRemaining = new ArrayList(); + for (Iterator it = d.descendingIterator(); it.hasNext(); ) + descending.add(it.next()); + d.descendingIterator().forEachRemaining( + e -> descendingForEachRemaining.add(e)); + Collections.reverse(descending); + Collections.reverse(descendingForEachRemaining); + assertEquals(iterated, descending); + assertEquals(iterated, descendingForEachRemaining); + } + } + + /** + * Calling Iterator#remove() after Iterator#forEachRemaining + * should remove last element + */ + public void testRemoveAfterForEachRemaining() { + Collection c = impl.emptyCollection(); + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + { + int n = 3 + rnd.nextInt(2); + for (int i = 0; i < n; i++) c.add(impl.makeElement(i)); + Iterator it = c.iterator(); + assertTrue(it.hasNext()); + 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))); + } + if (c instanceof Deque) { + Deque d = (Deque) impl.emptyCollection(); + int n = 3 + rnd.nextInt(2); + for (int i = 0; i < n; i++) d.add(impl.makeElement(i)); + Iterator it = d.descendingIterator(); + assertTrue(it.hasNext()); + 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))); + } + } + /** * stream().forEach returns elements in the collection */ - public void testForEach() throws Throwable { + public void testStreamForEach() throws Throwable { final Collection c = impl.emptyCollection(); final AtomicLong count = new AtomicLong(0L); final Object x = impl.makeElement(1); @@ -68,30 +322,93 @@ public class Collection8Test extends JSR assertTrue(found.isEmpty()); } + public void testStreamForEachConcurrentStressTest() throws Throwable { + if (!impl.isConcurrent()) return; + final Collection c = impl.emptyCollection(); + final long testDurationMillis = timeoutMillis(); + final AtomicBoolean done = new AtomicBoolean(false); + final Object elt = impl.makeElement(1); + final Future f1, f2; + 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.stream().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); + Thread.sleep(testDurationMillis); + } + assertNull(f1.get(0L, MILLISECONDS)); + assertNull(f2.get(0L, MILLISECONDS)); + } + + /** + * collection.forEach returns elements in the collection + */ + public void testForEach() throws Throwable { + final Collection c = impl.emptyCollection(); + final AtomicLong count = new AtomicLong(0L); + final Object x = impl.makeElement(1); + final Object y = impl.makeElement(2); + final ArrayList found = new ArrayList(); + Consumer spy = (o) -> { found.add(o); }; + c.forEach(spy); + assertTrue(found.isEmpty()); + + assertTrue(c.add(x)); + c.forEach(spy); + assertEquals(Collections.singletonList(x), found); + found.clear(); + + assertTrue(c.add(y)); + c.forEach(spy); + assertEquals(2, found.size()); + assertTrue(found.contains(x)); + assertTrue(found.contains(y)); + found.clear(); + + c.clear(); + c.forEach(spy); + assertTrue(found.isEmpty()); + } + public void testForEachConcurrentStressTest() throws Throwable { if (!impl.isConcurrent()) return; final Collection c = impl.emptyCollection(); - final long testDurationMillis = SHORT_DELAY_MS; + final long testDurationMillis = timeoutMillis(); final AtomicBoolean done = new AtomicBoolean(false); final Object elt = impl.makeElement(1); - ExecutorService pool = Executors.newCachedThreadPool(); - Runnable checkElt = () -> { - while (!done.get()) - c.stream().forEach((x) -> { assertSame(x, elt); }); }; - Runnable addRemove = () -> { - while (!done.get()) { - assertTrue(c.add(elt)); - assertTrue(c.remove(elt)); - }}; - Future f1 = pool.submit(checkElt); - Future f2 = pool.submit(addRemove); - Thread.sleep(testDurationMillis); - done.set(true); - pool.shutdown(); - assertTrue(pool.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); - assertNull(f1.get(LONG_DELAY_MS, MILLISECONDS)); - assertNull(f2.get(LONG_DELAY_MS, MILLISECONDS)); + final Future f1, f2; + 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); + Thread.sleep(testDurationMillis); + } + assertNull(f1.get(0L, MILLISECONDS)); + assertNull(f2.get(0L, MILLISECONDS)); } - // public void testCollection8DebugFail() { fail(); } +// public void testCollection8DebugFail() { +// fail(impl.klazz().getSimpleName()); +// } }