001/* 002 * Copyright (C) 2015 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.testing; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020import static junit.framework.Assert.assertTrue; 021 022import com.google.common.annotations.GwtCompatible; 023import com.google.errorprone.annotations.CanIgnoreReturnValue; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.List; 028import java.util.Objects; 029import java.util.function.BiPredicate; 030import java.util.stream.Collector; 031import org.checkerframework.checker.nullness.qual.Nullable; 032 033/** 034 * Tester for {@code Collector} implementations. 035 * 036 * <p>Example usage: 037 * 038 * <pre> 039 * CollectorTester.of(Collectors.summingInt(Integer::parseInt)) 040 * .expectCollects(3, "1", "2") 041 * .expectCollects(10, "1", "4", "3", "2") 042 * .expectCollects(5, "-3", "0", "8"); 043 * </pre> 044 * 045 * @author Louis Wasserman 046 * @since 21.0 047 */ 048@GwtCompatible 049@ElementTypesAreNonnullByDefault 050public final class CollectorTester< 051 T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> { 052 /** 053 * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code 054 * Collector} will be compared to the expected value using {@link Object#equals}. 055 */ 056 public static <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> 057 CollectorTester<T, A, R> of(Collector<T, A, R> collector) { 058 return of(collector, Objects::equals); 059 } 060 061 /** 062 * Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code 063 * Collector} will be compared to the expected value using the specified {@code equivalence}. 064 */ 065 public static <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> 066 CollectorTester<T, A, R> of( 067 Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) { 068 return new CollectorTester<>(collector, equivalence); 069 } 070 071 private final Collector<T, A, R> collector; 072 private final BiPredicate<? super R, ? super R> equivalence; 073 074 private CollectorTester( 075 Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) { 076 this.collector = checkNotNull(collector); 077 this.equivalence = checkNotNull(equivalence); 078 } 079 080 /** 081 * Different orderings for combining the elements of an input array, which must all produce the 082 * same result. 083 */ 084 enum CollectStrategy { 085 /** Get one accumulator and accumulate the elements into it sequentially. */ 086 SEQUENTIAL { 087 @Override 088 final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> 089 A result(Collector<T, A, R> collector, Iterable<T> inputs) { 090 A accum = collector.supplier().get(); 091 for (T input : inputs) { 092 collector.accumulator().accept(accum, input); 093 } 094 return accum; 095 } 096 }, 097 /** Get one accumulator for each element and merge the accumulators left-to-right. */ 098 MERGE_LEFT_ASSOCIATIVE { 099 @Override 100 final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> 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> 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 131 <E extends @Nullable Object> void push(List<E> stack, E value) { 132 stack.add(value); 133 } 134 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> 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. 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 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 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}