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