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