Skip to content

Commit f123ab3

Browse files
committed
Refactor functional retry tests to match production retry flow
1 parent 0f74387 commit f123ab3

File tree

1 file changed

+90
-112
lines changed

1 file changed

+90
-112
lines changed
Lines changed: 90 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,133 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
from asyncio import gather, sleep
5+
46
import pytest
57
from smithy_core.exceptions import CallError, RetryError
6-
from smithy_core.retries import StandardRetryQuota, StandardRetryStrategy
7-
8-
9-
def test_standard_retry_eventually_succeeds() -> None:
10-
retry_quota = StandardRetryQuota()
11-
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=retry_quota)
12-
error = CallError(is_retry_safe=True)
13-
8+
from smithy_core.interfaces import retries as retries_interface
9+
from smithy_core.retries import (
10+
ExponentialBackoffJitterType,
11+
ExponentialRetryBackoffStrategy,
12+
StandardRetryQuota,
13+
StandardRetryStrategy,
14+
)
15+
16+
17+
async def retry_operation(
18+
strategy: retries_interface.RetryStrategy,
19+
status_codes: list[int],
20+
) -> tuple[str, int]:
1421
token = strategy.acquire_initial_retry_token()
15-
assert token.retry_count == 0
16-
assert retry_quota.available_capacity == 500
22+
responses = iter(status_codes)
1723

18-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
19-
assert token.retry_count == 1
20-
assert retry_quota.available_capacity == 495
24+
while True:
25+
if token.retry_delay:
26+
await sleep(token.retry_delay)
2127

22-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
23-
assert token.retry_count == 2
24-
assert retry_quota.available_capacity == 490
28+
status_code = next(responses)
29+
attempt = token.retry_count + 1
2530

26-
strategy.record_success(token=token)
27-
assert retry_quota.available_capacity == 495
31+
if status_code == 200:
32+
strategy.record_success(token=token)
33+
return "success", attempt
2834

35+
error = CallError(
36+
fault="server" if status_code >= 500 else "client",
37+
message=f"HTTP {status_code}",
38+
is_retry_safe=status_code >= 500,
39+
)
2940

30-
def test_standard_retry_fails_due_to_max_attempts() -> None:
31-
retry_quota = StandardRetryQuota()
32-
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=retry_quota)
33-
error = CallError(is_retry_safe=True)
41+
try:
42+
token = strategy.refresh_retry_token_for_retry(
43+
token_to_renew=token, error=error
44+
)
45+
except RetryError:
46+
raise error
3447

35-
token = strategy.acquire_initial_retry_token()
3648

37-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
38-
assert token.retry_count == 1
39-
assert retry_quota.available_capacity == 495
49+
async def test_standard_retry_eventually_succeeds():
50+
quota = StandardRetryQuota(initial_capacity=500)
51+
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota)
4052

41-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
42-
assert token.retry_count == 2
43-
assert retry_quota.available_capacity == 490
53+
result, attempts = await retry_operation(strategy, [500, 500, 200])
4454

45-
with pytest.raises(RetryError, match="maximum number of allowed attempts"):
46-
strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
47-
assert retry_quota.available_capacity == 490
55+
assert result == "success"
56+
assert attempts == 3
57+
assert quota.available_capacity == 495
4858

4959

50-
def test_retry_quota_exhausted_after_single_retry() -> None:
51-
retry_quota = StandardRetryQuota(initial_capacity=5)
52-
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=retry_quota)
53-
error = CallError(is_retry_safe=True)
60+
async def test_standard_retry_fails_due_to_max_attempts():
61+
quota = StandardRetryQuota(initial_capacity=500)
62+
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota)
5463

55-
token = strategy.acquire_initial_retry_token()
56-
assert retry_quota.available_capacity == 5
57-
58-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
59-
assert token.retry_count == 1
60-
assert retry_quota.available_capacity == 0
64+
with pytest.raises(CallError, match="502"):
65+
await retry_operation(strategy, [502, 502, 502])
6166

62-
with pytest.raises(RetryError, match="Retry quota exceeded"):
63-
strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
64-
assert retry_quota.available_capacity == 0
67+
assert quota.available_capacity == 490
6568

6669

67-
def test_retry_quota_prevents_retries_when_zero() -> None:
68-
retry_quota = StandardRetryQuota(initial_capacity=0)
69-
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=retry_quota)
70-
error = CallError(is_retry_safe=True)
71-
72-
token = strategy.acquire_initial_retry_token()
73-
assert retry_quota.available_capacity == 0
70+
async def test_retry_quota_exhausted_after_single_retry():
71+
quota = StandardRetryQuota(initial_capacity=5)
72+
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota)
7473

75-
with pytest.raises(RetryError, match="Retry quota exceeded"):
76-
strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
77-
assert retry_quota.available_capacity == 0
74+
with pytest.raises(CallError, match="502"):
75+
await retry_operation(strategy, [500, 502])
7876

77+
assert quota.available_capacity == 0
7978

80-
def test_retry_quota_stops_retries_when_exhausted() -> None:
81-
retry_quota = StandardRetryQuota(initial_capacity=10)
82-
strategy = StandardRetryStrategy(max_attempts=5, retry_quota=retry_quota)
83-
error = CallError(is_retry_safe=True)
8479

85-
token = strategy.acquire_initial_retry_token()
86-
assert retry_quota.available_capacity == 10
80+
async def test_retry_quota_prevents_retries_when_quota_zero():
81+
quota = StandardRetryQuota(initial_capacity=0)
82+
strategy = StandardRetryStrategy(max_attempts=3, retry_quota=quota)
8783

88-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
89-
assert retry_quota.available_capacity == 5
84+
with pytest.raises(CallError, match="500"):
85+
await retry_operation(strategy, [500])
9086

91-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
92-
assert retry_quota.available_capacity == 0
87+
assert quota.available_capacity == 0
9388

94-
with pytest.raises(RetryError, match="Retry quota exceeded"):
95-
strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
96-
assert retry_quota.available_capacity == 0
9789

90+
async def test_retry_quota_stops_retries_when_exhausted():
91+
quota = StandardRetryQuota(initial_capacity=10)
92+
strategy = StandardRetryStrategy(max_attempts=5, retry_quota=quota)
9893

99-
def test_retry_quota_recovers_after_successful_responses() -> None:
100-
retry_quota = StandardRetryQuota(initial_capacity=15)
101-
strategy = StandardRetryStrategy(max_attempts=5, retry_quota=retry_quota)
102-
error = CallError(is_retry_safe=True)
94+
with pytest.raises(CallError, match="503"):
95+
await retry_operation(strategy, [500, 502, 503])
10396

104-
# First operation: 2 retries then success
105-
token = strategy.acquire_initial_retry_token()
106-
assert retry_quota.available_capacity == 15
97+
assert quota.available_capacity == 0
10798

108-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
109-
assert retry_quota.available_capacity == 10
11099

111-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
112-
assert retry_quota.available_capacity == 5
100+
async def test_retry_quota_recovers_after_successful_responses():
101+
quota = StandardRetryQuota(initial_capacity=15)
102+
strategy = StandardRetryStrategy(max_attempts=5, retry_quota=quota)
113103

114-
strategy.record_success(token=token)
115-
assert retry_quota.available_capacity == 10
104+
# First operation: 2 retries then success
105+
await retry_operation(strategy, [500, 502, 200])
106+
assert quota.available_capacity == 10
116107

117108
# Second operation: 1 retry then success
118-
token = strategy.acquire_initial_retry_token()
119-
token = strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
120-
assert retry_quota.available_capacity == 5
121-
strategy.record_success(token=token)
122-
assert retry_quota.available_capacity == 10
109+
await retry_operation(strategy, [500, 200])
110+
assert quota.available_capacity == 10
123111

124112

125-
def test_retry_quota_shared_correctly_across_multiple_operations() -> None:
126-
retry_quota = StandardRetryQuota()
127-
strategy = StandardRetryStrategy(max_attempts=5, retry_quota=retry_quota)
128-
error = CallError(is_retry_safe=True)
129-
130-
# Operation 1
131-
op1_token = strategy.acquire_initial_retry_token()
132-
assert retry_quota.available_capacity == 500
133-
134-
op1_token = strategy.refresh_retry_token_for_retry(
135-
token_to_renew=op1_token, error=error
113+
async def test_retry_quota_shared_across_concurrent_operations():
114+
quota = StandardRetryQuota(initial_capacity=500)
115+
backoff = ExponentialRetryBackoffStrategy(
116+
backoff_scale_value=1,
117+
max_backoff=10,
118+
jitter_type=ExponentialBackoffJitterType.FULL,
136119
)
137-
assert retry_quota.available_capacity == 495
138-
139-
op1_token = strategy.refresh_retry_token_for_retry(
140-
token_to_renew=op1_token, error=error
120+
strategy = StandardRetryStrategy(
121+
max_attempts=5,
122+
retry_quota=quota,
123+
backoff_strategy=backoff,
141124
)
142-
assert retry_quota.available_capacity == 490
143125

144-
# Operation 2 (while operation 1 is in progress)
145-
op2_token = strategy.acquire_initial_retry_token()
146-
op2_token = strategy.refresh_retry_token_for_retry(
147-
token_to_renew=op2_token, error=error
126+
result1, result2 = await gather(
127+
retry_operation(strategy, [500, 500, 200]),
128+
retry_operation(strategy, [500, 200]),
148129
)
149-
assert retry_quota.available_capacity == 485
150-
151-
strategy.record_success(token=op2_token)
152-
assert retry_quota.available_capacity == 490
153130

154-
strategy.record_success(token=op1_token)
155-
assert retry_quota.available_capacity == 495
131+
assert result1 == ("success", 3)
132+
assert result2 == ("success", 2)
133+
assert quota.available_capacity == 495

0 commit comments

Comments
 (0)