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