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