|
1 | 1 | """Tests for retry utilities.""" |
2 | 2 |
|
| 3 | +import asyncio |
| 4 | + |
3 | 5 | import pytest |
4 | 6 |
|
5 | 7 | from dqliteclient.retry import retry_with_backoff |
@@ -71,20 +73,33 @@ async def raise_type_error() -> str: |
71 | 73 | assert call_count == 1 # Should not retry |
72 | 74 |
|
73 | 75 | async def test_respects_max_delay(self) -> None: |
74 | | - import time |
| 76 | + """Verify that the backoff delay is capped at max_delay.""" |
| 77 | + from unittest.mock import AsyncMock, patch |
75 | 78 |
|
76 | 79 | call_count = 0 |
77 | | - timestamps: list[float] = [] |
| 80 | + sleep_args: list[float] = [] |
78 | 81 |
|
79 | | - async def track_time() -> str: |
| 82 | + async def fail_twice() -> str: |
80 | 83 | nonlocal call_count |
81 | | - timestamps.append(time.monotonic()) |
82 | 84 | call_count += 1 |
83 | 85 | if call_count < 3: |
84 | 86 | raise ValueError("not yet") |
85 | 87 | return "ok" |
86 | 88 |
|
87 | | - await retry_with_backoff(track_time, max_attempts=3, base_delay=0.01, max_delay=0.02) |
| 89 | + original_sleep = asyncio.sleep |
| 90 | + |
| 91 | + async def mock_sleep(delay: float) -> None: |
| 92 | + sleep_args.append(delay) |
| 93 | + await original_sleep(0) # Yield control without actual delay |
| 94 | + |
| 95 | + with patch("dqliteclient.retry.asyncio.sleep", side_effect=mock_sleep): |
| 96 | + await retry_with_backoff( |
| 97 | + fail_twice, max_attempts=3, base_delay=0.1, max_delay=0.05, jitter=0 |
| 98 | + ) |
88 | 99 |
|
89 | | - # Second retry delay should be capped at max_delay |
90 | | - assert len(timestamps) == 3 |
| 100 | + assert call_count == 3 |
| 101 | + assert len(sleep_args) == 2 # Two retries = two sleeps |
| 102 | + # First delay: 0.1 * 2^0 = 0.1, capped to 0.05 |
| 103 | + assert sleep_args[0] == pytest.approx(0.05) |
| 104 | + # Second delay: 0.1 * 2^1 = 0.2, capped to 0.05 |
| 105 | + assert sleep_args[1] == pytest.approx(0.05) |
0 commit comments