Skip to content

Commit f59c173

Browse files
committed
Add functional tests for StandardRetryStrategy and StandardRetryQuota
1 parent ef085ec commit f59c173

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import pytest
5+
from smithy_core.exceptions import CallError, RetryError
6+
from smithy_core.retries import StandardRetryQuota, StandardRetryStrategy
7+
8+
9+
def get_retry_quota(strategy: StandardRetryStrategy) -> int:
10+
return strategy._retry_quota.available_capacity # pyright: ignore[reportPrivateUsage]
11+
12+
13+
async def test_standard_retry_eventually_succeeds() -> None:
14+
strategy = StandardRetryStrategy(max_attempts=3)
15+
error = CallError(is_retry_safe=True)
16+
17+
token = await strategy.acquire_initial_retry_token()
18+
assert token.retry_count == 0
19+
assert get_retry_quota(strategy) == 500
20+
21+
token = await strategy.refresh_retry_token_for_retry(
22+
token_to_renew=token, error=error
23+
)
24+
assert token.retry_count == 1
25+
assert get_retry_quota(strategy) == 495
26+
27+
token = await strategy.refresh_retry_token_for_retry(
28+
token_to_renew=token, error=error
29+
)
30+
assert token.retry_count == 2
31+
assert get_retry_quota(strategy) == 490
32+
33+
await strategy.record_success(token=token)
34+
assert get_retry_quota(strategy) == 495
35+
36+
37+
async def test_standard_retry_fails_due_to_max_attempts() -> None:
38+
strategy = StandardRetryStrategy(max_attempts=3)
39+
error = CallError(is_retry_safe=True)
40+
41+
token = await strategy.acquire_initial_retry_token()
42+
43+
token = await strategy.refresh_retry_token_for_retry(
44+
token_to_renew=token, error=error
45+
)
46+
assert token.retry_count == 1
47+
assert get_retry_quota(strategy) == 495
48+
49+
token = await strategy.refresh_retry_token_for_retry(
50+
token_to_renew=token, error=error
51+
)
52+
assert token.retry_count == 2
53+
assert get_retry_quota(strategy) == 490
54+
55+
with pytest.raises(RetryError, match="maximum number of allowed attempts"):
56+
await strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
57+
assert get_retry_quota(strategy) == 490
58+
59+
60+
async def test_retry_quota_exhausted_after_single_retry(
61+
monkeypatch: pytest.MonkeyPatch,
62+
) -> None:
63+
monkeypatch.setattr(StandardRetryQuota, "INITIAL_RETRY_TOKENS", 5, raising=False)
64+
strategy = StandardRetryStrategy(max_attempts=3)
65+
error = CallError(is_retry_safe=True)
66+
67+
token = await strategy.acquire_initial_retry_token()
68+
assert get_retry_quota(strategy) == 5
69+
70+
token = await strategy.refresh_retry_token_for_retry(
71+
token_to_renew=token, error=error
72+
)
73+
assert token.retry_count == 1
74+
assert get_retry_quota(strategy) == 0
75+
76+
with pytest.raises(RetryError, match="Retry quota exceeded"):
77+
await strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
78+
assert get_retry_quota(strategy) == 0
79+
80+
81+
async def test_retry_quota_prevents_retries_when_zero(
82+
monkeypatch: pytest.MonkeyPatch,
83+
) -> None:
84+
monkeypatch.setattr(StandardRetryQuota, "INITIAL_RETRY_TOKENS", 0, raising=False)
85+
strategy = StandardRetryStrategy(max_attempts=3)
86+
error = CallError(is_retry_safe=True)
87+
88+
token = await strategy.acquire_initial_retry_token()
89+
assert get_retry_quota(strategy) == 0
90+
91+
with pytest.raises(RetryError, match="Retry quota exceeded"):
92+
await strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
93+
assert get_retry_quota(strategy) == 0
94+
95+
96+
async def test_retry_quota_stops_retries_when_exhauste(
97+
monkeypatch: pytest.MonkeyPatch,
98+
) -> None:
99+
monkeypatch.setattr(StandardRetryQuota, "INITIAL_RETRY_TOKENS", 10, raising=False)
100+
strategy = StandardRetryStrategy(max_attempts=5)
101+
error = CallError(is_retry_safe=True)
102+
103+
token = await strategy.acquire_initial_retry_token()
104+
assert get_retry_quota(strategy) == 10
105+
106+
token = await strategy.refresh_retry_token_for_retry(
107+
token_to_renew=token, error=error
108+
)
109+
assert get_retry_quota(strategy) == 5
110+
111+
token = await strategy.refresh_retry_token_for_retry(
112+
token_to_renew=token, error=error
113+
)
114+
assert get_retry_quota(strategy) == 0
115+
116+
with pytest.raises(RetryError, match="Retry quota exceeded"):
117+
await strategy.refresh_retry_token_for_retry(token_to_renew=token, error=error)
118+
assert get_retry_quota(strategy) == 0
119+
120+
121+
async def test_retry_quota_recovers_after_successful_responses(
122+
monkeypatch: pytest.MonkeyPatch,
123+
) -> None:
124+
monkeypatch.setattr(StandardRetryQuota, "INITIAL_RETRY_TOKENS", 15, raising=False)
125+
strategy = StandardRetryStrategy(max_attempts=5)
126+
error = CallError(is_retry_safe=True)
127+
128+
# First operation: 2 retries then success
129+
token = await strategy.acquire_initial_retry_token()
130+
assert get_retry_quota(strategy) == 15
131+
132+
token = await strategy.refresh_retry_token_for_retry(
133+
token_to_renew=token, error=error
134+
)
135+
assert get_retry_quota(strategy) == 10
136+
137+
token = await strategy.refresh_retry_token_for_retry(
138+
token_to_renew=token, error=error
139+
)
140+
assert get_retry_quota(strategy) == 5
141+
142+
await strategy.record_success(token=token)
143+
assert get_retry_quota(strategy) == 10
144+
145+
# Second operation: 1 retry then success
146+
token = await strategy.acquire_initial_retry_token()
147+
token = await strategy.refresh_retry_token_for_retry(
148+
token_to_renew=token, error=error
149+
)
150+
assert get_retry_quota(strategy) == 5
151+
await strategy.record_success(token=token)
152+
assert get_retry_quota(strategy) == 10
153+
154+
155+
async def test_retry_quota_shared_correctly_across_multiple_operations() -> None:
156+
strategy = StandardRetryStrategy(max_attempts=5)
157+
error = CallError(is_retry_safe=True)
158+
159+
# Operation 1
160+
op1_token = await strategy.acquire_initial_retry_token()
161+
assert get_retry_quota(strategy) == 500
162+
163+
op1_token = await strategy.refresh_retry_token_for_retry(
164+
token_to_renew=op1_token, error=error
165+
)
166+
assert get_retry_quota(strategy) == 495
167+
168+
op1_token = await strategy.refresh_retry_token_for_retry(
169+
token_to_renew=op1_token, error=error
170+
)
171+
assert get_retry_quota(strategy) == 490
172+
173+
# Operation 2 (while operation 1 is in progress)
174+
op2_token = await strategy.acquire_initial_retry_token()
175+
op2_token = await strategy.refresh_retry_token_for_retry(
176+
token_to_renew=op2_token, error=error
177+
)
178+
assert get_retry_quota(strategy) == 485
179+
180+
await strategy.record_success(token=op2_token)
181+
assert get_retry_quota(strategy) == 490
182+
183+
await strategy.record_success(token=op1_token)
184+
assert get_retry_quota(strategy) == 495

0 commit comments

Comments
 (0)