1+ // Copyright (c) Microsoft Corporation. All rights reserved.
2+ // Licensed under the MIT License.
3+
4+ package com .microsoft .durabletask ;
5+
6+ import org .junit .jupiter .api .Test ;
7+ import static org .junit .jupiter .api .Assertions .*;
8+
9+ /**
10+ * Tests for handling complex exception serialization scenarios.
11+ */
12+ public class ComplexExceptionTest {
13+
14+ @ Test
15+ public void testDeepNestedExceptions () {
16+ // Create a chain of 5 nested exceptions
17+ Exception level5 = new IllegalArgumentException ("Level 5 exception" );
18+ Exception level4 = new IllegalStateException ("Level 4 exception" , level5 );
19+ Exception level3 = new RuntimeException ("Level 3 exception" , level4 );
20+ Exception level2 = new Exception ("Level 2 exception" , level3 );
21+ Exception level1 = new Exception ("Level 1 exception" , level2 );
22+
23+ FailureDetails details = new FailureDetails (level1 );
24+
25+ assertEquals ("java.lang.Exception" , details .getErrorType ());
26+ assertEquals ("Level 1 exception" , details .getErrorMessage ());
27+
28+ String stackTrace = details .getStackTrace ();
29+ assertNotNull (stackTrace );
30+
31+ // Verify all exception levels are present in the stack trace
32+ assertTrue (stackTrace .contains ("Level 1 exception" ));
33+ assertTrue (stackTrace .contains ("Caused by: java.lang.Exception: Level 2 exception" ));
34+ assertTrue (stackTrace .contains ("Caused by: java.lang.RuntimeException: Level 3 exception" ));
35+ assertTrue (stackTrace .contains ("Caused by: java.lang.IllegalStateException: Level 4 exception" ));
36+ assertTrue (stackTrace .contains ("Caused by: java.lang.IllegalArgumentException: Level 5 exception" ));
37+ }
38+
39+ @ Test
40+ public void testExceptionWithSuppressedExceptions () {
41+ Exception mainException = new RuntimeException ("Main exception" );
42+ Exception suppressed1 = new IllegalArgumentException ("Suppressed exception 1" );
43+ Exception suppressed2 = new IllegalStateException ("Suppressed exception 2" );
44+
45+ mainException .addSuppressed (suppressed1 );
46+ mainException .addSuppressed (suppressed2 );
47+
48+ FailureDetails details = new FailureDetails (mainException );
49+
50+ assertEquals ("java.lang.RuntimeException" , details .getErrorType ());
51+ assertEquals ("Main exception" , details .getErrorMessage ());
52+
53+ String stackTrace = details .getStackTrace ();
54+ assertNotNull (stackTrace );
55+
56+ // Verify suppressed exceptions are in the stack trace
57+ assertTrue (stackTrace .contains ("Main exception" ));
58+ assertTrue (stackTrace .contains ("Suppressed: java.lang.IllegalArgumentException: Suppressed exception 1" ));
59+ assertTrue (stackTrace .contains ("Suppressed: java.lang.IllegalStateException: Suppressed exception 2" ));
60+ }
61+
62+ @ Test
63+ public void testNullMessageException () {
64+ NullPointerException exception = new NullPointerException (); // NPE typically has null message
65+
66+ FailureDetails details = new FailureDetails (exception );
67+
68+ assertEquals ("java.lang.NullPointerException" , details .getErrorType ());
69+ assertEquals ("" , details .getErrorMessage ()); // Should convert null to empty string
70+ assertNotNull (details .getStackTrace ());
71+ }
72+
73+ @ Test
74+ public void testCircularExceptionReference () {
75+ try {
76+ // Create an exception with a circular reference (should be handled gracefully)
77+ ExceptionWithCircularReference ex = new ExceptionWithCircularReference ("Circular" );
78+ ex .setCircularCause ();
79+
80+ FailureDetails details = new FailureDetails (ex );
81+
82+ assertEquals (ExceptionWithCircularReference .class .getName (), details .getErrorType ());
83+ assertEquals ("Circular" , details .getErrorMessage ());
84+ assertNotNull (details .getStackTrace ());
85+
86+ // No infinite loop, test passes if we get here
87+ } catch (StackOverflowError e ) {
88+ fail ("StackOverflowError occurred with circular exception reference" );
89+ }
90+ }
91+
92+ /**
93+ * Exception class that can create a circular reference in the cause chain.
94+ */
95+ private static class ExceptionWithCircularReference extends Exception {
96+ public ExceptionWithCircularReference (String message ) {
97+ super (message );
98+ }
99+
100+ public void setCircularCause () {
101+ try {
102+ // Use reflection to set the cause field directly to this exception
103+ // to create a circular reference
104+ java .lang .reflect .Field causeField = Throwable .class .getDeclaredField ("cause" );
105+ causeField .setAccessible (true );
106+ causeField .set (this , this );
107+ } catch (Exception e ) {
108+ // Ignore any reflection errors, this is just for testing
109+ }
110+ }
111+ }
112+ }
0 commit comments