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}