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