--- jsr166/src/test/tck/MapTest.java 2017/08/23 05:33:00 1.1 +++ jsr166/src/test/tck/MapTest.java 2019/09/29 20:40:48 1.9 @@ -5,13 +5,19 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -import junit.framework.Test; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; + +import junit.framework.Test; /** * Contains tests applicable to all Map implementations. @@ -95,6 +101,7 @@ public class MapTest extends JSR166TestC */ public void testBug8186171() { if (!impl.supportsSetValue()) return; + if (!atLeastJava10()) return; // jdk9 is no longer maintained final ThreadLocalRandom rnd = ThreadLocalRandom.current(); final boolean permitsNullValues = impl.permitsNullValues(); final Object v1 = (permitsNullValues && rnd.nextBoolean()) @@ -102,7 +109,7 @@ public class MapTest extends JSR166TestC final Object v2 = (permitsNullValues && rnd.nextBoolean() && v1 != null) ? null : impl.makeValue(2); - /** If true, always lands in first bucket in hash tables. */ + // If true, always lands in first bucket in hash tables. final boolean poorHash = rnd.nextBoolean(); class Key implements Comparable { final int i; @@ -139,6 +146,131 @@ public class MapTest extends JSR166TestC assertEquals(1, m.size()); } + /** + * "Missing" test found while investigating JDK-8210280. + * ant -Djsr166.tckTestClass=HashMapTest -Djsr166.methodFilter=testBug8210280 -Djsr166.runsPerTest=1000000 tck + */ + public void testBug8210280() { + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final int size1 = rnd.nextInt(32); + final int size2 = rnd.nextInt(128); + + final Map m1 = impl.emptyMap(); + for (int i = 0; i < size1; i++) { + int elt = rnd.nextInt(1024 * i, 1024 * (i + 1)); + assertNull(m1.put(impl.makeKey(elt), impl.makeValue(elt))); + } + + final Map m2 = impl.emptyMap(); + for (int i = 0; i < size2; i++) { + int elt = rnd.nextInt(Integer.MIN_VALUE + 1024 * i, + Integer.MIN_VALUE + 1024 * (i + 1)); + assertNull(m2.put(impl.makeKey(elt), impl.makeValue(-elt))); + } + + final Map m1Copy = impl.emptyMap(); + m1Copy.putAll(m1); + + m1.putAll(m2); + + for (Object elt : m2.keySet()) + assertEquals(m2.get(elt), m1.get(elt)); + for (Object elt : m1Copy.keySet()) + assertSame(m1Copy.get(elt), m1.get(elt)); + assertEquals(size1 + size2, m1.size()); + } + + /** + * 8222930: ConcurrentSkipListMap.clone() shares size variable between original and clone + */ + public void testClone() { + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final int size = rnd.nextInt(4); + final Map map = impl.emptyMap(); + for (int i = 0; i < size; i++) + map.put(impl.makeKey(i), impl.makeValue(i)); + final Map clone = cloneableClone(map); + if (clone == null) return; // not cloneable? + + assertEquals(size, map.size()); + assertEquals(size, clone.size()); + assertEquals(map.isEmpty(), clone.isEmpty()); + + clone.put(impl.makeKey(-1), impl.makeValue(-1)); + assertEquals(size, map.size()); + assertEquals(size + 1, clone.size()); + + clone.clear(); + assertEquals(size, map.size()); + assertEquals(0, clone.size()); + assertTrue(clone.isEmpty()); + } + + /** + * Concurrent access by compute methods behaves as expected + */ + public void testConcurrentAccess() throws Throwable { + final Map map = impl.emptyMap(); + final long testDurationMillis = expensiveTests ? 1000 : 2; + final int nTasks = impl.isConcurrent() + ? ThreadLocalRandom.current().nextInt(1, 10) + : 1; + final AtomicBoolean done = new AtomicBoolean(false); + final boolean remappingFunctionCalledAtMostOnce + = impl.remappingFunctionCalledAtMostOnce(); + final List futures = new ArrayList<>(); + final AtomicLong expectedSum = new AtomicLong(0); + final Action[] tasks = { + // repeatedly increment values using compute() + () -> { + long[] invocations = new long[2]; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + BiFunction incValue = (k, v) -> { + invocations[1]++; + int vi = (v == null) ? 1 : impl.valueToInt(v) + 1; + return impl.makeValue(vi); + }; + while (!done.getAcquire()) { + invocations[0]++; + Object key = impl.makeKey(3 * rnd.nextInt(10)); + map.compute(key, incValue); + } + if (remappingFunctionCalledAtMostOnce) + assertEquals(invocations[0], invocations[1]); + expectedSum.getAndAdd(invocations[0]); + }, + // repeatedly increment values using computeIfPresent() + () -> { + long[] invocations = new long[2]; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + BiFunction incValue = (k, v) -> { + invocations[1]++; + int vi = impl.valueToInt(v) + 1; + return impl.makeValue(vi); + }; + while (!done.getAcquire()) { + Object key = impl.makeKey(3 * rnd.nextInt(10)); + if (map.computeIfPresent(key, incValue) != null) + invocations[0]++; + } + if (remappingFunctionCalledAtMostOnce) + assertEquals(invocations[0], invocations[1]); + expectedSum.getAndAdd(invocations[0]); + }, + }; + for (int i = nTasks; i--> 0; ) { + Action task = chooseRandomly(tasks); + futures.add(CompletableFuture.runAsync(checkedRunnable(task))); + } + Thread.sleep(testDurationMillis); + done.setRelease(true); + for (var future : futures) + checkTimedGet(future, null); + + long sum = map.values().stream().mapToLong(x -> (int) x).sum(); + assertEquals(expectedSum.get(), sum); + } + // public void testFailsIntentionallyForDebugging() { // fail(impl.klazz().getSimpleName()); // }