guava
613 строк · 21.3 Кб
1/*
2* Copyright (C) 2008 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
17package com.google.common.collect.testing;
18
19import static junit.framework.Assert.assertEquals;
20import static junit.framework.Assert.fail;
21
22import com.google.common.annotations.GwtCompatible;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Iterator;
28import java.util.List;
29import java.util.ListIterator;
30import java.util.NoSuchElementException;
31import java.util.Set;
32import java.util.Stack;
33import org.checkerframework.checker.nullness.qual.Nullable;
34
35/**
36* Most of the logic for {@link IteratorTester} and {@link ListIteratorTester}.
37*
38* @param <E> the type of element returned by the iterator
39* @param <I> the type of the iterator ({@link Iterator} or {@link ListIterator})
40* @author Kevin Bourrillion
41* @author Chris Povirk
42*/
43@GwtCompatible
44@ElementTypesAreNonnullByDefault
45abstract class AbstractIteratorTester<E extends @Nullable Object, I extends Iterator<E>> {
46private Stimulus<E, ? super I>[] stimuli;
47private final Iterator<E> elementsToInsert;
48private final Set<IteratorFeature> features;
49private final List<E> expectedElements;
50private final int startIndex;
51private final KnownOrder knownOrder;
52
53/**
54* Meta-exception thrown by {@link AbstractIteratorTester.MultiExceptionListIterator} instead of
55* throwing any particular exception type.
56*/
57private abstract static class PermittedMetaException extends RuntimeException {
58static final PermittedMetaException UOE_OR_ISE =
59new PermittedMetaException("UnsupportedOperationException or IllegalStateException") {
60@Override
61boolean isPermitted(Exception exception) {
62return exception instanceof UnsupportedOperationException
63|| exception instanceof IllegalStateException;
64}
65};
66static final PermittedMetaException UOE =
67new PermittedMetaException("UnsupportedOperationException") {
68@Override
69boolean isPermitted(Exception exception) {
70return exception instanceof UnsupportedOperationException;
71}
72};
73static final PermittedMetaException ISE =
74new PermittedMetaException("IllegalStateException") {
75@Override
76boolean isPermitted(Exception exception) {
77return exception instanceof IllegalStateException;
78}
79};
80static final PermittedMetaException NSEE =
81new PermittedMetaException("NoSuchElementException") {
82@Override
83boolean isPermitted(Exception exception) {
84return exception instanceof NoSuchElementException;
85}
86};
87
88private PermittedMetaException(String message) {
89super(message);
90}
91
92abstract boolean isPermitted(Exception exception);
93
94void assertPermitted(Exception exception) {
95if (!isPermitted(exception)) {
96String message =
97"Exception "
98+ exception.getClass().getSimpleName()
99+ " was thrown; expected "
100+ getMessage();
101throw new AssertionError(message, exception);
102}
103}
104
105private static final long serialVersionUID = 0;
106}
107
108private static final class UnknownElementException extends RuntimeException {
109private UnknownElementException(Collection<?> expected, Object actual) {
110super("Returned value '" + actual + "' not found. Remaining elements: " + expected);
111}
112
113private static final long serialVersionUID = 0;
114}
115
116/**
117* Quasi-implementation of {@link ListIterator} that works from a list of elements and a set of
118* features to support (from the enclosing {@link AbstractIteratorTester} instance). Instead of
119* throwing exceptions like {@link NoSuchElementException} at the appropriate times, it throws
120* {@link PermittedMetaException} instances, which wrap a set of all exceptions that the iterator
121* could throw during the invocation of that method. This is necessary because, e.g., a call to
122* {@code iterator().remove()} of an unmodifiable list could throw either {@link
123* IllegalStateException} or {@link UnsupportedOperationException}. Note that iterator
124* implementations should always throw one of the exceptions in a {@code PermittedExceptions}
125* instance, since {@code PermittedExceptions} is thrown only when a method call is invalid.
126*
127* <p>This class is accessible but not supported in GWT as it references {@link
128* PermittedMetaException}.
129*/
130protected final class MultiExceptionListIterator implements ListIterator<E> {
131// TODO: track seen elements when order isn't guaranteed
132// TODO: verify contents afterward
133// TODO: something shiny and new instead of Stack
134// TODO: test whether null is supported (create a Feature)
135/**
136* The elements to be returned by future calls to {@code next()}, with the first at the top of
137* the stack.
138*/
139final Stack<E> nextElements = new Stack<>();
140
141/**
142* The elements to be returned by future calls to {@code previous()}, with the first at the top
143* of the stack.
144*/
145final Stack<E> previousElements = new Stack<>();
146
147/**
148* {@link #nextElements} if {@code next()} was called more recently then {@code previous},
149* {@link #previousElements} if the reverse is true, or -- overriding both of these -- {@code
150* null} if {@code remove()} or {@code add()} has been called more recently than either. We use
151* this to determine which stack to pop from on a call to {@code remove()} (or to pop from and
152* push to on a call to {@code set()}).
153*/
154@Nullable Stack<E> stackWithLastReturnedElementAtTop = null;
155
156MultiExceptionListIterator(List<E> expectedElements) {
157Helpers.addAll(nextElements, Helpers.reverse(expectedElements));
158for (int i = 0; i < startIndex; i++) {
159previousElements.push(nextElements.pop());
160}
161}
162
163@Override
164public void add(E e) {
165if (!features.contains(IteratorFeature.SUPPORTS_ADD)) {
166throw PermittedMetaException.UOE;
167}
168
169previousElements.push(e);
170stackWithLastReturnedElementAtTop = null;
171}
172
173@Override
174public boolean hasNext() {
175return !nextElements.isEmpty();
176}
177
178@Override
179public boolean hasPrevious() {
180return !previousElements.isEmpty();
181}
182
183@Override
184public E next() {
185return transferElement(nextElements, previousElements);
186}
187
188@Override
189public int nextIndex() {
190return previousElements.size();
191}
192
193@Override
194public E previous() {
195return transferElement(previousElements, nextElements);
196}
197
198@Override
199public int previousIndex() {
200return nextIndex() - 1;
201}
202
203@Override
204public void remove() {
205throwIfInvalid(IteratorFeature.SUPPORTS_REMOVE);
206
207stackWithLastReturnedElementAtTop.pop();
208stackWithLastReturnedElementAtTop = null;
209}
210
211@Override
212public void set(E e) {
213throwIfInvalid(IteratorFeature.SUPPORTS_SET);
214
215stackWithLastReturnedElementAtTop.pop();
216stackWithLastReturnedElementAtTop.push(e);
217}
218
219/**
220* Moves the given element from its current position in {@link #nextElements} to the top of the
221* stack so that it is returned by the next call to {@link Iterator#next()}. If the element is
222* not in {@link #nextElements}, this method throws an {@link UnknownElementException}.
223*
224* <p>This method is used when testing iterators without a known ordering. We poll the target
225* iterator's next element and pass it to the reference iterator through this method so it can
226* return the same element. This enables the assertion to pass and the reference iterator to
227* properly update its state.
228*/
229void promoteToNext(E e) {
230if (nextElements.remove(e)) {
231nextElements.push(e);
232} else {
233throw new UnknownElementException(nextElements, e);
234}
235}
236
237private E transferElement(Stack<E> source, Stack<E> destination) {
238if (source.isEmpty()) {
239throw PermittedMetaException.NSEE;
240}
241
242destination.push(source.pop());
243stackWithLastReturnedElementAtTop = destination;
244return destination.peek();
245}
246
247private void throwIfInvalid(IteratorFeature methodFeature) {
248if (!features.contains(methodFeature)) {
249if (stackWithLastReturnedElementAtTop == null) {
250throw PermittedMetaException.UOE_OR_ISE;
251} else {
252throw PermittedMetaException.UOE;
253}
254} else if (stackWithLastReturnedElementAtTop == null) {
255throw PermittedMetaException.ISE;
256}
257}
258
259private List<E> getElements() {
260List<E> elements = new ArrayList<>();
261Helpers.addAll(elements, previousElements);
262Helpers.addAll(elements, Helpers.reverse(nextElements));
263return elements;
264}
265}
266
267public enum KnownOrder {
268KNOWN_ORDER,
269UNKNOWN_ORDER
270}
271
272@SuppressWarnings("unchecked") // TODO(cpovirk): Stop using arrays.
273AbstractIteratorTester(
274int steps,
275Iterable<E> elementsToInsertIterable,
276Iterable<? extends IteratorFeature> features,
277Iterable<E> expectedElements,
278KnownOrder knownOrder,
279int startIndex) {
280// periodically we should manually try (steps * 3 / 2) here; all tests but
281// one should still pass (testVerifyGetsCalled()).
282stimuli = (Stimulus<E, ? super I>[]) new Stimulus<?, ?>[steps];
283if (!elementsToInsertIterable.iterator().hasNext()) {
284throw new IllegalArgumentException();
285}
286elementsToInsert = Helpers.cycle(elementsToInsertIterable);
287this.features = Helpers.copyToSet(features);
288this.expectedElements = Helpers.copyToList(expectedElements);
289this.knownOrder = knownOrder;
290this.startIndex = startIndex;
291}
292
293/**
294* I'd like to make this a parameter to the constructor, but I can't because the stimulus
295* instances refer to {@code this}.
296*/
297protected abstract Iterable<? extends Stimulus<E, ? super I>> getStimulusValues();
298
299/**
300* Returns a new target iterator each time it's called. This is the iterator you are trying to
301* test. This must return an Iterator that returns the expected elements passed to the constructor
302* in the given order. Warning: it is not enough to simply pull multiple iterators from the same
303* source Iterable, unless that Iterator is unmodifiable.
304*/
305protected abstract I newTargetIterator();
306
307/**
308* Override this to verify anything after running a list of Stimuli.
309*
310* <p>For example, verify that calls to remove() actually removed the correct elements.
311*
312* @param elements the expected elements passed to the constructor, as mutated by {@code
313* remove()}, {@code set()}, and {@code add()} calls
314*/
315protected void verify(List<E> elements) {}
316
317/** Executes the test. */
318@SuppressWarnings("CatchingUnchecked") // sneaky checked exception
319public final void test() {
320try {
321recurse(0);
322} catch (Exception e) { // sneaky checked exception
323throw new RuntimeException(Arrays.toString(stimuli), e);
324}
325}
326
327public void testForEachRemaining() {
328for (int i = 0; i < expectedElements.size() - 1; i++) {
329List<E> targetElements = new ArrayList<>();
330Iterator<E> iterator = newTargetIterator();
331for (int j = 0; j < i; j++) {
332targetElements.add(iterator.next());
333}
334iterator.forEachRemaining(targetElements::add);
335if (knownOrder == KnownOrder.KNOWN_ORDER) {
336assertEquals(expectedElements, targetElements);
337} else {
338Helpers.assertEqualIgnoringOrder(expectedElements, targetElements);
339}
340}
341}
342
343private void recurse(int level) {
344// We're going to reuse the stimuli array 3^steps times by overwriting it
345// in a recursive loop. Sneaky.
346if (level == stimuli.length) {
347// We've filled the array.
348compareResultsForThisListOfStimuli();
349} else {
350// Keep recursing to fill the array.
351for (Stimulus<E, ? super I> stimulus : getStimulusValues()) {
352stimuli[level] = stimulus;
353recurse(level + 1);
354}
355}
356}
357
358private void compareResultsForThisListOfStimuli() {
359int removes = Collections.frequency(Arrays.asList(stimuli), remove);
360if ((!features.contains(IteratorFeature.SUPPORTS_REMOVE) && removes > 1)
361|| (stimuli.length >= 5 && removes > 2)) {
362// removes are the most expensive thing to test, since they often throw exceptions with stack
363// traces, so we test them a bit less aggressively
364return;
365}
366
367MultiExceptionListIterator reference = new MultiExceptionListIterator(expectedElements);
368I target = newTargetIterator();
369for (int i = 0; i < stimuli.length; i++) {
370try {
371stimuli[i].executeAndCompare(reference, target);
372verify(reference.getElements());
373} catch (AssertionError cause) {
374throw new AssertionError("failed with stimuli " + subListCopy(stimuli, i + 1), cause);
375}
376}
377}
378
379private static List<Object> subListCopy(Object[] source, int size) {
380final Object[] copy = new Object[size];
381System.arraycopy(source, 0, copy, 0, size);
382return Arrays.asList(copy);
383}
384
385private interface IteratorOperation {
386@Nullable Object execute(Iterator<?> iterator);
387}
388
389/**
390* Apply this method to both iterators and return normally only if both produce the same response.
391*
392* @see Stimulus#executeAndCompare(ListIterator, Iterator)
393*/
394@SuppressWarnings("CatchingUnchecked") // sneaky checked exception
395private <T extends Iterator<E>> void internalExecuteAndCompare(
396T reference, T target, IteratorOperation method) {
397Object referenceReturnValue = null;
398PermittedMetaException referenceException = null;
399Object targetReturnValue = null;
400Exception targetException = null;
401
402try {
403targetReturnValue = method.execute(target);
404} catch (Exception e) { // sneaky checked exception
405targetException = e;
406}
407
408try {
409if (method == NEXT_METHOD
410&& targetException == null
411&& knownOrder == KnownOrder.UNKNOWN_ORDER) {
412/*
413* We already know the iterator is an Iterator<E>, and now we know that
414* we called next(), so the returned element must be of type E.
415*/
416@SuppressWarnings("unchecked")
417E targetReturnValueFromNext = (E) targetReturnValue;
418/*
419* We have an Iterator<E> and want to cast it to
420* MultiExceptionListIterator. Because we're inside an
421* AbstractIteratorTester<E>, that's implicitly a cast to
422* AbstractIteratorTester<E>.MultiExceptionListIterator. The runtime
423* won't be able to verify the AbstractIteratorTester<E> part, so it's
424* an unchecked cast. We know, however, that the only possible value for
425* the type parameter is <E>, since otherwise the
426* MultiExceptionListIterator wouldn't be an Iterator<E>. The cast is
427* safe, even though javac can't tell.
428*
429* Sun bug 6665356 is an additional complication. Until OpenJDK 7, javac
430* doesn't recognize this kind of cast as unchecked cast. Neither does
431* Eclipse 3.4. Right now, this suppression is mostly unnecessary.
432*/
433@SuppressWarnings("unchecked")
434MultiExceptionListIterator multiExceptionListIterator =
435(MultiExceptionListIterator) reference;
436multiExceptionListIterator.promoteToNext(targetReturnValueFromNext);
437}
438
439referenceReturnValue = method.execute(reference);
440} catch (PermittedMetaException e) {
441referenceException = e;
442} catch (UnknownElementException e) {
443throw new AssertionError(e);
444}
445
446if (referenceException == null) {
447if (targetException != null) {
448throw new AssertionError("Target threw exception when reference did not", targetException);
449}
450
451/*
452* Reference iterator returned a value, so we should expect the
453* same value from the target
454*/
455assertEquals(referenceReturnValue, targetReturnValue);
456
457return;
458}
459
460if (targetException == null) {
461fail("Target failed to throw " + referenceException);
462}
463
464/*
465* Reference iterator threw an exception, so we should expect an acceptable
466* exception from the target.
467*/
468referenceException.assertPermitted(targetException);
469}
470
471private static final IteratorOperation REMOVE_METHOD =
472new IteratorOperation() {
473@Override
474public @Nullable Object execute(Iterator<?> iterator) {
475iterator.remove();
476return null;
477}
478};
479
480private static final IteratorOperation NEXT_METHOD =
481new IteratorOperation() {
482@Override
483public @Nullable Object execute(Iterator<?> iterator) {
484return iterator.next();
485}
486};
487
488private static final IteratorOperation PREVIOUS_METHOD =
489new IteratorOperation() {
490@Override
491public @Nullable Object execute(Iterator<?> iterator) {
492return ((ListIterator<?>) iterator).previous();
493}
494};
495
496private final IteratorOperation newAddMethod() {
497final Object toInsert = elementsToInsert.next();
498return new IteratorOperation() {
499@Override
500public @Nullable Object execute(Iterator<?> iterator) {
501@SuppressWarnings("unchecked")
502ListIterator<Object> rawIterator = (ListIterator<Object>) iterator;
503rawIterator.add(toInsert);
504return null;
505}
506};
507}
508
509private final IteratorOperation newSetMethod() {
510final E toInsert = elementsToInsert.next();
511return new IteratorOperation() {
512@Override
513public @Nullable Object execute(Iterator<?> iterator) {
514@SuppressWarnings("unchecked")
515ListIterator<E> li = (ListIterator<E>) iterator;
516li.set(toInsert);
517return null;
518}
519};
520}
521
522abstract static class Stimulus<E extends @Nullable Object, T extends Iterator<E>> {
523private final String toString;
524
525protected Stimulus(String toString) {
526this.toString = toString;
527}
528
529/**
530* Send this stimulus to both iterators and return normally only if both produce the same
531* response.
532*/
533abstract void executeAndCompare(ListIterator<E> reference, T target);
534
535@Override
536public String toString() {
537return toString;
538}
539}
540
541Stimulus<E, Iterator<E>> hasNext =
542new Stimulus<E, Iterator<E>>("hasNext") {
543@Override
544void executeAndCompare(ListIterator<E> reference, Iterator<E> target) {
545assertEquals(reference.hasNext(), target.hasNext());
546}
547};
548Stimulus<E, Iterator<E>> next =
549new Stimulus<E, Iterator<E>>("next") {
550@Override
551void executeAndCompare(ListIterator<E> reference, Iterator<E> target) {
552internalExecuteAndCompare(reference, target, NEXT_METHOD);
553}
554};
555Stimulus<E, Iterator<E>> remove =
556new Stimulus<E, Iterator<E>>("remove") {
557@Override
558void executeAndCompare(ListIterator<E> reference, Iterator<E> target) {
559internalExecuteAndCompare(reference, target, REMOVE_METHOD);
560}
561};
562
563List<Stimulus<E, Iterator<E>>> iteratorStimuli() {
564return Arrays.asList(hasNext, next, remove);
565}
566
567Stimulus<E, ListIterator<E>> hasPrevious =
568new Stimulus<E, ListIterator<E>>("hasPrevious") {
569@Override
570void executeAndCompare(ListIterator<E> reference, ListIterator<E> target) {
571assertEquals(reference.hasPrevious(), target.hasPrevious());
572}
573};
574Stimulus<E, ListIterator<E>> nextIndex =
575new Stimulus<E, ListIterator<E>>("nextIndex") {
576@Override
577void executeAndCompare(ListIterator<E> reference, ListIterator<E> target) {
578assertEquals(reference.nextIndex(), target.nextIndex());
579}
580};
581Stimulus<E, ListIterator<E>> previousIndex =
582new Stimulus<E, ListIterator<E>>("previousIndex") {
583@Override
584void executeAndCompare(ListIterator<E> reference, ListIterator<E> target) {
585assertEquals(reference.previousIndex(), target.previousIndex());
586}
587};
588Stimulus<E, ListIterator<E>> previous =
589new Stimulus<E, ListIterator<E>>("previous") {
590@Override
591void executeAndCompare(ListIterator<E> reference, ListIterator<E> target) {
592internalExecuteAndCompare(reference, target, PREVIOUS_METHOD);
593}
594};
595Stimulus<E, ListIterator<E>> add =
596new Stimulus<E, ListIterator<E>>("add") {
597@Override
598void executeAndCompare(ListIterator<E> reference, ListIterator<E> target) {
599internalExecuteAndCompare(reference, target, newAddMethod());
600}
601};
602Stimulus<E, ListIterator<E>> set =
603new Stimulus<E, ListIterator<E>>("set") {
604@Override
605void executeAndCompare(ListIterator<E> reference, ListIterator<E> target) {
606internalExecuteAndCompare(reference, target, newSetMethod());
607}
608};
609
610List<Stimulus<E, ListIterator<E>>> listIteratorStimuli() {
611return Arrays.asList(hasPrevious, nextIndex, previousIndex, previous, add, set);
612}
613}
614