1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
41
42
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
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
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
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);
370 }
371 }
372
373
374
375
376
377 private void assertSuppressed(Suppression... expected) {
378 assertEquals(ImmutableList.copyOf(expected), suppressor.suppressions);
379 }
380
381
382
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
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 }