• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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