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 junit.framework.Assert.assertEquals;
024import static junit.framework.Assert.assertFalse;
025import static junit.framework.Assert.assertTrue;
026import static junit.framework.Assert.fail;
027
028import com.google.common.annotations.GwtCompatible;
029import com.google.common.collect.Ordering;
030import com.google.common.primitives.Ints;
031import java.util.ArrayList;
032import java.util.Arrays;
033import java.util.Comparator;
034import java.util.EnumSet;
035import java.util.List;
036import java.util.Spliterator;
037import java.util.function.Consumer;
038import java.util.function.Supplier;
039import org.checkerframework.checker.nullness.qual.Nullable;
040
041/** Tester for {@code Spliterator} implementations. */
042@GwtCompatible
043public final class SpliteratorTester<E> {
044  /** Return type from "contains the following elements" assertions. */
045  public interface Ordered {
046    /**
047     * Attests that the expected values must not just be present but must be present in the order
048     * they were given.
049     */
050    void inOrder();
051  }
052
053  /**
054   * Different ways of decomposing a Spliterator, all of which must produce the same elements (up to
055   * ordering, if Spliterator.ORDERED is not present).
056   */
057  enum SpliteratorDecompositionStrategy {
058    NO_SPLIT_FOR_EACH_REMAINING {
059      @Override
060      <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) {
061        spliterator.forEachRemaining(consumer);
062      }
063    },
064    NO_SPLIT_TRY_ADVANCE {
065      @Override
066      <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) {
067        while (spliterator.tryAdvance(consumer)) {
068          // do nothing
069        }
070      }
071    },
072    MAXIMUM_SPLIT {
073      @Override
074      <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) {
075        for (Spliterator<E> prefix = trySplitTestingSize(spliterator);
076            prefix != null;
077            prefix = trySplitTestingSize(spliterator)) {
078          forEach(prefix, consumer);
079        }
080        long size = spliterator.getExactSizeIfKnown();
081        long[] counter = {0};
082        spliterator.forEachRemaining(
083            e -> {
084              consumer.accept(e);
085              counter[0]++;
086            });
087        if (size >= 0) {
088          assertEquals(size, counter[0]);
089        }
090      }
091    },
092    ALTERNATE_ADVANCE_AND_SPLIT {
093      @Override
094      <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer) {
095        while (spliterator.tryAdvance(consumer)) {
096          Spliterator<E> prefix = trySplitTestingSize(spliterator);
097          if (prefix != null) {
098            forEach(prefix, consumer);
099          }
100        }
101      }
102    };
103
104    abstract <E> void forEach(Spliterator<E> spliterator, Consumer<? super E> consumer);
105  }
106
107  @Nullable
108  private static <E> Spliterator<E> trySplitTestingSize(Spliterator<E> spliterator) {
109    boolean subsized = spliterator.hasCharacteristics(Spliterator.SUBSIZED);
110    long originalSize = spliterator.estimateSize();
111    Spliterator<E> trySplit = spliterator.trySplit();
112    if (spliterator.estimateSize() > originalSize) {
113      fail(
114          format(
115              "estimated size of spliterator after trySplit (%s) is larger than original size (%s)",
116              spliterator.estimateSize(), originalSize));
117    }
118    if (trySplit != null) {
119      if (trySplit.estimateSize() > originalSize) {
120        fail(
121            format(
122                "estimated size of trySplit result (%s) is larger than original size (%s)",
123                trySplit.estimateSize(), originalSize));
124      }
125    }
126    if (subsized) {
127      if (trySplit != null) {
128        assertEquals(
129            "sum of estimated sizes of trySplit and original spliterator after trySplit",
130            originalSize,
131            trySplit.estimateSize() + spliterator.estimateSize());
132      } else {
133        assertEquals(
134            "estimated size of spliterator after failed trySplit",
135            originalSize,
136            spliterator.estimateSize());
137      }
138    }
139    return trySplit;
140  }
141
142  public static <E> SpliteratorTester<E> of(Supplier<Spliterator<E>> spliteratorSupplier) {
143    return new SpliteratorTester<E>(spliteratorSupplier);
144  }
145
146  private final Supplier<Spliterator<E>> spliteratorSupplier;
147
148  private SpliteratorTester(Supplier<Spliterator<E>> spliteratorSupplier) {
149    this.spliteratorSupplier = checkNotNull(spliteratorSupplier);
150  }
151
152  @SafeVarargs
153  public final Ordered expect(Object... elements) {
154    return expect(Arrays.asList(elements));
155  }
156
157  public final Ordered expect(Iterable<?> elements) {
158    List<List<E>> resultsForAllStrategies = new ArrayList<>();
159    Spliterator<E> spliterator = spliteratorSupplier.get();
160    int characteristics = spliterator.characteristics();
161    long estimatedSize = spliterator.estimateSize();
162    for (SpliteratorDecompositionStrategy strategy :
163        EnumSet.allOf(SpliteratorDecompositionStrategy.class)) {
164      List<E> resultsForStrategy = new ArrayList<>();
165      strategy.forEach(spliteratorSupplier.get(), resultsForStrategy::add);
166
167      // TODO(cpovirk): better failure messages
168      if ((characteristics & Spliterator.NONNULL) != 0) {
169        assertFalse(resultsForStrategy.contains(null));
170      }
171      if ((characteristics & Spliterator.SORTED) != 0) {
172        Comparator<? super E> comparator = spliterator.getComparator();
173        if (comparator == null) {
174          comparator = (Comparator) Comparator.naturalOrder();
175        }
176        assertTrue(Ordering.from(comparator).isOrdered(resultsForStrategy));
177      }
178      if ((characteristics & Spliterator.SIZED) != 0) {
179        assertEquals(Ints.checkedCast(estimatedSize), resultsForStrategy.size());
180      }
181
182      assertEqualIgnoringOrder(elements, resultsForStrategy);
183      resultsForAllStrategies.add(resultsForStrategy);
184    }
185    return new Ordered() {
186      @Override
187      public void inOrder() {
188        resultsForAllStrategies.forEach(
189            resultsForStrategy -> assertEqualInOrder(elements, resultsForStrategy));
190      }
191    };
192  }
193}