• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.testing;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static junit.framework.Assert.assertTrue;
21 
22 import com.google.common.annotations.GwtCompatible;
23 import com.google.errorprone.annotations.CanIgnoreReturnValue;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.function.BiPredicate;
30 import java.util.stream.Collector;
31 import org.checkerframework.checker.nullness.qual.Nullable;
32 
33 /**
34  * Tester for {@code Collector} implementations.
35  *
36  * <p>Example usage:
37  *
38  * <pre>
39  * CollectorTester.of(Collectors.summingInt(Integer::parseInt))
40  *     .expectCollects(3, "1", "2")
41  *     .expectCollects(10, "1", "4", "3", "2")
42  *     .expectCollects(5, "-3", "0", "8");
43  * </pre>
44  *
45  * @author Louis Wasserman
46  * @since 21.0
47  */
48 @GwtCompatible
49 @ElementTypesAreNonnullByDefault
50 public final class CollectorTester<
51     T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> {
52   /**
53    * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code
54    * Collector} will be compared to the expected value using {@link Object#equals}.
55    */
56   public static <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
of(Collector<T, A, R> collector)57       CollectorTester<T, A, R> of(Collector<T, A, R> collector) {
58     return of(collector, Objects::equals);
59   }
60 
61   /**
62    * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code
63    * Collector} will be compared to the expected value using the specified {@code equivalence}.
64    */
65   public static <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
of( Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence)66       CollectorTester<T, A, R> of(
67           Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) {
68     return new CollectorTester<>(collector, equivalence);
69   }
70 
71   private final Collector<T, A, R> collector;
72   private final BiPredicate<? super R, ? super R> equivalence;
73 
CollectorTester( Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence)74   private CollectorTester(
75       Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) {
76     this.collector = checkNotNull(collector);
77     this.equivalence = checkNotNull(equivalence);
78   }
79 
80   /**
81    * Different orderings for combining the elements of an input array, which must all produce the
82    * same result.
83    */
84   enum CollectStrategy {
85     /** Get one accumulator and accumulate the elements into it sequentially. */
86     SEQUENTIAL {
87       @Override
88       final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
result(Collector<T, A, R> collector, Iterable<T> inputs)89           A result(Collector<T, A, R> collector, Iterable<T> inputs) {
90         A accum = collector.supplier().get();
91         for (T input : inputs) {
92           collector.accumulator().accept(accum, input);
93         }
94         return accum;
95       }
96     },
97     /** Get one accumulator for each element and merge the accumulators left-to-right. */
98     MERGE_LEFT_ASSOCIATIVE {
99       @Override
100       final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
result(Collector<T, A, R> collector, Iterable<T> inputs)101           A result(Collector<T, A, R> collector, Iterable<T> inputs) {
102         A accum = collector.supplier().get();
103         for (T input : inputs) {
104           A newAccum = collector.supplier().get();
105           collector.accumulator().accept(newAccum, input);
106           accum = collector.combiner().apply(accum, newAccum);
107         }
108         return accum;
109       }
110     },
111     /** Get one accumulator for each element and merge the accumulators right-to-left. */
112     MERGE_RIGHT_ASSOCIATIVE {
113       @Override
114       final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
result(Collector<T, A, R> collector, Iterable<T> inputs)115           A result(Collector<T, A, R> collector, Iterable<T> inputs) {
116         List<A> stack = new ArrayList<>();
117         for (T input : inputs) {
118           A newAccum = collector.supplier().get();
119           collector.accumulator().accept(newAccum, input);
120           push(stack, newAccum);
121         }
122         push(stack, collector.supplier().get());
123         while (stack.size() > 1) {
124           A right = pop(stack);
125           A left = pop(stack);
126           push(stack, collector.combiner().apply(left, right));
127         }
128         return pop(stack);
129       }
130 
push(List<E> stack, E value)131       <E extends @Nullable Object> void push(List<E> stack, E value) {
132         stack.add(value);
133       }
134 
pop(List<E> stack)135       <E extends @Nullable Object> E pop(List<E> stack) {
136         return stack.remove(stack.size() - 1);
137       }
138     };
139 
140     abstract <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
result(Collector<T, A, R> collector, Iterable<T> inputs)141         A result(Collector<T, A, R> collector, Iterable<T> inputs);
142   }
143 
144   /**
145    * Verifies that the specified expected result is always produced by collecting the specified
146    * inputs, regardless of how the elements are divided.
147    */
148   @SafeVarargs
149   @CanIgnoreReturnValue
150   @SuppressWarnings("nullness") // TODO(cpovirk): Remove after we fix whatever the bug is.
expectCollects(R expectedResult, T... inputs)151   public final CollectorTester<T, A, R> expectCollects(R expectedResult, T... inputs) {
152     List<T> list = Arrays.asList(inputs);
153     doExpectCollects(expectedResult, list);
154     if (collector.characteristics().contains(Collector.Characteristics.UNORDERED)) {
155       Collections.reverse(list);
156       doExpectCollects(expectedResult, list);
157     }
158     return this;
159   }
160 
doExpectCollects(R expectedResult, List<T> inputs)161   private void doExpectCollects(R expectedResult, List<T> inputs) {
162     for (CollectStrategy scheme : CollectStrategy.values()) {
163       A finalAccum = scheme.result(collector, inputs);
164       if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
165         @SuppressWarnings("unchecked") // `R` and `A` match for an `IDENTITY_FINISH`
166         R result = (R) finalAccum;
167         assertEquivalent(expectedResult, result);
168       }
169       assertEquivalent(expectedResult, collector.finisher().apply(finalAccum));
170     }
171   }
172 
assertEquivalent(R expected, R actual)173   private void assertEquivalent(R expected, R actual) {
174     assertTrue(
175         "Expected " + expected + " got " + actual + " modulo equivalence " + equivalence,
176         equivalence.test(expected, actual));
177   }
178 }
179