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.ArrayList;
  28 import java.util.Arrays;
  29 import java.util.Collection;
  30 import java.util.Collections;
  31 import java.util.ConcurrentModificationException;
  32 import java.util.HashMap;
  33 import java.util.HashSet;
  34 import java.util.LinkedHashMap;
  35 import java.util.LinkedHashSet;
  36 import java.util.LinkedList;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.PriorityQueue;
  40 import java.util.Set;
  41 import java.util.Spliterator;
  42 import java.util.Stack;
  43 import java.util.TreeMap;
  44 import java.util.TreeSet;
  45 import java.util.Vector;
  46 import java.util.WeakHashMap;
  47 import java.util.function.Consumer;
  48 import java.util.function.Function;
  49 import java.util.function.Supplier;
  50 
  51 import static org.testng.Assert.*;
  52 
  53 /**
  54  * @test
  55  * @bug 8148748
  56  * @summary Spliterator last-binding and fail-fast tests
  57  * @run testng SpliteratorLateBindingFailFastTest
  58  */
  59 
  60 @Test
  61 public class SpliteratorLateBindingFailFastTest {
  62 
  63     private interface Source<T> {
  64         Collection<T> asCollection();
  65         void update();
  66     }
  67 
  68     private static class SpliteratorDataBuilder<T> {
  69         final List<Object[]> data;
  70 
  71         final T newValue;
  72 
  73         final List<T> exp;
  74 
  75         final Map<T, T> mExp;
  76 
  77         SpliteratorDataBuilder(List<Object[]> data, T newValue, List<T> exp) {
  78             this.data = data;
  79             this.newValue = newValue;
  80             this.exp = exp;
  81             this.mExp = createMap(exp);
  82         }
  83 
  84         Map<T, T> createMap(List<T> l) {
  85             Map<T, T> m = new LinkedHashMap<>();
  86             for (T t : l) {
  87                 m.put(t, t);
  88             }
  89             return m;
  90         }
  91 
  92         void add(String description, Supplier<Source<?>> s) {
  93             description = joiner(description).toString();
  94             data.add(new Object[]{description, s});
  95         }
  96 
  97         void addCollection(Function<Collection<T>, ? extends Collection<T>> f) {
  98             class CollectionSource implements Source<T> {
  99                 final Collection<T> c = f.apply(exp);
 100 
 101                 final Consumer<Collection<T>> updater;
 102 
 103                 CollectionSource(Consumer<Collection<T>> updater) {
 104                     this.updater = updater;
 105                 }
 106 
 107                 @Override
 108                 public Collection<T> asCollection() {
 109                     return c;
 110                 }
 111 
 112                 @Override
 113                 public void update() {
 114                     updater.accept(c);
 115                 }
 116             }
 117 
 118             String description = "new " + f.apply(Collections.<T>emptyList()).getClass().getName() + ".spliterator() ";
 119             add(description + "ADD", () -> new CollectionSource(c -> c.add(newValue)));
 120             add(description + "REMOVE", () -> new CollectionSource(c -> c.remove(c.iterator().next())));
 121         }
 122 
 123         void addList(Function<Collection<T>, ? extends List<T>> l) {
 124             addCollection(l);
 125             addCollection(l.andThen(list -> list.subList(0, list.size())));
 126         }
 127 
 128         void addMap(Function<Map<T, T>, ? extends Map<T, T>> mapConstructor) {
 129             class MapSource<U> implements Source<U> {
 130                 final Map<T, T> m = mapConstructor.apply(mExp);
 131 
 132                 final Collection<U> c;
 133 
 134                 final Consumer<Map<T, T>> updater;
 135 
 136                 MapSource(Function<Map<T, T>, Collection<U>> f, Consumer<Map<T, T>> updater) {
 137                     this.c = f.apply(m);
 138                     this.updater = updater;
 139                 }
 140 
 141                 @Override
 142                 public Collection<U> asCollection() {
 143                     return c;
 144                 }
 145 
 146                 @Override
 147                 public void update() {
 148                     updater.accept(m);
 149                 }
 150             }
 151 
 152             Map<String, Consumer<Map<T, T>>> actions = new HashMap<>();
 153             actions.put("ADD", m -> m.put(newValue, newValue));
 154             actions.put("REMOVE", m -> m.remove(m.keySet().iterator().next()));
 155 
 156             String description = "new " + mapConstructor.apply(Collections.<T, T>emptyMap()).getClass().getName();
 157             for (Map.Entry<String, Consumer<Map<T, T>>> e : actions.entrySet()) {
 158                 add(description + ".keySet().spliterator() " + e.getKey(),
 159                     () -> new MapSource<T>(m -> m.keySet(), e.getValue()));
 160                 add(description + ".values().spliterator() " + e.getKey(),
 161                     () -> new MapSource<T>(m -> m.values(), e.getValue()));
 162                 add(description + ".entrySet().spliterator() " + e.getKey(),
 163                     () -> new MapSource<Map.Entry<T, T>>(m -> m.entrySet(), e.getValue()));
 164             }
 165         }
 166 
 167         StringBuilder joiner(String description) {
 168             return new StringBuilder(description).
 169                     append(" {").
 170                     append("size=").append(exp.size()).
 171                     append("}");
 172         }
 173     }
 174 
 175     static Object[][] spliteratorDataProvider;
 176 
 177     @DataProvider(name = "Source")
 178     public static Object[][] spliteratorDataProvider() {
 179         if (spliteratorDataProvider != null) {
 180             return spliteratorDataProvider;
 181         }
 182 
 183         List<Object[]> data = new ArrayList<>();
 184         SpliteratorDataBuilder<Integer> db = new SpliteratorDataBuilder<>(data, 5, Arrays.asList(1, 2, 3, 4));
 185 
 186         // Collections
 187 
 188         db.addList(ArrayList::new);
 189 
 190         db.addList(LinkedList::new);
 191 
 192         db.addList(Vector::new);
 193 
 194 
 195         db.addCollection(HashSet::new);
 196 
 197         db.addCollection(LinkedHashSet::new);
 198 
 199         db.addCollection(TreeSet::new);
 200 
 201 
 202         db.addCollection(c -> { Stack<Integer> s = new Stack<>(); s.addAll(c); return s;});
 203 
 204         db.addCollection(PriorityQueue::new);
 205 
 206         // ArrayDeque fails some tests since its fail-fast support is weaker
 207         // than other collections and limited to detecting most, but not all,
 208         // removals.  It probably requires its own test since it is difficult
 209         // to abstract out the conditions under which it fails-fast.
 210 //        db.addCollection(ArrayDeque::new);
 211 
 212         // Maps
 213 
 214         db.addMap(HashMap::new);
 215 
 216         db.addMap(LinkedHashMap::new);
 217 
 218         // This fails when run through jtreg but passes when run through
 219         // ant
 220 //        db.addMap(IdentityHashMap::new);
 221 
 222         db.addMap(WeakHashMap::new);
 223 
 224         // @@@  Descending maps etc
 225         db.addMap(TreeMap::new);
 226 
 227         return spliteratorDataProvider = data.toArray(new Object[0][]);
 228     }
 229 
 230     @Test(dataProvider = "Source")
 231     public <T> void lateBindingTestWithForEach(String description, Supplier<Source<T>> ss) {
 232         Source<T> source = ss.get();
 233         Collection<T> c = source.asCollection();
 234         Spliterator<T> s = c.spliterator();
 235 
 236         source.update();
 237 
 238         Set<T> r = new HashSet<>();
 239         s.forEachRemaining(r::add);
 240 
 241         assertEquals(r, new HashSet<>(c));
 242     }
 243 
 244     @Test(dataProvider = "Source")
 245     public <T> void lateBindingTestWithTryAdvance(String description, Supplier<Source<T>> ss) {
 246         Source<T> source = ss.get();
 247         Collection<T> c = source.asCollection();
 248         Spliterator<T> s = c.spliterator();
 249 
 250         source.update();
 251 
 252         Set<T> r = new HashSet<>();
 253         while (s.tryAdvance(r::add)) { }
 254 
 255         assertEquals(r, new HashSet<>(c));
 256     }
 257 
 258     @Test(dataProvider = "Source")
 259     public <T> void lateBindingTestWithCharacteritics(String description, Supplier<Source<T>> ss) {
 260         Source<T> source = ss.get();
 261         Collection<T> c = source.asCollection();
 262         Spliterator<T> s = c.spliterator();
 263         s.characteristics();
 264 
 265         Set<T> r = new HashSet<>();
 266         s.forEachRemaining(r::add);
 267 
 268         assertEquals(r, new HashSet<>(c));
 269     }
 270 
 271 
 272     @Test(dataProvider = "Source")
 273     public <T> void testFailFastTestWithTryAdvance(String description, Supplier<Source<T>> ss) {
 274         {
 275             Source<T> source = ss.get();
 276             Collection<T> c = source.asCollection();
 277             Spliterator<T> s = c.spliterator();
 278 
 279             s.tryAdvance(e -> {
 280             });
 281             source.update();
 282 
 283             executeAndCatch(() -> s.tryAdvance(e -> { }));
 284         }
 285 
 286         {
 287             Source<T> source = ss.get();
 288             Collection<T> c = source.asCollection();
 289             Spliterator<T> s = c.spliterator();
 290 
 291             s.tryAdvance(e -> {
 292             });
 293             source.update();
 294 
 295             executeAndCatch(() -> s.forEachRemaining(e -> {
 296             }));
 297         }
 298     }
 299 
 300     @Test(dataProvider = "Source")
 301     public <T> void testFailFastTestWithForEach(String description, Supplier<Source<T>> ss) {
 302         Source<T> source = ss.get();
 303         Collection<T> c = source.asCollection();
 304         Spliterator<T> s = c.spliterator();
 305 
 306         executeAndCatch(() -> s.forEachRemaining(e -> {
 307             source.update();
 308         }));
 309     }
 310 
 311     @Test(dataProvider = "Source")
 312     public <T> void testFailFastTestWithEstimateSize(String description, Supplier<Source<T>> ss) {
 313         {
 314             Source<T> source = ss.get();
 315             Collection<T> c = source.asCollection();
 316             Spliterator<T> s = c.spliterator();
 317 
 318             s.estimateSize();
 319             source.update();
 320 
 321             executeAndCatch(() -> s.tryAdvance(e -> { }));
 322         }
 323 
 324         {
 325             Source<T> source = ss.get();
 326             Collection<T> c = source.asCollection();
 327             Spliterator<T> s = c.spliterator();
 328 
 329             s.estimateSize();
 330             source.update();
 331 
 332             executeAndCatch(() -> s.forEachRemaining(e -> {
 333             }));
 334         }
 335     }
 336 
 337     private void executeAndCatch(Runnable r) {
 338         executeAndCatch(ConcurrentModificationException.class, r);
 339     }
 340 
 341     private void executeAndCatch(Class<? extends Exception> expected, Runnable r) {
 342         Exception caught = null;
 343         try {
 344             r.run();
 345         }
 346         catch (Exception e) {
 347             caught = e;
 348         }
 349 
 350         assertNotNull(caught,
 351                       String.format("No Exception was thrown, expected an Exception of %s to be thrown",
 352                                     expected.getName()));
 353         assertTrue(expected.isInstance(caught),
 354                    String.format("Exception thrown %s not an instance of %s",
 355                                  caught.getClass().getName(), expected.getName()));
 356     }
 357 
 358 }