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.io.Closer.LoggingSuppressor; 28 import com.google.common.testing.TestLogHandler; 29 import java.io.Closeable; 30 import java.io.IOException; 31 import java.lang.reflect.Method; 32 import java.util.List; 33 import java.util.logging.LogRecord; 34 import junit.framework.TestCase; 35 import org.checkerframework.checker.nullness.qual.Nullable; 36 37 /** 38 * Tests for {@link Closer}. 39 * 40 * @author Colin Decker 41 */ 42 public class CloserTest extends TestCase { 43 44 private TestSuppressor suppressor; 45 46 @Override setUp()47 protected void setUp() throws Exception { 48 suppressor = new TestSuppressor(); 49 } 50 51 @AndroidIncompatible // TODO(cpovirk): Look up Build.VERSION.SDK_INT reflectively. testCreate()52 public void testCreate() { 53 assertThat(Closer.create().suppressor).isInstanceOf(Closer.SuppressingSuppressor.class); 54 } 55 testNoExceptionsThrown()56 public void testNoExceptionsThrown() throws IOException { 57 Closer closer = new Closer(suppressor); 58 59 TestCloseable c1 = closer.register(TestCloseable.normal()); 60 TestCloseable c2 = closer.register(TestCloseable.normal()); 61 TestCloseable c3 = closer.register(TestCloseable.normal()); 62 63 assertFalse(c1.isClosed()); 64 assertFalse(c2.isClosed()); 65 assertFalse(c3.isClosed()); 66 67 closer.close(); 68 69 assertTrue(c1.isClosed()); 70 assertTrue(c2.isClosed()); 71 assertTrue(c3.isClosed()); 72 73 assertTrue(suppressor.suppressions.isEmpty()); 74 } 75 testExceptionThrown_fromTryBlock()76 public void testExceptionThrown_fromTryBlock() throws IOException { 77 Closer closer = new Closer(suppressor); 78 79 TestCloseable c1 = closer.register(TestCloseable.normal()); 80 TestCloseable c2 = closer.register(TestCloseable.normal()); 81 82 IOException exception = new IOException(); 83 84 try { 85 try { 86 throw exception; 87 } catch (Throwable e) { 88 throw closer.rethrow(e); 89 } finally { 90 closer.close(); 91 } 92 } catch (Throwable expected) { 93 assertSame(exception, expected); 94 } 95 96 assertTrue(c1.isClosed()); 97 assertTrue(c2.isClosed()); 98 99 assertTrue(suppressor.suppressions.isEmpty()); 100 } 101 testExceptionThrown_whenCreatingCloseables()102 public void testExceptionThrown_whenCreatingCloseables() throws IOException { 103 Closer closer = new Closer(suppressor); 104 105 TestCloseable c1 = null; 106 TestCloseable c2 = null; 107 TestCloseable c3 = null; 108 try { 109 try { 110 c1 = closer.register(TestCloseable.normal()); 111 c2 = closer.register(TestCloseable.normal()); 112 c3 = closer.register(TestCloseable.throwsOnCreate()); 113 } catch (Throwable e) { 114 throw closer.rethrow(e); 115 } finally { 116 closer.close(); 117 } 118 } catch (Throwable expected) { 119 assertThat(expected).isInstanceOf(IOException.class); 120 } 121 122 assertTrue(c1.isClosed()); 123 assertTrue(c2.isClosed()); 124 assertNull(c3); 125 126 assertTrue(suppressor.suppressions.isEmpty()); 127 } 128 testExceptionThrown_whileClosingLastCloseable()129 public void testExceptionThrown_whileClosingLastCloseable() throws IOException { 130 Closer closer = new Closer(suppressor); 131 132 IOException exception = new IOException(); 133 134 // c1 is added first, closed last 135 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(exception)); 136 TestCloseable c2 = closer.register(TestCloseable.normal()); 137 138 try { 139 closer.close(); 140 } catch (Throwable expected) { 141 assertSame(exception, expected); 142 } 143 144 assertTrue(c1.isClosed()); 145 assertTrue(c2.isClosed()); 146 147 assertTrue(suppressor.suppressions.isEmpty()); 148 } 149 testExceptionThrown_whileClosingFirstCloseable()150 public void testExceptionThrown_whileClosingFirstCloseable() throws IOException { 151 Closer closer = new Closer(suppressor); 152 153 IOException exception = new IOException(); 154 155 // c2 is added last, closed first 156 TestCloseable c1 = closer.register(TestCloseable.normal()); 157 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(exception)); 158 159 try { 160 closer.close(); 161 } catch (Throwable expected) { 162 assertSame(exception, expected); 163 } 164 165 assertTrue(c1.isClosed()); 166 assertTrue(c2.isClosed()); 167 168 assertTrue(suppressor.suppressions.isEmpty()); 169 } 170 testCloseExceptionsSuppressed_whenExceptionThrownFromTryBlock()171 public void testCloseExceptionsSuppressed_whenExceptionThrownFromTryBlock() throws IOException { 172 Closer closer = new Closer(suppressor); 173 174 IOException tryException = new IOException(); 175 IOException c1Exception = new IOException(); 176 IOException c2Exception = new IOException(); 177 178 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 179 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 180 181 try { 182 try { 183 throw tryException; 184 } catch (Throwable e) { 185 throw closer.rethrow(e); 186 } finally { 187 closer.close(); 188 } 189 } catch (Throwable expected) { 190 assertSame(tryException, expected); 191 } 192 193 assertTrue(c1.isClosed()); 194 assertTrue(c2.isClosed()); 195 196 assertSuppressed( 197 new Suppression(c2, tryException, c2Exception), 198 new Suppression(c1, tryException, c1Exception)); 199 } 200 testCloseExceptionsSuppressed_whenExceptionThrownClosingFirstCloseable()201 public void testCloseExceptionsSuppressed_whenExceptionThrownClosingFirstCloseable() 202 throws IOException { 203 Closer closer = new Closer(suppressor); 204 205 IOException c1Exception = new IOException(); 206 IOException c2Exception = new IOException(); 207 IOException c3Exception = new IOException(); 208 209 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 210 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 211 TestCloseable c3 = closer.register(TestCloseable.throwsOnClose(c3Exception)); 212 213 try { 214 closer.close(); 215 } catch (Throwable expected) { 216 assertSame(c3Exception, expected); 217 } 218 219 assertTrue(c1.isClosed()); 220 assertTrue(c2.isClosed()); 221 assertTrue(c3.isClosed()); 222 223 assertSuppressed( 224 new Suppression(c2, c3Exception, c2Exception), 225 new Suppression(c1, c3Exception, c1Exception)); 226 } 227 testRuntimeExceptions()228 public void testRuntimeExceptions() throws IOException { 229 Closer closer = new Closer(suppressor); 230 231 RuntimeException tryException = new RuntimeException(); 232 RuntimeException c1Exception = new RuntimeException(); 233 RuntimeException c2Exception = new RuntimeException(); 234 235 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 236 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 237 238 try { 239 try { 240 throw tryException; 241 } catch (Throwable e) { 242 throw closer.rethrow(e); 243 } finally { 244 closer.close(); 245 } 246 } catch (Throwable expected) { 247 assertSame(tryException, expected); 248 } 249 250 assertTrue(c1.isClosed()); 251 assertTrue(c2.isClosed()); 252 253 assertSuppressed( 254 new Suppression(c2, tryException, c2Exception), 255 new Suppression(c1, tryException, c1Exception)); 256 } 257 testErrors()258 public void testErrors() throws IOException { 259 Closer closer = new Closer(suppressor); 260 261 Error c1Exception = new Error(); 262 Error c2Exception = new Error(); 263 Error c3Exception = new Error(); 264 265 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(c1Exception)); 266 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(c2Exception)); 267 TestCloseable c3 = closer.register(TestCloseable.throwsOnClose(c3Exception)); 268 269 try { 270 closer.close(); 271 } catch (Throwable expected) { 272 assertSame(c3Exception, expected); 273 } 274 275 assertTrue(c1.isClosed()); 276 assertTrue(c2.isClosed()); 277 assertTrue(c3.isClosed()); 278 279 assertSuppressed( 280 new Suppression(c2, c3Exception, c2Exception), 281 new Suppression(c1, c3Exception, c1Exception)); 282 } 283 testLoggingSuppressor()284 public static void testLoggingSuppressor() throws IOException { 285 TestLogHandler logHandler = new TestLogHandler(); 286 287 Closeables.logger.addHandler(logHandler); 288 try { 289 Closer closer = new Closer(new Closer.LoggingSuppressor()); 290 291 TestCloseable c1 = closer.register(TestCloseable.throwsOnClose(new IOException())); 292 TestCloseable c2 = closer.register(TestCloseable.throwsOnClose(new RuntimeException())); 293 try { 294 throw closer.rethrow(new IOException("thrown"), IOException.class); 295 } catch (IOException expected) { 296 } 297 298 assertTrue(logHandler.getStoredLogRecords().isEmpty()); 299 300 closer.close(); 301 302 assertEquals(2, logHandler.getStoredLogRecords().size()); 303 304 LogRecord record = logHandler.getStoredLogRecords().get(0); 305 assertEquals("Suppressing exception thrown when closing " + c2, record.getMessage()); 306 307 record = logHandler.getStoredLogRecords().get(1); 308 assertEquals("Suppressing exception thrown when closing " + c1, record.getMessage()); 309 } finally { 310 Closeables.logger.removeHandler(logHandler); 311 } 312 } 313 testSuppressingSuppressorIfPossible()314 public static void testSuppressingSuppressorIfPossible() throws IOException { 315 Closer closer = Closer.create(); 316 // can't test the JDK7 suppressor when not running on JDK7 317 if (closer.suppressor instanceof LoggingSuppressor) { 318 return; 319 } 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(@ullable Throwable throwOnClose)438 private TestCloseable(@Nullable 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