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