--- jsr166/src/test/tck/MapTest.java 2019/04/24 17:29:50 1.5 +++ jsr166/src/test/tck/MapTest.java 2021/01/26 13:33:06 1.11 @@ -5,17 +5,22 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -import junit.framework.Test; - 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. */ +@SuppressWarnings("unchecked") public class MapTest extends JSR166TestCase { final MapImplementation impl; @@ -37,12 +42,12 @@ public class MapTest extends JSR166TestC { Map m = impl.emptyMap(); assertTrue(m.isEmpty()); - assertEquals(0, m.size()); + mustEqual(0, m.size()); Object k = impl.makeKey(rnd.nextInt()); Object v = impl.makeValue(rnd.nextInt()); m.put(k, v); assertFalse(m.isEmpty()); - assertEquals(1, m.size()); + mustEqual(1, m.size()); assertTrue(m.containsKey(k)); assertTrue(m.containsValue(v)); } @@ -95,6 +100,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()) @@ -136,7 +142,7 @@ public class MapTest extends JSR166TestC assertFalse(m.containsValue(v1)); assertTrue(m.containsValue(v2)); assertTrue(m.containsKey(keyToFrob)); - assertEquals(1, m.size()); + mustEqual(1, m.size()); } /** @@ -167,10 +173,10 @@ public class MapTest extends JSR166TestC m1.putAll(m2); for (Object elt : m2.keySet()) - assertEquals(m2.get(elt), m1.get(elt)); + mustEqual(m2.get(elt), m1.get(elt)); for (Object elt : m1Copy.keySet()) assertSame(m1Copy.get(elt), m1.get(elt)); - assertEquals(size1 + size2, m1.size()); + mustEqual(size1 + size2, m1.size()); } /** @@ -180,25 +186,90 @@ public class MapTest extends JSR166TestC final ThreadLocalRandom rnd = ThreadLocalRandom.current(); final int size = rnd.nextInt(4); final Map map = impl.emptyMap(); - for (int i = size; i--> 0; ) + 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()); + mustEqual(size, map.size()); + mustEqual(size, clone.size()); + mustEqual(map.isEmpty(), clone.isEmpty()); clone.put(impl.makeKey(-1), impl.makeValue(-1)); - assertEquals(size, map.size()); - assertEquals(size + 1, clone.size()); + mustEqual(size, map.size()); + mustEqual(size + 1, clone.size()); clone.clear(); - assertEquals(size, map.size()); - assertEquals(0, clone.size()); + mustEqual(size, map.size()); + mustEqual(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) + mustEqual(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) + mustEqual(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(); + mustEqual(expectedSum.get(), sum); + } + // public void testFailsIntentionallyForDebugging() { // fail(impl.klazz().getSimpleName()); // }