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