• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.collect.testing;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder;
21 import static com.google.common.collect.testing.Helpers.assertEqualInOrder;
22 import static com.google.common.collect.testing.Platform.format;
23 import static java.util.Arrays.asList;
24 import static java.util.Collections.unmodifiableSet;
25 import static java.util.Comparator.naturalOrder;
26 import static junit.framework.Assert.assertEquals;
27 import static junit.framework.Assert.assertFalse;
28 import static junit.framework.Assert.assertTrue;
29 import static junit.framework.Assert.fail;
30 
31 import com.google.common.annotations.GwtCompatible;
32 import com.google.common.collect.ImmutableSet;
33 import com.google.common.collect.Ordering;
34 import com.google.common.primitives.Ints;
35 import com.google.errorprone.annotations.CanIgnoreReturnValue;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Comparator;
39 import java.util.LinkedHashSet;
40 import java.util.List;
41 import java.util.Set;
42 import java.util.Spliterator;
43 import java.util.function.Consumer;
44 import java.util.function.Function;
45 import java.util.function.Supplier;
46 import org.checkerframework.checker.nullness.qual.Nullable;
47 
48 /** Tester for {@code Spliterator} implementations. */
49 @GwtCompatible
50 public final class SpliteratorTester<E> {
51   /** Return type from "contains the following elements" assertions. */
52   public interface Ordered {
53     /**
54      * Attests that the expected values must not just be present but must be present in the order
55      * they were given.
56      */
inOrder()57     void inOrder();
58   }
59 
60   private abstract static class GeneralSpliterator<E> {
61     final Spliterator<E> spliterator;
62 
GeneralSpliterator(Spliterator<E> spliterator)63     GeneralSpliterator(Spliterator<E> spliterator) {
64       this.spliterator = checkNotNull(spliterator);
65     }
66 
forEachRemaining(Consumer<? super E> action)67     abstract void forEachRemaining(Consumer<? super E> action);
68 
tryAdvance(Consumer<? super E> action)69     abstract boolean tryAdvance(Consumer<? super E> action);
70 
trySplit()71     abstract GeneralSpliterator<E> trySplit();
72 
characteristics()73     final int characteristics() {
74       return spliterator.characteristics();
75     }
76 
estimateSize()77     final long estimateSize() {
78       return spliterator.estimateSize();
79     }
80 
getComparator()81     final Comparator<? super E> getComparator() {
82       return spliterator.getComparator();
83     }
84 
getExactSizeIfKnown()85     final long getExactSizeIfKnown() {
86       return spliterator.getExactSizeIfKnown();
87     }
88 
hasCharacteristics(int characteristics)89     final boolean hasCharacteristics(int characteristics) {
90       return spliterator.hasCharacteristics(characteristics);
91     }
92   }
93 
94   private static final class GeneralSpliteratorOfObject<E> extends GeneralSpliterator<E> {
GeneralSpliteratorOfObject(Spliterator<E> spliterator)95     GeneralSpliteratorOfObject(Spliterator<E> spliterator) {
96       super(spliterator);
97     }
98 
99     @Override
forEachRemaining(Consumer<? super E> action)100     void forEachRemaining(Consumer<? super E> action) {
101       spliterator.forEachRemaining(action);
102     }
103 
104     @Override
tryAdvance(Consumer<? super E> action)105     boolean tryAdvance(Consumer<? super E> action) {
106       return spliterator.tryAdvance(action);
107     }
108 
109     @Override
trySplit()110     @Nullable GeneralSpliterator<E> trySplit() {
111       Spliterator<E> split = spliterator.trySplit();
112       return split == null ? null : new GeneralSpliteratorOfObject<>(split);
113     }
114   }
115 
116   /*
117    * The AndroidJdkLibsChecker violation is informing us that this method isn't usable under
118    * Desugar. But we want to include it here for Nougat+ users -- and, mainly, for non-Android
119    * users. Fortunately, anyone who tries to use it under Desugar will presumably already see errors
120    * from creating the Spliterator.OfInt in the first place. So it's probably OK for us to suppress
121    * this particular violation.
122    */
123   @SuppressWarnings("AndroidJdkLibsChecker")
124   private static final class GeneralSpliteratorOfPrimitive<E, C> extends GeneralSpliterator<E> {
125     final Spliterator.OfPrimitive<E, C, ?> spliterator;
126     final Function<Consumer<? super E>, C> consumerizer;
127 
GeneralSpliteratorOfPrimitive( Spliterator.OfPrimitive<E, C, ?> spliterator, Function<Consumer<? super E>, C> consumerizer)128     GeneralSpliteratorOfPrimitive(
129         Spliterator.OfPrimitive<E, C, ?> spliterator,
130         Function<Consumer<? super E>, C> consumerizer) {
131       super(spliterator);
132       this.spliterator = spliterator;
133       this.consumerizer = consumerizer;
134     }
135 
136     @Override
forEachRemaining(Consumer<? super E> action)137     void forEachRemaining(Consumer<? super E> action) {
138       spliterator.forEachRemaining(consumerizer.apply(action));
139     }
140 
141     @Override
tryAdvance(Consumer<? super E> action)142     boolean tryAdvance(Consumer<? super E> action) {
143       return spliterator.tryAdvance(consumerizer.apply(action));
144     }
145 
146     @Override
trySplit()147     @Nullable GeneralSpliterator<E> trySplit() {
148       Spliterator.OfPrimitive<E, C, ?> split = spliterator.trySplit();
149       return split == null ? null : new GeneralSpliteratorOfPrimitive<>(split, consumerizer);
150     }
151   }
152 
153   /**
154    * Different ways of decomposing a Spliterator, all of which must produce the same elements (up to
155    * ordering, if Spliterator.ORDERED is not present).
156    */
157   enum SpliteratorDecompositionStrategy {
158     NO_SPLIT_FOR_EACH_REMAINING {
159       @Override
forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)160       <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
161         spliterator.forEachRemaining(consumer);
162       }
163     },
164     NO_SPLIT_TRY_ADVANCE {
165       @Override
forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)166       <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
167         while (spliterator.tryAdvance(consumer)) {
168           // do nothing
169         }
170       }
171     },
172     MAXIMUM_SPLIT {
173       @Override
forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)174       <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
175         for (GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator);
176             prefix != null;
177             prefix = trySplitTestingSize(spliterator)) {
178           forEach(prefix, consumer);
179         }
180         long size = spliterator.getExactSizeIfKnown();
181         long[] counter = {0};
182         spliterator.forEachRemaining(
183             e -> {
184               consumer.accept(e);
185               counter[0]++;
186             });
187         if (size >= 0) {
188           assertEquals(size, counter[0]);
189         }
190       }
191     },
192     ALTERNATE_ADVANCE_AND_SPLIT {
193       @Override
forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)194       <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) {
195         while (spliterator.tryAdvance(consumer)) {
196           GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator);
197           if (prefix != null) {
198             forEach(prefix, consumer);
199           }
200         }
201       }
202     };
203 
forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)204     abstract <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer);
205 
206     static final Set<SpliteratorDecompositionStrategy> ALL_STRATEGIES =
207         unmodifiableSet(new LinkedHashSet<>(asList(values())));
208   }
209 
trySplitTestingSize( GeneralSpliterator<E> spliterator)210   private static <E> @Nullable GeneralSpliterator<E> trySplitTestingSize(
211       GeneralSpliterator<E> spliterator) {
212     boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED);
213     long originalSize = spliterator.estimateSize();
214     GeneralSpliterator<E> trySplit = spliterator.trySplit();
215     if (spliterator.estimateSize() > originalSize) {
216       fail(
217           format(
218               "estimated size of spliterator after trySplit (%s) is larger than original size (%s)",
219               spliterator.estimateSize(), originalSize));
220     }
221     if (trySplit != null) {
222       if (trySplit.estimateSize() > originalSize) {
223         fail(
224             format(
225                 "estimated size of trySplit result (%s) is larger than original size (%s)",
226                 trySplit.estimateSize(), originalSize));
227       }
228     }
229     if (subsized) {
230       if (trySplit != null) {
231         assertEquals(
232             "sum of estimated sizes of trySplit and original spliterator after trySplit",
233             originalSize,
234             trySplit.estimateSize() + spliterator.estimateSize());
235       } else {
236         assertEquals(
237             "estimated size of spliterator after failed trySplit",
238             originalSize,
239             spliterator.estimateSize());
240       }
241     }
242     return trySplit;
243   }
244 
of(Supplier<Spliterator<E>> spliteratorSupplier)245   public static <E> SpliteratorTester<E> of(Supplier<Spliterator<E>> spliteratorSupplier) {
246     return new SpliteratorTester<>(
247         ImmutableSet.of(() -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get())));
248   }
249 
250   /** @since 28.1 */
251   @SuppressWarnings("AndroidJdkLibsChecker") // see comment on GeneralSpliteratorOfPrimitive
ofInt(Supplier<Spliterator.OfInt> spliteratorSupplier)252   public static SpliteratorTester<Integer> ofInt(Supplier<Spliterator.OfInt> spliteratorSupplier) {
253     return new SpliteratorTester<>(
254         ImmutableSet.of(
255             () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()),
256             () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept)));
257   }
258 
259   /** @since 28.1 */
260   @SuppressWarnings("AndroidJdkLibsChecker") // see comment on GeneralSpliteratorOfPrimitive
ofLong(Supplier<Spliterator.OfLong> spliteratorSupplier)261   public static SpliteratorTester<Long> ofLong(Supplier<Spliterator.OfLong> spliteratorSupplier) {
262     return new SpliteratorTester<>(
263         ImmutableSet.of(
264             () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()),
265             () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept)));
266   }
267 
268   /** @since 28.1 */
269   @SuppressWarnings("AndroidJdkLibsChecker") // see comment on GeneralSpliteratorOfPrimitive
ofDouble( Supplier<Spliterator.OfDouble> spliteratorSupplier)270   public static SpliteratorTester<Double> ofDouble(
271       Supplier<Spliterator.OfDouble> spliteratorSupplier) {
272     return new SpliteratorTester<>(
273         ImmutableSet.of(
274             () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()),
275             () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept)));
276   }
277 
278   private final ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers;
279 
SpliteratorTester(ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers)280   private SpliteratorTester(ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers) {
281     this.spliteratorSuppliers = checkNotNull(spliteratorSuppliers);
282   }
283 
284   @SafeVarargs
285   @CanIgnoreReturnValue
expect(Object... elements)286   public final Ordered expect(Object... elements) {
287     return expect(Arrays.asList(elements));
288   }
289 
290   @CanIgnoreReturnValue
expect(Iterable<?> elements)291   public final Ordered expect(Iterable<?> elements) {
292     List<List<E>> resultsForAllStrategies = new ArrayList<>();
293     for (Supplier<GeneralSpliterator<E>> spliteratorSupplier : spliteratorSuppliers) {
294       GeneralSpliterator<E> spliterator = spliteratorSupplier.get();
295       int characteristics = spliterator.characteristics();
296       long estimatedSize = spliterator.estimateSize();
297       for (SpliteratorDecompositionStrategy strategy :
298           SpliteratorDecompositionStrategy.ALL_STRATEGIES) {
299         List<E> resultsForStrategy = new ArrayList<>();
300         strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add);
301 
302         // TODO(cpovirk): better failure messages
303         if ((characteristics & Spliterator.NONNULL) != 0) {
304           assertFalse(resultsForStrategy.contains(null));
305         }
306         if ((characteristics & Spliterator.SORTED) != 0) {
307           Comparator<? super E> comparator = spliterator.getComparator();
308           if (comparator == null) {
309             comparator = (Comparator) naturalOrder();
310           }
311           assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy));
312         }
313         if ((characteristics & Spliterator.SIZED) != 0) {
314           assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size());
315         }
316 
317         assertEqualIgnoringOrder(elements, resultsForStrategy);
318         resultsForAllStrategies.add(resultsForStrategy);
319       }
320     }
321     return new Ordered() {
322       @Override
323       public void inOrder() {
324         for (List<E> resultsForStrategy : resultsForAllStrategies) {
325           assertEqualInOrder(elements, resultsForStrategy);
326         }
327       }
328     };
329   }
330 }
331