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