• 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 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