From 465683fb6b031c439196d63a3616ce0cfc0e77c6 Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Sat, 28 Feb 2026 03:31:34 +0000 Subject: [PATCH] fix(test): stabilize retry_with_timeout scenario MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch time.monotonic alongside time.sleep in the retry timeout step so stop_after_delay cannot elapse due to wall-clock overhead on busy CI runners. The fake monotonic clock advances by 1µs per call, keeping elapsed time well under the 0.01s timeout while preserving retry semantics. Closes #200 --- features/steps/retry_patterns_steps.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/features/steps/retry_patterns_steps.py b/features/steps/retry_patterns_steps.py index 3010b565c..5d09575c1 100644 --- a/features/steps/retry_patterns_steps.py +++ b/features/steps/retry_patterns_steps.py @@ -572,13 +572,24 @@ def step_verify_async_execute_attempts(context, attempts): @when("I apply retry with timeout of {max_attempts:d} attempts and {timeout} seconds") def step_apply_retry_with_timeout(context, max_attempts, timeout): - """Apply retry_with_timeout while stubbing out sleep delays.""" + """Apply retry_with_timeout while stubbing out sleep and monotonic clock.""" timeout_seconds = float(timeout) original_sleep = time.sleep + original_monotonic = time.monotonic + + _fake_base = original_monotonic() + _call_counter = [0] + + def _fake_monotonic(): + _call_counter[0] += 1 + return _fake_base + _call_counter[0] * 1e-6 + time.sleep = lambda _seconds: None + time.monotonic = _fake_monotonic def restore_sleep(): time.sleep = original_sleep + time.monotonic = original_monotonic # Register cleanup first so it runs even if the step body fails. context.add_cleanup(restore_sleep)