test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java

Print this page
rev 7597 : 8015318: Extend Collector with 'finish' operation
Reviewed-by:
Contributed-by: brian.goetz@oracle.com


   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 package org.openjdk.tests.java.util.stream;
  24 
  25 import java.util.ArrayList;

  26 import java.util.Collection;

  27 import java.util.HashMap;
  28 import java.util.HashSet;
  29 import java.util.Iterator;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.Optional;

  33 import java.util.TreeMap;
  34 import java.util.concurrent.ConcurrentHashMap;
  35 import java.util.concurrent.ConcurrentSkipListMap;
  36 import java.util.function.BinaryOperator;
  37 import java.util.function.Function;
  38 import java.util.function.Predicate;
  39 import java.util.function.Supplier;
  40 import java.util.stream.Collector;
  41 import java.util.stream.Collectors;
  42 import java.util.stream.LambdaTestHelpers;
  43 import java.util.stream.OpTestCase;
  44 import java.util.stream.Stream;
  45 import java.util.stream.StreamOpFlagTestHelper;
  46 import java.util.stream.StreamTestDataProvider;
  47 import java.util.stream.TestData;
  48 
  49 import org.testng.annotations.Test;
  50 
  51 import static java.util.stream.Collectors.groupingBy;
  52 import static java.util.stream.Collectors.groupingByConcurrent;
  53 import static java.util.stream.Collectors.partitioningBy;
  54 import static java.util.stream.Collectors.reducing;
  55 import static java.util.stream.Collectors.toCollection;

  56 import static java.util.stream.Collectors.toList;


  57 import static java.util.stream.LambdaTestHelpers.assertContents;
  58 import static java.util.stream.LambdaTestHelpers.assertContentsUnordered;
  59 import static java.util.stream.LambdaTestHelpers.mDoubler;
  60 
  61 /**
  62  * TabulatorsTest
  63  *
  64  * @author Brian Goetz
  65  */
  66 @SuppressWarnings({"rawtypes", "unchecked"})
  67 public class TabulatorsTest extends OpTestCase {
  68     // There are 8 versions of groupingBy:
  69     //   groupingBy: { map supplier, not } x { downstream collector, not } x { concurrent, not }
  70     // There are 2 versions of partition: { map supplier, not }
  71     // There are 4 versions of toMap
  72     //   mappedTo(function, mapSupplier?, mergeFunction?)
  73     // Each variety needs at least one test
  74     // Plus a variety of multi-level tests (groupBy(..., partition), partition(..., groupBy))
  75     // Plus negative tests for mapping to null
  76     // Each test should be matched by a nest of asserters (see TabulationAssertion...)
  77 
  78 
  79     private static abstract class TabulationAssertion<T, U> {
  80         abstract void assertValue(U value,
  81                                   Supplier<Stream<T>> source,
  82                                   boolean ordered) throws ReflectiveOperationException;
  83     }
  84 
  85     @SuppressWarnings({"rawtypes", "unchecked"})
  86     static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> {
  87         private final Class<? extends Map> clazz;
  88         private final Function<T, K> classifier;
  89         private final TabulationAssertion<T,V> downstream;
  90 
  91         protected GroupedMapAssertion(Function<T, K> classifier,
  92                                       Class<? extends Map> clazz,
  93                                       TabulationAssertion<T, V> downstream) {
  94             this.clazz = clazz;
  95             this.classifier = classifier;
  96             this.downstream = downstream;
  97         }
  98 
  99         void assertValue(M map,
 100                          Supplier<Stream<T>> source,
 101                          boolean ordered) throws ReflectiveOperationException {
 102             if (!clazz.isAssignableFrom(map.getClass()))
 103                 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
 104             assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(Collectors.toSet()));
 105             for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
 106                 K key = entry.getKey();
 107                 downstream.assertValue(entry.getValue(),
 108                                        () -> source.get().filter(e -> classifier.apply(e).equals(key)),
 109                                        ordered);
 110             }
 111         }
 112     }
 113 

































 114     static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
 115         private final Predicate<T> predicate;
 116         private final TabulationAssertion<T,D> downstream;
 117 
 118         protected PartitionAssertion(Predicate<T> predicate,
 119                                      TabulationAssertion<T, D> downstream) {
 120             this.predicate = predicate;
 121             this.downstream = downstream;
 122         }
 123 
 124         void assertValue(Map<Boolean, D> map,
 125                          Supplier<Stream<T>> source,
 126                          boolean ordered) throws ReflectiveOperationException {
 127             if (!Map.class.isAssignableFrom(map.getClass()))
 128                 fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass()));
 129             assertEquals(2, map.size());
 130             downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
 131             downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
 132         }
 133     }


 187             this.reducer = reducer;
 188         }
 189 
 190         @Override
 191         void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
 192                 throws ReflectiveOperationException {
 193             Optional<U> reduced = source.get().map(mapper).reduce(reducer);
 194             if (value == null)
 195                 assertTrue(!reduced.isPresent());
 196             else if (!reduced.isPresent()) {
 197                 assertEquals(value, identity);
 198             }
 199             else {
 200                 assertEquals(value, reduced.get());
 201             }
 202         }
 203     }
 204 
 205     private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
 206         return (act, exp, ord, par) -> {
 207             if (par & (!ordered || !ord)) {
 208                 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
 209             }
 210             else {
 211                 LambdaTestHelpers.assertContentsEqual(act, exp);
 212             }
 213         };
 214     }
 215 
 216     private<T, M extends Map>
 217     void exerciseMapTabulation(TestData<T, Stream<T>> data,
 218                                Collector<T, ? extends M> collector,
 219                                TabulationAssertion<T, M> assertion)
 220             throws ReflectiveOperationException {
 221         boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
 222 
 223         M m = withData(data)
 224                 .terminal(s -> s.collect(collector))
 225                 .resultAsserter(mapTabulationAsserter(ordered))
 226                 .exercise();
 227         assertion.assertValue(m, () -> data.stream(), ordered);
 228 
 229         m = withData(data)
 230                 .terminal(s -> s.unordered().collect(collector))
 231                 .resultAsserter(mapTabulationAsserter(ordered))
 232                 .exercise();
 233         assertion.assertValue(m, () -> data.stream(), false);
 234     }
 235 
 236     private static void nestedMapEqualityAssertion(Object o1, Object o2) {
 237         if (o1 instanceof Map) {
 238             Map m1 = (Map) o1;
 239             Map m2 = (Map) o2;
 240             assertContentsUnordered(m1.keySet(), m2.keySet());
 241             for (Object k : m1.keySet())
 242                 nestedMapEqualityAssertion(m1.get(k), m2.get(k));
 243         }
 244         else if (o1 instanceof Collection) {
 245             assertContentsUnordered(((Collection) o1), ((Collection) o2));
 246         }
 247         else
 248             assertEquals(o1, o2);













































































































































 249     }
 250 
 251     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 252     public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 253         Function<Integer, Integer> classifier = i -> i % 3;
 254 
 255         // Single-level groupBy
 256         exerciseMapTabulation(data, groupingBy(classifier),
 257                               new GroupedMapAssertion<>(classifier, HashMap.class,
 258                                                         new ListAssertion<>()));
 259         exerciseMapTabulation(data, groupingByConcurrent(classifier),
 260                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 261                                                         new ListAssertion<>()));
 262 
 263         // With explicit constructors
 264         exerciseMapTabulation(data,
 265                               groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
 266                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 267                                                         new CollectionAssertion<Integer>(HashSet.class, false)));
 268         exerciseMapTabulation(data,




   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 package org.openjdk.tests.java.util.stream;
  24 
  25 import java.util.ArrayList;
  26 import java.util.Arrays;
  27 import java.util.Collection;
  28 import java.util.Comparator;
  29 import java.util.HashMap;
  30 import java.util.HashSet;
  31 import java.util.Iterator;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.Optional;
  35 import java.util.Set;
  36 import java.util.TreeMap;
  37 import java.util.concurrent.ConcurrentHashMap;
  38 import java.util.concurrent.ConcurrentSkipListMap;
  39 import java.util.function.BinaryOperator;
  40 import java.util.function.Function;
  41 import java.util.function.Predicate;
  42 import java.util.function.Supplier;
  43 import java.util.stream.Collector;
  44 import java.util.stream.Collectors;
  45 import java.util.stream.LambdaTestHelpers;
  46 import java.util.stream.OpTestCase;
  47 import java.util.stream.Stream;
  48 import java.util.stream.StreamOpFlagTestHelper;
  49 import java.util.stream.StreamTestDataProvider;
  50 import java.util.stream.TestData;
  51 
  52 import org.testng.annotations.Test;
  53 
  54 import static java.util.stream.Collectors.groupingBy;
  55 import static java.util.stream.Collectors.groupingByConcurrent;
  56 import static java.util.stream.Collectors.partitioningBy;
  57 import static java.util.stream.Collectors.reducing;
  58 import static java.util.stream.Collectors.toCollection;
  59 import static java.util.stream.Collectors.toConcurrentMap;
  60 import static java.util.stream.Collectors.toList;
  61 import static java.util.stream.Collectors.toMap;
  62 import static java.util.stream.Collectors.toSet;
  63 import static java.util.stream.LambdaTestHelpers.assertContents;
  64 import static java.util.stream.LambdaTestHelpers.assertContentsUnordered;
  65 import static java.util.stream.LambdaTestHelpers.mDoubler;
  66 
  67 /**
  68  * TabulatorsTest
  69  *
  70  * @author Brian Goetz
  71  */
  72 @SuppressWarnings({"rawtypes", "unchecked"})
  73 public class TabulatorsTest extends OpTestCase {










  74 
  75     private static abstract class TabulationAssertion<T, U> {
  76         abstract void assertValue(U value,
  77                                   Supplier<Stream<T>> source,
  78                                   boolean ordered) throws ReflectiveOperationException;
  79     }
  80 
  81     @SuppressWarnings({"rawtypes", "unchecked"})
  82     static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> {
  83         private final Class<? extends Map> clazz;
  84         private final Function<T, K> classifier;
  85         private final TabulationAssertion<T,V> downstream;
  86 
  87         protected GroupedMapAssertion(Function<T, K> classifier,
  88                                       Class<? extends Map> clazz,
  89                                       TabulationAssertion<T, V> downstream) {
  90             this.clazz = clazz;
  91             this.classifier = classifier;
  92             this.downstream = downstream;
  93         }
  94 
  95         void assertValue(M map,
  96                          Supplier<Stream<T>> source,
  97                          boolean ordered) throws ReflectiveOperationException {
  98             if (!clazz.isAssignableFrom(map.getClass()))
  99                 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
 100             assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet()));
 101             for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
 102                 K key = entry.getKey();
 103                 downstream.assertValue(entry.getValue(),
 104                                        () -> source.get().filter(e -> classifier.apply(e).equals(key)),
 105                                        ordered);
 106             }
 107         }
 108     }
 109 
 110     static class ToMapAssertion<T, K, V, M extends Map<K,V>> extends TabulationAssertion<T, M> {
 111         private final Class<? extends Map> clazz;
 112         private final Function<T, K> keyFn;
 113         private final Function<T, V> valueFn;
 114         private final BinaryOperator<V> mergeFn;
 115 
 116         ToMapAssertion(Function<T, K> keyFn,
 117                        Function<T, V> valueFn,
 118                        BinaryOperator<V> mergeFn,
 119                        Class<? extends Map> clazz) {
 120             this.clazz = clazz;
 121             this.keyFn = keyFn;
 122             this.valueFn = valueFn;
 123             this.mergeFn = mergeFn;
 124         }
 125 
 126         @Override
 127         void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
 128             Set<K> uniqueKeys = source.get().map(keyFn).collect(toSet());
 129             assertTrue(clazz.isAssignableFrom(map.getClass()));
 130             assertEquals(uniqueKeys, map.keySet());
 131             source.get().forEach(t -> {
 132                 K key = keyFn.apply(t);
 133                 V v = source.get()
 134                             .filter(e -> key.equals(keyFn.apply(e)))
 135                             .map(valueFn)
 136                             .reduce(mergeFn)
 137                             .get();
 138                 assertEquals(map.get(key), v);
 139             });
 140         }
 141     }
 142 
 143     static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
 144         private final Predicate<T> predicate;
 145         private final TabulationAssertion<T,D> downstream;
 146 
 147         protected PartitionAssertion(Predicate<T> predicate,
 148                                      TabulationAssertion<T, D> downstream) {
 149             this.predicate = predicate;
 150             this.downstream = downstream;
 151         }
 152 
 153         void assertValue(Map<Boolean, D> map,
 154                          Supplier<Stream<T>> source,
 155                          boolean ordered) throws ReflectiveOperationException {
 156             if (!Map.class.isAssignableFrom(map.getClass()))
 157                 fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass()));
 158             assertEquals(2, map.size());
 159             downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
 160             downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
 161         }
 162     }


 216             this.reducer = reducer;
 217         }
 218 
 219         @Override
 220         void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
 221                 throws ReflectiveOperationException {
 222             Optional<U> reduced = source.get().map(mapper).reduce(reducer);
 223             if (value == null)
 224                 assertTrue(!reduced.isPresent());
 225             else if (!reduced.isPresent()) {
 226                 assertEquals(value, identity);
 227             }
 228             else {
 229                 assertEquals(value, reduced.get());
 230             }
 231         }
 232     }
 233 
 234     private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
 235         return (act, exp, ord, par) -> {
 236             if (par && (!ordered || !ord)) {
 237                 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
 238             }
 239             else {
 240                 LambdaTestHelpers.assertContentsEqual(act, exp);
 241             }
 242         };
 243     }
 244 
 245     private<T, M extends Map>
 246     void exerciseMapTabulation(TestData<T, Stream<T>> data,
 247                                Collector<T, ?, ? extends M> collector,
 248                                TabulationAssertion<T, M> assertion)
 249             throws ReflectiveOperationException {
 250         boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
 251 
 252         M m = withData(data)
 253                 .terminal(s -> s.collect(collector))
 254                 .resultAsserter(mapTabulationAsserter(ordered))
 255                 .exercise();
 256         assertion.assertValue(m, () -> data.stream(), ordered);
 257 
 258         m = withData(data)
 259                 .terminal(s -> s.unordered().collect(collector))
 260                 .resultAsserter(mapTabulationAsserter(ordered))
 261                 .exercise();
 262         assertion.assertValue(m, () -> data.stream(), false);
 263     }
 264 
 265     private static void nestedMapEqualityAssertion(Object o1, Object o2) {
 266         if (o1 instanceof Map) {
 267             Map m1 = (Map) o1;
 268             Map m2 = (Map) o2;
 269             assertContentsUnordered(m1.keySet(), m2.keySet());
 270             for (Object k : m1.keySet())
 271                 nestedMapEqualityAssertion(m1.get(k), m2.get(k));
 272         }
 273         else if (o1 instanceof Collection) {
 274             assertContentsUnordered(((Collection) o1), ((Collection) o2));
 275         }
 276         else
 277             assertEquals(o1, o2);
 278     }
 279 
 280     private<T, R> void assertCollect(TestData.OfRef<T> data,
 281                                      Collector<T, ?, R> collector,
 282                                      Function<Stream<T>, R> streamReduction) {
 283         R check = streamReduction.apply(data.stream());
 284         withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise();
 285     }
 286 
 287     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 288     public void testReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 289         assertCollect(data, Collectors.reducing(0, Integer::sum),
 290                       s -> s.reduce(0, Integer::sum));
 291         assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min),
 292                       s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE));
 293         assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max),
 294                       s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE));
 295 
 296         assertCollect(data, Collectors.reducing(Integer::sum),
 297                       s -> s.reduce(Integer::sum));
 298         assertCollect(data, Collectors.minBy(Comparator.naturalOrder()),
 299                       s -> s.min(Integer::compare));
 300         assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()),
 301                       s -> s.max(Integer::compare));
 302 
 303         assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum),
 304                       s -> s.map(x -> x*2).reduce(0, Integer::sum));
 305 
 306         assertCollect(data, Collectors.summingLong(x -> x * 2L),
 307                       s -> s.map(x -> x*2L).reduce(0L, Long::sum));
 308         assertCollect(data, Collectors.summingInt(x -> x * 2),
 309                       s -> s.map(x -> x*2).reduce(0, Integer::sum));
 310         assertCollect(data, Collectors.summingDouble(x -> x * 2.0d),
 311                       s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum));
 312 
 313         assertCollect(data, Collectors.averagingInt(x -> x * 2),
 314                       s -> s.mapToInt(x -> x * 2).average().orElse(0));
 315         assertCollect(data, Collectors.averagingLong(x -> x * 2),
 316                       s -> s.mapToLong(x -> x * 2).average().orElse(0));
 317         assertCollect(data, Collectors.averagingDouble(x -> x * 2),
 318                       s -> s.mapToDouble(x -> x * 2).average().orElse(0));
 319 
 320         // Test explicit Collector.of
 321         Collector<Integer, long[], Double> avg2xint = Collector.of(() -> new long[2],
 322                                                                    (a, b) -> {
 323                                                                        a[0] += b * 2;
 324                                                                        a[1]++;
 325                                                                    },
 326                                                                    (a, b) -> {
 327                                                                        a[0] += b[0];
 328                                                                        a[1] += b[1];
 329                                                                        return a;
 330                                                                    },
 331                                                                    a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]);
 332         assertCollect(data, avg2xint,
 333                       s -> s.mapToInt(x -> x * 2).average().orElse(0));
 334     }
 335 
 336     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 337     public void testJoin(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 338         withData(data)
 339                 .terminal(s -> s.map(Object::toString).collect(Collectors.joining()))
 340                 .expectedResult(join(data, ""))
 341                 .exercise();
 342 
 343         Collector<String, StringBuilder, String> likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString);
 344         withData(data)
 345                 .terminal(s -> s.map(Object::toString).collect(likeJoining))
 346                 .expectedResult(join(data, ""))
 347                 .exercise();
 348 
 349         withData(data)
 350                 .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",")))
 351                 .expectedResult(join(data, ","))
 352                 .exercise();
 353 
 354         withData(data)
 355                 .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]")))
 356                 .expectedResult("[" + join(data, ",") + "]")
 357                 .exercise();
 358     }
 359 
 360     private<T> String join(TestData.OfRef<T> data, String delim) {
 361         StringBuilder sb = new StringBuilder();
 362         boolean first = true;
 363         for (T i : data) {
 364             if (!first)
 365                 sb.append(delim);
 366             sb.append(i.toString());
 367             first = false;
 368         }
 369         return sb.toString();
 370     }
 371 
 372     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 373     public void testSimpleToMap(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 374         Function<Integer, Integer> keyFn = i -> i * 2;
 375         Function<Integer, Integer> valueFn = i -> i * 4;
 376 
 377         List<Integer> dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new));
 378         Set<Integer> dataAsSet = new HashSet<>(dataAsList);
 379 
 380         BinaryOperator<Integer> sum = Integer::sum;
 381         for (BinaryOperator<Integer> op : Arrays.asList((u, v) -> u,
 382                                                         (u, v) -> v,
 383                                                         sum)) {
 384             try {
 385                 exerciseMapTabulation(data, toMap(keyFn, valueFn),
 386                                       new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
 387                 if (dataAsList.size() != dataAsSet.size())
 388                     fail("Expected ISE on input with duplicates");
 389             }
 390             catch (IllegalStateException e) {
 391                 if (dataAsList.size() == dataAsSet.size())
 392                     fail("Expected no ISE on input without duplicates");
 393             }
 394 
 395             exerciseMapTabulation(data, toMap(keyFn, valueFn, op),
 396                                   new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
 397 
 398             exerciseMapTabulation(data, toMap(keyFn, valueFn, op, TreeMap::new),
 399                                   new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class));
 400         }
 401 
 402         // For concurrent maps, only use commutative merge functions
 403         try {
 404             exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn),
 405                                   new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
 406             if (dataAsList.size() != dataAsSet.size())
 407                 fail("Expected ISE on input with duplicates");
 408         }
 409         catch (IllegalStateException e) {
 410             if (dataAsList.size() == dataAsSet.size())
 411                 fail("Expected no ISE on input without duplicates");
 412         }
 413 
 414         exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum),
 415                               new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
 416 
 417         exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new),
 418                               new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class));
 419     }
 420 
 421     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 422     public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 423         Function<Integer, Integer> classifier = i -> i % 3;
 424 
 425         // Single-level groupBy
 426         exerciseMapTabulation(data, groupingBy(classifier),
 427                               new GroupedMapAssertion<>(classifier, HashMap.class,
 428                                                         new ListAssertion<>()));
 429         exerciseMapTabulation(data, groupingByConcurrent(classifier),
 430                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 431                                                         new ListAssertion<>()));
 432 
 433         // With explicit constructors
 434         exerciseMapTabulation(data,
 435                               groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
 436                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 437                                                         new CollectionAssertion<Integer>(HashSet.class, false)));
 438         exerciseMapTabulation(data,