| """Unit tests for orchestrator/retry.py.""" |
|
|
| from __future__ import annotations |
|
|
| import time |
|
|
| import pytest |
|
|
| from orchestrator.retry import RetryPolicy, with_retry_sync |
|
|
|
|
| class TestRetryPolicy: |
| def test_backoff_increases(self): |
| p = RetryPolicy(max_attempts=3, initial_backoff_seconds=0.1, max_backoff_seconds=1.0) |
| b1 = p.backoff(1) |
| b2 = p.backoff(2) |
| b3 = p.backoff(3) |
| |
| |
| assert b1 <= 0.2 |
| assert b2 <= 0.4 |
| assert b3 <= 0.8 |
|
|
| def test_backoff_capped(self): |
| p = RetryPolicy(max_attempts=10, initial_backoff_seconds=1.0, max_backoff_seconds=2.0) |
| for attempt in range(1, 10): |
| assert p.backoff(attempt) <= 2.0 |
|
|
|
|
| class TestWithRetrySync: |
| def test_succeeds_first_try(self): |
| calls = [0] |
| def fn(): |
| calls[0] += 1 |
| return "ok" |
| result = with_retry_sync(fn, RetryPolicy(max_attempts=3, initial_backoff_seconds=0.001), |
| label="test") |
| assert result == "ok" |
| assert calls[0] == 1 |
|
|
| def test_retries_on_retriable_exception(self): |
| calls = [0] |
| def fn(): |
| calls[0] += 1 |
| if calls[0] < 3: |
| raise ConnectionError("transient") |
| return "ok" |
| result = with_retry_sync(fn, RetryPolicy(max_attempts=3, initial_backoff_seconds=0.001), |
| label="test") |
| assert result == "ok" |
| assert calls[0] == 3 |
|
|
| def test_gives_up_after_max_attempts(self): |
| calls = [0] |
| def fn(): |
| calls[0] += 1 |
| raise ConnectionError("persistent") |
| with pytest.raises(ConnectionError): |
| with_retry_sync(fn, RetryPolicy(max_attempts=3, initial_backoff_seconds=0.001), |
| label="test") |
| assert calls[0] == 3 |
|
|
| def test_does_not_retry_non_retriable_exception(self): |
| calls = [0] |
| def fn(): |
| calls[0] += 1 |
| raise ValueError("not retriable") |
| with pytest.raises(ValueError): |
| with_retry_sync(fn, RetryPolicy(max_attempts=3, initial_backoff_seconds=0.001), |
| label="test") |
| |
| assert calls[0] == 1 |
|
|
| def test_on_retry_callback_called(self): |
| calls = [0] |
| retry_calls = [] |
| def fn(): |
| calls[0] += 1 |
| if calls[0] < 3: |
| raise ConnectionError("transient") |
| return "ok" |
| def on_retry(attempt, exc): |
| retry_calls.append((attempt, str(exc))) |
| result = with_retry_sync( |
| fn, |
| RetryPolicy(max_attempts=3, initial_backoff_seconds=0.001), |
| label="test", |
| on_retry=on_retry, |
| ) |
| assert result == "ok" |
| assert len(retry_calls) == 2 |
| assert retry_calls[0][0] == 1 |
| assert retry_calls[1][0] == 2 |
|
|