/* * Copyright (C) 2015 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect.testing; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.testing.Helpers.assertEqualIgnoringOrder; import static com.google.common.collect.testing.Helpers.assertEqualInOrder; import static com.google.common.collect.testing.Platform.format; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.Ordering; import com.google.common.primitives.Ints; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Supplier; import javax.annotation.Nullable; /** * Tester for {@code Spliterator} implementations. */ @GwtCompatible public final class SpliteratorTester<E> { /** * Return type from "contains the following elements" assertions. */ public interface Ordered { /** * Attests that the expected values must not just be present but must be present in the order * they were given. */ void inOrder(); } /** * Different ways of decomposing a Spliterator, all of which must produce the same * elements (up to ordering, if Spliterator.ORDERED is not present). */ enum SpliteratorDecompositionStrategy { NO_SPLIT_FOR_EACH_REMAINING { @Override <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) { spliterator.forEachRemaining(consumer); } }, NO_SPLIT_TRY_ADVANCE { @Override <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) { while (spliterator.tryAdvance(consumer)) { // do nothing } } }, MAXIMUM_SPLIT { @Override <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) { for (Spliterator<E> prefix = trySplitTestingSize(spliterator); prefix != null; prefix = trySplitTestingSize(spliterator)) { forEach(prefix, consumer); } long size = spliterator.getExactSizeIfKnown(); long[] counter = {0}; spliterator.forEachRemaining(e -> { consumer.accept(e); counter[0]++; }); if (size >= 0) { assertEquals(size, counter[0]); } } }, ALTERNATE_ADVANCE_AND_SPLIT { @Override <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) { while (spliterator.tryAdvance(consumer)) { Spliterator<E> prefix = trySplitTestingSize(spliterator); if (prefix != null) { forEach(prefix, consumer); } } } }; abstract <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer); } @Nullable private static <E> Spliterator<E> trySplitTestingSize(Spliterator<E> spliterator) { boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED); long originalSize = spliterator.estimateSize(); Spliterator<E> trySplit = spliterator.trySplit(); if (spliterator.estimateSize() > originalSize) { fail( format( "estimated size of spliterator after trySplit (%s) is larger than original size (%s)", spliterator.estimateSize(), originalSize)); } if (trySplit != null) { if (trySplit.estimateSize() > originalSize) { fail( format( "estimated size of trySplit result (%s) is larger than original size (%s)", trySplit.estimateSize(), originalSize)); } } if (subsized) { if (trySplit != null) { assertEquals( "sum of estimated sizes of trySplit and original spliterator after trySplit", originalSize, trySplit.estimateSize() + spliterator.estimateSize()); } else { assertEquals( "estimated size of spliterator after failed trySplit", originalSize, spliterator.estimateSize()); } } return trySplit; } public static <E> SpliteratorTester<E> of(Supplier<Spliterator<E>> spliteratorSupplier) { return new SpliteratorTester<E>(spliteratorSupplier); } private final Supplier<Spliterator<E>> spliteratorSupplier; private SpliteratorTester(Supplier<Spliterator<E>> spliteratorSupplier) { this.spliteratorSupplier = checkNotNull(spliteratorSupplier); } @SafeVarargs public final Ordered expect(Object... elements) { return expect(Arrays.asList(elements)); } public final Ordered expect(Iterable<?> elements) { List<List<E>> resultsForAllStrategies = new ArrayList<>(); Spliterator<E> spliterator = spliteratorSupplier.get(); int characteristics = spliterator.characteristics(); long estimatedSize = spliterator.estimateSize(); for (SpliteratorDecompositionStrategy strategy : EnumSet.allOf(SpliteratorDecompositionStrategy.class)) { List<E> resultsForStrategy = new ArrayList<E>(); strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add); // TODO(cpovirk): better failure messages if ((characteristics & Spliterator.NONNULL) != 0) { assertFalse(resultsForStrategy.contains(null)); } if ((characteristics & Spliterator.SORTED) != 0) { Comparator<? super E> comparator = spliterator.getComparator(); if (comparator == null) { comparator = (Comparator) Comparator.naturalOrder(); } assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy)); } if ((characteristics & Spliterator.SIZED) != 0) { assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size()); } assertEqualIgnoringOrder(elements, resultsForStrategy); resultsForAllStrategies.add(resultsForStrategy); } return new Ordered() { @Override public void inOrder() { resultsForAllStrategies.forEach( resultsForStrategy -> assertEqualInOrder(elements, resultsForStrategy)); } }; } }