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