1 |
/* |
2 |
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. |
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
5 |
* This code is free software; you can redistribute it and/or modify it |
6 |
* under the terms of the GNU General Public License version 2 only, as |
7 |
* published by the Free Software Foundation. |
8 |
* |
9 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
10 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
12 |
* version 2 for more details (a copy is included in the LICENSE file that |
13 |
* accompanied this code). |
14 |
* |
15 |
* You should have received a copy of the GNU General Public License version |
16 |
* 2 along with this work; if not, write to the Free Software Foundation, |
17 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
18 |
* |
19 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
20 |
* or visit www.oracle.com if you need additional information or have any |
21 |
* questions. |
22 |
*/ |
23 |
|
24 |
import org.testng.annotations.DataProvider; |
25 |
import org.testng.annotations.Test; |
26 |
|
27 |
import java.util.AbstractList; |
28 |
import java.util.ArrayList; |
29 |
import java.util.Arrays; |
30 |
import java.util.Collection; |
31 |
import java.util.Collections; |
32 |
import java.util.ConcurrentModificationException; |
33 |
import java.util.Iterator; |
34 |
import java.util.HashMap; |
35 |
import java.util.HashSet; |
36 |
import java.util.LinkedHashMap; |
37 |
import java.util.LinkedHashSet; |
38 |
import java.util.LinkedList; |
39 |
import java.util.List; |
40 |
import java.util.Map; |
41 |
import java.util.PriorityQueue; |
42 |
import java.util.RandomAccess; |
43 |
import java.util.Set; |
44 |
import java.util.Spliterator; |
45 |
import java.util.Stack; |
46 |
import java.util.TreeMap; |
47 |
import java.util.TreeSet; |
48 |
import java.util.Vector; |
49 |
import java.util.WeakHashMap; |
50 |
import java.util.function.Consumer; |
51 |
import java.util.function.Function; |
52 |
import java.util.function.Supplier; |
53 |
|
54 |
import static org.testng.Assert.*; |
55 |
|
56 |
/** |
57 |
* @test |
58 |
* @bug 8148748 |
59 |
* @summary Spliterator last-binding and fail-fast tests |
60 |
* @run testng SpliteratorLateBindingFailFastTest |
61 |
*/ |
62 |
|
63 |
@Test |
64 |
public class SpliteratorLateBindingFailFastTest { |
65 |
|
66 |
private interface Source<T> { |
67 |
Collection<T> asCollection(); |
68 |
void update(); |
69 |
} |
70 |
|
71 |
private static class SpliteratorDataBuilder<T> { |
72 |
final List<Object[]> data; |
73 |
|
74 |
final T newValue; |
75 |
|
76 |
final List<T> exp; |
77 |
|
78 |
final Map<T, T> mExp; |
79 |
|
80 |
SpliteratorDataBuilder(List<Object[]> data, T newValue, List<T> exp) { |
81 |
this.data = data; |
82 |
this.newValue = newValue; |
83 |
this.exp = exp; |
84 |
this.mExp = createMap(exp); |
85 |
} |
86 |
|
87 |
Map<T, T> createMap(List<T> l) { |
88 |
Map<T, T> m = new LinkedHashMap<>(); |
89 |
for (T t : l) { |
90 |
m.put(t, t); |
91 |
} |
92 |
return m; |
93 |
} |
94 |
|
95 |
void add(String description, Supplier<Source<?>> s) { |
96 |
description = joiner(description).toString(); |
97 |
data.add(new Object[]{description, s}); |
98 |
} |
99 |
|
100 |
void addCollection(Function<Collection<T>, ? extends Collection<T>> f) { |
101 |
class CollectionSource implements Source<T> { |
102 |
final Collection<T> c = f.apply(exp); |
103 |
|
104 |
final Consumer<Collection<T>> updater; |
105 |
|
106 |
CollectionSource(Consumer<Collection<T>> updater) { |
107 |
this.updater = updater; |
108 |
} |
109 |
|
110 |
@Override |
111 |
public Collection<T> asCollection() { |
112 |
return c; |
113 |
} |
114 |
|
115 |
@Override |
116 |
public void update() { |
117 |
updater.accept(c); |
118 |
} |
119 |
} |
120 |
|
121 |
String description = "new " + f.apply(Collections.<T>emptyList()).getClass().getName() + ".spliterator() "; |
122 |
add(description + "ADD", () -> new CollectionSource(c -> c.add(newValue))); |
123 |
add(description + "REMOVE", () -> new CollectionSource(c -> c.remove(c.iterator().next()))); |
124 |
} |
125 |
|
126 |
void addList(Function<Collection<T>, ? extends List<T>> l) { |
127 |
addCollection(l); |
128 |
addCollection(l.andThen(list -> list.subList(0, list.size()))); |
129 |
} |
130 |
|
131 |
void addMap(Function<Map<T, T>, ? extends Map<T, T>> mapConstructor) { |
132 |
class MapSource<U> implements Source<U> { |
133 |
final Map<T, T> m = mapConstructor.apply(mExp); |
134 |
|
135 |
final Collection<U> c; |
136 |
|
137 |
final Consumer<Map<T, T>> updater; |
138 |
|
139 |
MapSource(Function<Map<T, T>, Collection<U>> f, Consumer<Map<T, T>> updater) { |
140 |
this.c = f.apply(m); |
141 |
this.updater = updater; |
142 |
} |
143 |
|
144 |
@Override |
145 |
public Collection<U> asCollection() { |
146 |
return c; |
147 |
} |
148 |
|
149 |
@Override |
150 |
public void update() { |
151 |
updater.accept(m); |
152 |
} |
153 |
} |
154 |
|
155 |
Map<String, Consumer<Map<T, T>>> actions = new HashMap<>(); |
156 |
actions.put("ADD", m -> m.put(newValue, newValue)); |
157 |
actions.put("REMOVE", m -> m.remove(m.keySet().iterator().next())); |
158 |
|
159 |
String description = "new " + mapConstructor.apply(Collections.<T, T>emptyMap()).getClass().getName(); |
160 |
for (Map.Entry<String, Consumer<Map<T, T>>> e : actions.entrySet()) { |
161 |
add(description + ".keySet().spliterator() " + e.getKey(), |
162 |
() -> new MapSource<T>(m -> m.keySet(), e.getValue())); |
163 |
add(description + ".values().spliterator() " + e.getKey(), |
164 |
() -> new MapSource<T>(m -> m.values(), e.getValue())); |
165 |
add(description + ".entrySet().spliterator() " + e.getKey(), |
166 |
() -> new MapSource<Map.Entry<T, T>>(m -> m.entrySet(), e.getValue())); |
167 |
} |
168 |
} |
169 |
|
170 |
StringBuilder joiner(String description) { |
171 |
return new StringBuilder(description). |
172 |
append(" {"). |
173 |
append("size=").append(exp.size()). |
174 |
append("}"); |
175 |
} |
176 |
} |
177 |
|
178 |
static Object[][] spliteratorDataProvider; |
179 |
|
180 |
@DataProvider(name = "Source") |
181 |
public static Object[][] spliteratorDataProvider() { |
182 |
if (spliteratorDataProvider != null) { |
183 |
return spliteratorDataProvider; |
184 |
} |
185 |
|
186 |
List<Object[]> data = new ArrayList<>(); |
187 |
SpliteratorDataBuilder<Integer> db = new SpliteratorDataBuilder<>(data, 5, Arrays.asList(1, 2, 3, 4)); |
188 |
|
189 |
// Collections |
190 |
|
191 |
db.addList(ArrayList::new); |
192 |
|
193 |
db.addList(LinkedList::new); |
194 |
|
195 |
db.addList(Vector::new); |
196 |
|
197 |
class AbstractRandomAccessListImpl extends AbstractList<Integer> implements RandomAccess { |
198 |
List<Integer> l; |
199 |
|
200 |
AbstractRandomAccessListImpl(Collection<Integer> c) { |
201 |
this.l = new ArrayList<>(c); |
202 |
} |
203 |
|
204 |
@Override |
205 |
public boolean add(Integer integer) { |
206 |
modCount++; |
207 |
return l.add(integer); |
208 |
} |
209 |
|
210 |
@Override |
211 |
public Iterator<Integer> iterator() { |
212 |
return l.iterator(); |
213 |
} |
214 |
|
215 |
@Override |
216 |
public Integer get(int index) { |
217 |
return l.get(index); |
218 |
} |
219 |
|
220 |
@Override |
221 |
public boolean remove(Object o) { |
222 |
modCount++; |
223 |
return l.remove(o); |
224 |
} |
225 |
|
226 |
@Override |
227 |
public int size() { |
228 |
return l.size(); |
229 |
} |
230 |
|
231 |
@Override |
232 |
public List<Integer> subList(int fromIndex, int toIndex) { |
233 |
return l.subList(fromIndex, toIndex); |
234 |
} |
235 |
} |
236 |
db.addList(AbstractRandomAccessListImpl::new); |
237 |
|
238 |
db.addCollection(HashSet::new); |
239 |
|
240 |
db.addCollection(LinkedHashSet::new); |
241 |
|
242 |
db.addCollection(TreeSet::new); |
243 |
|
244 |
|
245 |
db.addCollection(c -> { Stack<Integer> s = new Stack<>(); s.addAll(c); return s;}); |
246 |
|
247 |
db.addCollection(PriorityQueue::new); |
248 |
|
249 |
// ArrayDeque fails some tests since its fail-fast support is weaker |
250 |
// than other collections and limited to detecting most, but not all, |
251 |
// removals. It probably requires its own test since it is difficult |
252 |
// to abstract out the conditions under which it fails-fast. |
253 |
// db.addCollection(ArrayDeque::new); |
254 |
|
255 |
// Maps |
256 |
|
257 |
db.addMap(HashMap::new); |
258 |
|
259 |
db.addMap(LinkedHashMap::new); |
260 |
|
261 |
// This fails when run through jtreg but passes when run through |
262 |
// ant |
263 |
// db.addMap(IdentityHashMap::new); |
264 |
|
265 |
db.addMap(WeakHashMap::new); |
266 |
|
267 |
// @@@ Descending maps etc |
268 |
db.addMap(TreeMap::new); |
269 |
|
270 |
return spliteratorDataProvider = data.toArray(new Object[0][]); |
271 |
} |
272 |
|
273 |
@Test(dataProvider = "Source") |
274 |
public <T> void lateBindingTestWithForEach(String description, Supplier<Source<T>> ss) { |
275 |
Source<T> source = ss.get(); |
276 |
Collection<T> c = source.asCollection(); |
277 |
Spliterator<T> s = c.spliterator(); |
278 |
|
279 |
source.update(); |
280 |
|
281 |
Set<T> r = new HashSet<>(); |
282 |
s.forEachRemaining(r::add); |
283 |
|
284 |
assertEquals(r, new HashSet<>(c)); |
285 |
} |
286 |
|
287 |
@Test(dataProvider = "Source") |
288 |
public <T> void lateBindingTestWithTryAdvance(String description, Supplier<Source<T>> ss) { |
289 |
Source<T> source = ss.get(); |
290 |
Collection<T> c = source.asCollection(); |
291 |
Spliterator<T> s = c.spliterator(); |
292 |
|
293 |
source.update(); |
294 |
|
295 |
Set<T> r = new HashSet<>(); |
296 |
while (s.tryAdvance(r::add)) { } |
297 |
|
298 |
assertEquals(r, new HashSet<>(c)); |
299 |
} |
300 |
|
301 |
@Test(dataProvider = "Source") |
302 |
public <T> void lateBindingTestWithCharacteritics(String description, Supplier<Source<T>> ss) { |
303 |
Source<T> source = ss.get(); |
304 |
Collection<T> c = source.asCollection(); |
305 |
Spliterator<T> s = c.spliterator(); |
306 |
s.characteristics(); |
307 |
|
308 |
Set<T> r = new HashSet<>(); |
309 |
s.forEachRemaining(r::add); |
310 |
|
311 |
assertEquals(r, new HashSet<>(c)); |
312 |
} |
313 |
|
314 |
|
315 |
@Test(dataProvider = "Source") |
316 |
public <T> void testFailFastTestWithTryAdvance(String description, Supplier<Source<T>> ss) { |
317 |
{ |
318 |
Source<T> source = ss.get(); |
319 |
Collection<T> c = source.asCollection(); |
320 |
Spliterator<T> s = c.spliterator(); |
321 |
|
322 |
s.tryAdvance(e -> { |
323 |
}); |
324 |
source.update(); |
325 |
|
326 |
executeAndCatch(() -> s.tryAdvance(e -> { })); |
327 |
} |
328 |
|
329 |
{ |
330 |
Source<T> source = ss.get(); |
331 |
Collection<T> c = source.asCollection(); |
332 |
Spliterator<T> s = c.spliterator(); |
333 |
|
334 |
s.tryAdvance(e -> { |
335 |
}); |
336 |
source.update(); |
337 |
|
338 |
executeAndCatch(() -> s.forEachRemaining(e -> { |
339 |
})); |
340 |
} |
341 |
} |
342 |
|
343 |
@Test(dataProvider = "Source") |
344 |
public <T> void testFailFastTestWithForEach(String description, Supplier<Source<T>> ss) { |
345 |
Source<T> source = ss.get(); |
346 |
Collection<T> c = source.asCollection(); |
347 |
Spliterator<T> s = c.spliterator(); |
348 |
|
349 |
executeAndCatch(() -> s.forEachRemaining(e -> { |
350 |
source.update(); |
351 |
})); |
352 |
} |
353 |
|
354 |
@Test(dataProvider = "Source") |
355 |
public <T> void testFailFastTestWithEstimateSize(String description, Supplier<Source<T>> ss) { |
356 |
{ |
357 |
Source<T> source = ss.get(); |
358 |
Collection<T> c = source.asCollection(); |
359 |
Spliterator<T> s = c.spliterator(); |
360 |
|
361 |
s.estimateSize(); |
362 |
source.update(); |
363 |
|
364 |
executeAndCatch(() -> s.tryAdvance(e -> { })); |
365 |
} |
366 |
|
367 |
{ |
368 |
Source<T> source = ss.get(); |
369 |
Collection<T> c = source.asCollection(); |
370 |
Spliterator<T> s = c.spliterator(); |
371 |
|
372 |
s.estimateSize(); |
373 |
source.update(); |
374 |
|
375 |
executeAndCatch(() -> s.forEachRemaining(e -> { |
376 |
})); |
377 |
} |
378 |
} |
379 |
|
380 |
private void executeAndCatch(Runnable r) { |
381 |
executeAndCatch(ConcurrentModificationException.class, r); |
382 |
} |
383 |
|
384 |
private void executeAndCatch(Class<? extends Exception> expected, Runnable r) { |
385 |
Exception caught = null; |
386 |
try { |
387 |
r.run(); |
388 |
} |
389 |
catch (Exception e) { |
390 |
caught = e; |
391 |
} |
392 |
|
393 |
assertNotNull(caught, |
394 |
String.format("No Exception was thrown, expected an Exception of %s to be thrown", |
395 |
expected.getName())); |
396 |
assertTrue(expected.isInstance(caught), |
397 |
String.format("Exception thrown %s not an instance of %s", |
398 |
caught.getClass().getName(), expected.getName())); |
399 |
} |
400 |
|
401 |
} |