1 /* 2 * Copyright (C) 2012 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.io; 18 19 import com.google.common.base.MoreObjects; 20 import com.google.common.base.Objects; 21 import com.google.common.base.Splitter; 22 import com.google.common.base.Throwables; 23 import com.google.common.collect.ImmutableList; 24 import com.google.common.collect.ImmutableSet; 25 import com.google.common.collect.Iterables; 26 import com.google.common.collect.Lists; 27 import com.google.common.testing.TestLogHandler; 28 29 import junit.framework.TestCase; 30 31 import java.io.Closeable; 32 import java.io.IOException; 33 import java.lang.reflect.Method; 34 import java.util.List; 35 import java.util.logging.LogRecord; 36 37 import javax.annotation.Nullable; 38 39 /** 40 * Tests for {@link Closer}. 41 * 42 * @author Colin Decker 43 */ 44 public class CloserTest extends TestCase { 45 46 private TestSuppressor suppressor; 47 48 @Override setUp()49 protected void setUp() throws Exception { 50 suppressor = new TestSuppressor(); 51 } 52 testCreate()53 public void testCreate() { 54 Closer closer = Closer.create(); 55 String javaVersion = System.getProperty("java.version"); 56 String secondPart = Iterables.get(Splitter.on('.').split(javaVersion), 1); 57 int versionNumber = Integer.parseInt(secondPart); 58 if (versionNumber < 7) { 59 assertTrue(closer.suppressor instanceof Closer.LoggingSuppressor); 60 } else { 61 assertTrue(closer.suppressor instanceof Closer.SuppressingSuppressor); 62 } 63 } 64 testNoExceptionsThrown()65 public void testNoExceptionsThrown() throws IOException { 66 Closer closer = new Closer(suppressor); 67 68 TestCloseable c1 = closer.register(TestCloseable.normal()); 69 TestCloseable c2 = closer.register(TestCloseable.normal()); 70 TestCloseable c3 = closer.register(TestCloseable.normal()); 71 72 assertFalse(c1.isClosed()); 73 assertFalse(c2.isClosed()); 74 assertFalse(c3.isClosed()); 75 76 closer.close(); 77 78 assertTrue(c1.isClosed()); 79 assertTrue(c2.isClosed()); 80 assertTrue(c3.isClosed()); 81 82 assertTrue(suppressor.suppressions.isEmpty()); 83 } 84 testExceptionThrown_fromTryBlock()85 public void testExceptionThrown_fromTryBlock() throws IOException { 86 Closer closer = new Closer(suppressor); 87 88 TestCloseable c1 = closer.register(TestCloseable.normal()); 89 TestCloseable c2 = closer.register(TestCloseable.normal()); 90 91 IOException exception = new IOException(); 92 93 try { 94 try { 95 throw exception; 96 } catch (Throwable e) { 97 throw closer.rethrow(e); 98 } finally { 99 closer.close(); 100 } 101 } catch (Throwable expected) { 102 assertSame(exception, expected); 103 } 104 105 assertTrue(c1.isClosed()); 106 assertTrue(c2.isClosed()); 107 108 assertTrue(suppressor.suppressions.isEmpty()); 109 } 110 testExceptionThrown_whenCreatingCloseables()111 public void testExceptionThrown_whenCreatingCloseables() throws IOException { 112 Closer closer = new Closer(suppressor); 113 114 TestCloseable c1 = null; 115 TestCloseable c2 = null; 116 TestCloseable c3 = null; 117 try { 118 try { 119 c1 = closer.register(TestCloseable.normal()); 120 c2 = closer.register(TestCloseable.normal()); 121 c3 = closer.register(TestCloseable.throwsOnCreate()); 122 } catch (Throwable e) { 123 throw closer.rethrow(e); 124 } finally { 125 closer.close(); 126 } 127 } catch (Throwable expected) { 128 assertTrue(expected instanceof IOException); 129 } 130 131 assertTrue(c1.isClosed()); 132 assertTrue(c2.isClosed()); 133 assertNull(c3); 134 135 assertTrue(suppressor.suppressions.isEmpty()); 136 } 137 testExceptionThrown_whileClosingLastCloseable()138 public void testExceptionThrown_whileClosingLastCloseable() throws IOException { 139 Closer closer = new Closer(suppressor); 140 141 IOException exception = new IOException(); 142 143 // c1 is added first, closed last 144 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(exception)); 145 TestCloseable c2 = closer.register(TestCloseable.normal()); 146 147 try { 148 closer.close(); 149 } catch (Throwable expected) { 150 assertSame(exception, expected); 151 } 152 153 assertTrue(c1.isClosed()); 154 assertTrue(c2.isClosed()); 155 156 assertTrue(suppressor.suppressions.isEmpty()); 157 } 158 testExceptionThrown_whileClosingFirstCloseable()159 public void testExceptionThrown_whileClosingFirstCloseable() throws IOException { 160 Closer closer = new Closer(suppressor); 161 162 IOException exception = new IOException(); 163 164 // c2 is added last, closed first 165 TestCloseable c1 = closer.register(TestCloseable.normal()); 166 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(exception)); 167 168 try { 169 closer.close(); 170 } catch (Throwable expected) { 171 assertSame(exception, expected); 172 } 173 174 assertTrue(c1.isClosed()); 175 assertTrue(c2.isClosed()); 176 177 assertTrue(suppressor.suppressions.isEmpty()); 178 } 179 testCloseExceptionsSuppressed_whenExceptionThrownFromTryBlock()180 public void testCloseExceptionsSuppressed_whenExceptionThrownFromTryBlock() throws IOException { 181 Closer closer = new Closer(suppressor); 182 183 IOException tryException = new IOException(); 184 IOException c1Exception = new IOException(); 185 IOException c2Exception = new IOException(); 186 187 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 188 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 189 190 try { 191 try { 192 throw tryException; 193 } catch (Throwable e) { 194 throw closer.rethrow(e); 195 } finally { 196 closer.close(); 197 } 198 } catch (Throwable expected) { 199 assertSame(tryException, expected); 200 } 201 202 assertTrue(c1.isClosed()); 203 assertTrue(c2.isClosed()); 204 205 assertSuppressed( 206 new Suppression(c2, tryException, c2Exception), 207 new Suppression(c1, tryException, c1Exception)); 208 } 209 testCloseExceptionsSuppressed_whenExceptionThrownClosingFirstCloseable()210 public void testCloseExceptionsSuppressed_whenExceptionThrownClosingFirstCloseable() 211 throws IOException { 212 Closer closer = new Closer(suppressor); 213 214 IOException c1Exception = new IOException(); 215 IOException c2Exception = new IOException(); 216 IOException c3Exception = new IOException(); 217 218 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 219 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 220 TestCloseable c3 = closer.register(TestCloseable.throwsOnClose(c3Exception)); 221 222 try { 223 closer.close(); 224 } catch (Throwable expected) { 225 assertSame(c3Exception, expected); 226 } 227 228 assertTrue(c1.isClosed()); 229 assertTrue(c2.isClosed()); 230 assertTrue(c3.isClosed()); 231 232 assertSuppressed( 233 new Suppression(c2, c3Exception, c2Exception), 234 new Suppression(c1, c3Exception, c1Exception)); 235 } 236 testRuntimeExceptions()237 public void testRuntimeExceptions() throws IOException { 238 Closer closer = new Closer(suppressor); 239 240 RuntimeException tryException = new RuntimeException(); 241 RuntimeException c1Exception = new RuntimeException(); 242 RuntimeException c2Exception = new RuntimeException(); 243 244 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 245 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 246 247 try { 248 try { 249 throw tryException; 250 } catch (Throwable e) { 251 throw closer.rethrow(e); 252 } finally { 253 closer.close(); 254 } 255 } catch (Throwable expected) { 256 assertSame(tryException, expected); 257 } 258 259 assertTrue(c1.isClosed()); 260 assertTrue(c2.isClosed()); 261 262 assertSuppressed( 263 new Suppression(c2, tryException, c2Exception), 264 new Suppression(c1, tryException, c1Exception)); 265 } 266 testErrors()267 public void testErrors() throws IOException { 268 Closer closer = new Closer(suppressor); 269 270 Error c1Exception = new Error(); 271 Error c2Exception = new Error(); 272 Error c3Exception = new Error(); 273 274 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 275 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 276 TestCloseable c3 = closer.register(TestCloseable.throwsOnClose(c3Exception)); 277 278 try { 279 closer.close(); 280 } catch (Throwable expected) { 281 assertSame(c3Exception, expected); 282 } 283 284 assertTrue(c1.isClosed()); 285 assertTrue(c2.isClosed()); 286 assertTrue(c3.isClosed()); 287 288 assertSuppressed( 289 new Suppression(c2, c3Exception, c2Exception), 290 new Suppression(c1, c3Exception, c1Exception)); 291 } 292 testLoggingSuppressor()293 public static void testLoggingSuppressor() throws IOException { 294 TestLogHandler logHandler = new TestLogHandler(); 295 296 Closeables.logger.addHandler(logHandler); 297 try { 298 Closer closer = new Closer(new Closer.LoggingSuppressor()); 299 300 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(new IOException())); 301 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(new RuntimeException())); 302 try { 303 throw closer.rethrow(new IOException("thrown"), IOException.class); 304 } catch (IOException expected) {} 305 306 assertTrue(logHandler.getStoredLogRecords().isEmpty()); 307 308 closer.close(); 309 310 assertEquals(2, logHandler.getStoredLogRecords().size()); 311 312 LogRecord record = logHandler.getStoredLogRecords().get(0); 313 assertEquals("Suppressing exception thrown when closing " + c2, record.getMessage()); 314 315 record = logHandler.getStoredLogRecords().get(1); 316 assertEquals("Suppressing exception thrown when closing " + c1, record.getMessage()); 317 } finally { 318 Closeables.logger.removeHandler(logHandler); 319 } 320 } 321 testSuppressingSuppressorIfPossible()322 public static void testSuppressingSuppressorIfPossible() throws IOException { 323 // can't test the JDK7 suppressor when not running on JDK7 324 if (!Closer.SuppressingSuppressor.isAvailable()) { 325 return; 326 } 327 328 Closer closer = new Closer(new Closer.SuppressingSuppressor()); 329 330 IOException thrownException = new IOException(); 331 IOException c1Exception = new IOException(); 332 RuntimeException c2Exception = new RuntimeException(); 333 334 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 335 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 336 try { 337 try { 338 throw thrownException; 339 } catch (Throwable e) { 340 throw closer.rethrow(thrownException, IOException.class); 341 } finally { 342 assertEquals(0, getSuppressed(thrownException).length); 343 closer.close(); 344 } 345 } catch (IOException expected) { 346 assertSame(thrownException, expected); 347 } 348 349 assertTrue(c1.isClosed()); 350 assertTrue(c2.isClosed()); 351 352 ImmutableSet<Throwable> suppressed = ImmutableSet.copyOf(getSuppressed(thrownException)); 353 assertEquals(2, suppressed.size()); 354 355 assertEquals(ImmutableSet.of(c1Exception, c2Exception), suppressed); 356 } 357 testNullCloseable()358 public void testNullCloseable() throws IOException { 359 Closer closer = Closer.create(); 360 closer.register(null); 361 closer.close(); 362 } 363 getSuppressed(Throwable throwable)364 static Throwable[] getSuppressed(Throwable throwable) { 365 try { 366 Method getSuppressed = Throwable.class.getDeclaredMethod("getSuppressed"); 367 return (Throwable[]) getSuppressed.invoke(throwable); 368 } catch (Exception e) { 369 throw new AssertionError(e); // only called if running on JDK7 370 } 371 } 372 373 /** 374 * Asserts that an exception was thrown when trying to close each of the given throwables and that 375 * each such exception was suppressed because of the given thrown exception. 376 */ assertSuppressed(Suppression... expected)377 private void assertSuppressed(Suppression... expected) { 378 assertEquals(ImmutableList.copyOf(expected), suppressor.suppressions); 379 } 380 381 /** 382 * Suppressor that records suppressions. 383 */ 384 private static class TestSuppressor implements Closer.Suppressor { 385 386 private final List<Suppression> suppressions = Lists.newArrayList(); 387 388 @Override suppress(Closeable closeable, Throwable thrown, Throwable suppressed)389 public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { 390 suppressions.add(new Suppression(closeable, thrown, suppressed)); 391 } 392 } 393 394 /** 395 * Record of a call to suppress. 396 */ 397 private static class Suppression { 398 private final Closeable closeable; 399 private final Throwable thrown; 400 private final Throwable suppressed; 401 Suppression(Closeable closeable, Throwable thrown, Throwable suppressed)402 private Suppression(Closeable closeable, Throwable thrown, Throwable suppressed) { 403 this.closeable = closeable; 404 this.thrown = thrown; 405 this.suppressed = suppressed; 406 } 407 408 @Override equals(Object obj)409 public boolean equals(Object obj) { 410 if (obj instanceof Suppression) { 411 Suppression other = (Suppression) obj; 412 return closeable.equals(other.closeable) 413 && thrown.equals(other.thrown) 414 && suppressed.equals(other.suppressed); 415 } 416 return false; 417 } 418 419 @Override hashCode()420 public int hashCode() { 421 return Objects.hashCode(closeable, thrown, suppressed); 422 } 423 424 @Override toString()425 public String toString() { 426 return MoreObjects.toStringHelper(this) 427 .add("closeable", closeable) 428 .add("thrown", thrown) 429 .add("suppressed", suppressed) 430 .toString(); 431 } 432 } 433 434 private static class TestCloseable implements Closeable { 435 436 private final Throwable throwOnClose; 437 private boolean closed; 438 normal()439 static TestCloseable normal() throws IOException { 440 return new TestCloseable(null); 441 } 442 throwsOnClose(Throwable throwOnClose)443 static TestCloseable throwsOnClose(Throwable throwOnClose) throws IOException { 444 return new TestCloseable(throwOnClose); 445 } 446 throwsOnCreate()447 static TestCloseable throwsOnCreate() throws IOException { 448 throw new IOException(); 449 } 450 TestCloseable(@ullable Throwable throwOnClose)451 private TestCloseable(@Nullable Throwable throwOnClose) { 452 this.throwOnClose = throwOnClose; 453 } 454 isClosed()455 public boolean isClosed() { 456 return closed; 457 } 458 459 @Override close()460 public void close() throws IOException { 461 closed = true; 462 if (throwOnClose != null) { 463 Throwables.propagateIfPossible(throwOnClose, IOException.class); 464 throw new AssertionError(throwOnClose); 465 } 466 } 467 } 468 } 469