View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.exception;
18  
19  import static org.apache.commons.lang3.LangAssertions.assertNullPointerException;
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNotSame;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertSame;
27  import static org.junit.jupiter.api.Assertions.assertThrows;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  import static org.junit.jupiter.api.Assertions.fail;
30  
31  import java.io.ByteArrayOutputStream;
32  import java.io.IOException;
33  import java.io.PrintStream;
34  import java.io.PrintWriter;
35  import java.io.StringWriter;
36  import java.lang.reflect.Constructor;
37  import java.lang.reflect.Modifier;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.List;
41  import java.util.stream.Collectors;
42  
43  import org.apache.commons.lang3.AbstractLangTest;
44  import org.apache.commons.lang3.test.NotVisibleExceptionFactory;
45  import org.junit.jupiter.api.AfterEach;
46  import org.junit.jupiter.api.BeforeEach;
47  import org.junit.jupiter.api.Test;
48  
49  /**
50   * Tests {@link ExceptionUtils}.
51   */
52  class ExceptionUtilsTest extends AbstractLangTest {
53  
54      private static final class CountingException extends Exception {
55          private static final long serialVersionUID = 1L;
56  
57          private int causeCalls;
58  
59          CountingException(final Throwable cause) {
60              super(null, cause, false, false);
61          }
62  
63          @Override
64          public boolean equals(final Object obj) {
65              throw new AssertionError("Chain walking must not invoke equals");
66          }
67  
68          @Override
69          public synchronized Throwable getCause() {
70              causeCalls++;
71              return super.getCause();
72          }
73  
74          @Override
75          public int hashCode() {
76              throw new AssertionError("Chain walking must not invoke hashCode");
77          }
78      }
79  
80      private static final class EqualException extends Exception {
81          private static final long serialVersionUID = 1L;
82  
83          @Override
84          public boolean equals(final Object obj) {
85              return obj instanceof EqualException;
86          }
87  
88          @Override
89          public int hashCode() {
90              return 1;
91          }
92      }
93  
94      /**
95       * Provides a method with a well known chained/nested exception
96       * name which matches the full signature (e.g. has a return value
97       * of {@code Throwable}).
98       */
99      private static final class ExceptionWithCause extends Exception {
100         private static final long serialVersionUID = 1L;
101 
102         private Throwable cause;
103 
104         ExceptionWithCause(final String str, final Throwable cause) {
105             super(str);
106             setCause(cause);
107         }
108 
109         ExceptionWithCause(final Throwable cause) {
110             setCause(cause);
111         }
112 
113         @Override
114         public synchronized Throwable getCause() {
115             return cause;
116         }
117 
118         public void setCause(final Throwable cause) {
119             this.cause = cause;
120         }
121     }
122 
123     /**
124      * Provides a method with a well known chained/nested exception
125      * name which does not match the full signature (e.g. lacks a
126      * return value of {@code Throwable}).
127      */
128     private static final class ExceptionWithoutCause extends Exception {
129         private static final long serialVersionUID = 1L;
130 
131         @SuppressWarnings("unused")
132         public void getTargetException() {
133             // noop
134         }
135     }
136 
137     // Temporary classes to allow the nested exception code to be removed
138     // prior to a rewrite of this test class.
139     private static final class NestableException extends Exception {
140         private static final long serialVersionUID = 1L;
141 
142         @SuppressWarnings("unused")
143         NestableException() {
144         }
145 
146         NestableException(final Throwable t) {
147             super(t);
148         }
149     }
150 
151     public static class TestThrowable extends Throwable {
152         private static final long serialVersionUID = 1L;
153     }
154 
155     private static int redeclareCheckedException() {
156         return throwsCheckedException();
157     }
158 
159     private static int throwsCheckedException() {
160         try {
161             throw new IOException();
162         } catch (final Exception e) {
163             ExceptionUtils.asRuntimeException(e);
164             return -1;
165         }
166     }
167 
168     private ExceptionWithCause cyclicCause;
169 
170     private Throwable jdkNoCause;
171 
172     private NestableException nested;
173 
174     private Throwable notVisibleException;
175 
176     private Throwable withCause;
177 
178     private Throwable withoutCause;
179 
180     private Throwable createExceptionWithCause() {
181         try {
182             try {
183                 throw new ExceptionWithCause(createExceptionWithoutCause());
184             } catch (final Throwable t) {
185                 throw new ExceptionWithCause(t);
186             }
187         } catch (final Throwable t) {
188             return t;
189         }
190     }
191 
192     private Throwable createExceptionWithoutCause() {
193         try {
194             throw new ExceptionWithoutCause();
195         } catch (final Throwable t) {
196             return t;
197         }
198     }
199 
200     @BeforeEach
201     public void setUp() {
202         withoutCause = createExceptionWithoutCause();
203         nested = new NestableException(withoutCause);
204         withCause = new ExceptionWithCause(nested);
205         jdkNoCause = new NullPointerException();
206         final ExceptionWithCause a = new ExceptionWithCause(null);
207         final ExceptionWithCause b = new ExceptionWithCause(a);
208         a.setCause(b);
209         cyclicCause = new ExceptionWithCause(a);
210         notVisibleException = NotVisibleExceptionFactory.createException(withoutCause);
211     }
212 
213     @AfterEach
214     public void tearDown() {
215         withoutCause = null;
216         nested = null;
217         withCause = null;
218         jdkNoCause = null;
219         cyclicCause = null;
220         notVisibleException = null;
221     }
222 
223     @Test
224     void test_getMessage_Throwable() {
225         Throwable th = null;
226         assertEquals("", ExceptionUtils.getMessage(th));
227 
228         th = new IllegalArgumentException("Base");
229         assertEquals("IllegalArgumentException: Base", ExceptionUtils.getMessage(th));
230 
231         th = new ExceptionWithCause("Wrapper", th);
232         assertEquals("ExceptionUtilsTest.ExceptionWithCause: Wrapper", ExceptionUtils.getMessage(th));
233     }
234 
235     @Test
236     void test_getRootCauseMessage_Throwable() {
237         Throwable th = null;
238         assertEquals("", ExceptionUtils.getRootCauseMessage(th));
239 
240         th = new IllegalArgumentException("Base");
241         assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
242 
243         th = new ExceptionWithCause("Wrapper", th);
244         assertEquals("IllegalArgumentException: Base", ExceptionUtils.getRootCauseMessage(th));
245     }
246 
247     @Test
248     void testAsRuntimeException() {
249         final Exception expected = new InterruptedException();
250         assertSame(expected, assertThrows(Exception.class, () -> ExceptionUtils.asRuntimeException(expected)));
251         assertNotSame(expected, assertThrows(Exception.class, () -> ExceptionUtils.asRuntimeException(new InterruptedException())));
252         // API return typed to compile to Object
253         assertThrows(expected.getClass(), () -> {
254             @SuppressWarnings("unused")
255             final Object retVal = ExceptionUtils.asRuntimeException(expected);
256         });
257         // API return typed to compile to RuntimeException
258         assertThrows(expected.getClass(), () -> {
259             @SuppressWarnings("unused")
260             final RuntimeException retVal = ExceptionUtils.asRuntimeException(expected);
261         });
262         // API return typed to compile to RuntimeException subclass
263         assertThrows(expected.getClass(), () -> {
264             @SuppressWarnings("unused")
265             final IllegalStateException retVal = ExceptionUtils.asRuntimeException(expected);
266         });
267     }
268 
269     @Test
270     void testCatchTechniques() {
271         IOException ioe = assertThrows(IOException.class, ExceptionUtilsTest::throwsCheckedException);
272         assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
273 
274         ioe = assertThrows(IOException.class, ExceptionUtilsTest::redeclareCheckedException);
275         assertEquals(1, ExceptionUtils.getThrowableCount(ioe));
276     }
277 
278     @Test
279     void testConstructor() {
280         assertNotNull(new ExceptionUtils());
281         final Constructor<?>[] cons = ExceptionUtils.class.getDeclaredConstructors();
282         assertEquals(1, cons.length);
283         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
284         assertTrue(Modifier.isPublic(ExceptionUtils.class.getModifiers()));
285         assertFalse(Modifier.isFinal(ExceptionUtils.class.getModifiers()));
286     }
287 
288     @Test
289     void testForEach_jdkNoCause() {
290         final List<Throwable> throwables = new ArrayList<>();
291         ExceptionUtils.forEach(jdkNoCause, throwables::add);
292         assertEquals(1, throwables.size());
293         assertSame(jdkNoCause, throwables.get(0));
294     }
295 
296     @Test
297     void testForEach_nested() {
298         final List<Throwable> throwables = new ArrayList<>();
299         ExceptionUtils.forEach(nested, throwables::add);
300         assertEquals(2, throwables.size());
301         assertSame(nested, throwables.get(0));
302         assertSame(withoutCause, throwables.get(1));
303     }
304 
305     @Test
306     void testForEach_null() {
307         final List<Throwable> throwables = new ArrayList<>();
308         ExceptionUtils.forEach(null, throwables::add);
309         assertEquals(0, throwables.size());
310     }
311 
312     @Test
313     void testForEach_recursiveCause() {
314         final List<Throwable> throwables = new ArrayList<>();
315         ExceptionUtils.forEach(cyclicCause, throwables::add);
316         assertEquals(3, throwables.size());
317         assertSame(cyclicCause, throwables.get(0));
318         assertSame(cyclicCause.getCause(), throwables.get(1));
319         assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
320     }
321 
322     @Test
323     void testForEach_withCause() {
324         final List<Throwable> throwables = new ArrayList<>();
325         ExceptionUtils.forEach(withCause, throwables::add);
326         assertEquals(3, throwables.size());
327         assertSame(withCause, throwables.get(0));
328         assertSame(nested, throwables.get(1));
329         assertSame(withoutCause, throwables.get(2));
330     }
331 
332     @Test
333     void testForEach_withoutCause() {
334         final List<Throwable> throwables = new ArrayList<>();
335         ExceptionUtils.forEach(withoutCause, throwables::add);
336         assertEquals(1, throwables.size());
337         assertSame(withoutCause, throwables.get(0));
338     }
339 
340     @SuppressWarnings("deprecation") // Specifically tests the deprecated methods
341     @Test
342     void testGetCause_Throwable() {
343         assertSame(null, ExceptionUtils.getCause(null));
344         assertSame(null, ExceptionUtils.getCause(withoutCause));
345         assertSame(withoutCause, ExceptionUtils.getCause(nested));
346         assertSame(nested, ExceptionUtils.getCause(withCause));
347         assertSame(null, ExceptionUtils.getCause(jdkNoCause));
348         assertSame(cyclicCause.getCause(), ExceptionUtils.getCause(cyclicCause));
349         assertSame(cyclicCause.getCause().getCause(), ExceptionUtils.getCause(cyclicCause.getCause()));
350         assertSame(cyclicCause.getCause(), ExceptionUtils.getCause(cyclicCause.getCause().getCause()));
351         assertSame(withoutCause, ExceptionUtils.getCause(notVisibleException));
352     }
353 
354     @SuppressWarnings("deprecation") // Specifically tests the deprecated methods
355     @Test
356     void testGetCause_ThrowableArray() {
357         assertSame(null, ExceptionUtils.getCause(null, null));
358         assertSame(null, ExceptionUtils.getCause(null, new String[0]));
359 
360         // not known type, so match on supplied method names
361         assertSame(nested, ExceptionUtils.getCause(withCause, null));  // default names
362         assertSame(null, ExceptionUtils.getCause(withCause, new String[0]));
363         assertSame(null, ExceptionUtils.getCause(withCause, new String[]{null}));
364         assertSame(nested, ExceptionUtils.getCause(withCause, new String[]{"getCause"}));
365 
366         // not known type, so match on supplied method names
367         assertSame(null, ExceptionUtils.getCause(withoutCause, null));
368         assertSame(null, ExceptionUtils.getCause(withoutCause, new String[0]));
369         assertSame(null, ExceptionUtils.getCause(withoutCause, new String[]{null}));
370         assertSame(null, ExceptionUtils.getCause(withoutCause, new String[]{"getCause"}));
371         assertSame(null, ExceptionUtils.getCause(withoutCause, new String[]{"getTargetException"}));
372     }
373 
374     @Test
375     void testGetRootCause_Throwable() {
376         assertSame(null, ExceptionUtils.getRootCause(null));
377         assertSame(withoutCause, ExceptionUtils.getRootCause(withoutCause));
378         assertSame(withoutCause, ExceptionUtils.getRootCause(nested));
379         assertSame(withoutCause, ExceptionUtils.getRootCause(withCause));
380         assertSame(jdkNoCause, ExceptionUtils.getRootCause(jdkNoCause));
381         assertSame(cyclicCause.getCause().getCause(), ExceptionUtils.getRootCause(cyclicCause));
382     }
383 
384     @Test
385     void testGetRootCauseStackTrace_Throwable() {
386         assertEquals(0, ExceptionUtils.getRootCauseStackTrace(null).length);
387 
388         final Throwable cause = createExceptionWithCause();
389         String[] stackTrace = ExceptionUtils.getRootCauseStackTrace(cause);
390         boolean match = false;
391         for (final String element : stackTrace) {
392             if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
393                 match = true;
394                 break;
395             }
396         }
397         assertTrue(match);
398 
399         stackTrace = ExceptionUtils.getRootCauseStackTrace(withoutCause);
400         match = false;
401         for (final String element : stackTrace) {
402             if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
403                 match = true;
404                 break;
405             }
406         }
407         assertFalse(match);
408     }
409 
410     @Test
411     void testGetRootCauseStackTraceList_Throwable() {
412         assertEquals(0, ExceptionUtils.getRootCauseStackTraceList(null).size());
413 
414         final Throwable cause = createExceptionWithCause();
415         List<String> stackTrace = ExceptionUtils.getRootCauseStackTraceList(cause);
416         boolean match = false;
417         for (final String element : stackTrace) {
418             if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
419                 match = true;
420                 break;
421             }
422         }
423         assertTrue(match);
424 
425         stackTrace = ExceptionUtils.getRootCauseStackTraceList(withoutCause);
426         match = false;
427         for (final String element : stackTrace) {
428             if (element.startsWith(ExceptionUtils.WRAPPED_MARKER)) {
429                 match = true;
430                 break;
431             }
432         }
433         assertFalse(match);
434     }
435 
436     /**
437      * Tests that ordinary message lines whose first non-whitespace characters happen to be "at" are no longer mistaken for stack
438      * frames, so a multi-line untrusted message can neither inject a non-frame-shaped line into the frame list nor suppress the
439      * real frames that follow it.
440      */
441     @Test
442     void testGetRootCauseStackTraceMessageLinesNotMistakenForFrames() {
443         final Throwable t = new IllegalArgumentException(
444                 "denied" + System.lineSeparator() + " attack detected" + System.lineSeparator() + "at your request, more text");
445         final String[] stackTrace = ExceptionUtils.getRootCauseStackTrace(t);
446         // No fabricated entries: every frame line after the header parses as "at <ref>(...".
447         boolean sawRealFrame = false;
448         for (int i = 1; i < stackTrace.length; i++) {
449             final String element = stackTrace[i];
450             if (element.contains("attack detected") || element.contains("at your request")) {
451                 fail("message text classified as a stack frame: " + element);
452             }
453             if (element.contains(getClass().getSimpleName())) {
454                 sawRealFrame = true;
455             }
456         }
457         // The real frames survive: this test method must be present in the parsed trace.
458         assertTrue(sawRealFrame, "real frames were suppressed");
459     }
460 
461     /**
462      * Tests that every frame shape {@code StackTraceElement.toString()} can emit is accepted by the tightened frame matcher,
463      * in particular JDK 9+ module-versioned frames ({@code mod@version/pkg.Class}), which a character whitelist without
464      * {@code '@'} would reject — silently dropping that frame and every real frame below it. Uses a throwable that prints a
465      * fixed trace so the shapes are deterministic without constructing module-versioned {@link StackTraceElement}s.
466      */
467     @Test
468     void testGetStackFrameListAcceptsAllRealFrameShapes() {
469         final String[] frames = {
470             "\tat com.example.Foo.bar(Foo.java:42)",                                            // classic
471             "\tat app//com.foo.Main.main(Main.java:10)",                                        // class loader prefix
472             "\tat com.foo.mod@1.0.3/com.foo.Helper.help(Helper.java:7)",                        // module name @ version
473             "\tat java.base/java.lang.Thread.run(Thread.java:833)",                             // module, no version
474             "\tat com.foo.Main$$Lambda$17/0x0000000800c02a48.run(Unknown Source)",              // lambda / hidden class
475             "\tat com.example.Foo.<init>(Foo.java:5)",                                          // constructor
476             "\tat java.base/java.lang.Object.wait(Native Method)"                               // native
477         };
478         final StringBuilder text = new StringBuilder("java.lang.RuntimeException: boom").append(System.lineSeparator());
479         for (final String frame : frames) {
480             text.append(frame).append(System.lineSeparator());
481         }
482         final Throwable fixed = new RuntimeException("boom") {
483             private static final long serialVersionUID = 1L;
484 
485             @Override
486             public void printStackTrace(final PrintWriter writer) {
487                 writer.print(text);
488             }
489         };
490         final List<String> list = ExceptionUtils.getStackFrameList(fixed);
491         assertEquals(Arrays.asList(frames), list, "a legitimate frame shape was rejected (and frames below it dropped)");
492     }
493 
494     /**
495      * Tests that message text is still rejected by the frame matcher: forged lines lacking the no-whitespace-before-'('
496      * frame syntax must not start or extend the frame list.
497      */
498     @Test
499     void testGetStackFrameListRejectsForgedMessageLines() {
500         final String[] forged = {
501             " attack detected",                              // "at" not followed by space-delimited reference
502             "at your request, more text",                    // no leading whitespace
503             "\tat your request, more text",                  // no '(' at all
504             "\tat forged frame entry(Evil.java:1)",          // whitespace between "at " and '('
505             "\tat (Evil.java:1)"                             // empty reference
506         };
507         final StringBuilder text = new StringBuilder("java.lang.RuntimeException: boom").append(System.lineSeparator());
508         for (final String line : forged) {
509             text.append(line).append(System.lineSeparator());
510         }
511         text.append("\tat com.example.Foo.bar(Foo.java:42)").append(System.lineSeparator());
512         final Throwable fixed = new RuntimeException("boom") {
513             private static final long serialVersionUID = 1L;
514 
515             @Override
516             public void printStackTrace(final PrintWriter writer) {
517                 writer.print(text);
518             }
519         };
520         final List<String> list = ExceptionUtils.getStackFrameList(fixed);
521         assertEquals(Arrays.asList("\tat com.example.Foo.bar(Foo.java:42)"), list,
522                 "forged message text was classified as a stack frame");
523     }
524 
525     @Test
526     /** getStackFrames returns empty string array when the argument is null */
527     void testgetStackFramesHappyPath() {
528         final String[] actual = ExceptionUtils.getStackFrames(new Throwable() {
529             private static final long serialVersionUID = 1L;
530 
531             // provide static stack trace to make test stable
532             @Override
533             public void printStackTrace(final PrintWriter s) {
534                 s.write("org.apache.commons.lang3.exception.ExceptionUtilsTest$1\n" +
535                     "\tat org.apache.commons.lang3.exception.ExceptionUtilsTest.testgetStackFramesGappyPath(ExceptionUtilsTest.java:706)\n" +
536                     "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" +
537                     "\tat com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)\n" +
538                     "\tat com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)\n");
539             }
540         });
541 
542         assertArrayEquals(new String[]{
543             "org.apache.commons.lang3.exception.ExceptionUtilsTest$1",
544             "\tat org.apache.commons.lang3.exception.ExceptionUtilsTest.testgetStackFramesGappyPath(ExceptionUtilsTest.java:706)",
545             "\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
546             "\tat com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)",
547             "\tat com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)"
548         }, actual);
549     }
550 
551     @Test
552     /** getStackFrames returns the string array of the stack frames when there is a real exception */
553     void testgetStackFramesNullArg() {
554         final String[] actual = ExceptionUtils.getStackFrames((Throwable) null);
555         assertEquals(0, actual.length);
556     }
557 
558     @Test
559     void testGetThrowableCount_Throwable() {
560         assertEquals(0, ExceptionUtils.getThrowableCount(null));
561         assertEquals(1, ExceptionUtils.getThrowableCount(withoutCause));
562         assertEquals(2, ExceptionUtils.getThrowableCount(nested));
563         assertEquals(3, ExceptionUtils.getThrowableCount(withCause));
564         assertEquals(1, ExceptionUtils.getThrowableCount(jdkNoCause));
565         assertEquals(3, ExceptionUtils.getThrowableCount(cyclicCause));
566     }
567 
568     @Test
569     void testGetThrowableList_Throwable_jdkNoCause() {
570         final List<?> throwables = ExceptionUtils.getThrowableList(jdkNoCause);
571         assertEquals(1, throwables.size());
572         assertSame(jdkNoCause, throwables.get(0));
573     }
574 
575     @Test
576     void testGetThrowableList_Throwable_nested() {
577         final List<?> throwables = ExceptionUtils.getThrowableList(nested);
578         assertEquals(2, throwables.size());
579         assertSame(nested, throwables.get(0));
580         assertSame(withoutCause, throwables.get(1));
581     }
582 
583     @Test
584     void testGetThrowableList_Throwable_null() {
585         final List<?> throwables = ExceptionUtils.getThrowableList(null);
586         assertEquals(0, throwables.size());
587     }
588 
589     @Test
590     void testGetThrowableList_Throwable_recursiveCause() {
591         final List<?> throwables = ExceptionUtils.getThrowableList(cyclicCause);
592         assertEquals(3, throwables.size());
593         assertSame(cyclicCause, throwables.get(0));
594         assertSame(cyclicCause.getCause(), throwables.get(1));
595         assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
596     }
597 
598     @Test
599     void testGetThrowableList_Throwable_withCause() {
600         final List<?> throwables = ExceptionUtils.getThrowableList(withCause);
601         assertEquals(3, throwables.size());
602         assertSame(withCause, throwables.get(0));
603         assertSame(nested, throwables.get(1));
604         assertSame(withoutCause, throwables.get(2));
605     }
606 
607     @Test
608     void testGetThrowableList_Throwable_withoutCause() {
609         final List<?> throwables = ExceptionUtils.getThrowableList(withoutCause);
610         assertEquals(1, throwables.size());
611         assertSame(withoutCause, throwables.get(0));
612     }
613 
614     @Test
615     void testGetThrowableListDeepChain() {
616         final CountingException[] chain = new CountingException[10_000];
617         for (int i = chain.length - 1; i >= 0; i--) {
618             chain[i] = new CountingException(i + 1 < chain.length ? chain[i + 1] : null);
619         }
620         final List<Throwable> throwables = ExceptionUtils.getThrowableList(chain[0]);
621         assertEquals(chain.length, throwables.size());
622         for (int i = 0; i < chain.length; i++) {
623             assertSame(chain[i], throwables.get(i));
624             assertEquals(1, chain[i].causeCalls);
625         }
626         assertEquals(chain.length, ExceptionUtils.getThrowableCount(chain[0]));
627         assertSame(chain[chain.length - 1], ExceptionUtils.getRootCause(chain[0]));
628         final Throwable[] array = ExceptionUtils.getThrowables(chain[0]);
629         final Throwable[] stream = ExceptionUtils.stream(chain[0]).toArray(Throwable[]::new);
630         assertEquals(chain.length, array.length);
631         assertEquals(chain.length, stream.length);
632         for (int i = 0; i < chain.length; i++) {
633             assertSame(chain[i], array[i]);
634             assertSame(chain[i], stream[i]);
635         }
636     }
637 
638     @Test
639     void testGetThrowableListEqualExceptions() {
640         final EqualException first = new EqualException();
641         final EqualException second = new EqualException();
642         final EqualException third = new EqualException();
643         first.initCause(second);
644         second.initCause(third);
645         final List<Throwable> throwables = ExceptionUtils.getThrowableList(first);
646         assertEquals(3, throwables.size());
647         assertSame(first, throwables.get(0));
648         assertSame(second, throwables.get(1));
649         assertSame(third, throwables.get(2));
650         third.initCause(second);
651         final List<Throwable> cyclic = ExceptionUtils.getThrowableList(first);
652         assertEquals(3, cyclic.size());
653         assertSame(first, cyclic.get(0));
654         assertSame(second, cyclic.get(1));
655         assertSame(third, cyclic.get(2));
656     }
657 
658     @Test
659     void testGetThrowableListSelfCause() {
660         final ExceptionWithCause exception = new ExceptionWithCause(null);
661         exception.setCause(exception);
662         final List<Throwable> throwables = ExceptionUtils.getThrowableList(exception);
663         assertEquals(1, throwables.size());
664         assertSame(exception, throwables.get(0));
665     }
666 
667     @Test
668     void testGetThrowables_Throwable_jdkNoCause() {
669         final Throwable[] throwables = ExceptionUtils.getThrowables(jdkNoCause);
670         assertEquals(1, throwables.length);
671         assertSame(jdkNoCause, throwables[0]);
672     }
673 
674     @Test
675     void testGetThrowables_Throwable_nested() {
676         final Throwable[] throwables = ExceptionUtils.getThrowables(nested);
677         assertEquals(2, throwables.length);
678         assertSame(nested, throwables[0]);
679         assertSame(withoutCause, throwables[1]);
680     }
681 
682     @Test
683     void testGetThrowables_Throwable_null() {
684         assertEquals(0, ExceptionUtils.getThrowables(null).length);
685     }
686 
687     @Test
688     void testGetThrowables_Throwable_recursiveCause() {
689         final Throwable[] throwables = ExceptionUtils.getThrowables(cyclicCause);
690         assertEquals(3, throwables.length);
691         assertSame(cyclicCause, throwables[0]);
692         assertSame(cyclicCause.getCause(), throwables[1]);
693         assertSame(cyclicCause.getCause().getCause(), throwables[2]);
694     }
695 
696     @Test
697     void testGetThrowables_Throwable_withCause() {
698         final Throwable[] throwables = ExceptionUtils.getThrowables(withCause);
699         assertEquals(3, throwables.length);
700         assertSame(withCause, throwables[0]);
701         assertSame(nested, throwables[1]);
702         assertSame(withoutCause, throwables[2]);
703     }
704 
705     @Test
706     void testGetThrowables_Throwable_withoutCause() {
707         final Throwable[] throwables = ExceptionUtils.getThrowables(withoutCause);
708         assertEquals(1, throwables.length);
709         assertSame(withoutCause, throwables[0]);
710     }
711 
712     @Test
713     void testIndexOf_ThrowableClass() {
714         assertEquals(-1, ExceptionUtils.indexOfThrowable(null, null));
715         assertEquals(-1, ExceptionUtils.indexOfThrowable(null, NestableException.class));
716 
717         assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, null));
718         assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithCause.class));
719         assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, NestableException.class));
720         assertEquals(0, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithoutCause.class));
721 
722         assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, null));
723         assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithCause.class));
724         assertEquals(0, ExceptionUtils.indexOfThrowable(nested, NestableException.class));
725         assertEquals(1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithoutCause.class));
726 
727         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, null));
728         assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class));
729         assertEquals(1, ExceptionUtils.indexOfThrowable(withCause, NestableException.class));
730         assertEquals(2, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithoutCause.class));
731 
732         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Exception.class));
733         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Throwable.class));
734     }
735 
736     @Test
737     void testIndexOf_ThrowableClassInt() {
738         assertEquals(-1, ExceptionUtils.indexOfThrowable(null, null, 0));
739         assertEquals(-1, ExceptionUtils.indexOfThrowable(null, NestableException.class, 0));
740 
741         assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, null));
742         assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithCause.class, 0));
743         assertEquals(-1, ExceptionUtils.indexOfThrowable(withoutCause, NestableException.class, 0));
744         assertEquals(0, ExceptionUtils.indexOfThrowable(withoutCause, ExceptionWithoutCause.class, 0));
745 
746         assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, null, 0));
747         assertEquals(-1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithCause.class, 0));
748         assertEquals(0, ExceptionUtils.indexOfThrowable(nested, NestableException.class, 0));
749         assertEquals(1, ExceptionUtils.indexOfThrowable(nested, ExceptionWithoutCause.class, 0));
750 
751         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, null));
752         assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 0));
753         assertEquals(1, ExceptionUtils.indexOfThrowable(withCause, NestableException.class, 0));
754         assertEquals(2, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithoutCause.class, 0));
755 
756         assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, -1));
757         assertEquals(0, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 0));
758         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 1));
759         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, ExceptionWithCause.class, 9));
760 
761         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Exception.class, 0));
762         assertEquals(-1, ExceptionUtils.indexOfThrowable(withCause, Throwable.class, 0));
763     }
764 
765     @Test
766     void testIndexOfType_ThrowableClass() {
767         assertEquals(-1, ExceptionUtils.indexOfType(null, null));
768         assertEquals(-1, ExceptionUtils.indexOfType(null, NestableException.class));
769 
770         assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, null));
771         assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, ExceptionWithCause.class));
772         assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, NestableException.class));
773         assertEquals(0, ExceptionUtils.indexOfType(withoutCause, ExceptionWithoutCause.class));
774 
775         assertEquals(-1, ExceptionUtils.indexOfType(nested, null));
776         assertEquals(-1, ExceptionUtils.indexOfType(nested, ExceptionWithCause.class));
777         assertEquals(0, ExceptionUtils.indexOfType(nested, NestableException.class));
778         assertEquals(1, ExceptionUtils.indexOfType(nested, ExceptionWithoutCause.class));
779 
780         assertEquals(-1, ExceptionUtils.indexOfType(withCause, null));
781         assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class));
782         assertEquals(1, ExceptionUtils.indexOfType(withCause, NestableException.class));
783         assertEquals(2, ExceptionUtils.indexOfType(withCause, ExceptionWithoutCause.class));
784 
785         assertEquals(0, ExceptionUtils.indexOfType(withCause, Exception.class));
786         assertEquals(0, ExceptionUtils.indexOfType(withCause, Throwable.class));
787     }
788 
789     @Test
790     void testIndexOfType_ThrowableClassInt() {
791         assertEquals(-1, ExceptionUtils.indexOfType(null, null, 0));
792         assertEquals(-1, ExceptionUtils.indexOfType(null, NestableException.class, 0));
793 
794         assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, null));
795         assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, ExceptionWithCause.class, 0));
796         assertEquals(-1, ExceptionUtils.indexOfType(withoutCause, NestableException.class, 0));
797         assertEquals(0, ExceptionUtils.indexOfType(withoutCause, ExceptionWithoutCause.class, 0));
798 
799         assertEquals(-1, ExceptionUtils.indexOfType(nested, null, 0));
800         assertEquals(-1, ExceptionUtils.indexOfType(nested, ExceptionWithCause.class, 0));
801         assertEquals(0, ExceptionUtils.indexOfType(nested, NestableException.class, 0));
802         assertEquals(1, ExceptionUtils.indexOfType(nested, ExceptionWithoutCause.class, 0));
803 
804         assertEquals(-1, ExceptionUtils.indexOfType(withCause, null));
805         assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 0));
806         assertEquals(1, ExceptionUtils.indexOfType(withCause, NestableException.class, 0));
807         assertEquals(2, ExceptionUtils.indexOfType(withCause, ExceptionWithoutCause.class, 0));
808 
809         assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, -1));
810         assertEquals(0, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 0));
811         assertEquals(-1, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 1));
812         assertEquals(-1, ExceptionUtils.indexOfType(withCause, ExceptionWithCause.class, 9));
813 
814         assertEquals(0, ExceptionUtils.indexOfType(withCause, Exception.class, 0));
815         assertEquals(0, ExceptionUtils.indexOfType(withCause, Throwable.class, 0));
816     }
817 
818     @Test
819     void testIsChecked_checked() {
820         assertTrue(ExceptionUtils.isChecked(new IOException()));
821     }
822 
823     @Test
824     void testIsChecked_error() {
825         assertFalse(ExceptionUtils.isChecked(new StackOverflowError()));
826     }
827 
828     @Test
829     void testIsChecked_null() {
830         assertFalse(ExceptionUtils.isChecked(null));
831     }
832 
833     @Test
834     void testIsChecked_unchecked() {
835         assertFalse(ExceptionUtils.isChecked(new IllegalArgumentException()));
836     }
837 
838     @Test
839     void testIsCheckedCustomThrowable() {
840         assertTrue(ExceptionUtils.isChecked(new TestThrowable()));
841     }
842 
843     @Test
844     void testIsUnchecked_checked() {
845         assertFalse(ExceptionUtils.isUnchecked(new IOException()));
846     }
847 
848     @Test
849     void testIsUnchecked_error() {
850         assertTrue(ExceptionUtils.isUnchecked(new StackOverflowError()));
851     }
852 
853     @Test
854     void testIsUnchecked_null() {
855         assertFalse(ExceptionUtils.isUnchecked(null));
856     }
857 
858     @Test
859     void testIsUnchecked_unchecked() {
860         assertTrue(ExceptionUtils.isUnchecked(new IllegalArgumentException()));
861     }
862 
863     @Test
864     void testIsUnCheckedCustomThrowable() {
865         assertFalse(ExceptionUtils.isUnchecked(new TestThrowable()));
866     }
867 
868     @Test
869     void testPrintRootCauseStackTrace_Throwable() {
870         ExceptionUtils.printRootCauseStackTrace(null);
871         // could pipe system.err to a known stream, but not much point as
872         // internally this method calls stream method anyway
873     }
874 
875     @Test
876     void testPrintRootCauseStackTrace_ThrowableStream() {
877         ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
878         ExceptionUtils.printRootCauseStackTrace(null, (PrintStream) null);
879         ExceptionUtils.printRootCauseStackTrace(null, new PrintStream(out));
880         assertEquals(0, out.toString().length());
881         assertNullPointerException(() -> ExceptionUtils.printRootCauseStackTrace(withCause, (PrintStream) null));
882         out = new ByteArrayOutputStream(1024);
883         final Throwable cause = createExceptionWithCause();
884         ExceptionUtils.printRootCauseStackTrace(cause, new PrintStream(out));
885         String stackTrace = out.toString();
886         assertTrue(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
887         out = new ByteArrayOutputStream(1024);
888         ExceptionUtils.printRootCauseStackTrace(withoutCause, new PrintStream(out));
889         stackTrace = out.toString();
890         assertFalse(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
891     }
892 
893     @Test
894     void testPrintRootCauseStackTrace_ThrowableWriter() {
895         StringWriter writer = new StringWriter(1024);
896         ExceptionUtils.printRootCauseStackTrace(null, (PrintWriter) null);
897         ExceptionUtils.printRootCauseStackTrace(null, new PrintWriter(writer));
898         assertEquals(0, writer.getBuffer().length());
899         assertNullPointerException(() -> ExceptionUtils.printRootCauseStackTrace(withCause, (PrintWriter) null));
900         writer = new StringWriter(1024);
901         final Throwable cause = createExceptionWithCause();
902         ExceptionUtils.printRootCauseStackTrace(cause, new PrintWriter(writer));
903         String stackTrace = writer.toString();
904         assertTrue(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
905         writer = new StringWriter(1024);
906         ExceptionUtils.printRootCauseStackTrace(withoutCause, new PrintWriter(writer));
907         stackTrace = writer.toString();
908         assertFalse(stackTrace.contains(ExceptionUtils.WRAPPED_MARKER));
909     }
910 
911     @Test
912     void testRemoveCommonFrames_ListList() {
913         assertNullPointerException(() -> ExceptionUtils.removeCommonFrames(null, null));
914     }
915 
916     @Test
917     void testRethrow() {
918         final Exception expected = new InterruptedException();
919         // API return typed to compile to Object
920         assertThrows(expected.getClass(), () -> {
921             @SuppressWarnings("unused")
922             final Object retVal = ExceptionUtils.rethrow(expected);
923         });
924         // API return typed to compile to Object subclass
925         assertThrows(expected.getClass(), () -> {
926             @SuppressWarnings("unused")
927             final String retVal = ExceptionUtils.rethrow(expected);
928         });
929         // API return typed to compile to primitive
930         assertThrows(expected.getClass(), () -> {
931             @SuppressWarnings("unused")
932             final int retVal = ExceptionUtils.rethrow(expected);
933         });
934         //
935         assertSame(expected, assertThrows(expected.getClass(), () -> ExceptionUtils.rethrow(expected)));
936         assertNotSame(expected, assertThrows(expected.getClass(), () -> ExceptionUtils.rethrow(new InterruptedException())));
937     }
938 
939     @Test
940     void testStream_jdkNoCause() {
941         assertEquals(1, ExceptionUtils.stream(jdkNoCause).count());
942         assertSame(jdkNoCause, ExceptionUtils.stream(jdkNoCause).toArray()[0]);
943     }
944 
945     @Test
946     void testStream_nested() {
947         assertEquals(2, ExceptionUtils.stream(nested).count());
948         final Object[] array = ExceptionUtils.stream(nested).toArray();
949         assertSame(nested, array[0]);
950         assertSame(withoutCause, array[1]);
951     }
952 
953     @Test
954     void testStream_null() {
955         assertEquals(0, ExceptionUtils.stream(null).count());
956     }
957 
958     @Test
959     void testStream_recursiveCause() {
960         final List<?> throwables = ExceptionUtils.stream(cyclicCause).collect(Collectors.toList());
961         assertEquals(3, throwables.size());
962         assertSame(cyclicCause, throwables.get(0));
963         assertSame(cyclicCause.getCause(), throwables.get(1));
964         assertSame(cyclicCause.getCause().getCause(), throwables.get(2));
965     }
966 
967     @Test
968     void testStream_withCause() {
969         final List<?> throwables = ExceptionUtils.stream(withCause).collect(Collectors.toList());
970         assertEquals(3, throwables.size());
971         assertSame(withCause, throwables.get(0));
972         assertSame(nested, throwables.get(1));
973         assertSame(withoutCause, throwables.get(2));
974     }
975 
976     @Test
977     void testStream_withoutCause() {
978         final List<?> throwables = ExceptionUtils.stream(withoutCause).collect(Collectors.toList());
979         assertEquals(1, throwables.size());
980         assertSame(withoutCause, throwables.get(0));
981     }
982 
983     @Test
984     void testThrowableOf_ThrowableClass() {
985         assertNull(ExceptionUtils.throwableOfThrowable(null, null));
986         assertNull(ExceptionUtils.throwableOfThrowable(null, NestableException.class));
987 
988         assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, null));
989         assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class));
990         assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class));
991         assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class));
992 
993         assertNull(ExceptionUtils.throwableOfThrowable(nested, null));
994         assertNull(ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class));
995         assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class));
996         assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class));
997 
998         assertNull(ExceptionUtils.throwableOfThrowable(withCause, null));
999         assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class));
1000         assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class));
1001         assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class));
1002 
1003         assertNull(ExceptionUtils.throwableOfThrowable(withCause, Exception.class));
1004         assertNull(ExceptionUtils.throwableOfThrowable(withCause, Throwable.class));
1005     }
1006 
1007     @Test
1008     void testThrowableOf_ThrowableClassInt() {
1009         assertNull(ExceptionUtils.throwableOfThrowable(null, null, 0));
1010         assertNull(ExceptionUtils.throwableOfThrowable(null, NestableException.class, 0));
1011 
1012         assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, null));
1013         assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithCause.class, 0));
1014         assertNull(ExceptionUtils.throwableOfThrowable(withoutCause, NestableException.class, 0));
1015         assertEquals(withoutCause, ExceptionUtils.throwableOfThrowable(withoutCause, ExceptionWithoutCause.class, 0));
1016 
1017         assertNull(ExceptionUtils.throwableOfThrowable(nested, null, 0));
1018         assertNull(ExceptionUtils.throwableOfThrowable(nested, ExceptionWithCause.class, 0));
1019         assertEquals(nested, ExceptionUtils.throwableOfThrowable(nested, NestableException.class, 0));
1020         assertEquals(nested.getCause(), ExceptionUtils.throwableOfThrowable(nested, ExceptionWithoutCause.class, 0));
1021 
1022         assertNull(ExceptionUtils.throwableOfThrowable(withCause, null));
1023         assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
1024         assertEquals(withCause.getCause(), ExceptionUtils.throwableOfThrowable(withCause, NestableException.class, 0));
1025         assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithoutCause.class, 0));
1026 
1027         assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, -1));
1028         assertEquals(withCause, ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 0));
1029         assertNull(ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 1));
1030         assertNull(ExceptionUtils.throwableOfThrowable(withCause, ExceptionWithCause.class, 9));
1031 
1032         assertNull(ExceptionUtils.throwableOfThrowable(withCause, Exception.class, 0));
1033         assertNull(ExceptionUtils.throwableOfThrowable(withCause, Throwable.class, 0));
1034     }
1035 
1036     @Test
1037     void testThrowableOfType_ThrowableClass() {
1038         assertNull(ExceptionUtils.throwableOfType(null, null));
1039         assertNull(ExceptionUtils.throwableOfType(null, NestableException.class));
1040 
1041         assertNull(ExceptionUtils.throwableOfType(withoutCause, null));
1042         assertNull(ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class));
1043         assertNull(ExceptionUtils.throwableOfType(withoutCause, NestableException.class));
1044         assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class));
1045 
1046         assertNull(ExceptionUtils.throwableOfType(nested, null));
1047         assertNull(ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class));
1048         assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class));
1049         assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class));
1050 
1051         assertNull(ExceptionUtils.throwableOfType(withCause, null));
1052         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class));
1053         assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class));
1054         assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class));
1055 
1056         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class));
1057         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class));
1058     }
1059 
1060     @Test
1061     void testThrowableOfType_ThrowableClassInt() {
1062         assertNull(ExceptionUtils.throwableOfType(null, null, 0));
1063         assertNull(ExceptionUtils.throwableOfType(null, NestableException.class, 0));
1064 
1065         assertNull(ExceptionUtils.throwableOfType(withoutCause, null));
1066         assertNull(ExceptionUtils.throwableOfType(withoutCause, ExceptionWithCause.class, 0));
1067         assertNull(ExceptionUtils.throwableOfType(withoutCause, NestableException.class, 0));
1068         assertEquals(withoutCause, ExceptionUtils.throwableOfType(withoutCause, ExceptionWithoutCause.class, 0));
1069 
1070         assertNull(ExceptionUtils.throwableOfType(nested, null, 0));
1071         assertNull(ExceptionUtils.throwableOfType(nested, ExceptionWithCause.class, 0));
1072         assertEquals(nested, ExceptionUtils.throwableOfType(nested, NestableException.class, 0));
1073         assertEquals(nested.getCause(), ExceptionUtils.throwableOfType(nested, ExceptionWithoutCause.class, 0));
1074 
1075         assertNull(ExceptionUtils.throwableOfType(withCause, null));
1076         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
1077         assertEquals(withCause.getCause(), ExceptionUtils.throwableOfType(withCause, NestableException.class, 0));
1078         assertEquals(withCause.getCause().getCause(), ExceptionUtils.throwableOfType(withCause, ExceptionWithoutCause.class, 0));
1079 
1080         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, -1));
1081         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 0));
1082         assertNull(ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 1));
1083         assertNull(ExceptionUtils.throwableOfType(withCause, ExceptionWithCause.class, 9));
1084 
1085         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Exception.class, 0));
1086         assertEquals(withCause, ExceptionUtils.throwableOfType(withCause, Throwable.class, 0));
1087     }
1088 
1089     @Test
1090     void testWrapAndUnwrapCheckedException() {
1091         final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new IOException()));
1092         assertTrue(ExceptionUtils.hasCause(t, IOException.class));
1093     }
1094 
1095     @Test
1096     void testWrapAndUnwrapError() {
1097         final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new OutOfMemoryError()));
1098         assertTrue(ExceptionUtils.hasCause(t, Error.class));
1099     }
1100 
1101     @Test
1102     void testWrapAndUnwrapRuntimeException() {
1103         final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new IllegalArgumentException()));
1104         assertTrue(ExceptionUtils.hasCause(t, RuntimeException.class));
1105     }
1106 
1107     @Test
1108     void testWrapAndUnwrapThrowable() {
1109         final Throwable t = assertThrows(Throwable.class, () -> ExceptionUtils.wrapAndThrow(new TestThrowable()));
1110         assertTrue(ExceptionUtils.hasCause(t, TestThrowable.class));
1111     }
1112 }