1/*
2 * Copyright (C) 2007 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
17package com.google.common.base;
18
19import static com.google.common.base.Throwables.getStackTraceAsString;
20import static java.util.Arrays.asList;
21import static java.util.regex.Pattern.quote;
22
23import com.google.common.collect.Iterables;
24import com.google.common.testing.NullPointerTester;
25
26import junit.framework.TestCase;
27
28import java.io.FileNotFoundException;
29import java.util.List;
30
31/**
32 * Unit test for {@link Throwables}.
33 *
34 * @author Kevin Bourrillion
35 */
36@SuppressWarnings("serial") // this warning is silly for exceptions in tests
37public class ThrowablesTest extends TestCase {
38  public void testPropagateIfPossible_NoneDeclared_NoneThrown() {
39    Sample sample = new Sample() {
40      @Override public void noneDeclared() {
41        try {
42          methodThatDoesntThrowAnything();
43        } catch (Throwable t) {
44          Throwables.propagateIfPossible(t);
45          throw new SomeChainingException(t);
46        }
47      }
48    };
49
50    // Expect no exception to be thrown
51    sample.noneDeclared();
52  }
53
54  public void testPropagateIfPossible_NoneDeclared_UncheckedThrown() {
55    Sample sample = new Sample() {
56      @Override public void noneDeclared() {
57        try {
58          methodThatThrowsUnchecked();
59        } catch (Throwable t) {
60          Throwables.propagateIfPossible(t);
61          throw new SomeChainingException(t);
62        }
63      }
64    };
65
66    // Expect the unchecked exception to propagate as-is
67    try {
68      sample.noneDeclared();
69      fail();
70    } catch (SomeUncheckedException expected) {
71    }
72  }
73
74  public void testPropagateIfPossible_NoneDeclared_UndeclaredThrown() {
75    Sample sample = new Sample() {
76      @Override public void noneDeclared() {
77        try {
78          methodThatThrowsUndeclaredChecked();
79        } catch (Throwable t) {
80          Throwables.propagateIfPossible(t);
81          throw new SomeChainingException(t);
82        }
83      }
84    };
85
86    // Expect the undeclared exception to have been chained inside another
87    try {
88      sample.noneDeclared();
89      fail();
90    } catch (SomeChainingException expected) {
91    }
92  }
93
94  public void testPropagateIfPossible_OneDeclared_NoneThrown()
95      throws SomeCheckedException {
96    Sample sample = new Sample() {
97      @Override public void oneDeclared() throws SomeCheckedException {
98        try {
99          methodThatDoesntThrowAnything();
100        } catch (Throwable t) {
101          // yes, this block is never reached, but for purposes of illustration
102          // we're keeping it the same in each test
103          Throwables.propagateIfPossible(t, SomeCheckedException.class);
104          throw new SomeChainingException(t);
105        }
106      }
107    };
108
109    // Expect no exception to be thrown
110    sample.oneDeclared();
111  }
112
113  public void testPropagateIfPossible_OneDeclared_UncheckedThrown()
114      throws SomeCheckedException {
115    Sample sample = new Sample() {
116      @Override public void oneDeclared() throws SomeCheckedException {
117        try {
118          methodThatThrowsUnchecked();
119        } catch (Throwable t) {
120          Throwables.propagateIfPossible(t, SomeCheckedException.class);
121          throw new SomeChainingException(t);
122        }
123      }
124    };
125
126    // Expect the unchecked exception to propagate as-is
127    try {
128      sample.oneDeclared();
129      fail();
130    } catch (SomeUncheckedException expected) {
131    }
132  }
133
134  public void testPropagateIfPossible_OneDeclared_CheckedThrown() {
135    Sample sample = new Sample() {
136      @Override public void oneDeclared() throws SomeCheckedException {
137        try {
138          methodThatThrowsChecked();
139        } catch (Throwable t) {
140          Throwables.propagateIfPossible(t, SomeCheckedException.class);
141          throw new SomeChainingException(t);
142        }
143      }
144    };
145
146    // Expect the checked exception to propagate as-is
147    try {
148      sample.oneDeclared();
149      fail();
150    } catch (SomeCheckedException expected) {
151    }
152  }
153
154  public void testPropagateIfPossible_OneDeclared_UndeclaredThrown()
155      throws SomeCheckedException {
156    Sample sample = new Sample() {
157      @Override public void oneDeclared() throws SomeCheckedException {
158        try {
159          methodThatThrowsUndeclaredChecked();
160        } catch (Throwable t) {
161          Throwables.propagateIfPossible(t, SomeCheckedException.class);
162          throw new SomeChainingException(t);
163        }
164      }
165    };
166
167    // Expect the undeclared exception to have been chained inside another
168    try {
169      sample.oneDeclared();
170      fail();
171    } catch (SomeChainingException expected) {
172    }
173  }
174
175  public void testPropagateIfPossible_TwoDeclared_NoneThrown()
176      throws SomeCheckedException, SomeOtherCheckedException {
177    Sample sample = new Sample() {
178      @Override public void twoDeclared() throws SomeCheckedException,
179          SomeOtherCheckedException {
180        try {
181          methodThatDoesntThrowAnything();
182        } catch (Throwable t) {
183          Throwables.propagateIfPossible(t, SomeCheckedException.class,
184              SomeOtherCheckedException.class);
185          throw new SomeChainingException(t);
186        }
187      }
188    };
189
190    // Expect no exception to be thrown
191    sample.twoDeclared();
192  }
193
194  public void testPropagateIfPossible_TwoDeclared_UncheckedThrown()
195      throws SomeCheckedException, SomeOtherCheckedException {
196    Sample sample = new Sample() {
197      @Override public void twoDeclared() throws SomeCheckedException,
198          SomeOtherCheckedException {
199        try {
200          methodThatThrowsUnchecked();
201        } catch (Throwable t) {
202          Throwables.propagateIfPossible(t, SomeCheckedException.class,
203              SomeOtherCheckedException.class);
204          throw new SomeChainingException(t);
205        }
206      }
207    };
208
209    // Expect the unchecked exception to propagate as-is
210    try {
211      sample.twoDeclared();
212      fail();
213    } catch (SomeUncheckedException expected) {
214    }
215  }
216
217  public void testPropagateIfPossible_TwoDeclared_CheckedThrown()
218      throws SomeOtherCheckedException {
219    Sample sample = new Sample() {
220      @Override public void twoDeclared() throws SomeCheckedException,
221          SomeOtherCheckedException {
222        try {
223          methodThatThrowsChecked();
224        } catch (Throwable t) {
225          Throwables.propagateIfPossible(t, SomeCheckedException.class,
226              SomeOtherCheckedException.class);
227          throw new SomeChainingException(t);
228        }
229      }
230    };
231
232    // Expect the checked exception to propagate as-is
233    try {
234      sample.twoDeclared();
235      fail();
236    } catch (SomeCheckedException expected) {
237    }
238  }
239
240  public void testPropagateIfPossible_TwoDeclared_OtherCheckedThrown()
241      throws SomeCheckedException {
242    Sample sample = new Sample() {
243      @Override public void twoDeclared() throws SomeCheckedException,
244          SomeOtherCheckedException {
245        try {
246          methodThatThrowsOtherChecked();
247        } catch (Throwable t) {
248          Throwables.propagateIfPossible(t, SomeCheckedException.class,
249              SomeOtherCheckedException.class);
250          throw new SomeChainingException(t);
251        }
252      }
253    };
254
255    // Expect the checked exception to propagate as-is
256    try {
257      sample.twoDeclared();
258      fail();
259    } catch (SomeOtherCheckedException expected) {
260    }
261  }
262
263  public void testPropageIfPossible_null() throws SomeCheckedException {
264    Throwables.propagateIfPossible(null);
265    Throwables.propagateIfPossible(null, SomeCheckedException.class);
266    Throwables.propagateIfPossible(null, SomeCheckedException.class,
267        SomeUncheckedException.class);
268  }
269
270  public void testPropagate_NoneDeclared_NoneThrown() {
271    Sample sample = new Sample() {
272      @Override public void noneDeclared() {
273        try {
274          methodThatDoesntThrowAnything();
275        } catch (Throwable t) {
276          throw Throwables.propagate(t);
277        }
278      }
279    };
280
281    // Expect no exception to be thrown
282    sample.noneDeclared();
283  }
284
285  public void testPropagate_NoneDeclared_UncheckedThrown() {
286    Sample sample = new Sample() {
287      @Override public void noneDeclared() {
288        try {
289          methodThatThrowsUnchecked();
290        } catch (Throwable t) {
291          throw Throwables.propagate(t);
292        }
293      }
294    };
295
296    // Expect the unchecked exception to propagate as-is
297    try {
298      sample.noneDeclared();
299      fail();
300    } catch (SomeUncheckedException expected) {
301    }
302  }
303
304  public void testPropagate_NoneDeclared_ErrorThrown() {
305    Sample sample = new Sample() {
306      @Override public void noneDeclared() {
307        try {
308          methodThatThrowsError();
309        } catch (Throwable t) {
310          throw Throwables.propagate(t);
311        }
312      }
313    };
314
315    // Expect the error to propagate as-is
316    try {
317      sample.noneDeclared();
318      fail();
319    } catch (SomeError expected) {
320    }
321  }
322
323  public void testPropagate_NoneDeclared_CheckedThrown() {
324    Sample sample = new Sample() {
325      @Override public void noneDeclared() {
326        try {
327          methodThatThrowsChecked();
328        } catch (Throwable t) {
329          throw Throwables.propagate(t);
330        }
331      }
332    };
333
334    // Expect the undeclared exception to have been chained inside another
335    try {
336      sample.noneDeclared();
337      fail();
338    } catch (RuntimeException expected) {
339      assertTrue(expected.getCause() instanceof SomeCheckedException);
340    }
341  }
342
343  public void testPropagateIfInstanceOf_NoneThrown()
344      throws SomeCheckedException {
345    Sample sample = new Sample() {
346      @Override public void oneDeclared() throws SomeCheckedException {
347        try {
348          methodThatDoesntThrowAnything();
349        } catch (Throwable t) {
350          Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
351          throw Throwables.propagate(t);
352        }
353      }
354    };
355
356    // Expect no exception to be thrown
357    sample.oneDeclared();
358  }
359
360  public void testPropagateIfInstanceOf_DeclaredThrown() {
361    Sample sample = new Sample() {
362      @Override public void oneDeclared() throws SomeCheckedException {
363        try {
364          methodThatThrowsChecked();
365        } catch (Throwable t) {
366          Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
367          throw Throwables.propagate(t);
368        }
369      }
370    };
371
372    // Expect declared exception to be thrown as-is
373    try {
374      sample.oneDeclared();
375      fail();
376    } catch (SomeCheckedException e) {
377    }
378  }
379
380  public void testPropagateIfInstanceOf_UncheckedThrown()
381      throws SomeCheckedException {
382    Sample sample = new Sample() {
383      @Override public void oneDeclared() throws SomeCheckedException {
384        try {
385          methodThatThrowsUnchecked();
386        } catch (Throwable t) {
387          Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
388          throw Throwables.propagate(t);
389        }
390      }
391    };
392
393    // Expect unchecked exception to be thrown as-is
394    try {
395      sample.oneDeclared();
396      fail();
397    } catch (SomeUncheckedException e) {
398    }
399  }
400
401  public void testPropagateIfInstanceOf_UndeclaredThrown()
402      throws SomeCheckedException {
403    Sample sample = new Sample() {
404      @Override public void oneDeclared() throws SomeCheckedException {
405        try {
406          methodThatThrowsOtherChecked();
407        } catch (Throwable t) {
408          Throwables.propagateIfInstanceOf(t, SomeCheckedException.class);
409          throw Throwables.propagate(t);
410        }
411      }
412    };
413
414    // Expect undeclared exception wrapped by RuntimeException to be thrown
415    try {
416      sample.oneDeclared();
417      fail();
418    } catch (RuntimeException e) {
419      assertTrue(e.getCause() instanceof SomeOtherCheckedException);
420    }
421  }
422
423  public void testPropageIfInstanceOf_null() throws SomeCheckedException {
424    Throwables.propagateIfInstanceOf(null, SomeCheckedException.class);
425  }
426
427  public void testGetRootCause_NoCause() {
428    SomeCheckedException exception = new SomeCheckedException();
429    assertSame(exception, Throwables.getRootCause(exception));
430  }
431
432  public void testGetRootCause_SingleWrapped() {
433    SomeCheckedException cause = new SomeCheckedException();
434    SomeChainingException exception = new SomeChainingException(cause);
435    assertSame(cause, Throwables.getRootCause(exception));
436  }
437
438  public void testGetRootCause_DoubleWrapped() {
439    SomeCheckedException cause = new SomeCheckedException();
440    SomeChainingException exception =
441        new SomeChainingException(new SomeChainingException(cause));
442    assertSame(cause, Throwables.getRootCause(exception));
443  }
444
445  private static class SomeThrowable extends Throwable {}
446  private static class SomeError extends Error {}
447  private static class SomeCheckedException extends Exception {}
448  private static class SomeOtherCheckedException extends Exception {}
449  private static class SomeUncheckedException extends RuntimeException {}
450  private static class SomeUndeclaredCheckedException extends Exception {}
451  private static class SomeChainingException extends RuntimeException {
452    public SomeChainingException(Throwable cause) {
453      super(cause);
454    }
455  }
456
457  static class Sample {
458    void noneDeclared() {}
459    /*
460     * Subclasses of Sample will define methods with these signatures that throw
461     * these exceptions, so we must declare them in the throws clause here.
462     * Eclipse doesn't think being thrown from a subclass's non-public,
463     * non-protected method with the same signature counts as being "used."
464     */
465    @SuppressWarnings("unused")
466    void oneDeclared() throws SomeCheckedException {}
467    @SuppressWarnings("unused")
468    void twoDeclared() throws SomeCheckedException, SomeOtherCheckedException {}
469  }
470
471  static void methodThatDoesntThrowAnything() {}
472  static void methodThatThrowsError() {
473    throw new SomeError();
474  }
475  static void methodThatThrowsUnchecked() {
476    throw new SomeUncheckedException();
477  }
478  static void methodThatThrowsChecked() throws SomeCheckedException {
479    throw new SomeCheckedException();
480  }
481  static void methodThatThrowsOtherChecked() throws SomeOtherCheckedException {
482    throw new SomeOtherCheckedException();
483  }
484  static void methodThatThrowsUndeclaredChecked()
485      throws SomeUndeclaredCheckedException {
486    throw new SomeUndeclaredCheckedException();
487  }
488
489  public void testGetStackTraceAsString() {
490    class StackTraceException extends Exception {
491      StackTraceException(String message) {
492        super(message);
493      }
494    }
495
496    StackTraceException e = new StackTraceException("my message");
497
498    String firstLine = quote(e.getClass().getName() + ": " + e.getMessage());
499    String secondLine = "\\s*at " + ThrowablesTest.class.getName() + "\\..*";
500    String moreLines = "(?:.*\n?)*";
501    String expected = firstLine + "\n" + secondLine + "\n" + moreLines;
502    assertTrue(getStackTraceAsString(e).matches(expected));
503  }
504
505  public void testGetCausalChain() {
506    FileNotFoundException fnfe = new FileNotFoundException();
507    IllegalArgumentException iae = new IllegalArgumentException(fnfe);
508    RuntimeException re = new RuntimeException(iae);
509    IllegalStateException ex = new IllegalStateException(re);
510
511    assertEquals(asList(ex, re, iae, fnfe), Throwables.getCausalChain(ex));
512    assertSame(fnfe, Iterables.getOnlyElement(Throwables.getCausalChain(fnfe)));
513    try {
514      Throwables.getCausalChain(null);
515      fail("Should have throw NPE");
516    } catch (NullPointerException expected) {
517    }
518
519    List<Throwable> causes = Throwables.getCausalChain(ex);
520    try {
521      causes.add(new RuntimeException());
522      fail("List should be unmodifiable");
523    } catch (UnsupportedOperationException expected) {
524    }
525  }
526
527  public void testNullPointers() throws Exception {
528    NullPointerTester tester = new NullPointerTester();
529    tester.setDefault(Throwable.class, new SomeCheckedException());
530    tester.setDefault(Class.class, SomeCheckedException.class);
531    tester.testAllPublicStaticMethods(Throwables.class);
532  }
533}
534