• 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 java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.EnumSet;
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 public final class CollectorTester<T, A, R> {
50   /**
51    * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code
52    * Collector} will be compared to the expected value using {@link Object.equals}.
53    */
of(Collector<T, A, R> collector)54   public static <T, A, R> CollectorTester<T, A, R> of(Collector<T, A, R> collector) {
55     return of(collector, Objects::equals);
56   }
57 
58   /**
59    * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code
60    * Collector} will be compared to the expected value using the specified {@code equivalence}.
61    */
of( Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence)62   public static <T, A, R> CollectorTester<T, A, R> of(
63       Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) {
64     return new CollectorTester<>(collector, equivalence);
65   }
66 
67   private final Collector<T, A, R> collector;
68   private final BiPredicate<? super R, ? super R> equivalence;
69 
CollectorTester( Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence)70   private CollectorTester(
71       Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) {
72     this.collector = checkNotNull(collector);
73     this.equivalence = checkNotNull(equivalence);
74   }
75 
76   /**
77    * Different orderings for combining the elements of an input array, which must all produce the
78    * same result.
79    */
80   enum CollectStrategy {
81     /** Get one accumulator and accumulate the elements into it sequentially. */
82     SEQUENTIAL {
83       @Override
result(Collector<T, A, R> collector, Iterable<T> inputs)84       final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) {
85         A accum = collector.supplier().get();
86         for (T input : inputs) {
87           collector.accumulator().accept(accum, input);
88         }
89         return accum;
90       }
91     },
92     /** Get one accumulator for each element and merge the accumulators left-to-right. */
93     MERGE_LEFT_ASSOCIATIVE {
94       @Override
result(Collector<T, A, R> collector, Iterable<T> inputs)95       final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) {
96         A accum = collector.supplier().get();
97         for (T input : inputs) {
98           A newAccum = collector.supplier().get();
99           collector.accumulator().accept(newAccum, input);
100           accum = collector.combiner().apply(accum, newAccum);
101         }
102         return accum;
103       }
104     },
105     /** Get one accumulator for each element and merge the accumulators right-to-left. */
106     MERGE_RIGHT_ASSOCIATIVE {
107       @Override
result(Collector<T, A, R> collector, Iterable<T> inputs)108       final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) {
109         List<A> stack = new ArrayList<>();
110         for (T input : inputs) {
111           A newAccum = collector.supplier().get();
112           collector.accumulator().accept(newAccum, input);
113           push(stack, newAccum);
114         }
115         push(stack, collector.supplier().get());
116         while (stack.size() > 1) {
117           A right = pop(stack);
118           A left = pop(stack);
119           push(stack, collector.combiner().apply(left, right));
120         }
121         return pop(stack);
122       }
123 
push(List<E> stack, E value)124       <E> void push(List<E> stack, E value) {
125         stack.add(value);
126       }
127 
pop(List<E> stack)128       <E> E pop(List<E> stack) {
129         return stack.remove(stack.size() - 1);
130       }
131     };
132 
result(Collector<T, A, R> collector, Iterable<T> inputs)133     abstract <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs);
134   }
135 
136   /**
137    * Verifies that the specified expected result is always produced by collecting the specified
138    * inputs, regardless of how the elements are divided.
139    */
140   @SafeVarargs
expectCollects(@ullable R expectedResult, T... inputs)141   public final CollectorTester<T, A, R> expectCollects(@Nullable R expectedResult, T... inputs) {
142     List<T> list = Arrays.asList(inputs);
143     doExpectCollects(expectedResult, list);
144     if (collector.characteristics().contains(Collector.Characteristics.UNORDERED)) {
145       Collections.reverse(list);
146       doExpectCollects(expectedResult, list);
147     }
148     return this;
149   }
150 
doExpectCollects(@ullable R expectedResult, List<T> inputs)151   private void doExpectCollects(@Nullable R expectedResult, List<T> inputs) {
152     for (CollectStrategy scheme : EnumSet.allOf(CollectStrategy.class)) {
153       A finalAccum = scheme.result(collector, inputs);
154       if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
155         assertEquivalent(expectedResult, (R) finalAccum);
156       }
157       assertEquivalent(expectedResult, collector.finisher().apply(finalAccum));
158     }
159   }
160 
assertEquivalent(@ullable R expected, @Nullable R actual)161   private void assertEquivalent(@Nullable R expected, @Nullable R actual) {
162     assertTrue(
163         "Expected " + expected + " got " + actual + " modulo equivalence " + equivalence,
164         equivalence.test(expected, actual));
165   }
166 }
167