2424"""
2525
2626import asyncio
27+ import functools
2728import logging
2829import os
2930import sys
5657 TaskExecutionStarted , TaskExecutionCompleted , TaskExecutionFailure ,
5758 TaskUpdateFailure
5859)
59- from tests .integration .retry_helpers import wait_for_workflow_terminal
60+ from tests .integration .retry_helpers import (
61+ DEFAULT_OVERALL_DEADLINE_SECONDS ,
62+ retry_scenario ,
63+ wait_for_workflow_terminal ,
64+ )
6065
6166# Event collector
6267class EventCollector (TaskRunnerEventsListener ):
@@ -134,6 +139,29 @@ def failing_task(should_fail: bool) -> dict:
134139
135140
136141# Main test class
142+ def _retry_on_transient_until_deadline (test_method ):
143+ """Wrap a ``test_0xx`` method so a transient (status-0) transport blip against
144+ the shared dev server re-runs the *whole* test until the class-wide deadline,
145+ instead of failing the suite on a one-off dropped connection.
146+
147+ This is the same model ``test_all`` uses via ``retry_scenario``: only a
148+ transient ``ApiException(status=0)`` (no HTTP response arrived) is retried;
149+ real failures (assertions, genuine 4xx/5xx) raise immediately, and a blip
150+ at/after the deadline re-raises so the suite still fails cleanly rather than
151+ hanging past the CI job timeout. Each test here starts from the shared
152+ class state (workflow registered in test_01, workers up from test_02), so a
153+ retry re-reads/re-executes against that same state safely.
154+ """
155+ @functools .wraps (test_method )
156+ def wrapper (self , * args , ** kwargs ):
157+ deadline = getattr (self .__class__ , '_retry_deadline' , None )
158+ return retry_scenario (
159+ test_method .__name__ ,
160+ lambda : test_method (self , * args , ** kwargs ),
161+ deadline = deadline )
162+ return wrapper
163+
164+
137165class TestComprehensiveE2E (unittest .TestCase ):
138166
139167 # Annotated workers this suite relies on. Every one must be discovered by
@@ -172,6 +200,12 @@ def setUpClass(cls):
172200 cls .workers_started = False
173201 cls .task_handler = None
174202
203+ # One shared wall-clock budget for the whole suite: a test that hits a
204+ # transient (status-0) transport blip retries until this deadline passes
205+ # (see _retry_on_transient_until_deadline). Real failures raise at once.
206+ cls ._retry_deadline = time .monotonic () + DEFAULT_OVERALL_DEADLINE_SECONDS
207+
208+ @_retry_on_transient_until_deadline
175209 def test_01_create_workflow (self ):
176210 """Create test workflow."""
177211 print ("\n " + "=" * 80 + "\n TEST 1: Create Workflow\n " + "=" * 80 )
@@ -198,10 +232,19 @@ def test_01_create_workflow(self):
198232 print ("✓ Workflow registered" )
199233 self .assertTrue (True ) # Workflow registration succeeded
200234
235+ @_retry_on_transient_until_deadline
201236 def test_02_start_workers (self ):
202237 """Start workers and verify they initialize."""
203238 print ("\n " + "=" * 80 + "\n TEST 2: Start Workers\n " + "=" * 80 )
204239
240+ # If a prior attempt started a handler (this test can be retried on a
241+ # transient blip), stop it first so a retry doesn't orphan its worker
242+ # processes.
243+ prior = getattr (self .__class__ , 'task_handler' , None )
244+ if prior is not None :
245+ prior .stop_processes ()
246+ self .__class__ .task_handler = None
247+
205248 # Start the workers and keep the handler on the class so it stays alive
206249 # for the remaining tests and is stopped deterministically in
207250 # tearDownClass. (A previous version ran the handler in a daemon thread
@@ -302,6 +345,7 @@ def _show(wf):
302345 swallow = 'none' , log = lambda _msg : None , on_poll = _show )
303346 return wf_id , wf
304347
348+ @_retry_on_transient_until_deadline
305349 def test_03_execute_workflow (self ):
306350 """Execute workflow and verify completion."""
307351 print ("\n " + "=" * 80 + "\n TEST 3: Execute Workflow\n " + "=" * 80 )
@@ -363,6 +407,7 @@ def test_03_execute_workflow(self):
363407
364408 print ("✓ All task assertions passed" )
365409
410+ @_retry_on_transient_until_deadline
366411 def test_04_verify_events (self ):
367412 """Verify event system works (via metrics and task execution)."""
368413 print ("\n " + "=" * 80 + "\n TEST 4: Event System\n " + "=" * 80 )
@@ -390,6 +435,7 @@ def test_04_verify_events(self):
390435 print ("✓ Event system architecture verified" )
391436 print (" (Events are process-local - actual event testing done in unit tests)" )
392437
438+ @_retry_on_transient_until_deadline
393439 def test_05_verify_task_definitions (self ):
394440 """Verify task definitions and schemas were registered."""
395441 print ("\n " + "=" * 80 + "\n TEST 5: Task Registration & Schemas\n " + "=" * 80 )
@@ -419,6 +465,7 @@ def test_05_verify_task_definitions(self):
419465
420466 print ("✓ All task definition assertions passed" )
421467
468+ @_retry_on_transient_until_deadline
422469 def test_06_verify_metrics (self ):
423470 """Verify metrics were collected."""
424471 print ("\n " + "=" * 80 + "\n TEST 6: Metrics Collection\n " + "=" * 80 )
@@ -464,6 +511,7 @@ def test_06_verify_metrics(self):
464511
465512 print ("✓ Metrics system verified and operational" )
466513
514+ @_retry_on_transient_until_deadline
467515 def test_07_configuration_assertions (self ):
468516 """Verify configuration system works."""
469517 print ("\n " + "=" * 80 + "\n TEST 7: Configuration System\n " + "=" * 80 )
@@ -485,6 +533,7 @@ def test_07_configuration_assertions(self):
485533
486534 self .assertTrue (True )
487535
536+ @_retry_on_transient_until_deadline
488537 def test_08_summary_assertions (self ):
489538 """Final comprehensive assertions."""
490539 print ("\n " + "=" * 80 + "\n TEST 8: Summary & Final Assertions\n " + "=" * 80 )
0 commit comments