blob: 9478a1fd20cf7023b7e33ffdd6596fa130a0e920 [file] [log] [blame]
/*
* 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));
}
}