/** * Copyright (c) 2016-present, RxJava Contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.reactivex.exceptions; import static org.junit.Assert.*; import java.io.*; import java.util.*; import org.junit.Test; import io.reactivex.exceptions.CompositeException.CompositeExceptionCausalChain; public class CompositeExceptionTest { private final Throwable ex1 = new Throwable("Ex1"); private final Throwable ex2 = new Throwable("Ex2", ex1); private final Throwable ex3 = new Throwable("Ex3", ex2); private CompositeException getNewCompositeExceptionWithEx123() { List<Throwable> throwables = new ArrayList<Throwable>(); throwables.add(ex1); throwables.add(ex2); throwables.add(ex3); return new CompositeException(throwables); } @Test(timeout = 1000) public void testMultipleWithSameCause() { Throwable rootCause = new Throwable("RootCause"); Throwable e1 = new Throwable("1", rootCause); Throwable e2 = new Throwable("2", rootCause); Throwable e3 = new Throwable("3", rootCause); CompositeException ce = new CompositeException(e1, e2, e3); System.err.println("----------------------------- print composite stacktrace"); ce.printStackTrace(); assertEquals(3, ce.getExceptions().size()); assertNoCircularReferences(ce); assertNotNull(getRootCause(ce)); System.err.println("----------------------------- print cause stacktrace"); ce.getCause().printStackTrace(); } @Test public void testEmptyErrors() { try { new CompositeException(); fail("CompositeException should fail if errors is empty"); } catch (IllegalArgumentException e) { assertEquals("errors is empty", e.getMessage()); } try { new CompositeException(new ArrayList<Throwable>()); fail("CompositeException should fail if errors is empty"); } catch (IllegalArgumentException e) { assertEquals("errors is empty", e.getMessage()); } } @Test(timeout = 1000) public void testCompositeExceptionFromParentThenChild() { CompositeException cex = new CompositeException(ex1, ex2); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(2, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @Test(timeout = 1000) public void testCompositeExceptionFromChildThenParent() { CompositeException cex = new CompositeException(ex2, ex1); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(2, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @Test(timeout = 1000) public void testCompositeExceptionFromChildAndComposite() { CompositeException cex = new CompositeException(ex1, getNewCompositeExceptionWithEx123()); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(3, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @Test(timeout = 1000) public void testCompositeExceptionFromCompositeAndChild() { CompositeException cex = new CompositeException(getNewCompositeExceptionWithEx123(), ex1); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(3, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @Test(timeout = 1000) public void testCompositeExceptionFromTwoDuplicateComposites() { List<Throwable> exs = new ArrayList<Throwable>(); exs.add(getNewCompositeExceptionWithEx123()); exs.add(getNewCompositeExceptionWithEx123()); CompositeException cex = new CompositeException(exs); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(3, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } /** * This hijacks the Throwable.printStackTrace() output and puts it in a string, where we can look for * "CIRCULAR REFERENCE" (a String added by Throwable.printEnclosedStackTrace) */ private static void assertNoCircularReferences(Throwable ex) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream printStream = new PrintStream(baos); ex.printStackTrace(printStream); assertFalse(baos.toString().contains("CIRCULAR REFERENCE")); } private static Throwable getRootCause(Throwable ex) { Throwable root = ex.getCause(); if (root == null) { return null; } else { while (true) { if (root.getCause() == null) { return root; } else { root = root.getCause(); } } } } @Test public void testNullCollection() { CompositeException composite = new CompositeException((List<Throwable>)null); composite.getCause(); composite.printStackTrace(); } @Test public void testNullElement() { CompositeException composite = new CompositeException(Collections.singletonList((Throwable) null)); composite.getCause(); composite.printStackTrace(); } @Test(timeout = 1000) public void testCompositeExceptionWithUnsupportedInitCause() { Throwable t = new Throwable() { private static final long serialVersionUID = -3282577447436848385L; @Override public synchronized Throwable initCause(Throwable cause) { throw new UnsupportedOperationException(); } }; CompositeException cex = new CompositeException(t, ex1); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(2, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @Test(timeout = 1000) public void testCompositeExceptionWithNullInitCause() { Throwable t = new Throwable("ThrowableWithNullInitCause") { private static final long serialVersionUID = -7984762607894527888L; @Override public synchronized Throwable initCause(Throwable cause) { return null; } }; CompositeException cex = new CompositeException(t, ex1); System.err.println("----------------------------- print composite stacktrace"); cex.printStackTrace(); assertEquals(2, cex.getExceptions().size()); assertNoCircularReferences(cex); assertNotNull(getRootCause(cex)); System.err.println("----------------------------- print cause stacktrace"); cex.getCause().printStackTrace(); } @Test public void messageCollection() { CompositeException compositeException = new CompositeException(ex1, ex3); assertEquals("2 exceptions occurred. ", compositeException.getMessage()); } @Test public void messageVarargs() { CompositeException compositeException = new CompositeException(ex1, ex2, ex3); assertEquals("3 exceptions occurred. ", compositeException.getMessage()); } @Test public void complexCauses() { Throwable e1 = new Throwable("1"); Throwable e2 = new Throwable("2"); e1.initCause(e2); Throwable e3 = new Throwable("3"); Throwable e4 = new Throwable("4"); e3.initCause(e4); Throwable e5 = new Throwable("5"); Throwable e6 = new Throwable("6"); e5.initCause(e6); CompositeException compositeException = new CompositeException(e1, e3, e5); assertTrue(compositeException.getCause() instanceof CompositeExceptionCausalChain); List<Throwable> causeChain = new ArrayList<Throwable>(); Throwable cause = compositeException.getCause().getCause(); while (cause != null) { causeChain.add(cause); cause = cause.getCause(); } // The original relations // // e1 -> e2 // e3 -> e4 // e5 -> e6 // // will be set to // // e1 -> e2 -> e3 -> e4 -> e5 -> e6 assertEquals(Arrays.asList(e1, e2, e3, e4, e5, e6), causeChain); } @Test public void constructorWithNull() { assertTrue(new CompositeException((Throwable[])null).getExceptions().get(0) instanceof NullPointerException); assertTrue(new CompositeException((Iterable<Throwable>)null).getExceptions().get(0) instanceof NullPointerException); assertTrue(new CompositeException(null, new TestException()).getExceptions().get(0) instanceof NullPointerException); } @Test public void printStackTrace() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); new CompositeException(new TestException()).printStackTrace(pw); assertTrue(sw.toString().contains("TestException")); } @Test public void cyclicRootCause() { RuntimeException te = new RuntimeException() { private static final long serialVersionUID = -8492568224555229753L; Throwable cause; @Override public Throwable initCause(Throwable c) { return this; } @Override public Throwable getCause() { return cause; } }; assertSame(te, new CompositeException(te).getCause().getCause()); assertSame(te, new CompositeException(new RuntimeException(te)).getCause().getCause().getCause()); } @Test public void nullRootCause() { RuntimeException te = new RuntimeException() { private static final long serialVersionUID = -8492568224555229753L; @Override public Throwable getCause() { return null; } }; assertSame(te, new CompositeException(te).getCause().getCause()); assertSame(te, new CompositeException(new RuntimeException(te)).getCause().getCause().getCause()); } @Test public void badException() { Throwable e = new BadException(); assertSame(e, new CompositeException(e).getCause().getCause()); assertSame(e, new CompositeException(new RuntimeException(e)).getCause().getCause().getCause()); } } final class BadException extends Throwable { private static final long serialVersionUID = 8999507293896399171L; @Override public synchronized Throwable getCause() { return this; } }