Skip to content

Commit 4ffe01b

Browse files
test with additional resilience
1 parent c197c5e commit 4ffe01b

1 file changed

Lines changed: 50 additions & 1 deletion

File tree

tests/integration/test_comprehensive_e2e.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"""
2525

2626
import asyncio
27+
import functools
2728
import logging
2829
import os
2930
import sys
@@ -56,7 +57,11 @@
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
6267
class 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+
137165
class 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 + "\nTEST 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 + "\nTEST 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 + "\nTEST 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 + "\nTEST 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 + "\nTEST 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 + "\nTEST 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 + "\nTEST 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 + "\nTEST 8: Summary & Final Assertions\n" + "="*80)

0 commit comments

Comments
 (0)