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