/* * Copyright (C) 2015 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.testing; import static com.google.common.base.Preconditions.checkNotNull; import static junit.framework.Assert.assertTrue; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.function.BiPredicate; import java.util.stream.Collector; import javax.annotation.Nullable; /** * Tester for {@code Collector} implementations. * * <p>Example usage: * <pre> * CollectorTester.of(Collectors.summingInt(Integer::parseInt)) * .expectCollects(3, "1", "2") * .expectCollects(10, "1", "4", "3", "2") * .expectCollects(5, "-3", "0", "8"); * </pre> * * @author Louis Wasserman * @since 21.0 */ @Beta @GwtCompatible public final class CollectorTester<T, A, R> { /** * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code * Collector} will be compared to the expected value using {@link Object.equals}. */ public static <T, A, R> CollectorTester<T, A, R> of(Collector<T, A, R> collector) { return of(collector, Objects::equals); } /** * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code * Collector} will be compared to the expected value using the specified {@code equivalence}. */ public static <T, A, R> CollectorTester<T, A, R> of(Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) { return new CollectorTester<>(collector, equivalence); } private final Collector<T, A, R> collector; private final BiPredicate<? super R, ? super R> equivalence; private CollectorTester( Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) { this.collector = checkNotNull(collector); this.equivalence = checkNotNull(equivalence); } /** * Different orderings for combining the elements of an input array, which must * all produce the same result. */ enum CollectStrategy { /** * Get one accumulator and accumulate the elements into it sequentially. */ SEQUENTIAL { @Override final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) { A accum = collector.supplier().get(); for (T input : inputs) { collector.accumulator().accept(accum, input); } return accum; } }, /** * Get one accumulator for each element and merge the accumulators * left-to-right. */ MERGE_LEFT_ASSOCIATIVE { @Override final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) { A accum = collector.supplier().get(); for (T input : inputs) { A newAccum = collector.supplier().get(); collector.accumulator().accept(newAccum, input); accum = collector.combiner().apply(accum, newAccum); } return accum; } }, /** * Get one accumulator for each element and merge the accumulators * right-to-left. */ MERGE_RIGHT_ASSOCIATIVE { @Override final <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs) { List<A> stack = new ArrayList<>(); for (T input : inputs) { A newAccum = collector.supplier().get(); collector.accumulator().accept(newAccum, input); push(stack, newAccum); } push(stack, collector.supplier().get()); while (stack.size() > 1) { A right = pop(stack); A left = pop(stack); push(stack, collector.combiner().apply(left, right)); } return pop(stack); } <E> void push(List<E> stack, E value) { stack.add(value); } <E> E pop(List<E> stack) { return stack.remove(stack.size() - 1); } }; abstract <T, A, R> A result(Collector<T, A, R> collector, Iterable<T> inputs); } /** * Verifies that the specified expected result is always produced by collecting the * specified inputs, regardless of how the elements are divided. */ @SafeVarargs public final CollectorTester<T, A, R> expectCollects(@Nullable R expectedResult, T... inputs) { List<T> list = Arrays.asList(inputs); doExpectCollects(expectedResult, list); if (collector.characteristics().contains(Collector.Characteristics.UNORDERED)) { Collections.reverse(list); doExpectCollects(expectedResult, list); } return this; } private void doExpectCollects(@Nullable R expectedResult, List<T> inputs) { for (CollectStrategy scheme : EnumSet.allOf(CollectStrategy.class)) { A finalAccum = scheme.result(collector, inputs); if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { assertEquivalent(expectedResult, (R) finalAccum); } assertEquivalent(expectedResult, collector.finisher().apply(finalAccum)); } } private void assertEquivalent(@Nullable R expected, @Nullable R actual) { assertTrue( "Expected " + expected + " got " + actual + " modulo equivalence " + equivalence, equivalence.test(expected, actual)); } }