| /* |
| * 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 org.checkerframework.checker.nullness.qual.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)); |
| } |
| } |