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 .Tag ;
7+ import org .junit .jupiter .api .Test ;
8+ import org .junit .jupiter .api .extension .ExtendWith ;
9+
10+ import java .util .concurrent .TimeoutException ;
11+
12+ import static org .junit .jupiter .api .Assertions .*;
13+
14+ /**
15+ * Integration tests for validating the serialization of exceptions in various scenarios.
16+ */
17+ @ Tag ("integration" )
18+ @ ExtendWith (TestRetryExtension .class )
19+ public class ExceptionSerializationIntegrationTest extends IntegrationTestBase {
20+
21+ @ RetryingTest
22+ void testMultilineExceptionMessage () throws TimeoutException {
23+ final String orchestratorName = "MultilineExceptionOrchestrator" ;
24+ final String multilineErrorMessage = "Line 1\n Line 2\n Line 3" ;
25+
26+ DurableTaskGrpcWorker worker = this .createWorkerBuilder ()
27+ .addOrchestrator (orchestratorName , ctx -> {
28+ throw new RuntimeException (multilineErrorMessage );
29+ })
30+ .buildAndStart ();
31+
32+ DurableTaskClient client = new DurableTaskGrpcClientBuilder ().build ();
33+ try (worker ; client ) {
34+ String instanceId = client .scheduleNewOrchestrationInstance (orchestratorName , 0 );
35+ OrchestrationMetadata instance = client .waitForInstanceCompletion (instanceId , defaultTimeout , true );
36+ assertNotNull (instance );
37+ assertEquals (OrchestrationRuntimeStatus .FAILED , instance .getRuntimeStatus ());
38+
39+ FailureDetails details = instance .getFailureDetails ();
40+ assertNotNull (details );
41+ assertEquals ("java.lang.RuntimeException" , details .getErrorType ());
42+ assertEquals (multilineErrorMessage , details .getErrorMessage ());
43+ assertNotNull (details .getStackTrace ());
44+ }
45+ }
46+
47+ @ RetryingTest
48+ void testNestedExceptions () throws TimeoutException {
49+ final String orchestratorName = "NestedExceptionOrchestrator" ;
50+ final String innerMessage = "Inner exception" ;
51+ final String outerMessage = "Outer exception" ;
52+
53+ DurableTaskGrpcWorker worker = this .createWorkerBuilder ()
54+ .addOrchestrator (orchestratorName , ctx -> {
55+ Exception innerException = new IllegalArgumentException (innerMessage );
56+ throw new RuntimeException (outerMessage , innerException );
57+ })
58+ .buildAndStart ();
59+
60+ DurableTaskClient client = new DurableTaskGrpcClientBuilder ().build ();
61+ try (worker ; client ) {
62+ String instanceId = client .scheduleNewOrchestrationInstance (orchestratorName , 0 );
63+ OrchestrationMetadata instance = client .waitForInstanceCompletion (instanceId , defaultTimeout , true );
64+ assertNotNull (instance );
65+ assertEquals (OrchestrationRuntimeStatus .FAILED , instance .getRuntimeStatus ());
66+
67+ FailureDetails details = instance .getFailureDetails ();
68+ assertNotNull (details );
69+ assertEquals ("java.lang.RuntimeException" , details .getErrorType ());
70+ assertEquals (outerMessage , details .getErrorMessage ());
71+ assertNotNull (details .getStackTrace ());
72+
73+ // Verify both exceptions are in the stack trace
74+ String stackTrace = details .getStackTrace ();
75+ assertTrue (stackTrace .contains (outerMessage ), "Stack trace should contain outer exception message" );
76+ assertTrue (stackTrace .contains (innerMessage ), "Stack trace should contain inner exception message" );
77+ assertTrue (stackTrace .contains ("Caused by: java.lang.IllegalArgumentException" ),
78+ "Stack trace should include 'Caused by' section for inner exception" );
79+ }
80+ }
81+
82+ @ RetryingTest
83+ void testCustomExceptionWithNonStandardToString () throws TimeoutException {
84+ final String orchestratorName = "CustomExceptionOrchestrator" ;
85+ final String customMessage = "Custom exception message" ;
86+
87+ DurableTaskGrpcWorker worker = this .createWorkerBuilder ()
88+ .addOrchestrator (orchestratorName , ctx -> {
89+ throw new CustomException (customMessage );
90+ })
91+ .buildAndStart ();
92+
93+ DurableTaskClient client = new DurableTaskGrpcClientBuilder ().build ();
94+ try (worker ; client ) {
95+ String instanceId = client .scheduleNewOrchestrationInstance (orchestratorName , 0 );
96+ OrchestrationMetadata instance = client .waitForInstanceCompletion (instanceId , defaultTimeout , true );
97+ assertNotNull (instance );
98+ assertEquals (OrchestrationRuntimeStatus .FAILED , instance .getRuntimeStatus ());
99+
100+ FailureDetails details = instance .getFailureDetails ();
101+ assertNotNull (details );
102+ String expectedType = CustomException .class .getName ();
103+ assertEquals (expectedType , details .getErrorType ());
104+ assertEquals (customMessage , details .getErrorMessage ());
105+ assertNotNull (details .getStackTrace ());
106+ }
107+ }
108+
109+ /**
110+ * Custom exception class with a non-standard toString implementation.
111+ */
112+ private static class CustomException extends RuntimeException {
113+ public CustomException (String message ) {
114+ super (message );
115+ }
116+
117+ @ Override
118+ public String toString () {
119+ return "CUSTOM_EXCEPTION_FORMAT: " + getMessage ();
120+ }
121+ }
122+ }
0 commit comments