ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/tck/MapTest.java
Revision: 1.11
Committed: Tue Jan 26 13:33:06 2021 UTC (3 years, 2 months ago) by dl
Branch: MAIN
CVS Tags: HEAD
Changes since 1.10: +16 -15 lines
Log Message:
Replace Integer with Item class

File Contents

# Content
1 /*
2 * Written by Doug Lea and Martin Buchholz with assistance from
3 * members of JCP JSR-166 Expert Group and released to the public
4 * domain, as explained at
5 * http://creativecommons.org/publicdomain/zero/1.0/
6 */
7
8 import java.util.ArrayList;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.concurrent.CompletableFuture;
13 import java.util.concurrent.ThreadLocalRandom;
14 import java.util.concurrent.atomic.AtomicBoolean;
15 import java.util.concurrent.atomic.AtomicLong;
16 import java.util.function.BiFunction;
17
18 import junit.framework.Test;
19
20 /**
21 * Contains tests applicable to all Map implementations.
22 */
23 @SuppressWarnings("unchecked")
24 public class MapTest extends JSR166TestCase {
25 final MapImplementation impl;
26
27 /** Tests are parameterized by a Map implementation. */
28 MapTest(MapImplementation impl, String methodName) {
29 super(methodName);
30 this.impl = impl;
31 }
32
33 public static Test testSuite(MapImplementation impl) {
34 return newTestSuite(
35 parameterizedTestSuite(MapTest.class,
36 MapImplementation.class,
37 impl));
38 }
39
40 public void testImplSanity() {
41 final ThreadLocalRandom rnd = ThreadLocalRandom.current();
42 {
43 Map m = impl.emptyMap();
44 assertTrue(m.isEmpty());
45 mustEqual(0, m.size());
46 Object k = impl.makeKey(rnd.nextInt());
47 Object v = impl.makeValue(rnd.nextInt());
48 m.put(k, v);
49 assertFalse(m.isEmpty());
50 mustEqual(1, m.size());
51 assertTrue(m.containsKey(k));
52 assertTrue(m.containsValue(v));
53 }
54 {
55 Map m = impl.emptyMap();
56 Object v = impl.makeValue(rnd.nextInt());
57 if (impl.permitsNullKeys()) {
58 m.put(null, v);
59 assertTrue(m.containsKey(null));
60 assertTrue(m.containsValue(v));
61 } else {
62 assertThrows(NullPointerException.class, () -> m.put(null, v));
63 }
64 }
65 {
66 Map m = impl.emptyMap();
67 Object k = impl.makeKey(rnd.nextInt());
68 if (impl.permitsNullValues()) {
69 m.put(k, null);
70 assertTrue(m.containsKey(k));
71 assertTrue(m.containsValue(null));
72 } else {
73 assertThrows(NullPointerException.class, () -> m.put(k, null));
74 }
75 }
76 {
77 Map m = impl.emptyMap();
78 Object k = impl.makeKey(rnd.nextInt());
79 Object v1 = impl.makeValue(rnd.nextInt());
80 Object v2 = impl.makeValue(rnd.nextInt());
81 m.put(k, v1);
82 if (impl.supportsSetValue()) {
83 ((Map.Entry)(m.entrySet().iterator().next())).setValue(v2);
84 assertSame(v2, m.get(k));
85 assertTrue(m.containsKey(k));
86 assertTrue(m.containsValue(v2));
87 assertFalse(m.containsValue(v1));
88 } else {
89 assertThrows(UnsupportedOperationException.class,
90 () -> ((Map.Entry)(m.entrySet().iterator().next())).setValue(v2));
91 }
92 }
93 }
94
95 /**
96 * Tests and extends the scenario reported in
97 * https://bugs.openjdk.java.net/browse/JDK-8186171
98 * HashMap: Entry.setValue may not work after Iterator.remove() called for previous entries
99 * ant -Djsr166.tckTestClass=HashMapTest -Djsr166.methodFilter=testBug8186171 -Djsr166.runsPerTest=1000 tck
100 */
101 public void testBug8186171() {
102 if (!impl.supportsSetValue()) return;
103 if (!atLeastJava10()) return; // jdk9 is no longer maintained
104 final ThreadLocalRandom rnd = ThreadLocalRandom.current();
105 final boolean permitsNullValues = impl.permitsNullValues();
106 final Object v1 = (permitsNullValues && rnd.nextBoolean())
107 ? null : impl.makeValue(1);
108 final Object v2 = (permitsNullValues && rnd.nextBoolean() && v1 != null)
109 ? null : impl.makeValue(2);
110
111 // If true, always lands in first bucket in hash tables.
112 final boolean poorHash = rnd.nextBoolean();
113 class Key implements Comparable<Key> {
114 final int i;
115 Key(int i) { this.i = i; }
116 public int hashCode() { return poorHash ? 0 : super.hashCode(); }
117 public int compareTo(Key x) {
118 return Integer.compare(this.i, x.i);
119 }
120 }
121
122 // Both HashMap and ConcurrentHashMap have:
123 // TREEIFY_THRESHOLD = 8; UNTREEIFY_THRESHOLD = 6;
124 final int size = rnd.nextInt(1, 25);
125
126 List<Key> keys = new ArrayList<>();
127 for (int i = size; i-->0; ) keys.add(new Key(i));
128 Key keyToFrob = keys.get(rnd.nextInt(keys.size()));
129
130 Map<Key, Object> m = impl.emptyMap();
131 for (Key key : keys) m.put(key, v1);
132
133 for (Iterator<Map.Entry<Key, Object>> it = m.entrySet().iterator();
134 it.hasNext(); ) {
135 Map.Entry<Key, Object> entry = it.next();
136 if (entry.getKey() == keyToFrob)
137 entry.setValue(v2); // does this have the expected effect?
138 else
139 it.remove();
140 }
141
142 assertFalse(m.containsValue(v1));
143 assertTrue(m.containsValue(v2));
144 assertTrue(m.containsKey(keyToFrob));
145 mustEqual(1, m.size());
146 }
147
148 /**
149 * "Missing" test found while investigating JDK-8210280.
150 * ant -Djsr166.tckTestClass=HashMapTest -Djsr166.methodFilter=testBug8210280 -Djsr166.runsPerTest=1000000 tck
151 */
152 public void testBug8210280() {
153 final ThreadLocalRandom rnd = ThreadLocalRandom.current();
154 final int size1 = rnd.nextInt(32);
155 final int size2 = rnd.nextInt(128);
156
157 final Map m1 = impl.emptyMap();
158 for (int i = 0; i < size1; i++) {
159 int elt = rnd.nextInt(1024 * i, 1024 * (i + 1));
160 assertNull(m1.put(impl.makeKey(elt), impl.makeValue(elt)));
161 }
162
163 final Map m2 = impl.emptyMap();
164 for (int i = 0; i < size2; i++) {
165 int elt = rnd.nextInt(Integer.MIN_VALUE + 1024 * i,
166 Integer.MIN_VALUE + 1024 * (i + 1));
167 assertNull(m2.put(impl.makeKey(elt), impl.makeValue(-elt)));
168 }
169
170 final Map m1Copy = impl.emptyMap();
171 m1Copy.putAll(m1);
172
173 m1.putAll(m2);
174
175 for (Object elt : m2.keySet())
176 mustEqual(m2.get(elt), m1.get(elt));
177 for (Object elt : m1Copy.keySet())
178 assertSame(m1Copy.get(elt), m1.get(elt));
179 mustEqual(size1 + size2, m1.size());
180 }
181
182 /**
183 * 8222930: ConcurrentSkipListMap.clone() shares size variable between original and clone
184 */
185 public void testClone() {
186 final ThreadLocalRandom rnd = ThreadLocalRandom.current();
187 final int size = rnd.nextInt(4);
188 final Map map = impl.emptyMap();
189 for (int i = 0; i < size; i++)
190 map.put(impl.makeKey(i), impl.makeValue(i));
191 final Map clone = cloneableClone(map);
192 if (clone == null) return; // not cloneable?
193
194 mustEqual(size, map.size());
195 mustEqual(size, clone.size());
196 mustEqual(map.isEmpty(), clone.isEmpty());
197
198 clone.put(impl.makeKey(-1), impl.makeValue(-1));
199 mustEqual(size, map.size());
200 mustEqual(size + 1, clone.size());
201
202 clone.clear();
203 mustEqual(size, map.size());
204 mustEqual(0, clone.size());
205 assertTrue(clone.isEmpty());
206 }
207
208 /**
209 * Concurrent access by compute methods behaves as expected
210 */
211 public void testConcurrentAccess() throws Throwable {
212 final Map map = impl.emptyMap();
213 final long testDurationMillis = expensiveTests ? 1000 : 2;
214 final int nTasks = impl.isConcurrent()
215 ? ThreadLocalRandom.current().nextInt(1, 10)
216 : 1;
217 final AtomicBoolean done = new AtomicBoolean(false);
218 final boolean remappingFunctionCalledAtMostOnce
219 = impl.remappingFunctionCalledAtMostOnce();
220 final List<CompletableFuture> futures = new ArrayList<>();
221 final AtomicLong expectedSum = new AtomicLong(0);
222 final Action[] tasks = {
223 // repeatedly increment values using compute()
224 () -> {
225 long[] invocations = new long[2];
226 ThreadLocalRandom rnd = ThreadLocalRandom.current();
227 BiFunction<Object, Object, Object> incValue = (k, v) -> {
228 invocations[1]++;
229 int vi = (v == null) ? 1 : impl.valueToInt(v) + 1;
230 return impl.makeValue(vi);
231 };
232 while (!done.getAcquire()) {
233 invocations[0]++;
234 Object key = impl.makeKey(3 * rnd.nextInt(10));
235 map.compute(key, incValue);
236 }
237 if (remappingFunctionCalledAtMostOnce)
238 mustEqual(invocations[0], invocations[1]);
239 expectedSum.getAndAdd(invocations[0]);
240 },
241 // repeatedly increment values using computeIfPresent()
242 () -> {
243 long[] invocations = new long[2];
244 ThreadLocalRandom rnd = ThreadLocalRandom.current();
245 BiFunction<Object, Object, Object> incValue = (k, v) -> {
246 invocations[1]++;
247 int vi = impl.valueToInt(v) + 1;
248 return impl.makeValue(vi);
249 };
250 while (!done.getAcquire()) {
251 Object key = impl.makeKey(3 * rnd.nextInt(10));
252 if (map.computeIfPresent(key, incValue) != null)
253 invocations[0]++;
254 }
255 if (remappingFunctionCalledAtMostOnce)
256 mustEqual(invocations[0], invocations[1]);
257 expectedSum.getAndAdd(invocations[0]);
258 },
259 };
260 for (int i = nTasks; i--> 0; ) {
261 Action task = chooseRandomly(tasks);
262 futures.add(CompletableFuture.runAsync(checkedRunnable(task)));
263 }
264 Thread.sleep(testDurationMillis);
265 done.setRelease(true);
266 for (var future : futures)
267 checkTimedGet(future, null);
268
269 long sum = map.values().stream().mapToLong(x -> (int) x).sum();
270 mustEqual(expectedSum.get(), sum);
271 }
272
273 // public void testFailsIntentionallyForDebugging() {
274 // fail(impl.klazz().getSimpleName());
275 // }
276 }