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