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 junit.framework.Assert.assertEquals; 26 import static junit.framework.Assert.assertFalse; 27 import static junit.framework.Assert.assertTrue; 28 import static junit.framework.Assert.fail; 29 30 import com.google.common.annotations.GwtCompatible; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.collect.Ordering; 33 import com.google.common.primitives.Ints; 34 import com.google.errorprone.annotations.CanIgnoreReturnValue; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Comparator; 38 import java.util.LinkedHashSet; 39 import java.util.List; 40 import java.util.Set; 41 import java.util.Spliterator; 42 import java.util.Spliterator.OfPrimitive; 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 /** 49 * Tester for {@code Spliterator} implementations. 50 * 51 * @since 21.0 52 */ 53 @GwtCompatible 54 @ElementTypesAreNonnullByDefault 55 public final class SpliteratorTester<E extends @Nullable Object> { 56 /** Return type from "contains the following elements" assertions. */ 57 public interface Ordered { 58 /** 59 * Attests that the expected values must not just be present but must be present in the order 60 * they were given. 61 */ inOrder()62 void inOrder(); 63 } 64 65 private abstract static class GeneralSpliterator<E extends @Nullable Object> { 66 final Spliterator<E> spliterator; 67 GeneralSpliterator(Spliterator<E> spliterator)68 GeneralSpliterator(Spliterator<E> spliterator) { 69 this.spliterator = checkNotNull(spliterator); 70 } 71 forEachRemaining(Consumer<? super E> action)72 abstract void forEachRemaining(Consumer<? super E> action); 73 tryAdvance(Consumer<? super E> action)74 abstract boolean tryAdvance(Consumer<? super E> action); 75 trySplit()76 abstract @Nullable GeneralSpliterator<E> trySplit(); 77 characteristics()78 final int characteristics() { 79 return spliterator.characteristics(); 80 } 81 estimateSize()82 final long estimateSize() { 83 return spliterator.estimateSize(); 84 } 85 getComparator()86 final Comparator<? super E> getComparator() { 87 return spliterator.getComparator(); 88 } 89 getExactSizeIfKnown()90 final long getExactSizeIfKnown() { 91 return spliterator.getExactSizeIfKnown(); 92 } 93 hasCharacteristics(int characteristics)94 final boolean hasCharacteristics(int characteristics) { 95 return spliterator.hasCharacteristics(characteristics); 96 } 97 } 98 99 private static final class GeneralSpliteratorOfObject<E extends @Nullable Object> 100 extends GeneralSpliterator<E> { GeneralSpliteratorOfObject(Spliterator<E> spliterator)101 GeneralSpliteratorOfObject(Spliterator<E> spliterator) { 102 super(spliterator); 103 } 104 105 @Override forEachRemaining(Consumer<? super E> action)106 void forEachRemaining(Consumer<? super E> action) { 107 spliterator.forEachRemaining(action); 108 } 109 110 @Override tryAdvance(Consumer<? super E> action)111 boolean tryAdvance(Consumer<? super E> action) { 112 return spliterator.tryAdvance(action); 113 } 114 115 @Override trySplit()116 @Nullable GeneralSpliterator<E> trySplit() { 117 Spliterator<E> split = spliterator.trySplit(); 118 return split == null ? null : new GeneralSpliteratorOfObject<>(split); 119 } 120 } 121 122 private static final class GeneralSpliteratorOfPrimitive< 123 E extends @Nullable Object, C, S extends Spliterator.OfPrimitive<E, C, S>> 124 extends GeneralSpliterator<E> { 125 final OfPrimitive<E, C, S> spliteratorOfPrimitive; 126 final Function<Consumer<? super E>, C> consumerizer; 127 GeneralSpliteratorOfPrimitive( Spliterator.OfPrimitive<E, C, S> spliterator, Function<Consumer<? super E>, C> consumerizer)128 GeneralSpliteratorOfPrimitive( 129 Spliterator.OfPrimitive<E, C, S> spliterator, 130 Function<Consumer<? super E>, C> consumerizer) { 131 super(spliterator); 132 this.spliteratorOfPrimitive = spliterator; 133 this.consumerizer = consumerizer; 134 } 135 136 @Override forEachRemaining(Consumer<? super E> action)137 void forEachRemaining(Consumer<? super E> action) { 138 spliteratorOfPrimitive.forEachRemaining(consumerizer.apply(action)); 139 } 140 141 @Override tryAdvance(Consumer<? super E> action)142 boolean tryAdvance(Consumer<? super E> action) { 143 return spliteratorOfPrimitive.tryAdvance(consumerizer.apply(action)); 144 } 145 146 @Override trySplit()147 @Nullable GeneralSpliterator<E> trySplit() { 148 Spliterator.OfPrimitive<E, C, ?> split = spliteratorOfPrimitive.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 extends @Nullable Object> void forEach( 161 GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 162 spliterator.forEachRemaining(consumer); 163 } 164 }, 165 NO_SPLIT_TRY_ADVANCE { 166 @Override forEach( GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)167 <E extends @Nullable Object> void forEach( 168 GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 169 while (spliterator.tryAdvance(consumer)) { 170 // do nothing 171 } 172 } 173 }, 174 MAXIMUM_SPLIT { 175 @Override forEach( GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)176 <E extends @Nullable Object> void forEach( 177 GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 178 for (GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator); 179 prefix != null; 180 prefix = trySplitTestingSize(spliterator)) { 181 forEach(prefix, consumer); 182 } 183 long size = spliterator.getExactSizeIfKnown(); 184 long[] counter = {0}; 185 spliterator.forEachRemaining( 186 e -> { 187 consumer.accept(e); 188 counter[0]++; 189 }); 190 if (size >= 0) { 191 assertEquals(size, counter[0]); 192 } 193 } 194 }, 195 ALTERNATE_ADVANCE_AND_SPLIT { 196 @Override forEach( GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)197 <E extends @Nullable Object> void forEach( 198 GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 199 while (spliterator.tryAdvance(consumer)) { 200 GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator); 201 if (prefix != null) { 202 forEach(prefix, consumer); 203 } 204 } 205 } 206 }; 207 forEach( GeneralSpliterator<E> spliterator, Consumer<? super E> consumer)208 abstract <E extends @Nullable Object> void forEach( 209 GeneralSpliterator<E> spliterator, Consumer<? super E> consumer); 210 211 static final Set<SpliteratorDecompositionStrategy> ALL_STRATEGIES = 212 unmodifiableSet(new LinkedHashSet<>(asList(values()))); 213 } 214 trySplitTestingSize( GeneralSpliterator<E> spliterator)215 private static <E extends @Nullable Object> @Nullable GeneralSpliterator<E> trySplitTestingSize( 216 GeneralSpliterator<E> spliterator) { 217 boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED); 218 long originalSize = spliterator.estimateSize(); 219 GeneralSpliterator<E> trySplit = spliterator.trySplit(); 220 if (spliterator.estimateSize() > originalSize) { 221 fail( 222 format( 223 "estimated size of spliterator after trySplit (%s) is larger than original size (%s)", 224 spliterator.estimateSize(), originalSize)); 225 } 226 if (trySplit != null) { 227 if (trySplit.estimateSize() > originalSize) { 228 fail( 229 format( 230 "estimated size of trySplit result (%s) is larger than original size (%s)", 231 trySplit.estimateSize(), originalSize)); 232 } 233 } 234 if (subsized) { 235 if (trySplit != null) { 236 assertEquals( 237 "sum of estimated sizes of trySplit and original spliterator after trySplit", 238 originalSize, 239 trySplit.estimateSize() + spliterator.estimateSize()); 240 } else { 241 assertEquals( 242 "estimated size of spliterator after failed trySplit", 243 originalSize, 244 spliterator.estimateSize()); 245 } 246 } 247 return trySplit; 248 } 249 of( Supplier<Spliterator<E>> spliteratorSupplier)250 public static <E extends @Nullable Object> SpliteratorTester<E> of( 251 Supplier<Spliterator<E>> spliteratorSupplier) { 252 return new SpliteratorTester<>( 253 ImmutableSet.of(() -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()))); 254 } 255 256 /** 257 * @since 28.1 258 */ ofInt(Supplier<Spliterator.OfInt> spliteratorSupplier)259 public static SpliteratorTester<Integer> ofInt(Supplier<Spliterator.OfInt> spliteratorSupplier) { 260 return new SpliteratorTester<>( 261 ImmutableSet.of( 262 () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), 263 () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); 264 } 265 266 /** 267 * @since 28.1 268 */ ofLong(Supplier<Spliterator.OfLong> spliteratorSupplier)269 public static SpliteratorTester<Long> ofLong(Supplier<Spliterator.OfLong> spliteratorSupplier) { 270 return new SpliteratorTester<>( 271 ImmutableSet.of( 272 () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), 273 () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); 274 } 275 276 /** 277 * @since 28.1 278 */ ofDouble( Supplier<Spliterator.OfDouble> spliteratorSupplier)279 public static SpliteratorTester<Double> ofDouble( 280 Supplier<Spliterator.OfDouble> spliteratorSupplier) { 281 return new SpliteratorTester<>( 282 ImmutableSet.of( 283 () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), 284 () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); 285 } 286 287 private final ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers; 288 SpliteratorTester(ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers)289 private SpliteratorTester(ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers) { 290 this.spliteratorSuppliers = checkNotNull(spliteratorSuppliers); 291 } 292 293 @SafeVarargs 294 @CanIgnoreReturnValue expect(Object... elements)295 public final Ordered expect(Object... elements) { 296 return expect(Arrays.asList(elements)); 297 } 298 299 @CanIgnoreReturnValue expect(Iterable<?> elements)300 public final Ordered expect(Iterable<?> elements) { 301 List<List<E>> resultsForAllStrategies = new ArrayList<>(); 302 for (Supplier<GeneralSpliterator<E>> spliteratorSupplier : spliteratorSuppliers) { 303 GeneralSpliterator<E> spliterator = spliteratorSupplier.get(); 304 int characteristics = spliterator.characteristics(); 305 long estimatedSize = spliterator.estimateSize(); 306 for (SpliteratorDecompositionStrategy strategy : 307 SpliteratorDecompositionStrategy.ALL_STRATEGIES) { 308 List<E> resultsForStrategy = new ArrayList<>(); 309 strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add); 310 311 // TODO(cpovirk): better failure messages 312 if ((characteristics & Spliterator.NONNULL) != 0) { 313 assertFalse(resultsForStrategy.contains(null)); 314 } 315 if ((characteristics & Spliterator.SORTED) != 0) { 316 Comparator<? super E> comparator = spliterator.getComparator(); 317 if (comparator == null) { 318 // A sorted spliterator with no comparator is already using natural order. 319 // (We could probably find a way to avoid rawtypes here if we wanted.) 320 @SuppressWarnings({"unchecked", "rawtypes"}) 321 Comparator<? super E> naturalOrder = 322 (Comparator<? super E>) Comparator.<Comparable>naturalOrder(); 323 comparator = naturalOrder; 324 } 325 assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy)); 326 } 327 if ((characteristics & Spliterator.SIZED) != 0) { 328 assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size()); 329 } 330 331 assertEqualIgnoringOrder(elements, resultsForStrategy); 332 resultsForAllStrategies.add(resultsForStrategy); 333 } 334 } 335 return new Ordered() { 336 @Override 337 public void inOrder() { 338 for (List<E> resultsForStrategy : resultsForAllStrategies) { 339 assertEqualInOrder(elements, resultsForStrategy); 340 } 341 } 342 }; 343 } 344 } 345