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