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