44
55namespace Temporal \Tests \Acceptance \App ;
66
7+ use Google \Protobuf \Timestamp ;
78use Psr \Log \LoggerInterface ;
89use Spiral \Core \Container ;
910use Spiral \Core \Scope ;
11+ use Temporal \Api \Enums \V1 \EventType ;
12+ use Temporal \Api \Failure \V1 \Failure ;
13+ use Temporal \Client \WorkflowClientInterface ;
1014use Temporal \Client \WorkflowStubInterface ;
15+ use Temporal \Exception \TemporalException ;
1116use Temporal \Tests \Acceptance \App \Logger \ClientLogger ;
1217use Temporal \Tests \Acceptance \App \Logger \LoggerFactory ;
1318use Temporal \Tests \Acceptance \App \Runtime \ContainerFacade ;
@@ -59,6 +64,11 @@ function (Container $container): mixed {
5964 $ runner ->stop ();
6065 $ runner ->start ();
6166
67+ if ($ e instanceof TemporalException) {
68+ echo "\n=== Workflow history for failed test {$ this ->name ()} === \n" ;
69+ $ this ->printWorkflowHistory ($ container ->get (WorkflowClientInterface::class), $ args );
70+ }
71+
6272 throw $ e ;
6373 } finally {
6474 // Cleanup: terminate injected workflow if any
@@ -75,4 +85,72 @@ function (Container $container): mixed {
7585 },
7686 );
7787 }
88+
89+ private function printWorkflowHistory (WorkflowClientInterface $ workflowClient , array $ args ): void
90+ {
91+ foreach ($ args as $ arg ) {
92+ if (!$ arg instanceof WorkflowStubInterface) {
93+ continue ;
94+ }
95+
96+ $ fnTime = static fn (?Timestamp $ ts ): float => $ ts === null
97+ ? 0
98+ : $ ts ->getSeconds () + \round ($ ts ->getNanos () / 1_000_000_000 , 6 );
99+
100+ foreach ($ workflowClient ->getWorkflowHistory (
101+ $ arg ->getExecution (),
102+ ) as $ event ) {
103+ $ start ??= $ fnTime ($ event ->getEventTime ());
104+ echo "\n" . \str_pad ((string ) $ event ->getEventId (), 3 , ' ' , STR_PAD_LEFT ) . ' ' ;
105+ # Calculate delta time
106+ $ deltaMs = \round (1_000 * ($ fnTime ($ event ->getEventTime ()) - $ start ));
107+ echo \str_pad (\number_format ($ deltaMs , 0 , '. ' , "' " ), 6 , ' ' , STR_PAD_LEFT ) . 'ms ' ;
108+ echo \str_pad (EventType::name ($ event ->getEventType ()), 40 , ' ' , STR_PAD_RIGHT ) . ' ' ;
109+
110+ $ cause = $ event ->getStartChildWorkflowExecutionFailedEventAttributes ()?->getCause()
111+ ?? $ event ->getSignalExternalWorkflowExecutionFailedEventAttributes ()?->getCause()
112+ ?? $ event ->getRequestCancelExternalWorkflowExecutionFailedEventAttributes ()?->getCause();
113+ if ($ cause !== null ) {
114+ echo "Cause: $ cause " ;
115+ continue ;
116+ }
117+
118+ $ failure = $ event ->getActivityTaskFailedEventAttributes ()?->getFailure()
119+ ?? $ event ->getWorkflowTaskFailedEventAttributes ()?->getFailure()
120+ ?? $ event ->getNexusOperationFailedEventAttributes ()?->getFailure()
121+ ?? $ event ->getWorkflowExecutionFailedEventAttributes ()?->getFailure()
122+ ?? $ event ->getChildWorkflowExecutionFailedEventAttributes ()?->getFailure()
123+ ?? $ event ->getNexusOperationCancelRequestFailedEventAttributes ()?->getFailure();
124+
125+ if ($ failure === null ) {
126+ continue ;
127+ }
128+
129+ # Render failure
130+ echo "Failure: \n" ;
131+ echo " ========== BEGIN =========== \n" ;
132+ $ this ->renderFailure ($ failure , 1 );
133+ echo " =========== END ============ " ;
134+ }
135+ }
136+ }
137+
138+ private function renderFailure (Failure $ failure , int $ level ): void
139+ {
140+ $ fnPad = static function (string $ str ) use ($ level ): string {
141+ $ pad = \str_repeat (' ' , $ level );
142+ return $ pad . \str_replace ("\n" , "\n$ pad " , $ str );
143+ };
144+ echo $ fnPad ('Source: ' . $ failure ->getSource ()) . "\n" ;
145+ echo $ fnPad ('Info: ' . $ failure ->getFailureInfo ()) . "\n" ;
146+ echo $ fnPad ('Message: ' . $ failure ->getMessage ()) . "\n" ;
147+ echo $ fnPad ("Stack trace: " ) . "\n" ;
148+ echo $ fnPad ($ failure ->getStackTrace ()) . "\n" ;
149+ $ previous = $ failure ->getCause ();
150+ if ($ previous !== null ) {
151+ echo $ fnPad ('———————————————————————————— ' ) . "\n" ;
152+ echo $ fnPad ('Caused by: ' ) . "\n" ;
153+ $ this ->renderFailure ($ previous , $ level + 1 );
154+ }
155+ }
78156}
0 commit comments