1 /* 2 * Copyright (C) 2007 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; 18 19 import com.google.common.annotations.GwtCompatible; 20 import com.google.common.annotations.GwtIncompatible; 21 import com.google.common.testing.GcFinalization; 22 23 import junit.framework.TestCase; 24 25 import java.lang.ref.WeakReference; 26 import java.util.Iterator; 27 import java.util.NoSuchElementException; 28 29 /** 30 * Unit test for {@code AbstractIterator}. 31 * 32 * @author Kevin Bourrillion 33 */ 34 @SuppressWarnings("serial") // No serialization is used in this test 35 @GwtCompatible(emulated = true) 36 // TODO(cpovirk): why is this slow (>1m/test) under GWT when fully optimized? 37 public class AbstractIteratorTest extends TestCase { 38 39 public void testDefaultBehaviorOfNextAndHasNext() { 40 41 // This sample AbstractIterator returns 0 on the first call, 1 on the 42 // second, then signals that it's reached the end of the data 43 Iterator<Integer> iter = new AbstractIterator<Integer>() { 44 private int rep; 45 @Override public Integer computeNext() { 46 switch (rep++) { 47 case 0: 48 return 0; 49 case 1: 50 return 1; 51 case 2: 52 return endOfData(); 53 default: 54 fail("Should not have been invoked again"); 55 return null; 56 } 57 } 58 }; 59 60 assertTrue(iter.hasNext()); 61 assertEquals(0, (int) iter.next()); 62 63 // verify idempotence of hasNext() 64 assertTrue(iter.hasNext()); 65 assertTrue(iter.hasNext()); 66 assertTrue(iter.hasNext()); 67 assertEquals(1, (int) iter.next()); 68 69 assertFalse(iter.hasNext()); 70 71 // Make sure computeNext() doesn't get invoked again 72 assertFalse(iter.hasNext()); 73 74 try { 75 iter.next(); 76 fail("no exception thrown"); 77 } catch (NoSuchElementException expected) { 78 } 79 } 80 81 public void testDefaultBehaviorOfPeek() { 82 /* 83 * This sample AbstractIterator returns 0 on the first call, 1 on the 84 * second, then signals that it's reached the end of the data 85 */ 86 AbstractIterator<Integer> iter = new AbstractIterator<Integer>() { 87 private int rep; 88 @Override public Integer computeNext() { 89 switch (rep++) { 90 case 0: 91 return 0; 92 case 1: 93 return 1; 94 case 2: 95 return endOfData(); 96 default: 97 fail("Should not have been invoked again"); 98 return null; 99 } 100 } 101 }; 102 103 assertEquals(0, (int) iter.peek()); 104 assertEquals(0, (int) iter.peek()); 105 assertTrue(iter.hasNext()); 106 assertEquals(0, (int) iter.peek()); 107 assertEquals(0, (int) iter.next()); 108 109 assertEquals(1, (int) iter.peek()); 110 assertEquals(1, (int) iter.next()); 111 112 try { 113 iter.peek(); 114 fail("peek() should throw NoSuchElementException at end"); 115 } catch (NoSuchElementException expected) { 116 } 117 118 try { 119 iter.peek(); 120 fail("peek() should continue to throw NoSuchElementException at end"); 121 } catch (NoSuchElementException expected) { 122 } 123 124 try { 125 iter.next(); 126 fail("next() should throw NoSuchElementException as usual"); 127 } catch (NoSuchElementException expected) { 128 } 129 130 try { 131 iter.peek(); 132 fail("peek() should still throw NoSuchElementException after next()"); 133 } catch (NoSuchElementException expected) { 134 } 135 } 136 137 @GwtIncompatible("weak references") 138 public void testFreesNextReference() { 139 Iterator<Object> itr = new AbstractIterator<Object>() { 140 @Override public Object computeNext() { 141 return new Object(); 142 } 143 }; 144 WeakReference<Object> ref = new WeakReference<Object>(itr.next()); 145 GcFinalization.awaitClear(ref); 146 } 147 148 public void testDefaultBehaviorOfPeekForEmptyIteration() { 149 150 AbstractIterator<Integer> empty = new AbstractIterator<Integer>() { 151 private boolean alreadyCalledEndOfData; 152 @Override public Integer computeNext() { 153 if (alreadyCalledEndOfData) { 154 fail("Should not have been invoked again"); 155 } 156 alreadyCalledEndOfData = true; 157 return endOfData(); 158 } 159 }; 160 161 try { 162 empty.peek(); 163 fail("peek() should throw NoSuchElementException at end"); 164 } catch (NoSuchElementException expected) { 165 } 166 167 try { 168 empty.peek(); 169 fail("peek() should continue to throw NoSuchElementException at end"); 170 } catch (NoSuchElementException expected) { 171 } 172 } 173 174 public void testSneakyThrow() throws Exception { 175 Iterator<Integer> iter = new AbstractIterator<Integer>() { 176 boolean haveBeenCalled; 177 @Override public Integer computeNext() { 178 if (haveBeenCalled) { 179 fail("Should not have been called again"); 180 } else { 181 haveBeenCalled = true; 182 sneakyThrow(new SomeCheckedException()); 183 } 184 return null; // never reached 185 } 186 }; 187 188 // The first time, the sneakily-thrown exception comes out 189 try { 190 iter.hasNext(); 191 fail("No exception thrown"); 192 } catch (Exception e) { 193 if (!(e instanceof SomeCheckedException)) { 194 throw e; 195 } 196 } 197 198 // But the second time, AbstractIterator itself throws an ISE 199 try { 200 iter.hasNext(); 201 fail("No exception thrown"); 202 } catch (IllegalStateException expected) { 203 } 204 } 205 206 public void testException() { 207 final SomeUncheckedException exception = new SomeUncheckedException(); 208 Iterator<Integer> iter = new AbstractIterator<Integer>() { 209 @Override public Integer computeNext() { 210 throw exception; 211 } 212 }; 213 214 // It should pass through untouched 215 try { 216 iter.hasNext(); 217 fail("No exception thrown"); 218 } catch (SomeUncheckedException e) { 219 assertSame(exception, e); 220 } 221 } 222 223 public void testExceptionAfterEndOfData() { 224 Iterator<Integer> iter = new AbstractIterator<Integer>() { 225 @Override public Integer computeNext() { 226 endOfData(); 227 throw new SomeUncheckedException(); 228 } 229 }; 230 try { 231 iter.hasNext(); 232 fail("No exception thrown"); 233 } catch (SomeUncheckedException expected) { 234 } 235 } 236 237 public void testCantRemove() { 238 Iterator<Integer> iter = new AbstractIterator<Integer>() { 239 boolean haveBeenCalled; 240 @Override public Integer computeNext() { 241 if (haveBeenCalled) { 242 endOfData(); 243 } 244 haveBeenCalled = true; 245 return 0; 246 } 247 }; 248 249 assertEquals(0, (int) iter.next()); 250 251 try { 252 iter.remove(); 253 fail("No exception thrown"); 254 } catch (UnsupportedOperationException expected) { 255 } 256 } 257 258 public void testReentrantHasNext() { 259 Iterator<Integer> iter = new AbstractIterator<Integer>() { 260 @Override protected Integer computeNext() { 261 hasNext(); 262 return null; 263 } 264 }; 265 try { 266 iter.hasNext(); 267 fail(); 268 } catch (IllegalStateException expected) { 269 } 270 } 271 272 // Technically we should test other reentrant scenarios (9 combinations of 273 // hasNext/next/peek), but we'll cop out for now, knowing that peek() and 274 // next() both start by invoking hasNext() anyway. 275 276 /** 277 * Throws a undeclared checked exception. 278 */ 279 private static void sneakyThrow(Throwable t) { 280 class SneakyThrower<T extends Throwable> { 281 @SuppressWarnings("unchecked") // not really safe, but that's the point 282 void throwIt(Throwable t) throws T { 283 throw (T) t; 284 } 285 } 286 new SneakyThrower<Error>().throwIt(t); 287 } 288 289 private static class SomeCheckedException extends Exception { 290 } 291 292 private static class SomeUncheckedException extends RuntimeException { 293 } 294 } 295