From cfe563704317d43f5278fadd5ee56a0f411ae8d0 Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Mon, 3 Nov 2025 20:12:35 +0200 Subject: [PATCH 1/6] Fixing flaky tests --- tests/test_asyncio/test_multidb/test_client.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_asyncio/test_multidb/test_client.py b/tests/test_asyncio/test_multidb/test_client.py index e028655c38..8d1bd5b16f 100644 --- a/tests/test_asyncio/test_multidb/test_client.py +++ b/tests/test_asyncio/test_multidb/test_client.py @@ -201,6 +201,7 @@ async def mock_check_health(database): assert await db1_became_unhealthy.wait(), ( "Timeout waiting for mock_db1 to become unhealthy" ) + await asyncio.sleep(0.01) assert await client.set("key", "value") == "OK2" @@ -209,7 +210,14 @@ async def mock_check_health(database): assert await db2_became_unhealthy.wait(), ( "Timeout waiting for mock_db2 to become unhealthy" ) - await asyncio.sleep(0.01) + + # Wait for circuit breaker state to actually reflect the unhealthy status + # (instead of just sleeping) + max_retries = 10 + for _ in range(max_retries): + if cb2.state == CBState.OPEN: # Circuit is open (unhealthy) + break + await asyncio.sleep(0.01) assert await client.set("key", "value") == "OK" From e13495ab1ca54f24fb4c7925f4dc17615e27e57e Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Mon, 3 Nov 2025 20:49:18 +0200 Subject: [PATCH 2/6] Fixing flaky vset test. --- tests/test_asyncio/test_vsets.py | 2 +- tests/test_vsets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_asyncio/test_vsets.py b/tests/test_asyncio/test_vsets.py index 896d98b192..447e474e9c 100644 --- a/tests/test_asyncio/test_vsets.py +++ b/tests/test_asyncio/test_vsets.py @@ -454,7 +454,7 @@ async def test_vsim_truth_no_thread_enabled(d_client): elements_count = 5000 vector_dim = 50 for i in range(1, elements_count + 1): - float_array = [random.uniform(10 * i, 1000 * i) for x in range(vector_dim)] + float_array = [i for _ in range(vector_dim)] await d_client.vset().vadd("myset", float_array, f"elem_{i}") await d_client.vset().vadd("myset", [-22 for _ in range(vector_dim)], "elem_man_2") diff --git a/tests/test_vsets.py b/tests/test_vsets.py index 36295201e7..6376eeb898 100644 --- a/tests/test_vsets.py +++ b/tests/test_vsets.py @@ -456,7 +456,7 @@ def test_vsim_truth_no_thread_enabled(d_client): elements_count = 5000 vector_dim = 50 for i in range(1, elements_count + 1): - float_array = [random.uniform(10 * i, 1000 * i) for x in range(vector_dim)] + float_array = [i for _ in range(vector_dim)] d_client.vset().vadd("myset", float_array, f"elem_{i}") d_client.vset().vadd("myset", [-22 for _ in range(vector_dim)], "elem_man_2") From 55dcfda1f7c67bd8ab603c031acf401ab184a79a Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Mon, 3 Nov 2025 21:32:11 +0200 Subject: [PATCH 3/6] Fixing test_execute_command_against_correct_db_on_background_health_check_determine_active_db_unhealthy --- tests/test_asyncio/test_multidb/test_client.py | 8 ++++++++ tests/test_multidb/test_client.py | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/test_asyncio/test_multidb/test_client.py b/tests/test_asyncio/test_multidb/test_client.py index 8d1bd5b16f..27833674de 100644 --- a/tests/test_asyncio/test_multidb/test_client.py +++ b/tests/test_asyncio/test_multidb/test_client.py @@ -279,6 +279,14 @@ async def mock_check_health(database): async with MultiDBClient(mock_multi_db_config) as client: assert await client.set("key", "value") == "OK1" await error_event.wait() + # Wait for circuit breaker to actually open (not just the event) + max_retries = 10 + for _ in range(max_retries): + if mock_db1.circuit.state == CBState.OPEN: # Circuit is open + break + await asyncio.sleep(0.01) + + # Now the failover strategy will select mock_db2 assert await client.set("key", "value") == "OK2" await asyncio.sleep(0.5) assert await client.set("key", "value") == "OK1" diff --git a/tests/test_multidb/test_client.py b/tests/test_multidb/test_client.py index b342b4b91b..15534b770b 100644 --- a/tests/test_multidb/test_client.py +++ b/tests/test_multidb/test_client.py @@ -268,6 +268,15 @@ def mock_check_health(database): client = MultiDBClient(mock_multi_db_config) assert client.set("key", "value") == "OK1" error_event.wait(timeout=0.5) + + # Wait for circuit breaker to actually open (not just the event) + max_retries = 10 + for _ in range(max_retries): + if mock_db1.circuit.state == CBState.OPEN: # Circuit is open + break + sleep(0.01) + + # Now the failover strategy will select mock_db2 assert client.set("key", "value") == "OK2" sleep(0.5) assert client.set("key", "value") == "OK1" From 204f1c0ac7478a057b79fc7b878587099d95aed5 Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Tue, 4 Nov 2025 08:19:58 +0200 Subject: [PATCH 4/6] Fixing with patch + one more test --- .../test_asyncio/test_multidb/test_client.py | 72 ++++++++----------- tests/test_multidb/test_client.py | 70 +++++++----------- 2 files changed, 55 insertions(+), 87 deletions(-) diff --git a/tests/test_asyncio/test_multidb/test_client.py b/tests/test_asyncio/test_multidb/test_client.py index 27833674de..537fdb0c82 100644 --- a/tests/test_asyncio/test_multidb/test_client.py +++ b/tests/test_asyncio/test_multidb/test_client.py @@ -36,9 +36,7 @@ async def test_execute_command_against_correct_db_on_successful_initialization( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command = AsyncMock(return_value="OK1") mock_hc.check_health.return_value = True @@ -71,9 +69,7 @@ async def test_execute_command_against_correct_db_and_closed_circuit( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command = AsyncMock(return_value="OK1") mock_hc.check_health.side_effect = [ @@ -88,7 +84,8 @@ async def test_execute_command_against_correct_db_and_closed_circuit( client = MultiDBClient(mock_multi_db_config) assert mock_multi_db_config.failover_strategy.set_databases.call_count == 1 - assert await client.set("key", "value") == "OK1" + result = await client.set("key", "value") + assert result == "OK1" assert mock_hc.check_health.call_count == 7 assert mock_db.circuit.state == CBState.CLOSED @@ -185,9 +182,7 @@ async def mock_check_health(database): mock_hc.check_health.side_effect = mock_check_health mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db.client.execute_command.return_value = "OK" mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -213,7 +208,7 @@ async def mock_check_health(database): # Wait for circuit breaker state to actually reflect the unhealthy status # (instead of just sleeping) - max_retries = 10 + max_retries = 20 for _ in range(max_retries): if cb2.state == CBState.OPEN: # Circuit is open (unhealthy) break @@ -266,9 +261,7 @@ async def mock_check_health(database): mock_hc.check_health.side_effect = mock_check_health mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db.client.execute_command.return_value = "OK" mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -280,7 +273,7 @@ async def mock_check_health(database): assert await client.set("key", "value") == "OK1" await error_event.wait() # Wait for circuit breaker to actually open (not just the event) - max_retries = 10 + max_retries = 20 for _ in range(max_retries): if mock_db1.circuit.state == CBState.OPEN: # Circuit is open break @@ -328,9 +321,7 @@ async def mock_check_health(database): mock_hc.check_health.side_effect = mock_check_health mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db.client.execute_command.return_value = "OK" mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -341,6 +332,15 @@ async def mock_check_health(database): async with MultiDBClient(mock_multi_db_config) as client: assert await client.set("key", "value") == "OK1" await error_event.wait() + # Wait for circuit breaker state to actually reflect the unhealthy status + # (instead of just sleeping) + max_retries = 20 + for _ in range(max_retries): + if ( + mock_db1.circuit.state == CBState.OPEN + ): # Circuit is open (unhealthy) + break + await asyncio.sleep(0.01) assert await client.set("key", "value") == "OK2" await asyncio.sleep(0.5) assert await client.set("key", "value") == "OK2" @@ -364,9 +364,7 @@ async def test_execute_command_throws_exception_on_failed_initialization( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_hc.check_health.return_value = False client = MultiDBClient(mock_multi_db_config) @@ -398,9 +396,7 @@ async def test_add_database_throws_exception_on_same_database( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_hc.check_health.return_value = False client = MultiDBClient(mock_multi_db_config) @@ -429,9 +425,7 @@ async def test_add_database_makes_new_database_active( databases = create_weighted_list(mock_db, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -467,9 +461,7 @@ async def test_remove_highest_weighted_database( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -503,9 +495,7 @@ async def test_update_database_weight_to_be_highest( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -541,9 +531,7 @@ async def test_add_new_failure_detector( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_multi_db_config.event_dispatcher = EventDispatcher() mock_fd = mock_multi_db_config.failure_detectors[0] @@ -562,7 +550,7 @@ async def test_add_new_failure_detector( assert mock_hc.check_health.call_count == 9 # Simulate failing command events that lead to a failure detection - for i in range(5): + for _ in range(5): await mock_multi_db_config.event_dispatcher.dispatch_async( command_fail_event ) @@ -573,7 +561,7 @@ async def test_add_new_failure_detector( client.add_failure_detector(another_fd) # Simulate failing command events that lead to a failure detection - for i in range(5): + for _ in range(5): await mock_multi_db_config.event_dispatcher.dispatch_async( command_fail_event ) @@ -600,9 +588,7 @@ async def test_add_new_health_check( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_hc.check_health.return_value = True @@ -640,9 +626,7 @@ async def test_set_active_database( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db.client.execute_command.return_value = "OK" diff --git a/tests/test_multidb/test_client.py b/tests/test_multidb/test_client.py index 15534b770b..490126fdf2 100644 --- a/tests/test_multidb/test_client.py +++ b/tests/test_multidb/test_client.py @@ -1,6 +1,6 @@ import threading from time import sleep -from unittest.mock import patch, Mock +from unittest.mock import MagicMock, patch, Mock import pybreaker import pytest @@ -36,9 +36,7 @@ def test_execute_command_against_correct_db_on_successful_initialization( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_hc.check_health.return_value = True @@ -70,10 +68,8 @@ def test_execute_command_against_correct_db_and_closed_circuit( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): - mock_db1.client.execute_command.return_value = "OK1" + with patch.object(mock_multi_db_config, "databases", return_value=databases): + mock_db1.client.execute_command = MagicMock(return_value="OK1") mock_hc.check_health.side_effect = [ False, @@ -87,7 +83,8 @@ def test_execute_command_against_correct_db_and_closed_circuit( client = MultiDBClient(mock_multi_db_config) assert mock_multi_db_config.failover_strategy.set_databases.call_count == 1 - assert client.set("key", "value") == "OK1" + result = client.set("key", "value") + assert result == "OK1" assert mock_hc.check_health.call_count == 7 assert mock_db.circuit.state == CBState.CLOSED @@ -183,9 +180,7 @@ def mock_check_health(database): mock_hc.check_health.side_effect = mock_check_health mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_multi_db_config.health_check_interval = 0.1 mock_multi_db_config.failover_strategy = WeightBasedFailoverStrategy() mock_db.client.execute_command.return_value = "OK" @@ -255,9 +250,7 @@ def mock_check_health(database): mock_hc.check_health.side_effect = mock_check_health mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db.client.execute_command.return_value = "OK" mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -270,7 +263,7 @@ def mock_check_health(database): error_event.wait(timeout=0.5) # Wait for circuit breaker to actually open (not just the event) - max_retries = 10 + max_retries = 20 for _ in range(max_retries): if mock_db1.circuit.state == CBState.OPEN: # Circuit is open break @@ -317,9 +310,7 @@ def mock_check_health(database): mock_hc.check_health.side_effect = mock_check_health mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db.client.execute_command.return_value = "OK" mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -330,6 +321,15 @@ def mock_check_health(database): client = MultiDBClient(mock_multi_db_config) assert client.set("key", "value") == "OK1" error_event.wait(timeout=0.5) + # Wait for circuit breaker state to actually reflect the unhealthy status + # (instead of just sleeping) + max_retries = 20 + for _ in range(max_retries): + if ( + mock_db1.circuit.state == CBState.OPEN + ): # Circuit is open (unhealthy) + break + sleep(0.01) assert client.set("key", "value") == "OK2" sleep(0.5) assert client.set("key", "value") == "OK2" @@ -352,9 +352,7 @@ def test_execute_command_throws_exception_on_failed_initialization( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_hc.check_health.return_value = False client = MultiDBClient(mock_multi_db_config) @@ -386,9 +384,7 @@ def test_add_database_throws_exception_on_same_database( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_hc.check_health.return_value = False client = MultiDBClient(mock_multi_db_config) @@ -416,9 +412,7 @@ def test_add_database_makes_new_database_active( databases = create_weighted_list(mock_db, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -453,9 +447,7 @@ def test_remove_highest_weighted_database( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -489,9 +481,7 @@ def test_update_database_weight_to_be_highest( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db2.client.execute_command.return_value = "OK2" @@ -526,9 +516,7 @@ def test_add_new_failure_detector( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_multi_db_config.event_dispatcher = EventDispatcher() mock_fd = mock_multi_db_config.failure_detectors[0] @@ -580,9 +568,7 @@ def test_add_new_health_check( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_hc.check_health.return_value = True @@ -619,9 +605,7 @@ def test_set_active_database( databases = create_weighted_list(mock_db, mock_db1, mock_db2) mock_multi_db_config.health_checks = [mock_hc] - with ( - patch.object(mock_multi_db_config, "databases", return_value=databases), - ): + with patch.object(mock_multi_db_config, "databases", return_value=databases): mock_db1.client.execute_command.return_value = "OK1" mock_db.client.execute_command.return_value = "OK" From 0c90a7e624e94a90118432ccd4ae6fbda215f5c9 Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Tue, 4 Nov 2025 09:41:38 +0200 Subject: [PATCH 5/6] Fixing MSETEX tests - time set with pxat might be approximated to 1 sec higher than it was set as test expectation --- tests/test_asyncio/test_commands.py | 12 ++++++------ tests/test_commands.py | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py index cf25118022..f30578ceca 100644 --- a/tests/test_asyncio/test_commands.py +++ b/tests/test_asyncio/test_commands.py @@ -1321,7 +1321,7 @@ async def test_msetex_expiration_pxat_and_nx_with_cluster_client(self, r): for ttl in old_ttls: assert 10 < ttl <= 30 for ttl in new_ttls: - assert ttl <= 10 + assert ttl <= 11 assert await r.mget(*mapping.keys(), "new:{test:1}", "new_2:{test:1}") == [ b"1", b"2", @@ -1370,8 +1370,8 @@ async def test_msetex_expiration_exat_and_xx_with_cluster_client(self, r): == 1 ) ttls = await asyncio.gather(*[r.ttl(key) for key in mapping.keys()]) - assert ttls[0] <= 10 - assert ttls[1] <= 10 + assert ttls[0] <= 11 + assert ttls[1] <= 11 assert 10 < ttls[2] <= 30 assert await r.mget( "1:{test:1}", "2:{test:1}", "3:{test:1}", "new:{test:1}" @@ -1483,7 +1483,7 @@ async def test_msetex_expiration_pxat_and_nx(self, r): for ttl in old_ttls: assert 10 < ttl <= 30 for ttl in new_ttls: - assert ttl <= 10 + assert ttl <= 11 assert await r.mget(*mapping.keys(), "new", "new_2") == [ b"1", b"2", @@ -1527,8 +1527,8 @@ async def test_msetex_expiration_exat_and_xx(self, r): == 1 ) ttls = await asyncio.gather(*[r.ttl(key) for key in mapping.keys()]) - assert ttls[0] <= 10 - assert ttls[1] <= 10 + assert ttls[0] <= 11 + assert ttls[1] <= 11 assert 10 < ttls[2] <= 30 assert await r.mget("1", "2", "3", "new") == [ b"new_value", diff --git a/tests/test_commands.py b/tests/test_commands.py index 68017236ee..a8ddec31ae 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1896,6 +1896,7 @@ def test_msetex_expiration_pxat_and_nx_with_cluster_client(self, r): ) == 0 ) + ttls = [r.ttl(key) for key in mapping.keys()] for ttl in ttls: assert 10 < ttl <= 30 @@ -1915,7 +1916,7 @@ def test_msetex_expiration_pxat_and_nx_with_cluster_client(self, r): for ttl in old_ttls: assert 10 < ttl <= 30 for ttl in new_ttls: - assert ttl <= 10 + assert ttl <= 11 assert r.mget(*mapping.keys(), "new:{test:1}", "new_2:{test:1}") == [ b"1", b"2", @@ -1959,8 +1960,8 @@ def test_msetex_expiration_exat_and_xx_with_cluster_client(self, r): == 1 ) ttls = [r.ttl(key) for key in mapping.keys()] - assert ttls[0] <= 10 - assert ttls[1] <= 10 + assert ttls[0] <= 11 + assert ttls[1] <= 11 assert 10 < ttls[2] <= 30 assert r.mget("1:{test:1}", "2:{test:1}", "3:{test:1}", "new:{test:1}") == [ b"new_value", @@ -2070,7 +2071,7 @@ def test_msetex_expiration_pxat_and_nx(self, r): for ttl in old_ttls: assert 10 < ttl <= 30 for ttl in new_ttls: - assert ttl <= 10 + assert ttl <= 11 assert r.mget(*mapping.keys(), "new", "new_2") == [ b"1", b"2", @@ -2114,8 +2115,8 @@ def test_msetex_expiration_exat_and_xx(self, r): == 1 ) ttls = [r.ttl(key) for key in mapping.keys()] - assert ttls[0] <= 10 - assert ttls[1] <= 10 + assert ttls[0] <= 11 + assert ttls[1] <= 11 assert 10 < ttls[2] <= 30 assert r.mget("1", "2", "3", "new") == [ b"new_value", From 6d6492d5b852b0e465852683d0eb4c12affaad0d Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Tue, 4 Nov 2025 10:14:08 +0200 Subject: [PATCH 6/6] Fix Monitor's next_command parsing for CLUSTER SHARDS command - issue catched by a flaky test behavior --- redis/client.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/redis/client.py b/redis/client.py index 51b699721e..d3ab3cfcfe 100755 --- a/redis/client.py +++ b/redis/client.py @@ -761,9 +761,14 @@ def next_command(self): client_port = client_info[5:] client_type = "unix" else: - # use rsplit as ipv6 addresses contain colons - client_address, client_port = client_info.rsplit(":", 1) - client_type = "tcp" + if client_info == "": + client_address = "" + client_port = "" + client_type = "unknown" + else: + # use rsplit as ipv6 addresses contain colons + client_address, client_port = client_info.rsplit(":", 1) + client_type = "tcp" return { "time": float(command_time), "db": int(db_id),