Skip to content

Commit e981734

Browse files
committed
Fixing flaky tests - part 2
1 parent 6bbc5c7 commit e981734

File tree

8 files changed

+78
-55
lines changed

8 files changed

+78
-55
lines changed

dev_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pytest==8.3.4 ; platform_python_implementation == "PyPy"
1212
pytest-asyncio>=0.23.0
1313
pytest-asyncio==1.1.0 ; platform_python_implementation == "PyPy"
1414
pytest-cov
15+
coverage<7.11.1
1516
pytest-cov==6.0.0 ; platform_python_implementation == "PyPy"
1617
coverage==7.6.12 ; platform_python_implementation == "PyPy"
1718
pytest-profiling==1.8.1

redis/asyncio/multidb/healthcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async def execute(self, health_checks: List[HealthCheck], database) -> bool:
8181
for health_check in health_checks:
8282
for attempt in range(self.health_check_probes):
8383
try:
84+
print(f"Checking health of {database}")
8485
if not await health_check.check_health(database):
8586
return False
8687
except Exception as e:

redis/multidb/healthcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def execute(self, health_checks: List[HealthCheck], database) -> bool:
8080
for health_check in health_checks:
8181
for attempt in range(self.health_check_probes):
8282
try:
83+
print(f"Checking health of {database}")
8384
if not health_check.check_health(database):
8485
return False
8586
except Exception as e:

tests/test_asyncio/test_multidb/test_client.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,37 @@ async def test_execute_command_against_correct_db_on_successful_initialization(
6666
async def test_execute_command_against_correct_db_and_closed_circuit(
6767
self, mock_multi_db_config, mock_db, mock_db1, mock_db2, mock_hc
6868
):
69+
"""
70+
Validates that commands are executed against the correct
71+
database when one database becomes unhealthy during initialization.
72+
Ensures the client selects the highest-weighted
73+
healthy database (mock_db1) and executes commands against it
74+
with a CLOSED circuit.
75+
"""
6976
databases = create_weighted_list(mock_db, mock_db1, mock_db2)
7077
mock_multi_db_config.health_checks = [mock_hc]
7178

7279
with patch.object(mock_multi_db_config, "databases", return_value=databases):
80+
mock_db.client.execute_command = AsyncMock(
81+
return_value="NOT_OK-->Response from unexpected db - mock_db"
82+
)
7383
mock_db1.client.execute_command = AsyncMock(return_value="OK1")
84+
mock_db2.client.execute_command = AsyncMock(
85+
return_value="NOT_OK-->Response from unexpected db - mock_db2"
86+
)
7487

75-
mock_hc.check_health.side_effect = [
76-
False,
77-
True,
78-
True,
79-
True,
80-
True,
81-
True,
82-
True,
83-
]
88+
async def mock_check_health(database):
89+
if database == mock_db2:
90+
return False
91+
else:
92+
return True
8493

94+
mock_hc.check_health.side_effect = mock_check_health
8595
client = MultiDBClient(mock_multi_db_config)
8696
assert mock_multi_db_config.failover_strategy.set_databases.call_count == 1
8797
result = await client.set("key", "value")
8898
assert result == "OK1"
89-
assert mock_hc.check_health.call_count == 7
99+
assert mock_hc.check_health.call_count >= 7
90100

91101
assert mock_db.circuit.state == CBState.CLOSED
92102
assert mock_db1.circuit.state == CBState.CLOSED

tests/test_asyncio/test_multidb/test_pipeline.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,10 @@ async def callback(pipe: Pipeline):
299299
pipe.get("key1")
300300

301301
assert await client.transaction(callback) == ["OK1", "value1"]
302-
assert mock_hc.check_health.call_count == 9
302+
# if we assume at least 3 health checks have run per each database
303+
# we should have at least 9 total calls
304+
assert len(mock_hc.check_health.call_args_list) >= 9
305+
assert mock_hc.check_health.call_count >= 9
303306

304307
@pytest.mark.asyncio
305308
@pytest.mark.parametrize(
@@ -327,15 +330,13 @@ async def test_execute_transaction_against_correct_db_and_closed_circuit(
327330
):
328331
mock_db1.client.transaction.return_value = ["OK1", "value1"]
329332

330-
mock_hc.check_health.side_effect = [
331-
False,
332-
True,
333-
True,
334-
True,
335-
True,
336-
True,
337-
True,
338-
]
333+
async def mock_check_health(database):
334+
if database == mock_db2:
335+
return False
336+
else:
337+
return True
338+
339+
mock_hc.check_health.side_effect = mock_check_health
339340

340341
client = MultiDBClient(mock_multi_db_config)
341342
assert mock_multi_db_config.failover_strategy.set_databases.call_count == 1
@@ -345,7 +346,7 @@ async def callback(pipe: Pipeline):
345346
pipe.get("key1")
346347

347348
assert await client.transaction(callback) == ["OK1", "value1"]
348-
assert mock_hc.check_health.call_count == 7
349+
assert mock_hc.check_health.call_count >= 7
349350

350351
assert mock_db.circuit.state == CBState.CLOSED
351352
assert mock_db1.circuit.state == CBState.CLOSED

tests/test_auth/test_token_manager.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ def test_token_renewal_with_skip_initial(self):
174174
mock_provider.request_token.side_effect = [
175175
SimpleToken(
176176
"value",
177-
(datetime.now(timezone.utc).timestamp() * 1000) + 50,
177+
(datetime.now(timezone.utc).timestamp() * 1000) + 1000,
178178
(datetime.now(timezone.utc).timestamp() * 1000),
179179
{"oid": "test"},
180180
),
181181
SimpleToken(
182182
"value",
183-
(datetime.now(timezone.utc).timestamp() * 1000) + 150,
183+
(datetime.now(timezone.utc).timestamp() * 1000) + 1500,
184184
(datetime.now(timezone.utc).timestamp() * 1000),
185185
{"oid": "test"},
186186
),
@@ -194,12 +194,12 @@ def on_next(token):
194194
mock_listener.on_next = on_next
195195

196196
retry_policy = RetryPolicy(3, 10)
197-
config = TokenManagerConfig(1, 0, 1000, retry_policy)
197+
config = TokenManagerConfig(0.5, 0, 1000, retry_policy)
198198
mgr = TokenManager(mock_provider, config)
199199
mgr.start(mock_listener, skip_initial=True)
200-
# Should be less than a 0.1, or it will be flacky due to
201-
# additional token renewal.
202-
sleep(0.1)
200+
assert len(tokens) == 0
201+
202+
sleep(0.6)
203203

204204
assert len(tokens) > 0
205205

@@ -210,19 +210,19 @@ async def test_async_token_renewal_with_skip_initial(self):
210210
mock_provider.request_token.side_effect = [
211211
SimpleToken(
212212
"value",
213-
(datetime.now(timezone.utc).timestamp() * 1000) + 100,
213+
(datetime.now(timezone.utc).timestamp() * 1000) + 1000,
214214
(datetime.now(timezone.utc).timestamp() * 1000),
215215
{"oid": "test"},
216216
),
217217
SimpleToken(
218218
"value",
219-
(datetime.now(timezone.utc).timestamp() * 1000) + 120,
219+
(datetime.now(timezone.utc).timestamp() * 1000) + 1200,
220220
(datetime.now(timezone.utc).timestamp() * 1000),
221221
{"oid": "test"},
222222
),
223223
SimpleToken(
224224
"value",
225-
(datetime.now(timezone.utc).timestamp() * 1000) + 140,
225+
(datetime.now(timezone.utc).timestamp() * 1000) + 1400,
226226
(datetime.now(timezone.utc).timestamp() * 1000),
227227
{"oid": "test"},
228228
),
@@ -236,13 +236,12 @@ async def on_next(token):
236236
mock_listener.on_next = on_next
237237

238238
retry_policy = RetryPolicy(3, 10)
239-
config = TokenManagerConfig(1, 0, 1000, retry_policy)
239+
config = TokenManagerConfig(0.5, 0, 1000, retry_policy)
240240
mgr = TokenManager(mock_provider, config)
241241
await mgr.start_async(mock_listener, skip_initial=True)
242-
# Should be less than a 0.1, or it will be flacky
243-
# due to additional token renewal.
244-
await asyncio.sleep(0.2)
242+
assert len(tokens) == 0
245243

244+
await asyncio.sleep(0.6)
246245
assert len(tokens) > 0
247246

248247
def test_success_token_renewal_with_retry(self):

tests/test_multidb/test_client.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,27 +65,38 @@ def test_execute_command_against_correct_db_on_successful_initialization(
6565
def test_execute_command_against_correct_db_and_closed_circuit(
6666
self, mock_multi_db_config, mock_db, mock_db1, mock_db2, mock_hc
6767
):
68+
"""
69+
Validates that commands are executed against the correct
70+
database when one database becomes unhealthy during initialization.
71+
Ensures the client selects the highest-weighted
72+
healthy database (mock_db1) and executes commands against it
73+
with a CLOSED circuit.
74+
"""
6875
databases = create_weighted_list(mock_db, mock_db1, mock_db2)
6976
mock_multi_db_config.health_checks = [mock_hc]
7077

7178
with patch.object(mock_multi_db_config, "databases", return_value=databases):
79+
mock_db.client.execute_command = MagicMock(
80+
return_value="NOT_OK-->Response from unexpected db - mock_db"
81+
)
7282
mock_db1.client.execute_command = MagicMock(return_value="OK1")
83+
mock_db2.client.execute_command = MagicMock(
84+
return_value="NOT_OK-->Response from unexpected db - mock_db2"
85+
)
86+
87+
def mock_check_health(database):
88+
if database == mock_db2:
89+
return False
90+
else:
91+
return True
7392

74-
mock_hc.check_health.side_effect = [
75-
False,
76-
True,
77-
True,
78-
True,
79-
True,
80-
True,
81-
True,
82-
]
93+
mock_hc.check_health.side_effect = mock_check_health
8394

8495
client = MultiDBClient(mock_multi_db_config)
8596
assert mock_multi_db_config.failover_strategy.set_databases.call_count == 1
8697
result = client.set("key", "value")
8798
assert result == "OK1"
88-
assert mock_hc.check_health.call_count == 7
99+
assert mock_hc.check_health.call_count >= 7
89100

90101
assert mock_db.circuit.state == CBState.CLOSED
91102
assert mock_db1.circuit.state == CBState.CLOSED

tests/test_multidb/test_pipeline.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,13 @@ def test_execute_pipeline_against_correct_db_and_closed_circuit(
8989
pipe.execute.return_value = ["OK1", "value1"]
9090
mock_db1.client.pipeline.return_value = pipe
9191

92-
mock_hc.check_health.side_effect = [
93-
False,
94-
True,
95-
True,
96-
True,
97-
True,
98-
True,
99-
True,
100-
]
92+
def mock_check_health(database):
93+
if database == mock_db2:
94+
return False
95+
else:
96+
return True
97+
98+
mock_hc.check_health.side_effect = mock_check_health
10199

102100
client = MultiDBClient(mock_multi_db_config)
103101
assert mock_multi_db_config.failover_strategy.set_databases.call_count == 1
@@ -107,7 +105,7 @@ def test_execute_pipeline_against_correct_db_and_closed_circuit(
107105
pipe.get("key1")
108106

109107
assert pipe.execute() == ["OK1", "value1"]
110-
assert mock_hc.check_health.call_count == 7
108+
assert mock_hc.check_health.call_count >= 7
111109

112110
assert mock_db.circuit.state == CBState.CLOSED
113111
assert mock_db1.circuit.state == CBState.CLOSED
@@ -298,7 +296,8 @@ def callback(pipe: Pipeline):
298296
pipe.get("key1")
299297

300298
assert client.transaction(callback) == ["OK1", "value1"]
301-
assert mock_hc.check_health.call_count == 9
299+
assert len(mock_hc.check_health.call_args_list) >= 9
300+
assert mock_hc.check_health.call_count >= 9
302301

303302
@pytest.mark.parametrize(
304303
"mock_multi_db_config,mock_db, mock_db1, mock_db2",

0 commit comments

Comments
 (0)