@@ -195,36 +195,66 @@ def test_02_start_workers(self):
195195 self .__class__ .workers_started = True
196196 self .assertTrue (self .workers_started )
197197
198- def test_03_execute_workflow (self ):
199- """Execute workflow and verify completion."""
200- print ("\n " + "=" * 80 + "\n TEST 3: Execute Workflow\n " + "=" * 80 )
201-
202- self .assertTrue (self .workers_started , "Workers must be started first" )
203-
204- workflow_client = OrkesWorkflowClient (self .config )
198+ EXPECTED_TASK_COUNT = 5
199+
200+ def _run_workflow_to_terminal (self , workflow_client , timeout_s = 90 ):
201+ """Start the e2e workflow and wait until it is genuinely terminal with
202+ all expected tasks present. Returns (wf_id, workflow_or_None). A None
203+ workflow (or a non-terminal / short task list) signals a timed-out run
204+ the caller can retry, rather than asserting against a half-scheduled
205+ workflow (which is what produced the flaky "4 != 5 tasks" failure).
206+ """
205207 req = StartWorkflowRequest ()
206208 req .name = 'e2e_comprehensive_test'
207209 req .version = 1
208210 req .input = {}
209-
211+
210212 wf_id = workflow_client .start_workflow (start_workflow_request = req )
211213 print (f"✓ Started workflow: { wf_id } " )
212-
213- # Wait for completion
214- for i in range (30 ):
214+
215+ deadline = time .time () + timeout_s
216+ wf = None
217+ while time .time () < deadline :
215218 wf = workflow_client .get_workflow (wf_id , include_tasks = True )
216- print (f" Status: { wf .status } - ({ i * 2 } s)" )
217- if wf .status in ['COMPLETED' , 'FAILED' ]:
218- break
219- for task in wf .tasks :
219+ print (f" Status: { wf .status } - tasks={ len (wf .tasks or [])} " )
220+ if wf .status in ('COMPLETED' , 'FAILED' ) \
221+ and len (wf .tasks or []) == self .EXPECTED_TASK_COUNT :
222+ return wf_id , wf
223+ for task in (wf .tasks or []):
220224 print (f'task { task .task_def_name } : { task .status } ' )
221225 time .sleep (1 )
226+
227+ return wf_id , wf
228+
229+ def test_03_execute_workflow (self ):
230+ """Execute workflow and verify completion."""
231+ print ("\n " + "=" * 80 + "\n TEST 3: Execute Workflow\n " + "=" * 80 )
222232
223- final_wf = workflow_client . get_workflow ( wf_id , include_tasks = True )
233+ self . assertTrue ( self . workers_started , "Workers must be started first" )
224234
235+ workflow_client = OrkesWorkflowClient (self .config )
236+
237+ # Each attempt starts a fresh workflow, so retrying a pathologically
238+ # slow run (cold workers / slow CI) is safe and self-contained. This
239+ # keeps a one-off timeout from forcing a manual CI job re-run.
240+ final_wf = None
241+ for attempt in range (3 ):
242+ wf_id , wf = self ._run_workflow_to_terminal (workflow_client , timeout_s = 90 )
243+ if wf is not None and wf .status in ('COMPLETED' , 'FAILED' ) \
244+ and len (wf .tasks or []) == self .EXPECTED_TASK_COUNT :
245+ final_wf = wf
246+ break
247+ print (f"attempt { attempt + 1 } : wf={ wf_id } "
248+ f"status={ getattr (wf , 'status' , None )} "
249+ f"tasks={ len (getattr (wf , 'tasks' , []) or [])} ; retrying" )
250+
225251 # Assertions
226- self .assertIsNotNone (final_wf )
227- self .assertEqual (len (final_wf .tasks ), 5 , "Should have 5 tasks" )
252+ self .assertIsNotNone (
253+ final_wf ,
254+ "workflow never reached a terminal state with all "
255+ f"{ self .EXPECTED_TASK_COUNT } tasks after retries" )
256+ self .assertEqual (len (final_wf .tasks ), self .EXPECTED_TASK_COUNT ,
257+ f"Should have { self .EXPECTED_TASK_COUNT } tasks" )
228258
229259 # Verify each task
230260 tasks_by_ref = {t .reference_task_name : t for t in final_wf .tasks }
0 commit comments