Home | History | Annotate | Download | only in testing
      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    */
     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    */
     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 
    114     PermittedMetaException(
    115         Set<? extends Class<? extends RuntimeException>> exceptionClasses) {
    116       super("one of " + exceptionClasses);
    117       this.exceptionClasses = exceptionClasses;
    118     }
    119 
    120     PermittedMetaException(Class<? extends RuntimeException> exceptionClass) {
    121       this(Collections.singleton(exceptionClass));
    122     }
    123 
    124     // It's not supported In GWT, it always returns true.
    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.
    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 
    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 {
    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 
    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
    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
    220     public boolean hasNext() {
    221       return !nextElements.isEmpty();
    222     }
    223 
    224     @Override
    225     public boolean hasPrevious() {
    226       return !previousElements.isEmpty();
    227     }
    228 
    229     @Override
    230     public E next() {
    231       return transferElement(nextElements, previousElements);
    232     }
    233 
    234     @Override
    235     public int nextIndex() {
    236       return previousElements.size();
    237     }
    238 
    239     @Override
    240     public E previous() {
    241       return transferElement(previousElements, nextElements);
    242     }
    243 
    244     @Override
    245     public int previousIndex() {
    246       return nextIndex() - 1;
    247     }
    248 
    249     @Override
    250     public void remove() {
    251       throwIfInvalid(IteratorFeature.SUPPORTS_REMOVE);
    252 
    253       stackWithLastReturnedElementAtTop.pop();
    254       stackWithLastReturnedElementAtTop = null;
    255     }
    256 
    257     @Override
    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      */
    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 
    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 
    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 
    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
    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>>
    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    */
    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    */
    365   protected void verify(List<E> elements) {}
    366 
    367   /**
    368    * Executes the test.
    369    */
    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 
    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 
    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 
    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 {
    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    */
    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 
    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