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