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