Skip to content

Commit 294c251

Browse files
cpovirkGoogle Java Core Libraries
authored andcommitted
Run MoreCollectorsTest and TableCollectorsTest as part of the normal collect Android test target.
As part of doing so, make `CollectorTester` available to external Android users. Internally, we'd been running them as part of a separate target from back in the days before we open-sourced our `Collector` APIs. Now that we've open-sourced those APIs, we can run their tests normally. RELNOTES=`testing`: Made `CollectorTester` available to Android users. PiperOrigin-RevId: 759308083
1 parent ce9f87c commit 294c251

File tree

4 files changed

+567
-1
lines changed

4 files changed

+567
-1
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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 com.google.errorprone.annotations.CanIgnoreReturnValue;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.Collections;
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.jspecify.annotations.NullMarked;
32+
import org.jspecify.annotations.Nullable;
33+
34+
/**
35+
* Tester for {@code Collector} implementations.
36+
*
37+
* <p>Example usage:
38+
*
39+
* <pre>
40+
* CollectorTester.of(Collectors.summingInt(Integer::parseInt))
41+
* .expectCollects(3, "1", "2")
42+
* .expectCollects(10, "1", "4", "3", "2")
43+
* .expectCollects(5, "-3", "0", "8");
44+
* </pre>
45+
*
46+
* @author Louis Wasserman
47+
* @since NEXT (but since 21.0 in the JRE flavor)
48+
*/
49+
@GwtCompatible
50+
@NullMarked
51+
@IgnoreJRERequirement // Users will use this only if they're already using Collector.
52+
public final class CollectorTester<
53+
T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object> {
54+
/**
55+
* Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code
56+
* Collector} will be compared to the expected value using {@link Object#equals}.
57+
*/
58+
public static <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
59+
CollectorTester<T, A, R> of(Collector<T, A, R> collector) {
60+
return of(collector, Objects::equals);
61+
}
62+
63+
/**
64+
* Creates a {@code CollectorTester} for the specified {@code Collector}. The result of the {@code
65+
* Collector} will be compared to the expected value using the specified {@code equivalence}.
66+
*/
67+
public static <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
68+
CollectorTester<T, A, R> of(
69+
Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) {
70+
return new CollectorTester<>(collector, equivalence);
71+
}
72+
73+
private final Collector<T, A, R> collector;
74+
private final BiPredicate<? super R, ? super R> equivalence;
75+
76+
private CollectorTester(
77+
Collector<T, A, R> collector, BiPredicate<? super R, ? super R> equivalence) {
78+
this.collector = checkNotNull(collector);
79+
this.equivalence = checkNotNull(equivalence);
80+
}
81+
82+
/**
83+
* Different orderings for combining the elements of an input array, which must all produce the
84+
* same result.
85+
*/
86+
@IgnoreJRERequirement // *should* be redundant with the one on CollectorTester
87+
enum CollectStrategy {
88+
/** Get one accumulator and accumulate the elements into it sequentially. */
89+
SEQUENTIAL {
90+
@Override
91+
final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
92+
A result(Collector<T, A, R> collector, Iterable<T> inputs) {
93+
A accum = collector.supplier().get();
94+
for (T input : inputs) {
95+
collector.accumulator().accept(accum, input);
96+
}
97+
return accum;
98+
}
99+
},
100+
/** Get one accumulator for each element and merge the accumulators left-to-right. */
101+
MERGE_LEFT_ASSOCIATIVE {
102+
@Override
103+
final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
104+
A result(Collector<T, A, R> collector, Iterable<T> inputs) {
105+
A accum = collector.supplier().get();
106+
for (T input : inputs) {
107+
A newAccum = collector.supplier().get();
108+
collector.accumulator().accept(newAccum, input);
109+
accum = collector.combiner().apply(accum, newAccum);
110+
}
111+
return accum;
112+
}
113+
},
114+
/** Get one accumulator for each element and merge the accumulators right-to-left. */
115+
MERGE_RIGHT_ASSOCIATIVE {
116+
@Override
117+
final <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
118+
A result(Collector<T, A, R> collector, Iterable<T> inputs) {
119+
List<A> stack = new ArrayList<>();
120+
for (T input : inputs) {
121+
A newAccum = collector.supplier().get();
122+
collector.accumulator().accept(newAccum, input);
123+
push(stack, newAccum);
124+
}
125+
push(stack, collector.supplier().get());
126+
while (stack.size() > 1) {
127+
A right = pop(stack);
128+
A left = pop(stack);
129+
push(stack, collector.combiner().apply(left, right));
130+
}
131+
return pop(stack);
132+
}
133+
134+
<E extends @Nullable Object> void push(List<E> stack, E value) {
135+
stack.add(value);
136+
}
137+
138+
<E extends @Nullable Object> E pop(List<E> stack) {
139+
return stack.remove(stack.size() - 1);
140+
}
141+
};
142+
143+
abstract <T extends @Nullable Object, A extends @Nullable Object, R extends @Nullable Object>
144+
A result(Collector<T, A, R> collector, Iterable<T> inputs);
145+
}
146+
147+
/**
148+
* Verifies that the specified expected result is always produced by collecting the specified
149+
* inputs, regardless of how the elements are divided.
150+
*/
151+
@SafeVarargs
152+
@CanIgnoreReturnValue
153+
@SuppressWarnings("nullness") // TODO(cpovirk): Remove after we fix whatever the bug is.
154+
public final CollectorTester<T, A, R> expectCollects(R expectedResult, T... inputs) {
155+
List<T> list = Arrays.asList(inputs);
156+
doExpectCollects(expectedResult, list);
157+
if (collector.characteristics().contains(Collector.Characteristics.UNORDERED)) {
158+
Collections.reverse(list);
159+
doExpectCollects(expectedResult, list);
160+
}
161+
return this;
162+
}
163+
164+
private void doExpectCollects(R expectedResult, List<T> inputs) {
165+
for (CollectStrategy scheme : CollectStrategy.values()) {
166+
A finalAccum = scheme.result(collector, inputs);
167+
if (collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
168+
@SuppressWarnings("unchecked") // `R` and `A` match for an `IDENTITY_FINISH`
169+
R result = (R) finalAccum;
170+
assertEquivalent(expectedResult, result);
171+
}
172+
assertEquivalent(expectedResult, collector.finisher().apply(finalAccum));
173+
}
174+
}
175+
176+
private void assertEquivalent(R expected, R actual) {
177+
assertTrue(
178+
"Expected " + expected + " got " + actual + " modulo equivalence " + equivalence,
179+
equivalence.test(expected, actual));
180+
}
181+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (C) 2016 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.collect;
18+
19+
import static com.google.common.collect.MoreCollectors.onlyElement;
20+
import static com.google.common.collect.MoreCollectors.toOptional;
21+
import static com.google.common.collect.ReflectionFreeAssertThrows.assertThrows;
22+
import static com.google.common.truth.Truth.assertThat;
23+
24+
import com.google.common.annotations.GwtCompatible;
25+
import java.util.NoSuchElementException;
26+
import java.util.stream.Stream;
27+
import junit.framework.TestCase;
28+
import org.jspecify.annotations.NullMarked;
29+
import org.jspecify.annotations.Nullable;
30+
31+
/**
32+
* Tests for {@code MoreCollectors}.
33+
*
34+
* @author Louis Wasserman
35+
*/
36+
@GwtCompatible
37+
@NullMarked
38+
public class MoreCollectorsTest extends TestCase {
39+
public void testToOptionalEmpty() {
40+
assertThat(Stream.empty().collect(toOptional())).isEmpty();
41+
}
42+
43+
public void testToOptionalSingleton() {
44+
assertThat(Stream.of(1).collect(toOptional())).hasValue(1);
45+
}
46+
47+
public void testToOptionalNull() {
48+
Stream<@Nullable Object> stream = Stream.of((Object) null);
49+
assertThrows(NullPointerException.class, () -> stream.collect(toOptional()));
50+
}
51+
52+
public void testToOptionalMultiple() {
53+
IllegalArgumentException expected =
54+
assertThrows(IllegalArgumentException.class, () -> Stream.of(1, 2).collect(toOptional()));
55+
assertThat(expected).hasMessageThat().contains("1, 2");
56+
}
57+
58+
public void testToOptionalMultipleWithNull() {
59+
assertThrows(NullPointerException.class, () -> Stream.of(1, null).collect(toOptional()));
60+
}
61+
62+
public void testToOptionalMany() {
63+
IllegalArgumentException expected =
64+
assertThrows(
65+
IllegalArgumentException.class,
66+
() -> Stream.of(1, 2, 3, 4, 5, 6).collect(toOptional()));
67+
assertThat(expected).hasMessageThat().contains("1, 2, 3, 4, 5, ...");
68+
}
69+
70+
public void testOnlyElement() {
71+
assertThrows(NoSuchElementException.class, () -> Stream.empty().collect(onlyElement()));
72+
}
73+
74+
public void testOnlyElementSingleton() {
75+
assertThat(Stream.of(1).collect(onlyElement())).isEqualTo(1);
76+
}
77+
78+
public void testOnlyElementNull() {
79+
assertThat(Stream.<@Nullable Object>of((Object) null).collect(onlyElement())).isNull();
80+
}
81+
82+
public void testOnlyElementMultiple() {
83+
IllegalArgumentException expected =
84+
assertThrows(IllegalArgumentException.class, () -> Stream.of(1, 2).collect(onlyElement()));
85+
assertThat(expected).hasMessageThat().contains("1, 2");
86+
}
87+
88+
public void testOnlyElementMany() {
89+
IllegalArgumentException expected =
90+
assertThrows(
91+
IllegalArgumentException.class,
92+
() -> Stream.of(1, 2, 3, 4, 5, 6).collect(onlyElement()));
93+
assertThat(expected).hasMessageThat().contains("1, 2, 3, 4, 5, ...");
94+
}
95+
}

0 commit comments

Comments
 (0)