--- old/test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java 2013-07-05 16:29:57.362020203 -0700 +++ new/test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java 2013-07-05 16:29:53.542020271 -0700 @@ -23,13 +23,16 @@ package org.openjdk.tests.java.util.stream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; @@ -53,7 +56,10 @@ import static java.util.stream.Collectors.partitioningBy; import static java.util.stream.Collectors.reducing; import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toConcurrentMap; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; import static java.util.stream.LambdaTestHelpers.assertContents; import static java.util.stream.LambdaTestHelpers.assertContentsUnordered; import static java.util.stream.LambdaTestHelpers.mDoubler; @@ -65,16 +71,6 @@ */ @SuppressWarnings({"rawtypes", "unchecked"}) public class TabulatorsTest extends OpTestCase { - // There are 8 versions of groupingBy: - // groupingBy: { map supplier, not } x { downstream collector, not } x { concurrent, not } - // There are 2 versions of partition: { map supplier, not } - // There are 4 versions of toMap - // mappedTo(function, mapSupplier?, mergeFunction?) - // Each variety needs at least one test - // Plus a variety of multi-level tests (groupBy(..., partition), partition(..., groupBy)) - // Plus negative tests for mapping to null - // Each test should be matched by a nest of asserters (see TabulationAssertion...) - private static abstract class TabulationAssertion { abstract void assertValue(U value, @@ -101,7 +97,7 @@ boolean ordered) throws ReflectiveOperationException { if (!clazz.isAssignableFrom(map.getClass())) fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass())); - assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(Collectors.toSet())); + assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet())); for (Map.Entry entry : map.entrySet()) { K key = entry.getKey(); downstream.assertValue(entry.getValue(), @@ -111,6 +107,39 @@ } } + static class ToMapAssertion> extends TabulationAssertion { + private final Class clazz; + private final Function keyFn; + private final Function valueFn; + private final BinaryOperator mergeFn; + + ToMapAssertion(Function keyFn, + Function valueFn, + BinaryOperator mergeFn, + Class clazz) { + this.clazz = clazz; + this.keyFn = keyFn; + this.valueFn = valueFn; + this.mergeFn = mergeFn; + } + + @Override + void assertValue(M map, Supplier> source, boolean ordered) throws ReflectiveOperationException { + Set uniqueKeys = source.get().map(keyFn).collect(toSet()); + assertTrue(clazz.isAssignableFrom(map.getClass())); + assertEquals(uniqueKeys, map.keySet()); + source.get().forEach(t -> { + K key = keyFn.apply(t); + V v = source.get() + .filter(e -> key.equals(keyFn.apply(e))) + .map(valueFn) + .reduce(mergeFn) + .get(); + assertEquals(map.get(key), v); + }); + } + } + static class PartitionAssertion extends TabulationAssertion> { private final Predicate predicate; private final TabulationAssertion downstream; @@ -204,7 +233,7 @@ private ResultAsserter mapTabulationAsserter(boolean ordered) { return (act, exp, ord, par) -> { - if (par & (!ordered || !ord)) { + if (par && (!ordered || !ord)) { TabulatorsTest.nestedMapEqualityAssertion(act, exp); } else { @@ -215,7 +244,7 @@ private void exerciseMapTabulation(TestData> data, - Collector collector, + Collector collector, TabulationAssertion assertion) throws ReflectiveOperationException { boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED); @@ -248,6 +277,147 @@ assertEquals(o1, o2); } + private void assertCollect(TestData.OfRef data, + Collector collector, + Function, R> streamReduction) { + R check = streamReduction.apply(data.stream()); + withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise(); + } + + @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) + public void testReduce(String name, TestData.OfRef data) throws ReflectiveOperationException { + assertCollect(data, Collectors.reducing(0, Integer::sum), + s -> s.reduce(0, Integer::sum)); + assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min), + s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE)); + assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max), + s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE)); + + assertCollect(data, Collectors.reducing(Integer::sum), + s -> s.reduce(Integer::sum)); + assertCollect(data, Collectors.minBy(Comparator.naturalOrder()), + s -> s.min(Integer::compare)); + assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()), + s -> s.max(Integer::compare)); + + assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum), + s -> s.map(x -> x*2).reduce(0, Integer::sum)); + + assertCollect(data, Collectors.summingLong(x -> x * 2L), + s -> s.map(x -> x*2L).reduce(0L, Long::sum)); + assertCollect(data, Collectors.summingInt(x -> x * 2), + s -> s.map(x -> x*2).reduce(0, Integer::sum)); + assertCollect(data, Collectors.summingDouble(x -> x * 2.0d), + s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum)); + + assertCollect(data, Collectors.averagingInt(x -> x * 2), + s -> s.mapToInt(x -> x * 2).average().orElse(0)); + assertCollect(data, Collectors.averagingLong(x -> x * 2), + s -> s.mapToLong(x -> x * 2).average().orElse(0)); + assertCollect(data, Collectors.averagingDouble(x -> x * 2), + s -> s.mapToDouble(x -> x * 2).average().orElse(0)); + + // Test explicit Collector.of + Collector avg2xint = Collector.of(() -> new long[2], + (a, b) -> { + a[0] += b * 2; + a[1]++; + }, + (a, b) -> { + a[0] += b[0]; + a[1] += b[1]; + return a; + }, + a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]); + assertCollect(data, avg2xint, + s -> s.mapToInt(x -> x * 2).average().orElse(0)); + } + + @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) + public void testJoin(String name, TestData.OfRef data) throws ReflectiveOperationException { + withData(data) + .terminal(s -> s.map(Object::toString).collect(Collectors.joining())) + .expectedResult(join(data, "")) + .exercise(); + + Collector likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString); + withData(data) + .terminal(s -> s.map(Object::toString).collect(likeJoining)) + .expectedResult(join(data, "")) + .exercise(); + + withData(data) + .terminal(s -> s.map(Object::toString).collect(Collectors.joining(","))) + .expectedResult(join(data, ",")) + .exercise(); + + withData(data) + .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]"))) + .expectedResult("[" + join(data, ",") + "]") + .exercise(); + } + + private String join(TestData.OfRef data, String delim) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (T i : data) { + if (!first) + sb.append(delim); + sb.append(i.toString()); + first = false; + } + return sb.toString(); + } + + @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) + public void testSimpleToMap(String name, TestData.OfRef data) throws ReflectiveOperationException { + Function keyFn = i -> i * 2; + Function valueFn = i -> i * 4; + + List dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new)); + Set dataAsSet = new HashSet<>(dataAsList); + + BinaryOperator sum = Integer::sum; + for (BinaryOperator op : Arrays.asList((u, v) -> u, + (u, v) -> v, + sum)) { + try { + exerciseMapTabulation(data, toMap(keyFn, valueFn), + new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class)); + if (dataAsList.size() != dataAsSet.size()) + fail("Expected ISE on input with duplicates"); + } + catch (IllegalStateException e) { + if (dataAsList.size() == dataAsSet.size()) + fail("Expected no ISE on input without duplicates"); + } + + exerciseMapTabulation(data, toMap(keyFn, valueFn, op), + new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class)); + + exerciseMapTabulation(data, toMap(keyFn, valueFn, op, TreeMap::new), + new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class)); + } + + // For concurrent maps, only use commutative merge functions + try { + exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn), + new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class)); + if (dataAsList.size() != dataAsSet.size()) + fail("Expected ISE on input with duplicates"); + } + catch (IllegalStateException e) { + if (dataAsList.size() == dataAsSet.size()) + fail("Expected no ISE on input without duplicates"); + } + + exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum), + new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class)); + + exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new), + new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class)); + } + @Test(dataProvider = "StreamTestData", dataProviderClass = StreamTestDataProvider.class) public void testSimpleGroupBy(String name, TestData.OfRef data) throws ReflectiveOperationException { Function classifier = i -> i % 3;