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.collect.testing; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; 021import static com.google.common.collect.testing.Helpers.assertEqualInOrder; 022import static com.google.common.collect.testing.Platform.format; 023import static java.util.Comparator.naturalOrder; 024import static junit.framework.Assert.assertEquals; 025import static junit.framework.Assert.assertFalse; 026import static junit.framework.Assert.assertTrue; 027import static junit.framework.Assert.fail; 028 029import com.google.common.annotations.GwtCompatible; 030import com.google.common.collect.ImmutableSet; 031import com.google.common.collect.Ordering; 032import com.google.common.primitives.Ints; 033import com.google.errorprone.annotations.CanIgnoreReturnValue; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Comparator; 037import java.util.EnumSet; 038import java.util.List; 039import java.util.Spliterator; 040import java.util.function.Consumer; 041import java.util.function.Function; 042import java.util.function.Supplier; 043import org.checkerframework.checker.nullness.qual.Nullable; 044 045/** Tester for {@code Spliterator} implementations. */ 046@GwtCompatible 047public final class SpliteratorTester<E> { 048 /** Return type from "contains the following elements" assertions. */ 049 public interface Ordered { 050 /** 051 * Attests that the expected values must not just be present but must be present in the order 052 * they were given. 053 */ 054 void inOrder(); 055 } 056 057 private abstract static class GeneralSpliterator<E> { 058 final Spliterator<E> spliterator; 059 060 GeneralSpliterator(Spliterator<E> spliterator) { 061 this.spliterator = checkNotNull(spliterator); 062 } 063 064 abstract void forEachRemaining(Consumer<? super E> action); 065 066 abstract boolean tryAdvance(Consumer<? super E> action); 067 068 abstract GeneralSpliterator<E> trySplit(); 069 070 final int characteristics() { 071 return spliterator.characteristics(); 072 } 073 074 final long estimateSize() { 075 return spliterator.estimateSize(); 076 } 077 078 final Comparator<? super E> getComparator() { 079 return spliterator.getComparator(); 080 } 081 082 final long getExactSizeIfKnown() { 083 return spliterator.getExactSizeIfKnown(); 084 } 085 086 final boolean hasCharacteristics(int characteristics) { 087 return spliterator.hasCharacteristics(characteristics); 088 } 089 } 090 091 private static final class GeneralSpliteratorOfObject<E> extends GeneralSpliterator<E> { 092 GeneralSpliteratorOfObject(Spliterator<E> spliterator) { 093 super(spliterator); 094 } 095 096 @Override 097 void forEachRemaining(Consumer<? super E> action) { 098 spliterator.forEachRemaining(action); 099 } 100 101 @Override 102 boolean tryAdvance(Consumer<? super E> action) { 103 return spliterator.tryAdvance(action); 104 } 105 106 @Override 107 @Nullable GeneralSpliterator<E> trySplit() { 108 Spliterator<E> split = spliterator.trySplit(); 109 return split == null ? null : new GeneralSpliteratorOfObject<>(split); 110 } 111 } 112 113 /* 114 * The AndroidJdkLibsChecker violation is informing us that this method isn't usable under 115 * Desugar. But we want to include it here for Nougat+ users -- and, mainly, for non-Android 116 * users. Fortunately, anyone who tries to use it under Desugar will presumably already see errors 117 * from creating the Spliterator.OfInt in the first place. So it's probably OK for us to suppress 118 * this particular violation. 119 */ 120 @SuppressWarnings("AndroidJdkLibsChecker") 121 private static final class GeneralSpliteratorOfPrimitive<E, C> extends GeneralSpliterator<E> { 122 final Spliterator.OfPrimitive<E, C, ?> spliterator; 123 final Function<Consumer<? super E>, C> consumerizer; 124 125 GeneralSpliteratorOfPrimitive( 126 Spliterator.OfPrimitive<E, C, ?> spliterator, 127 Function<Consumer<? super E>, C> consumerizer) { 128 super(spliterator); 129 this.spliterator = spliterator; 130 this.consumerizer = consumerizer; 131 } 132 133 @Override 134 void forEachRemaining(Consumer<? super E> action) { 135 spliterator.forEachRemaining(consumerizer.apply(action)); 136 } 137 138 @Override 139 boolean tryAdvance(Consumer<? super E> action) { 140 return spliterator.tryAdvance(consumerizer.apply(action)); 141 } 142 143 @Override 144 @Nullable GeneralSpliterator<E> trySplit() { 145 Spliterator.OfPrimitive<E, C, ?> split = spliterator.trySplit(); 146 return split == null ? null : new GeneralSpliteratorOfPrimitive<>(split, consumerizer); 147 } 148 } 149 150 /** 151 * Different ways of decomposing a Spliterator, all of which must produce the same elements (up to 152 * ordering, if Spliterator.ORDERED is not present). 153 */ 154 enum SpliteratorDecompositionStrategy { 155 NO_SPLIT_FOR_EACH_REMAINING { 156 @Override 157 <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 158 spliterator.forEachRemaining(consumer); 159 } 160 }, 161 NO_SPLIT_TRY_ADVANCE { 162 @Override 163 <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 164 while (spliterator.tryAdvance(consumer)) { 165 // do nothing 166 } 167 } 168 }, 169 MAXIMUM_SPLIT { 170 @Override 171 <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 172 for (GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator); 173 prefix != null; 174 prefix = trySplitTestingSize(spliterator)) { 175 forEach(prefix, consumer); 176 } 177 long size = spliterator.getExactSizeIfKnown(); 178 long[] counter = {0}; 179 spliterator.forEachRemaining( 180 e -> { 181 consumer.accept(e); 182 counter[0]++; 183 }); 184 if (size >= 0) { 185 assertEquals(size, counter[0]); 186 } 187 } 188 }, 189 ALTERNATE_ADVANCE_AND_SPLIT { 190 @Override 191 <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer) { 192 while (spliterator.tryAdvance(consumer)) { 193 GeneralSpliterator<E> prefix = trySplitTestingSize(spliterator); 194 if (prefix != null) { 195 forEach(prefix, consumer); 196 } 197 } 198 } 199 }; 200 201 abstract <E> void forEach(GeneralSpliterator<E> spliterator, Consumer<? super E> consumer); 202 } 203 204 private static <E> @Nullable GeneralSpliterator<E> trySplitTestingSize( 205 GeneralSpliterator<E> spliterator) { 206 boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED); 207 long originalSize = spliterator.estimateSize(); 208 GeneralSpliterator<E> trySplit = spliterator.trySplit(); 209 if (spliterator.estimateSize() > originalSize) { 210 fail( 211 format( 212 "estimated size of spliterator after trySplit (%s) is larger than original size (%s)", 213 spliterator.estimateSize(), originalSize)); 214 } 215 if (trySplit != null) { 216 if (trySplit.estimateSize() > originalSize) { 217 fail( 218 format( 219 "estimated size of trySplit result (%s) is larger than original size (%s)", 220 trySplit.estimateSize(), originalSize)); 221 } 222 } 223 if (subsized) { 224 if (trySplit != null) { 225 assertEquals( 226 "sum of estimated sizes of trySplit and original spliterator after trySplit", 227 originalSize, 228 trySplit.estimateSize() + spliterator.estimateSize()); 229 } else { 230 assertEquals( 231 "estimated size of spliterator after failed trySplit", 232 originalSize, 233 spliterator.estimateSize()); 234 } 235 } 236 return trySplit; 237 } 238 239 public static <E> SpliteratorTester<E> of(Supplier<Spliterator<E>> spliteratorSupplier) { 240 return new SpliteratorTester<>( 241 ImmutableSet.of(() -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()))); 242 } 243 244 /** @since 28.1 */ 245 @SuppressWarnings("AndroidJdkLibsChecker") // see comment on GeneralSpliteratorOfPrimitive 246 public static SpliteratorTester<Integer> ofInt(Supplier<Spliterator.OfInt> spliteratorSupplier) { 247 return new SpliteratorTester<>( 248 ImmutableSet.of( 249 () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), 250 () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); 251 } 252 253 /** @since 28.1 */ 254 @SuppressWarnings("AndroidJdkLibsChecker") // see comment on GeneralSpliteratorOfPrimitive 255 public static SpliteratorTester<Long> ofLong(Supplier<Spliterator.OfLong> spliteratorSupplier) { 256 return new SpliteratorTester<>( 257 ImmutableSet.of( 258 () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), 259 () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); 260 } 261 262 /** @since 28.1 */ 263 @SuppressWarnings("AndroidJdkLibsChecker") // see comment on GeneralSpliteratorOfPrimitive 264 public static SpliteratorTester<Double> ofDouble( 265 Supplier<Spliterator.OfDouble> spliteratorSupplier) { 266 return new SpliteratorTester<>( 267 ImmutableSet.of( 268 () -> new GeneralSpliteratorOfObject<>(spliteratorSupplier.get()), 269 () -> new GeneralSpliteratorOfPrimitive<>(spliteratorSupplier.get(), c -> c::accept))); 270 } 271 272 private final ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers; 273 274 private SpliteratorTester(ImmutableSet<Supplier<GeneralSpliterator<E>>> spliteratorSuppliers) { 275 this.spliteratorSuppliers = checkNotNull(spliteratorSuppliers); 276 } 277 278 @SafeVarargs 279 @CanIgnoreReturnValue 280 public final Ordered expect(Object... elements) { 281 return expect(Arrays.asList(elements)); 282 } 283 284 @CanIgnoreReturnValue 285 public final Ordered expect(Iterable<?> elements) { 286 List<List<E>> resultsForAllStrategies = new ArrayList<>(); 287 for (Supplier<GeneralSpliterator<E>> spliteratorSupplier : spliteratorSuppliers) { 288 GeneralSpliterator<E> spliterator = spliteratorSupplier.get(); 289 int characteristics = spliterator.characteristics(); 290 long estimatedSize = spliterator.estimateSize(); 291 for (SpliteratorDecompositionStrategy strategy : 292 EnumSet.allOf(SpliteratorDecompositionStrategy.class)) { 293 List<E> resultsForStrategy = new ArrayList<>(); 294 strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add); 295 296 // TODO(cpovirk): better failure messages 297 if ((characteristics & Spliterator.NONNULL) != 0) { 298 assertFalse(resultsForStrategy.contains(null)); 299 } 300 if ((characteristics & Spliterator.SORTED) != 0) { 301 Comparator<? super E> comparator = spliterator.getComparator(); 302 if (comparator == null) { 303 comparator = (Comparator) naturalOrder(); 304 } 305 assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy)); 306 } 307 if ((characteristics & Spliterator.SIZED) != 0) { 308 assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size()); 309 } 310 311 assertEqualIgnoringOrder(elements, resultsForStrategy); 312 resultsForAllStrategies.add(resultsForStrategy); 313 } 314 } 315 return new Ordered() { 316 @Override 317 public void inOrder() { 318 for (List<E> resultsForStrategy : resultsForAllStrategies) { 319 assertEqualInOrder(elements, resultsForStrategy); 320 } 321 } 322 }; 323 } 324}