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
17package com.google.common.collect.testing;
18
19import static com.google.common.collect.Lists.newArrayList;
20import static com.google.common.collect.testing.IteratorFeature.MODIFIABLE;
21import static java.util.Collections.emptyList;
22
23import com.google.common.annotations.GwtCompatible;
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.Lists;
26
27import junit.framework.AssertionFailedError;
28import junit.framework.TestCase;
29
30import java.util.Iterator;
31import java.util.List;
32import java.util.NoSuchElementException;
33
34/**
35 * Unit test for IteratorTester.
36 *
37 * @author Mick Killianey
38 */
39@GwtCompatible
40@SuppressWarnings("serial") // No serialization is used in this test
41public class IteratorTesterTest extends TestCase {
42
43  public void testCanCatchDifferentLengthOfIteration() {
44    IteratorTester<Integer> tester =
45        new IteratorTester<Integer>(4, MODIFIABLE, newArrayList(1, 2, 3),
46            IteratorTester.KnownOrder.KNOWN_ORDER) {
47          @Override protected Iterator<Integer> newTargetIterator() {
48            return Lists.newArrayList(1, 2, 3, 4).iterator();
49          }
50        };
51    assertFailure(tester);
52  }
53
54  public void testCanCatchDifferentContents() {
55    IteratorTester<Integer> tester =
56        new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2, 3),
57            IteratorTester.KnownOrder.KNOWN_ORDER) {
58          @Override protected Iterator<Integer> newTargetIterator() {
59            return Lists.newArrayList(1, 3, 2).iterator();
60          }
61        };
62    assertFailure(tester);
63  }
64
65  public void testCanCatchDifferentRemoveBehaviour() {
66    IteratorTester<Integer> tester =
67        new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2),
68            IteratorTester.KnownOrder.KNOWN_ORDER) {
69          @Override protected Iterator<Integer> newTargetIterator() {
70            return ImmutableList.of(1, 2).iterator();
71          }
72        };
73    assertFailure(tester);
74  }
75
76  public void testUnknownOrder() {
77    new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2),
78        IteratorTester.KnownOrder.UNKNOWN_ORDER) {
79      @Override protected Iterator<Integer> newTargetIterator() {
80        return newArrayList(2, 1).iterator();
81      }
82    }.test();
83  }
84
85  public void testUnknownOrderUnrecognizedElement() {
86    IteratorTester<Integer> tester =
87        new IteratorTester<Integer>(3, MODIFIABLE, newArrayList(1, 2, 50),
88            IteratorTester.KnownOrder.UNKNOWN_ORDER) {
89          @Override protected Iterator<Integer> newTargetIterator() {
90            return newArrayList(2, 1, 3).iterator();
91          }
92        };
93    assertFailure(tester);
94  }
95
96  /**
97   * This Iterator wraps another iterator and gives it a bug found
98   * in JDK6.
99   *
100   * <p>This bug is this: if you create an iterator from a TreeSet
101   * and call next() on that iterator when hasNext() is false, so
102   * that next() throws a NoSuchElementException, then subsequent
103   * calls to remove() will incorrectly throw an IllegalStateException,
104   * instead of removing the last element returned.
105   *
106   * <p>See
107   * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6529795">
108   * Sun bug 6529795</a>
109   */
110  static class IteratorWithSunJavaBug6529795<T> implements Iterator<T> {
111    Iterator<T> iterator;
112    boolean nextThrewException;
113    IteratorWithSunJavaBug6529795(Iterator<T> iterator) {
114      this.iterator = iterator;
115    }
116
117    @Override
118    public boolean hasNext() {
119      return iterator.hasNext();
120    }
121
122    @Override
123    public T next() {
124      try {
125        return iterator.next();
126      } catch (NoSuchElementException e) {
127        nextThrewException = true;
128        throw e;
129      }
130    }
131
132    @Override
133    public void remove() {
134      if (nextThrewException) {
135        throw new IllegalStateException();
136      }
137      iterator.remove();
138    }
139  }
140
141  public void testCanCatchSunJavaBug6529795InTargetIterator() {
142    try {
143      /* Choose 4 steps to get sequence [next, next, next, remove] */
144      new IteratorTester<Integer>(4, MODIFIABLE, newArrayList(1, 2),
145          IteratorTester.KnownOrder.KNOWN_ORDER) {
146        @Override protected Iterator<Integer> newTargetIterator() {
147          Iterator<Integer> iterator = Lists.newArrayList(1, 2).iterator();
148          return new IteratorWithSunJavaBug6529795<Integer>(iterator);
149        }
150      }.test();
151    } catch (AssertionFailedError e) {
152      return;
153    }
154    fail("Should have caught jdk6 bug in target iterator");
155  }
156
157  public void testCanWorkAroundSunJavaBug6529795InTargetIterator() {
158    IteratorTester<Integer> tester =
159        new IteratorTester<Integer>(4, MODIFIABLE, newArrayList(1, 2),
160            IteratorTester.KnownOrder.KNOWN_ORDER) {
161          @Override protected Iterator<Integer> newTargetIterator() {
162            Iterator<Integer> iterator = Lists.newArrayList(1, 2).iterator();
163            return new IteratorWithSunJavaBug6529795<Integer>(iterator);
164          }
165        };
166
167    /*
168     * Calling this method on an IteratorTester should avoid flagging
169     * the bug exposed by the preceding test.
170     */
171    tester.ignoreSunJavaBug6529795();
172
173    tester.test();
174  }
175
176  private static final int STEPS = 3;
177  static class TesterThatCountsCalls extends IteratorTester<Integer> {
178    TesterThatCountsCalls() {
179      super(STEPS, MODIFIABLE, newArrayList(1),
180          IteratorTester.KnownOrder.KNOWN_ORDER);
181    }
182    int numCallsToNewTargetIterator;
183    int numCallsToVerify;
184    @Override protected Iterator<Integer> newTargetIterator() {
185      numCallsToNewTargetIterator++;
186      return Lists.newArrayList(1).iterator();
187    }
188    @Override protected void verify(List<Integer> elements) {
189      numCallsToVerify++;
190      super.verify(elements);
191    }
192  }
193
194  public void testVerifyGetsCalled() {
195    TesterThatCountsCalls tester = new TesterThatCountsCalls();
196
197    tester.test();
198
199    assertEquals("Should have verified once per stimulus executed",
200        tester.numCallsToVerify,
201        tester.numCallsToNewTargetIterator * STEPS);
202  }
203
204  public void testVerifyCanThrowAssertionThatFailsTest() {
205    final String message = "Important info about why verify failed";
206    IteratorTester<Integer> tester =
207        new IteratorTester<Integer>(1, MODIFIABLE, newArrayList(1, 2, 3),
208            IteratorTester.KnownOrder.KNOWN_ORDER) {
209          @Override protected Iterator<Integer> newTargetIterator() {
210            return Lists.newArrayList(1, 2, 3).iterator();
211          }
212
213          @Override protected void verify(List<Integer> elements) {
214            throw new AssertionFailedError(message);
215          }
216        };
217    AssertionFailedError actual = null;
218    try {
219      tester.test();
220    } catch (AssertionFailedError e) {
221      actual = e;
222    }
223    assertNotNull("verify() should be able to cause test failure", actual);
224    assertTrue("AssertionFailedError should have info about why test failed",
225        actual.getCause().getMessage().contains(message));
226  }
227
228  public void testMissingException() {
229    List<Integer> emptyList = newArrayList();
230
231    IteratorTester<Integer> tester =
232        new IteratorTester<Integer>(1, MODIFIABLE, emptyList,
233            IteratorTester.KnownOrder.KNOWN_ORDER) {
234          @Override protected Iterator<Integer> newTargetIterator() {
235            return new Iterator<Integer>() {
236              @Override
237              public void remove() {
238                // We should throw here, but we won't!
239              }
240              @Override
241              public Integer next() {
242                // We should throw here, but we won't!
243                return null;
244              }
245              @Override
246              public boolean hasNext() {
247                return false;
248              }
249            };
250          }
251        };
252    assertFailure(tester);
253  }
254
255  public void testUnexpectedException() {
256    IteratorTester<Integer> tester =
257        new IteratorTester<Integer>(1, MODIFIABLE, newArrayList(1),
258            IteratorTester.KnownOrder.KNOWN_ORDER) {
259          @Override protected Iterator<Integer> newTargetIterator() {
260            return new ThrowingIterator<Integer>(new IllegalStateException());
261          }
262        };
263    assertFailure(tester);
264  }
265
266  public void testSimilarException() {
267    List<Integer> emptyList = emptyList();
268    IteratorTester<Integer> tester =
269        new IteratorTester<Integer>(1, MODIFIABLE, emptyList,
270            IteratorTester.KnownOrder.KNOWN_ORDER) {
271          @Override protected Iterator<Integer> newTargetIterator() {
272            return new Iterator<Integer>() {
273              @Override
274              public void remove() {
275                throw new IllegalStateException() { /* subclass */};
276              }
277              @Override
278              public Integer next() {
279                throw new NoSuchElementException() { /* subclass */};
280              }
281              @Override
282              public boolean hasNext() {
283                return false;
284              }
285            };
286          }
287        };
288    tester.test();
289  }
290
291  public void testMismatchedException() {
292    List<Integer> emptyList = emptyList();
293    IteratorTester<Integer> tester =
294        new IteratorTester<Integer>(1, MODIFIABLE, emptyList,
295            IteratorTester.KnownOrder.KNOWN_ORDER) {
296          @Override protected Iterator<Integer> newTargetIterator() {
297            return new Iterator<Integer>() {
298              @Override
299              public void remove() {
300                // Wrong exception type.
301                throw new IllegalArgumentException();
302              }
303              @Override
304              public Integer next() {
305                // Wrong exception type.
306                throw new UnsupportedOperationException();
307              }
308              @Override
309              public boolean hasNext() {
310                return false;
311              }
312            };
313          }
314        };
315    assertFailure(tester);
316  }
317
318  private static void assertFailure(IteratorTester<?> tester) {
319    try {
320      tester.test();
321      fail();
322    } catch (AssertionFailedError expected) {
323    }
324  }
325
326  private static final class ThrowingIterator<E> implements Iterator<E> {
327    private final RuntimeException ex;
328
329    private ThrowingIterator(RuntimeException ex) {
330      this.ex = ex;
331    }
332
333    @Override
334    public boolean hasNext() {
335      // IteratorTester doesn't expect exceptions for hasNext().
336      return true;
337    }
338
339    @Override
340    public E next() {
341      ex.fillInStackTrace();
342      throw ex;
343    }
344
345    @Override
346    public void remove() {
347      ex.fillInStackTrace();
348      throw ex;
349    }
350  }
351}
352