diff --git a/tests/test_asyncio/test_cluster.py b/tests/test_asyncio/test_cluster.py index 6b477e26..6e85f16c 100644 --- a/tests/test_asyncio/test_cluster.py +++ b/tests/test_asyncio/test_cluster.py @@ -12,6 +12,8 @@ from tests.conftest import ( assert_resp_response, is_resp2_connection, + skip_if_server_version_gte, + skip_if_server_version_lt, skip_unless_arch_bits, ) from valkey._parsers import AsyncCommandsParser @@ -1050,6 +1052,7 @@ async def test_cluster_myid(self, r: ValkeyCluster) -> None: myid = await r.cluster_myid(node) assert len(myid) == 40 + @skip_if_server_version_lt("7.2.0") async def test_cluster_myshardid(self, r: ValkeyCluster) -> None: node = r.get_random_node() myshardid = await r.cluster_myshardid(node) @@ -1068,6 +1071,7 @@ async def test_cluster_addslots(self, r: ValkeyCluster) -> None: mock_node_resp(node, "OK") assert await r.cluster_addslots(node, 1, 2, 3) is True + @skip_if_server_version_lt("7.0.0") async def test_cluster_addslotsrange(self, r: ValkeyCluster): node = r.get_random_node() mock_node_resp(node, "OK") @@ -1099,6 +1103,7 @@ async def test_cluster_delslots(self) -> None: await r.aclose() + @skip_if_server_version_lt("7.0.0") async def test_cluster_delslotsrange(self): r = await get_mocked_valkey_client(host=default_host, port=default_port) mock_all_nodes_resp(r, "OK") @@ -1275,6 +1280,7 @@ async def test_cluster_replicas(self, r: ValkeyCluster) -> None: == "r4xfga22229cf3c652b6fca0d09ff69f3e0d4d" ) + @skip_if_server_version_lt("7.0.0") async def test_cluster_links(self, r: ValkeyCluster): node = r.get_random_node() res = await r.cluster_links(node) @@ -1405,13 +1411,16 @@ async def test_time(self, r: ValkeyCluster) -> None: assert isinstance(t[0], int) assert isinstance(t[1], int) + @skip_if_server_version_lt("4.0.0") async def test_memory_usage(self, r: ValkeyCluster) -> None: await r.set("foo", "bar") assert isinstance(await r.memory_usage("foo"), int) + @skip_if_server_version_lt("4.0.0") async def test_memory_malloc_stats(self, r: ValkeyCluster) -> None: assert await r.memory_malloc_stats() + @skip_if_server_version_lt("4.0.0") async def test_memory_stats(self, r: ValkeyCluster) -> None: # put a key into the current db to make sure that "db." # has data @@ -1423,10 +1432,12 @@ async def test_memory_stats(self, r: ValkeyCluster) -> None: if key.startswith("db."): assert isinstance(value, dict) + @skip_if_server_version_lt("4.0.0") async def test_memory_help(self, r: ValkeyCluster) -> None: with pytest.raises(NotImplementedError): await r.memory_help() + @skip_if_server_version_lt("4.0.0") async def test_memory_doctor(self, r: ValkeyCluster) -> None: with pytest.raises(NotImplementedError): await r.memory_doctor() @@ -1439,6 +1450,7 @@ async def test_cluster_echo(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] assert await r.echo("foo bar", target_nodes=node) == b"foo bar" + @skip_if_server_version_lt("1.0.0") async def test_debug_segfault(self, r: ValkeyCluster) -> None: with pytest.raises(NotImplementedError): await r.debug_segfault() @@ -1456,12 +1468,14 @@ async def test_config_resetstat(self, r: ValkeyCluster) -> None: ) assert reset_commands_processed < prior_commands_processed + @skip_if_server_version_lt("6.2.0") async def test_client_trackinginfo(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] res = await r.client_trackinginfo(target_nodes=node) assert len(res) > 2 assert "prefixes" in res or b"prefixes" in res + @skip_if_server_version_lt("2.9.50") async def test_client_pause(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] assert await r.client_pause(1, target_nodes=node) @@ -1469,13 +1483,16 @@ async def test_client_pause(self, r: ValkeyCluster) -> None: with pytest.raises(ValkeyError): await r.client_pause(timeout="not an integer", target_nodes=node) + @skip_if_server_version_lt("6.2.0") async def test_client_unpause(self, r: ValkeyCluster) -> None: assert await r.client_unpause() + @skip_if_server_version_lt("5.0.0") async def test_client_id(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] assert await r.client_id(target_nodes=node) > 0 + @skip_if_server_version_lt("5.0.0") async def test_client_unblock(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] myid = await r.client_id(target_nodes=node) @@ -1483,17 +1500,20 @@ async def test_client_unblock(self, r: ValkeyCluster) -> None: assert not await r.client_unblock(myid, error=True, target_nodes=node) assert not await r.client_unblock(myid, error=False, target_nodes=node) + @skip_if_server_version_lt("6.0.0") async def test_client_getredir(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] assert isinstance(await r.client_getredir(target_nodes=node), int) assert await r.client_getredir(target_nodes=node) == -1 + @skip_if_server_version_lt("6.2.0") async def test_client_info(self, r: ValkeyCluster) -> None: node = r.get_primaries()[0] info = await r.client_info(target_nodes=node) assert isinstance(info, dict) assert "addr" in info + @skip_if_server_version_lt("2.6.9") async def test_client_kill( self, r: ValkeyCluster, create_valkey: Callable[..., ValkeyCluster] ) -> None: @@ -1521,11 +1541,13 @@ async def test_client_kill( assert clients[0].get("name") == "valkey-py-c1" await r2.aclose() + @skip_if_server_version_lt("2.6.0") async def test_cluster_bitop_not_empty_string(self, r: ValkeyCluster) -> None: await r.set("{foo}a", "") await r.bitop("not", "{foo}r", "{foo}a") assert await r.get("{foo}r") is None + @skip_if_server_version_lt("2.6.0") async def test_cluster_bitop_not(self, r: ValkeyCluster) -> None: test_str = b"\xAA\x00\xFF\x55" correct = ~0xAA00FF55 & 0xFFFFFFFF @@ -1533,6 +1555,7 @@ async def test_cluster_bitop_not(self, r: ValkeyCluster) -> None: await r.bitop("not", "{foo}r", "{foo}a") assert int(binascii.hexlify(await r.get("{foo}r")), 16) == correct + @skip_if_server_version_lt("2.6.0") async def test_cluster_bitop_not_in_place(self, r: ValkeyCluster) -> None: test_str = b"\xAA\x00\xFF\x55" correct = ~0xAA00FF55 & 0xFFFFFFFF @@ -1540,6 +1563,7 @@ async def test_cluster_bitop_not_in_place(self, r: ValkeyCluster) -> None: await r.bitop("not", "{foo}a", "{foo}a") assert int(binascii.hexlify(await r.get("{foo}a")), 16) == correct + @skip_if_server_version_lt("2.6.0") async def test_cluster_bitop_single_string(self, r: ValkeyCluster) -> None: test_str = b"\x01\x02\xFF" await r.set("{foo}a", test_str) @@ -1550,6 +1574,7 @@ async def test_cluster_bitop_single_string(self, r: ValkeyCluster) -> None: assert await r.get("{foo}res2") == test_str assert await r.get("{foo}res3") == test_str + @skip_if_server_version_lt("2.6.0") async def test_cluster_bitop_string_operands(self, r: ValkeyCluster) -> None: await r.set("{foo}a", b"\x01\x02\xFF\xFF") await r.set("{foo}b", b"\x01\x02\xFF") @@ -1560,6 +1585,7 @@ async def test_cluster_bitop_string_operands(self, r: ValkeyCluster) -> None: assert int(binascii.hexlify(await r.get("{foo}res2")), 16) == 0x0102FFFF assert int(binascii.hexlify(await r.get("{foo}res3")), 16) == 0x000000FF + @skip_if_server_version_lt("6.2.0") async def test_cluster_copy(self, r: ValkeyCluster) -> None: assert await r.copy("{foo}a", "{foo}b") == 0 await r.set("{foo}a", "bar") @@ -1567,17 +1593,20 @@ async def test_cluster_copy(self, r: ValkeyCluster) -> None: assert await r.get("{foo}a") == b"bar" assert await r.get("{foo}b") == b"bar" + @skip_if_server_version_lt("6.2.0") async def test_cluster_copy_and_replace(self, r: ValkeyCluster) -> None: await r.set("{foo}a", "foo1") await r.set("{foo}b", "foo2") assert await r.copy("{foo}a", "{foo}b") == 0 assert await r.copy("{foo}a", "{foo}b", replace=True) == 1 + @skip_if_server_version_lt("6.2.0") async def test_cluster_lmove(self, r: ValkeyCluster) -> None: await r.rpush("{foo}a", "one", "two", "three", "four") assert await r.lmove("{foo}a", "{foo}b") assert await r.lmove("{foo}a", "{foo}b", "right", "left") + @skip_if_server_version_lt("6.2.0") async def test_cluster_blmove(self, r: ValkeyCluster) -> None: await r.rpush("{foo}a", "one", "two", "three", "four") assert await r.blmove("{foo}a", "{foo}b", 5) @@ -1738,6 +1767,7 @@ async def test_cluster_sunionstore(self, r: ValkeyCluster) -> None: assert await r.sunionstore("{foo}c", "{foo}a", "{foo}b") == 3 assert await r.smembers("{foo}c") == {b"1", b"2", b"3"} + @skip_if_server_version_lt("6.2.0") async def test_cluster_zdiff(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3}) await r.zadd("{foo}b", {"a1": 1, "a2": 2}) @@ -1745,6 +1775,7 @@ async def test_cluster_zdiff(self, r: ValkeyCluster) -> None: response = await r.zdiff(["{foo}a", "{foo}b"], withscores=True) assert_resp_response(r, response, [b"a3", b"3"], [[b"a3", 3.0]]) + @skip_if_server_version_lt("6.2.0") async def test_cluster_zdiffstore(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3}) await r.zadd("{foo}b", {"a1": 1, "a2": 2}) @@ -1753,6 +1784,7 @@ async def test_cluster_zdiffstore(self, r: ValkeyCluster) -> None: response = await r.zrange("{foo}out", 0, -1, withscores=True) assert_resp_response(r, response, [(b"a3", 3.0)], [[b"a3", 3.0]]) + @skip_if_server_version_lt("6.2.0") async def test_cluster_zinter(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 1}) await r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2}) @@ -1848,6 +1880,7 @@ async def test_cluster_zinterstore_with_weight(self, r: ValkeyCluster) -> None: [[b"a3", 20.0], [b"a1", 23.0]], ) + @skip_if_server_version_lt("4.9.0") async def test_cluster_bzpopmax(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 2}) await r.zadd("{foo}b", {"b1": 10, "b2": 20}) @@ -1884,6 +1917,7 @@ async def test_cluster_bzpopmax(self, r: ValkeyCluster) -> None: [b"{foo}c", b"c1", 100], ) + @skip_if_server_version_lt("4.9.0") async def test_cluster_bzpopmin(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 2}) await r.zadd("{foo}b", {"b1": 10, "b2": 20}) @@ -1920,6 +1954,7 @@ async def test_cluster_bzpopmin(self, r: ValkeyCluster) -> None: [b"{foo}c", b"c1", 100], ) + @skip_if_server_version_lt("6.2.0") async def test_cluster_zrangestore(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3}) assert await r.zrangestore("{foo}b", "{foo}a", 0, 1) @@ -1946,6 +1981,7 @@ async def test_cluster_zrangestore(self, r: ValkeyCluster) -> None: ) assert await r.zrange("{foo}b", 0, -1) == [b"a2"] + @skip_if_server_version_lt("6.2.0") async def test_cluster_zunion(self, r: ValkeyCluster) -> None: await r.zadd("{foo}a", {"a1": 1, "a2": 1, "a3": 1}) await r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2}) @@ -2049,6 +2085,7 @@ async def test_cluster_zunionstore_with_weight(self, r: ValkeyCluster) -> None: [[b"a2", 5.0], [b"a4", 12.0], [b"a3", 20.0], [b"a1", 23.0]], ) + @skip_if_server_version_lt("2.8.9") async def test_cluster_pfcount(self, r: ValkeyCluster) -> None: members = {b"1", b"2", b"3"} await r.pfadd("{foo}a", *members) @@ -2058,6 +2095,7 @@ async def test_cluster_pfcount(self, r: ValkeyCluster) -> None: assert await r.pfcount("{foo}b") == len(members_b) assert await r.pfcount("{foo}a", "{foo}b") == len(members_b.union(members)) + @skip_if_server_version_lt("2.8.9") async def test_cluster_pfmerge(self, r: ValkeyCluster) -> None: mema = {b"1", b"2", b"3"} memb = {b"2", b"3", b"4"} @@ -2076,6 +2114,7 @@ async def test_cluster_sort_store(self, r: ValkeyCluster) -> None: assert await r.lrange("{foo}sorted_values", 0, -1) == [b"1", b"2", b"3"] # GEO COMMANDS + @skip_if_server_version_lt("6.2.0") async def test_cluster_geosearchstore(self, r: ValkeyCluster) -> None: values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2094,6 +2133,7 @@ async def test_cluster_geosearchstore(self, r: ValkeyCluster) -> None: assert await r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("6.2.0") async def test_geosearchstore_dist(self, r: ValkeyCluster) -> None: values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2113,6 +2153,7 @@ async def test_geosearchstore_dist(self, r: ValkeyCluster) -> None: # instead of save the geo score, the distance is saved. assert await r.zscore("{foo}places_barcelona", "place1") == 88.05060698409301 + @skip_if_server_version_lt("3.2.0") async def test_cluster_georadius_store(self, r: ValkeyCluster) -> None: values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2127,6 +2168,7 @@ async def test_cluster_georadius_store(self, r: ValkeyCluster) -> None: assert await r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") async def test_cluster_georadius_store_dist(self, r: ValkeyCluster) -> None: values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2159,6 +2201,7 @@ async def test_cluster_keys(self, r: ValkeyCluster) -> None: assert set(await r.keys(pattern="test*", target_nodes="primaries")) == keys # SCAN COMMANDS + @skip_if_server_version_lt("2.8.0") async def test_cluster_scan(self, r: ValkeyCluster) -> None: await r.set("a", 1) await r.set("b", 2) @@ -2177,6 +2220,7 @@ async def test_cluster_scan(self, r: ValkeyCluster) -> None: assert sorted(cursors.keys()) == sorted(node.name for node in nodes) assert all(cursor == 0 for cursor in cursors.values()) + @skip_if_server_version_lt("6.0.0") async def test_cluster_scan_type(self, r: ValkeyCluster) -> None: await r.sadd("a-set", 1) await r.sadd("b-set", 1) @@ -2199,6 +2243,7 @@ async def test_cluster_scan_type(self, r: ValkeyCluster) -> None: assert sorted(cursors.keys()) == sorted(node.name for node in nodes) assert all(cursor == 0 for cursor in cursors.values()) + @skip_if_server_version_lt("2.8.0") async def test_cluster_scan_iter(self, r: ValkeyCluster) -> None: keys_all = [] keys_1 = [] @@ -2227,6 +2272,7 @@ async def test_cluster_randomkey(self, r: ValkeyCluster) -> None: await r.set(key, 1) assert await r.randomkey(target_nodes=node) in (b"{foo}a", b"{foo}b", b"{foo}c") + @skip_if_server_version_lt("6.0.0") async def test_acl_log( self, r: ValkeyCluster, create_valkey: Callable[..., ValkeyCluster] ) -> None: @@ -2728,6 +2774,43 @@ async def test_asking_error(self, r: ValkeyCluster) -> None: assert ask_node._free.pop().read_response.await_count assert res == ["MOCK_OK"] + @skip_if_server_version_gte("7.0.0") + async def test_moved_redirection_on_slave_with_default( + self, r: ValkeyCluster + ) -> None: + """Test MovedError handling.""" + key = "foo" + await r.set("foo", "bar") + # set read_from_replicas to True + r.read_from_replicas = True + primary = r.get_node_from_key(key, False) + moved_error = f"{r.keyslot(key)} {primary.host}:{primary.port}" + + parse_response_orig = primary.parse_response + with mock.patch.object( + ClusterNode, "parse_response", autospec=True + ) as parse_response_mock: + + async def parse_response( + self, connection: Connection, command: str, **kwargs: Any + ) -> Any: + if ( + command == "GET" + and self.host != primary.host + and self.port != primary.port + ): + raise MovedError(moved_error) + + return await parse_response_orig(connection, command, **kwargs) + + parse_response_mock.side_effect = parse_response + + async with r.pipeline() as readwrite_pipe: + assert r.reinitialize_counter == 0 + readwrite_pipe.get(key).get(key) + assert r.reinitialize_counter == 0 + assert await readwrite_pipe.execute() == [b"bar", b"bar"] + async def test_readonly_pipeline_from_readonly_client( self, r: ValkeyCluster ) -> None: diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py index 21b4077c..3b570c90 100644 --- a/tests/test_asyncio/test_commands.py +++ b/tests/test_asyncio/test_commands.py @@ -16,6 +16,7 @@ assert_resp_response, assert_resp_response_in, is_resp2_connection, + skip_if_server_version_gte, skip_if_server_version_lt, skip_unless_arch_bits, ) @@ -33,6 +34,8 @@ else: from async_timeout import timeout as async_timeout +VALKEY_6_VERSION = "5.9.0" + @pytest_asyncio.fixture() async def r_teardown(r: valkey.Valkey): @@ -106,16 +109,19 @@ async def test_command_on_invalid_key_type(self, r: valkey.Valkey): await r.get("a") # SERVER INFORMATION + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_cat_no_category(self, r: valkey.Valkey): categories = await r.acl_cat() assert isinstance(categories, list) assert "read" in categories or b"read" in categories + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_cat_with_category(self, r: valkey.Valkey): commands = await r.acl_cat("read") assert isinstance(commands, list) assert "get" in commands or b"get" in commands + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_deluser(self, r_teardown): username = "valkey-py-user" r = r_teardown(username) @@ -124,10 +130,12 @@ async def test_acl_deluser(self, r_teardown): assert await r.acl_setuser(username, enabled=False, reset=True) assert await r.acl_deluser(username) == 1 + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_genpass(self, r: valkey.Valkey): password = await r.acl_genpass() assert isinstance(password, (str, bytes)) + @skip_if_server_version_lt("7.0.0") async def test_acl_getuser_setuser(self, r_teardown): username = "valkey-py-user" r = r_teardown(username) @@ -227,6 +235,7 @@ async def test_acl_getuser_setuser(self, r_teardown): ) assert len((await r.acl_getuser(username))["passwords"]) == 1 + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_list(self, r_teardown): username = "valkey-py-user" r = r_teardown(username) @@ -235,6 +244,7 @@ async def test_acl_list(self, r_teardown): users = await r.acl_list() assert len(users) == len(start) + 1 + @skip_if_server_version_lt(VALKEY_6_VERSION) @pytest.mark.onlynoncluster async def test_acl_log(self, r_teardown, create_valkey): username = "valkey-py-user" @@ -271,6 +281,7 @@ async def test_acl_log(self, r_teardown, create_valkey): assert_resp_response_in(r, "client-info", expected, expected.keys()) assert await r.acl_log_reset() + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_setuser_categories_without_prefix_fails(self, r_teardown): username = "valkey-py-user" r = r_teardown(username) @@ -278,6 +289,7 @@ async def test_acl_setuser_categories_without_prefix_fails(self, r_teardown): with pytest.raises(exceptions.DataError): await r.acl_setuser(username, categories=["list"]) + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_setuser_commands_without_prefix_fails(self, r_teardown): username = "valkey-py-user" r = r_teardown(username) @@ -285,6 +297,7 @@ async def test_acl_setuser_commands_without_prefix_fails(self, r_teardown): with pytest.raises(exceptions.DataError): await r.acl_setuser(username, commands=["get"]) + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_setuser_add_passwords_and_nopass_fails(self, r_teardown): username = "valkey-py-user" r = r_teardown(username) @@ -292,11 +305,13 @@ async def test_acl_setuser_add_passwords_and_nopass_fails(self, r_teardown): with pytest.raises(exceptions.DataError): await r.acl_setuser(username, passwords="+mypass", nopass=True) + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_users(self, r: valkey.Valkey): users = await r.acl_users() assert isinstance(users, list) assert len(users) > 0 + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_acl_whoami(self, r: valkey.Valkey): username = await r.acl_whoami() assert isinstance(username, (str, bytes)) @@ -307,6 +322,7 @@ async def test_client_list(self, r: valkey.Valkey): assert isinstance(clients[0], dict) assert "addr" in clients[0] + @skip_if_server_version_lt("5.0.0") async def test_client_list_type(self, r: valkey.Valkey): with pytest.raises(exceptions.ValkeyError): await r.client_list(_type="not a client type") @@ -314,10 +330,12 @@ async def test_client_list_type(self, r: valkey.Valkey): clients = await r.client_list(_type=client_type) assert isinstance(clients, list) + @skip_if_server_version_lt("5.0.0") @pytest.mark.onlynoncluster async def test_client_id(self, r: valkey.Valkey): assert await r.client_id() > 0 + @skip_if_server_version_lt("5.0.0") @pytest.mark.onlynoncluster async def test_client_unblock(self, r: valkey.Valkey): myid = await r.client_id() @@ -325,10 +343,12 @@ async def test_client_unblock(self, r: valkey.Valkey): assert not await r.client_unblock(myid, error=True) assert not await r.client_unblock(myid, error=False) + @skip_if_server_version_lt("2.6.9") @pytest.mark.onlynoncluster async def test_client_getname(self, r: valkey.Valkey): assert await r.client_getname() is None + @skip_if_server_version_lt("2.6.9") @pytest.mark.onlynoncluster async def test_client_setname(self, r: valkey.Valkey): assert await r.client_setname("valkey_py_test") @@ -336,6 +356,7 @@ async def test_client_setname(self, r: valkey.Valkey): r, await r.client_getname(), "valkey_py_test", b"valkey_py_test" ) + @skip_if_server_version_lt("7.2.0") async def test_client_setinfo(self, r: valkey.Valkey): await r.ping() info = await r.client_info() @@ -357,6 +378,7 @@ async def test_client_setinfo(self, r: valkey.Valkey): assert info["lib-ver"] == "" await r3.aclose() + @skip_if_server_version_lt("2.6.9") @pytest.mark.onlynoncluster async def test_client_kill(self, r: valkey.Valkey, r2): await r.client_setname("valkey-py-c1") @@ -381,6 +403,7 @@ async def test_client_kill(self, r: valkey.Valkey, r2): assert len(clients) == 1 assert clients[0].get("name") == "valkey-py-c1" + @skip_if_server_version_lt("2.8.12") async def test_client_kill_filter_invalid_params(self, r: valkey.Valkey): # empty with pytest.raises(exceptions.DataError): @@ -394,6 +417,7 @@ async def test_client_kill_filter_invalid_params(self, r: valkey.Valkey): with pytest.raises(exceptions.DataError): await r.client_kill_filter(_type="caster") # type: ignore + @skip_if_server_version_lt("2.8.12") @pytest.mark.onlynoncluster async def test_client_kill_filter_by_id(self, r: valkey.Valkey, r2): await r.client_setname("valkey-py-c1") @@ -419,6 +443,7 @@ async def test_client_kill_filter_by_id(self, r: valkey.Valkey, r2): assert len(clients) == 1 assert clients[0].get("name") == "valkey-py-c1" + @skip_if_server_version_lt("2.8.12") @pytest.mark.onlynoncluster async def test_client_kill_filter_by_addr(self, r: valkey.Valkey, r2): await r.client_setname("valkey-py-c1") @@ -444,12 +469,14 @@ async def test_client_kill_filter_by_addr(self, r: valkey.Valkey, r2): assert len(clients) == 1 assert clients[0].get("name") == "valkey-py-c1" + @skip_if_server_version_lt("2.6.9") async def test_client_list_after_client_setname(self, r: valkey.Valkey): await r.client_setname("valkey_py_test") clients = await r.client_list() # we don't know which client ours will be assert "valkey_py_test" in [c["name"] for c in clients] + @skip_if_server_version_lt("2.9.50") @pytest.mark.onlynoncluster async def test_client_pause(self, r: valkey.Valkey): assert await r.client_pause(1) @@ -457,6 +484,7 @@ async def test_client_pause(self, r: valkey.Valkey): with pytest.raises(exceptions.ValkeyError): await r.client_pause(timeout="not an integer") + @skip_if_server_version_lt("7.2.0") @pytest.mark.onlynoncluster async def test_client_no_touch(self, r: valkey.Valkey): assert await r.client_no_touch("ON") == b"OK" @@ -553,6 +581,7 @@ async def test_slowlog_length(self, r: valkey.Valkey, slowlog): await r.get("foo") assert isinstance(await r.slowlog_len(), int) + @skip_if_server_version_lt("2.6.0") async def test_time(self, r: valkey.Valkey): t = await r.time() assert len(t) == 2 @@ -576,6 +605,7 @@ async def test_append(self, r: valkey.Valkey): assert await r.append("a", "a2") == 4 assert await r.get("a") == b"a1a2" + @skip_if_server_version_lt("2.6.0") async def test_bitcount(self, r: valkey.Valkey): await r.setbit("a", 5, True) assert await r.bitcount("a") == 1 @@ -594,12 +624,14 @@ async def test_bitcount(self, r: valkey.Valkey): assert await r.bitcount("a", -2, -1) == 2 assert await r.bitcount("a", 1, 1) == 1 + @skip_if_server_version_lt("2.6.0") @pytest.mark.onlynoncluster async def test_bitop_not_empty_string(self, r: valkey.Valkey): await r.set("a", "") await r.bitop("not", "r", "a") assert await r.get("r") is None + @skip_if_server_version_lt("2.6.0") @pytest.mark.onlynoncluster async def test_bitop_not(self, r: valkey.Valkey): test_str = b"\xAA\x00\xFF\x55" @@ -608,6 +640,7 @@ async def test_bitop_not(self, r: valkey.Valkey): await r.bitop("not", "r", "a") assert int(binascii.hexlify(await r.get("r")), 16) == correct + @skip_if_server_version_lt("2.6.0") @pytest.mark.onlynoncluster async def test_bitop_not_in_place(self, r: valkey.Valkey): test_str = b"\xAA\x00\xFF\x55" @@ -616,6 +649,7 @@ async def test_bitop_not_in_place(self, r: valkey.Valkey): await r.bitop("not", "a", "a") assert int(binascii.hexlify(await r.get("a")), 16) == correct + @skip_if_server_version_lt("2.6.0") @pytest.mark.onlynoncluster async def test_bitop_single_string(self, r: valkey.Valkey): test_str = b"\x01\x02\xFF" @@ -627,6 +661,7 @@ async def test_bitop_single_string(self, r: valkey.Valkey): assert await r.get("res2") == test_str assert await r.get("res3") == test_str + @skip_if_server_version_lt("2.6.0") @pytest.mark.onlynoncluster async def test_bitop_string_operands(self, r: valkey.Valkey): await r.set("a", b"\x01\x02\xFF\xFF") @@ -639,6 +674,7 @@ async def test_bitop_string_operands(self, r: valkey.Valkey): assert int(binascii.hexlify(await r.get("res3")), 16) == 0x000000FF @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.7") async def test_bitpos(self, r: valkey.Valkey): key = "key:bitpos" await r.set(key, b"\xff\xf0\x00") @@ -651,6 +687,7 @@ async def test_bitpos(self, r: valkey.Valkey): await r.set(key, b"\x00\x00\x00") assert await r.bitpos(key, 1) == -1 + @skip_if_server_version_lt("2.8.7") async def test_bitpos_wrong_arguments(self, r: valkey.Valkey): key = "key:bitpos:wrong:args" await r.set(key, b"\xff\xf0\x00") @@ -689,12 +726,14 @@ async def test_delitem(self, r: valkey.Valkey): await r.delete("a") assert await r.get("a") is None + @skip_if_server_version_lt("4.0.0") async def test_unlink(self, r: valkey.Valkey): assert await r.unlink("a") == 0 await r.set("a", "foo") assert await r.unlink("a") == 1 assert await r.get("a") is None + @skip_if_server_version_lt("4.0.0") async def test_unlink_with_multiple_keys(self, r: valkey.Valkey): await r.set("a", "foo") await r.set("b", "bar") @@ -702,6 +741,7 @@ async def test_unlink_with_multiple_keys(self, r: valkey.Valkey): assert await r.get("a") is None assert await r.get("b") is None + @skip_if_server_version_lt("2.6.0") async def test_dump_and_restore(self, r: valkey.Valkey): await r.set("a", "foo") dumped = await r.dump("a") @@ -709,6 +749,7 @@ async def test_dump_and_restore(self, r: valkey.Valkey): await r.restore("a", 0, dumped) assert await r.get("a") == b"foo" + @skip_if_server_version_lt("3.0.0") async def test_dump_and_restore_and_replace(self, r: valkey.Valkey): await r.set("a", "bar") dumped = await r.dump("a") @@ -718,6 +759,7 @@ async def test_dump_and_restore_and_replace(self, r: valkey.Valkey): await r.restore("a", 0, dumped, replace=True) assert await r.get("a") == b"bar" + @skip_if_server_version_lt("5.0.0") async def test_dump_and_restore_absttl(self, r: valkey.Valkey): await r.set("a", "foo") dumped = await r.dump("a") @@ -820,6 +862,7 @@ async def test_incrby(self, r: valkey.Valkey): assert await r.incrby("a", 4) == 5 assert await r.get("a") == b"5" + @skip_if_server_version_lt("2.6.0") async def test_incrbyfloat(self, r: valkey.Valkey): assert await r.incrbyfloat("a") == 1.0 assert await r.get("a") == b"1" @@ -862,6 +905,7 @@ async def test_msetnx(self, r: valkey.Valkey): assert await r.get(k) == v assert await r.get("d") is None + @skip_if_server_version_lt("2.6.0") async def test_pexpire(self, r: valkey.Valkey): assert not await r.pexpire("a", 60000) await r.set("a", "foo") @@ -870,16 +914,19 @@ async def test_pexpire(self, r: valkey.Valkey): assert await r.persist("a") assert await r.pttl("a") == -1 + @skip_if_server_version_lt("2.6.0") async def test_pexpireat_datetime(self, r: valkey.Valkey): expire_at = await valkey_server_time(r) + datetime.timedelta(minutes=1) await r.set("a", "foo") assert await r.pexpireat("a", expire_at) assert 0 < await r.pttl("a") <= 61000 + @skip_if_server_version_lt("2.6.0") async def test_pexpireat_no_key(self, r: valkey.Valkey): expire_at = await valkey_server_time(r) + datetime.timedelta(minutes=1) assert not await r.pexpireat("a", expire_at) + @skip_if_server_version_lt("2.6.0") async def test_pexpireat_unixtime(self, r: valkey.Valkey): expire_at = await valkey_server_time(r) + datetime.timedelta(minutes=1) await r.set("a", "foo") @@ -887,17 +934,20 @@ async def test_pexpireat_unixtime(self, r: valkey.Valkey): assert await r.pexpireat("a", expire_at_milliseconds) assert 0 < await r.pttl("a") <= 61000 + @skip_if_server_version_lt("2.6.0") async def test_psetex(self, r: valkey.Valkey): assert await r.psetex("a", 1000, "value") assert await r.get("a") == b"value" assert 0 < await r.pttl("a") <= 1000 + @skip_if_server_version_lt("2.6.0") async def test_psetex_timedelta(self, r: valkey.Valkey): expire_at = datetime.timedelta(milliseconds=1000) assert await r.psetex("a", expire_at, "value") assert await r.get("a") == b"value" assert 0 < await r.pttl("a") <= 1000 + @skip_if_server_version_lt("2.6.0") async def test_pttl(self, r: valkey.Valkey): assert not await r.pexpire("a", 10000) await r.set("a", "1") @@ -906,10 +956,12 @@ async def test_pttl(self, r: valkey.Valkey): assert await r.persist("a") assert await r.pttl("a") == -1 + @skip_if_server_version_lt("2.8.0") async def test_pttl_no_key(self, r: valkey.Valkey): """PTTL on servers 2.8 and after return -2 when the key doesn't exist""" assert await r.pttl("a") == -2 + @skip_if_server_version_lt("6.2.0") async def test_hrandfield(self, r): assert await r.hrandfield("key") is None await r.hset("key", mapping={"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}) @@ -944,11 +996,13 @@ async def test_renamenx(self, r: valkey.Valkey): assert await r.get("a") == b"1" assert await r.get("b") == b"2" + @skip_if_server_version_lt("2.6.0") async def test_set_nx(self, r: valkey.Valkey): assert await r.set("a", "1", nx=True) assert not await r.set("a", "2", nx=True) assert await r.get("a") == b"1" + @skip_if_server_version_lt("2.6.0") async def test_set_xx(self, r: valkey.Valkey): assert not await r.set("a", "1", xx=True) assert await r.get("a") is None @@ -956,32 +1010,38 @@ async def test_set_xx(self, r: valkey.Valkey): assert await r.set("a", "2", xx=True) assert await r.get("a") == b"2" + @skip_if_server_version_lt("2.6.0") async def test_set_px(self, r: valkey.Valkey): assert await r.set("a", "1", px=10000) assert await r.get("a") == b"1" assert 0 < await r.pttl("a") <= 10000 assert 0 < await r.ttl("a") <= 10 + @skip_if_server_version_lt("2.6.0") async def test_set_px_timedelta(self, r: valkey.Valkey): expire_at = datetime.timedelta(milliseconds=1000) assert await r.set("a", "1", px=expire_at) assert 0 < await r.pttl("a") <= 1000 assert 0 < await r.ttl("a") <= 1 + @skip_if_server_version_lt("2.6.0") async def test_set_ex(self, r: valkey.Valkey): assert await r.set("a", "1", ex=10) assert 0 < await r.ttl("a") <= 10 + @skip_if_server_version_lt("2.6.0") async def test_set_ex_timedelta(self, r: valkey.Valkey): expire_at = datetime.timedelta(seconds=60) assert await r.set("a", "1", ex=expire_at) assert 0 < await r.ttl("a") <= 60 + @skip_if_server_version_lt("2.6.0") async def test_set_multipleoptions(self, r: valkey.Valkey): await r.set("a", "val") assert await r.set("a", "1", xx=True, px=10000) assert 0 < await r.ttl("a") <= 10 + @skip_if_server_version_lt(VALKEY_6_VERSION) async def test_set_keepttl(self, r: valkey.Valkey): await r.set("a", "val") assert await r.set("a", "1", xx=True, px=10000) @@ -1026,6 +1086,7 @@ async def test_ttl(self, r: valkey.Valkey): assert await r.persist("a") assert await r.ttl("a") == -1 + @skip_if_server_version_lt("2.8.0") async def test_ttl_nokey(self, r: valkey.Valkey): """TTL on servers 2.8 and after return -2 when the key doesn't exist""" assert await r.ttl("a") == -2 @@ -1191,6 +1252,7 @@ async def test_rpush(self, r: valkey.Valkey): assert await r.rpush("a", "3", "4") == 4 assert await r.lrange("a", 0, -1) == [b"1", b"2", b"3", b"4"] + @skip_if_server_version_lt("6.0.6") async def test_lpos(self, r: valkey.Valkey): assert await r.rpush("a", "a", "b", "c", "1", "2", "3", "c", "c") == 8 assert await r.lpos("a", "a") == 0 @@ -1230,6 +1292,7 @@ async def test_rpushx(self, r: valkey.Valkey): assert await r.lrange("a", 0, -1) == [b"1", b"2", b"3", b"4"] # SCAN COMMANDS + @skip_if_server_version_lt("2.8.0") @pytest.mark.onlynoncluster async def test_scan(self, r: valkey.Valkey): await r.set("a", 1) @@ -1241,6 +1304,7 @@ async def test_scan(self, r: valkey.Valkey): _, keys = await r.scan(match="a") assert set(keys) == {b"a"} + @skip_if_server_version_lt(VALKEY_6_VERSION) @pytest.mark.onlynoncluster async def test_scan_type(self, r: valkey.Valkey): await r.sadd("a-set", 1) @@ -1249,6 +1313,7 @@ async def test_scan_type(self, r: valkey.Valkey): _, keys = await r.scan(match="a*", _type="SET") assert set(keys) == {b"a-set"} + @skip_if_server_version_lt("2.8.0") @pytest.mark.onlynoncluster async def test_scan_iter(self, r: valkey.Valkey): await r.set("a", 1) @@ -1259,6 +1324,7 @@ async def test_scan_iter(self, r: valkey.Valkey): keys = [k async for k in r.scan_iter(match="a")] assert set(keys) == {b"a"} + @skip_if_server_version_lt("2.8.0") async def test_sscan(self, r: valkey.Valkey): await r.sadd("a", 1, 2, 3) cursor, members = await r.sscan("a") @@ -1267,6 +1333,7 @@ async def test_sscan(self, r: valkey.Valkey): _, members = await r.sscan("a", match=b"1") assert set(members) == {b"1"} + @skip_if_server_version_lt("2.8.0") async def test_sscan_iter(self, r: valkey.Valkey): await r.sadd("a", 1, 2, 3) members = [k async for k in r.sscan_iter("a")] @@ -1274,6 +1341,7 @@ async def test_sscan_iter(self, r: valkey.Valkey): members = [k async for k in r.sscan_iter("a", match=b"1")] assert set(members) == {b"1"} + @skip_if_server_version_lt("2.8.0") async def test_hscan(self, r: valkey.Valkey): await r.hset("a", mapping={"a": 1, "b": 2, "c": 3}) cursor, dic = await r.hscan("a") @@ -1295,6 +1363,7 @@ async def test_hscan_novalues(self, r: valkey.Valkey): _, keys = await r.hscan("a_notset", match="a", no_values=True) assert keys == [] + @skip_if_server_version_lt("2.8.0") async def test_hscan_iter(self, r: valkey.Valkey): await r.hset("a", mapping={"a": 1, "b": 2, "c": 3}) dic = {k: v async for k, v in r.hscan_iter("a")} @@ -1316,6 +1385,7 @@ async def test_hscan_iter_novalues(self, r: valkey.Valkey): ) assert keys == [] + @skip_if_server_version_lt("2.8.0") async def test_zscan(self, r: valkey.Valkey): await r.zadd("a", {"a": 1, "b": 2, "c": 3}) cursor, pairs = await r.zscan("a") @@ -1324,6 +1394,7 @@ async def test_zscan(self, r: valkey.Valkey): _, pairs = await r.zscan("a", match="a") assert set(pairs) == {(b"a", 1)} + @skip_if_server_version_lt("2.8.0") async def test_zscan_iter(self, r: valkey.Valkey): await r.zadd("a", {"a": 1, "b": 2, "c": 3}) pairs = [k async for k in r.zscan_iter("a")] @@ -1399,6 +1470,7 @@ async def test_spop(self, r: valkey.Valkey): assert value in s assert await r.smembers("a") == set(s) - {value} + @skip_if_server_version_lt("3.2.0") async def test_spop_multi_value(self, r: valkey.Valkey): s = [b"1", b"2", b"3"] await r.sadd("a", *s) @@ -1418,6 +1490,7 @@ async def test_srandmember(self, r: valkey.Valkey): await r.sadd("a", *s) assert await r.srandmember("a") in s + @skip_if_server_version_lt("2.6.0") async def test_srandmember_multi_value(self, r: valkey.Valkey): s = [b"1", b"2", b"3"] await r.sadd("a", *s) @@ -1513,6 +1586,7 @@ async def test_zcount(self, r: valkey.Valkey): assert await r.zcount("a", 10, 20) == 0 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") async def test_zdiff(self, r): await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) await r.zadd("b", {"a1": 1, "a2": 2}) @@ -1521,6 +1595,7 @@ async def test_zdiff(self, r): assert_resp_response(r, response, [b"a3", b"3"], [[b"a3", 3.0]]) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") async def test_zdiffstore(self, r): await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) await r.zadd("b", {"a1": 1, "a2": 2}) @@ -1536,6 +1611,7 @@ async def test_zincrby(self, r: valkey.Valkey): assert await r.zscore("a", "a2") == 3.0 assert await r.zscore("a", "a3") == 8.0 + @skip_if_server_version_lt("2.8.9") async def test_zlexcount(self, r: valkey.Valkey): await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert await r.zlexcount("a", "-", "+") == 7 @@ -1585,6 +1661,7 @@ async def test_zinterstore_with_weight(self, r: valkey.Valkey): r, response, [(b"a3", 20), (b"a1", 23)], [[b"a3", 20], [b"a1", 23]] ) + @skip_if_server_version_lt("4.9.0") async def test_zpopmax(self, r: valkey.Valkey): await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) response = await r.zpopmax("a") @@ -1596,6 +1673,7 @@ async def test_zpopmax(self, r: valkey.Valkey): r, response, [(b"a2", 2), (b"a1", 1)], [[b"a2", 2], [b"a1", 1]] ) + @skip_if_server_version_lt("4.9.0") async def test_zpopmin(self, r: valkey.Valkey): await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) response = await r.zpopmin("a") @@ -1607,6 +1685,7 @@ async def test_zpopmin(self, r: valkey.Valkey): r, response, [(b"a2", 2), (b"a3", 3)], [[b"a2", 2], [b"a3", 3]] ) + @skip_if_server_version_lt("4.9.0") @pytest.mark.onlynoncluster async def test_bzpopmax(self, r: valkey.Valkey): await r.zadd("a", {"a1": 1, "a2": 2}) @@ -1641,6 +1720,7 @@ async def test_bzpopmax(self, r: valkey.Valkey): r, await r.bzpopmax("c", timeout=1), (b"c", b"c1", 100), [b"c", b"c1", 100] ) + @skip_if_server_version_lt("4.9.0") @pytest.mark.onlynoncluster async def test_bzpopmin(self, r: valkey.Valkey): await r.zadd("a", {"a1": 1, "a2": 2}) @@ -1696,6 +1776,7 @@ async def test_zrange(self, r: valkey.Valkey): # (b"a2", 2), # ] + @skip_if_server_version_lt("2.8.9") async def test_zrangebylex(self, r: valkey.Valkey): await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert await r.zrangebylex("a", "-", "[c") == [b"a", b"b", b"c"] @@ -1704,6 +1785,7 @@ async def test_zrangebylex(self, r: valkey.Valkey): assert await r.zrangebylex("a", "[f", "+") == [b"f", b"g"] assert await r.zrangebylex("a", "-", "+", start=3, num=2) == [b"d", b"e"] + @skip_if_server_version_lt("2.9.9") async def test_zrevrangebylex(self, r: valkey.Valkey): await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert await r.zrevrangebylex("a", "[c", "-") == [b"c", b"b", b"a"] @@ -1751,6 +1833,7 @@ async def test_zrank(self, r: valkey.Valkey): assert await r.zrank("a", "a2") == 1 assert await r.zrank("a", "a6") is None + @skip_if_server_version_lt("7.2.0") async def test_zrank_withscore(self, r: valkey.Valkey): await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5}) assert await r.zrank("a", "a1") == 0 @@ -1773,6 +1856,7 @@ async def test_zrem_multiple_keys(self, r: valkey.Valkey): assert await r.zrem("a", "a1", "a2") == 2 assert await r.zrange("a", 0, 5) == [b"a3"] + @skip_if_server_version_lt("2.8.9") async def test_zremrangebylex(self, r: valkey.Valkey): await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert await r.zremrangebylex("a", "-", "[c") == 3 @@ -1848,6 +1932,7 @@ async def test_zrevrank(self, r: valkey.Valkey): assert await r.zrevrank("a", "a2") == 3 assert await r.zrevrank("a", "a6") is None + @skip_if_server_version_lt("7.2.0") async def test_zrevrank_withscore(self, r: valkey.Valkey): await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5}) assert await r.zrevrank("a", "a1") == 4 @@ -1921,12 +2006,14 @@ async def test_zunionstore_with_weight(self, r: valkey.Valkey): ) # HYPERLOGLOG TESTS + @skip_if_server_version_lt("2.8.9") async def test_pfadd(self, r: valkey.Valkey): members = {b"1", b"2", b"3"} assert await r.pfadd("a", *members) == 1 assert await r.pfadd("a", *members) == 0 assert await r.pfcount("a") == len(members) + @skip_if_server_version_lt("2.8.9") @pytest.mark.onlynoncluster async def test_pfcount(self, r: valkey.Valkey): members = {b"1", b"2", b"3"} @@ -1937,6 +2024,7 @@ async def test_pfcount(self, r: valkey.Valkey): assert await r.pfcount("b") == len(members_b) assert await r.pfcount("a", "b") == len(members_b.union(members)) + @skip_if_server_version_lt("2.8.9") @pytest.mark.onlynoncluster async def test_pfmerge(self, r: valkey.Valkey): mema = {b"1", b"2", b"3"} @@ -2009,6 +2097,7 @@ async def test_hincrby(self, r: valkey.Valkey): assert await r.hincrby("a", "1", amount=2) == 3 assert await r.hincrby("a", "1", amount=-2) == 1 + @skip_if_server_version_lt("2.6.0") async def test_hincrbyfloat(self, r: valkey.Valkey): assert await r.hincrbyfloat("a", "1") == 1.0 assert await r.hincrbyfloat("a", "1") == 2.0 @@ -2053,6 +2142,7 @@ async def test_hvals(self, r: valkey.Valkey): remote_vals = await r.hvals("a") assert sorted(local_vals) == sorted(remote_vals) + @skip_if_server_version_lt("3.2.0") async def test_hstrlen(self, r: valkey.Valkey): await r.hset("a", mapping={"1": "22", "2": "333"}) assert await r.hstrlen("a", "1") == 2 @@ -2278,16 +2368,25 @@ async def test_cluster_slaves(self, mock_cluster_resp_slaves): await mock_cluster_resp_slaves.cluster("slaves", "nodeid"), dict ) + @skip_if_server_version_lt("3.0.0") + @skip_if_server_version_gte("7.0.0") + @pytest.mark.onlynoncluster + async def test_readwrite(self, r: valkey.Valkey): + assert await r.readwrite() + + @skip_if_server_version_lt("3.0.0") @pytest.mark.onlynoncluster async def test_readonly_invalid_cluster_state(self, r: valkey.Valkey): with pytest.raises(exceptions.ValkeyError): await r.readonly() + @skip_if_server_version_lt("3.0.0") @pytest.mark.onlynoncluster async def test_readonly(self, mock_cluster_resp_ok): assert await mock_cluster_resp_ok.readonly() is True # GEO COMMANDS + @skip_if_server_version_lt("3.2.0") async def test_geoadd(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2298,10 +2397,12 @@ async def test_geoadd(self, r: valkey.Valkey): assert await r.geoadd("barcelona", values) == 2 assert await r.zcard("barcelona") == 2 + @skip_if_server_version_lt("3.2.0") async def test_geoadd_invalid_params(self, r: valkey.Valkey): with pytest.raises(exceptions.ValkeyError): await r.geoadd("barcelona", (1, 2)) + @skip_if_server_version_lt("3.2.0") async def test_geodist(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2312,6 +2413,7 @@ async def test_geodist(self, r: valkey.Valkey): assert await r.geoadd("barcelona", values) == 2 assert await r.geodist("barcelona", "place1", "place2") == 3067.4157 + @skip_if_server_version_lt("3.2.0") async def test_geodist_units(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2322,15 +2424,18 @@ async def test_geodist_units(self, r: valkey.Valkey): await r.geoadd("barcelona", values) assert await r.geodist("barcelona", "place1", "place2", "km") == 3.0674 + @skip_if_server_version_lt("3.2.0") async def test_geodist_missing_one_member(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") await r.geoadd("barcelona", values) assert await r.geodist("barcelona", "place1", "missing_member", "km") is None + @skip_if_server_version_lt("3.2.0") async def test_geodist_invalid_units(self, r: valkey.Valkey): with pytest.raises(exceptions.ValkeyError): assert await r.geodist("x", "y", "z", "inches") + @skip_if_server_version_lt("3.2.0") async def test_geohash(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2346,6 +2451,7 @@ async def test_geohash(self, r: valkey.Valkey): [b"sp3e9yg3kd0", b"sp3e9cbc3t0", None], ) + @skip_if_server_version_lt("3.2.0") async def test_geopos(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2368,9 +2474,16 @@ async def test_geopos(self, r: valkey.Valkey): ], ) + @skip_if_server_version_lt("4.0.0") async def test_geopos_no_value(self, r: valkey.Valkey): assert await r.geopos("barcelona", "place1", "place2") == [None, None] + @skip_if_server_version_lt("3.2.0") + @skip_if_server_version_gte("4.0.0") + async def test_old_geopos_no_value(self, r: valkey.Valkey): + assert await r.geopos("barcelona", "place1", "place2") == [] + + @skip_if_server_version_lt("3.2.0") async def test_georadius(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2382,6 +2495,7 @@ async def test_georadius(self, r: valkey.Valkey): assert await r.georadius("barcelona", 2.191, 41.433, 1000) == [b"place1"] assert await r.georadius("barcelona", 2.187, 41.406, 1000) == [b"\x80place2"] + @skip_if_server_version_lt("3.2.0") async def test_georadius_no_values(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2392,6 +2506,7 @@ async def test_georadius_no_values(self, r: valkey.Valkey): await r.geoadd("barcelona", values) assert await r.georadius("barcelona", 1, 2, 1000) == [] + @skip_if_server_version_lt("3.2.0") async def test_georadius_units(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2405,6 +2520,7 @@ async def test_georadius_units(self, r: valkey.Valkey): ] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") async def test_georadius_with(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2459,6 +2575,7 @@ async def test_georadius_with(self, r: valkey.Valkey): == [] ) + @skip_if_server_version_lt("3.2.0") async def test_georadius_count(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2471,6 +2588,7 @@ async def test_georadius_count(self, r: valkey.Valkey): b"place1" ] + @skip_if_server_version_lt("3.2.0") async def test_georadius_sort(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2488,6 +2606,7 @@ async def test_georadius_sort(self, r: valkey.Valkey): b"place1", ] + @skip_if_server_version_lt("3.2.0") @pytest.mark.onlynoncluster async def test_georadius_store(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( @@ -2501,6 +2620,7 @@ async def test_georadius_store(self, r: valkey.Valkey): assert await r.zrange("places_barcelona", 0, -1) == [b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") @pytest.mark.onlynoncluster async def test_georadius_store_dist(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( @@ -2517,6 +2637,7 @@ async def test_georadius_store_dist(self, r: valkey.Valkey): assert await r.zscore("places_barcelona", "place1") == 88.05060698409301 @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") async def test_georadiusmember(self, r: valkey.Valkey): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2548,6 +2669,7 @@ async def test_georadiusmember(self, r: valkey.Valkey): ], ] + @skip_if_server_version_lt("5.0.0") async def test_xack(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2568,6 +2690,7 @@ async def test_xack(self, r: valkey.Valkey): assert await r.xack(stream, group, m1) == 1 assert await r.xack(stream, group, m2, m3) == 2 + @skip_if_server_version_lt("5.0.0") async def test_xadd(self, r: valkey.Valkey): stream = "stream" message_id = await r.xadd(stream, {"foo": "bar"}) @@ -2581,6 +2704,7 @@ async def test_xadd(self, r: valkey.Valkey): await r.xadd(stream, {"foo": "bar"}, maxlen=2, approximate=False) assert await r.xlen(stream) == 2 + @skip_if_server_version_lt("5.0.0") async def test_xclaim(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2618,6 +2742,7 @@ async def test_xclaim(self, r: valkey.Valkey): justid=True, ) == [message_id] + @skip_if_server_version_lt("7.0.0") async def test_xclaim_trimmed(self, r: valkey.Valkey): # xclaim should not raise an exception if the item is not there stream = "stream" @@ -2641,6 +2766,7 @@ async def test_xclaim_trimmed(self, r: valkey.Valkey): assert len(item) == 1 assert item[0][0] == sid2 + @skip_if_server_version_lt("5.0.0") async def test_xdel(self, r: valkey.Valkey): stream = "stream" @@ -2655,6 +2781,7 @@ async def test_xdel(self, r: valkey.Valkey): assert await r.xdel(stream, m1) == 1 assert await r.xdel(stream, m2, m3) == 2 + @skip_if_server_version_lt("7.0.0") async def test_xgroup_create(self, r: valkey.Valkey): # tests xgroup_create and xinfo_groups stream = "stream" @@ -2677,6 +2804,7 @@ async def test_xgroup_create(self, r: valkey.Valkey): ] assert await r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("7.0.0") async def test_xgroup_create_mkstream(self, r: valkey.Valkey): # tests xgroup_create and xinfo_groups stream = "stream" @@ -2702,6 +2830,7 @@ async def test_xgroup_create_mkstream(self, r: valkey.Valkey): ] assert await r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("5.0.0") async def test_xgroup_delconsumer(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2719,6 +2848,7 @@ async def test_xgroup_delconsumer(self, r: valkey.Valkey): # deleting the consumer should return 2 pending messages assert await r.xgroup_delconsumer(stream, group, consumer) == 2 + @skip_if_server_version_lt("5.0.0") async def test_xgroup_destroy(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2730,6 +2860,7 @@ async def test_xgroup_destroy(self, r: valkey.Valkey): await r.xgroup_create(stream, group, 0) assert await r.xgroup_destroy(stream, group) + @skip_if_server_version_lt("7.0.0") async def test_xgroup_setid(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2750,6 +2881,7 @@ async def test_xgroup_setid(self, r: valkey.Valkey): ] assert await r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("7.2.0") async def test_xinfo_consumers(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2776,6 +2908,7 @@ async def test_xinfo_consumers(self, r: valkey.Valkey): assert isinstance(info[1].pop("inactive"), int) assert info == expected + @skip_if_server_version_lt("5.0.0") async def test_xinfo_stream(self, r: valkey.Valkey): stream = "stream" m1 = await r.xadd(stream, {"foo": "bar"}) @@ -2786,6 +2919,7 @@ async def test_xinfo_stream(self, r: valkey.Valkey): assert info["first-entry"] == await get_stream_message(r, stream, m1) assert info["last-entry"] == await get_stream_message(r, stream, m2) + @skip_if_server_version_lt("5.0.0") async def test_xlen(self, r: valkey.Valkey): stream = "stream" assert await r.xlen(stream) == 0 @@ -2793,6 +2927,7 @@ async def test_xlen(self, r: valkey.Valkey): await r.xadd(stream, {"foo": "bar"}) assert await r.xlen(stream) == 2 + @skip_if_server_version_lt("5.0.0") async def test_xpending(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2821,6 +2956,7 @@ async def test_xpending(self, r: valkey.Valkey): } assert await r.xpending(stream, group) == expected + @skip_if_server_version_lt("5.0.0") async def test_xpending_range(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2844,6 +2980,7 @@ async def test_xpending_range(self, r: valkey.Valkey): assert response[1]["message_id"] == m2 assert response[1]["consumer"] == consumer2.encode() + @skip_if_server_version_lt("5.0.0") async def test_xrange(self, r: valkey.Valkey): stream = "stream" m1 = await r.xadd(stream, {"foo": "bar"}) @@ -2866,6 +3003,7 @@ def get_ids(results): results = await r.xrange(stream, max=m2, count=1) assert get_ids(results) == [m1] + @skip_if_server_version_lt("5.0.0") async def test_xread(self, r: valkey.Valkey): stream = "stream" m1 = await r.xadd(stream, {"foo": "bar"}) @@ -2896,6 +3034,7 @@ async def test_xread(self, r: valkey.Valkey): r, res, [[strem_name, expected_entries]], {strem_name: [expected_entries]} ) + @skip_if_server_version_lt("5.0.0") async def test_xreadgroup(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -2962,6 +3101,7 @@ async def test_xreadgroup(self, r: valkey.Valkey): r, res, [[strem_name, expected_entries]], {strem_name: [expected_entries]} ) + @skip_if_server_version_lt("5.0.0") async def test_xrevrange(self, r: valkey.Valkey): stream = "stream" m1 = await r.xadd(stream, {"foo": "bar"}) @@ -2984,6 +3124,7 @@ def get_ids(results): results = await r.xrevrange(stream, min=m2, count=1) assert get_ids(results) == [m4] + @skip_if_server_version_lt("5.0.0") async def test_xtrim(self, r: valkey.Valkey): stream = "stream" @@ -3072,6 +3213,7 @@ async def test_bitfield_operations(self, r: valkey.Valkey): ) assert resp == [0, None, 255] + @skip_if_server_version_lt("6.0.0") async def test_bitfield_ro(self, r: valkey.Valkey): bf = r.bitfield("a") resp = await bf.set("u8", 8, 255).execute() @@ -3084,6 +3226,7 @@ async def test_bitfield_ro(self, r: valkey.Valkey): resp = await r.bitfield_ro("a", "u8", 0, items) assert resp == [0, 15, 15, 14] + @skip_if_server_version_lt("4.0.0") async def test_memory_stats(self, r: valkey.Valkey): # put a key into the current db to make sure that "db." # has data @@ -3094,10 +3237,12 @@ async def test_memory_stats(self, r: valkey.Valkey): if key.startswith("db."): assert isinstance(value, dict) + @skip_if_server_version_lt("4.0.0") async def test_memory_usage(self, r: valkey.Valkey): await r.set("foo", "bar") assert isinstance(await r.memory_usage("foo"), int) + @skip_if_server_version_lt("4.0.0") async def test_module_list(self, r: valkey.Valkey): assert isinstance(await r.module_list(), list) for x in await r.module_list(): diff --git a/tests/test_asyncio/test_connection.py b/tests/test_asyncio/test_connection.py index 2d15e615..4da68b43 100644 --- a/tests/test_asyncio/test_connection.py +++ b/tests/test_asyncio/test_connection.py @@ -5,6 +5,7 @@ import pytest import valkey +from tests.conftest import skip_if_server_version_lt from valkey._parsers import ( _AsyncHiredisParser, _AsyncRESP2Parser, @@ -89,6 +90,7 @@ async def get_conn(_): await r.aclose() +@skip_if_server_version_lt("4.0.0") @pytest.mark.valkeymod @pytest.mark.onlynoncluster async def test_loading_external_modules(r): diff --git a/tests/test_asyncio/test_connection_pool.py b/tests/test_asyncio/test_connection_pool.py index 8d384bf5..ce8d792a 100644 --- a/tests/test_asyncio/test_connection_pool.py +++ b/tests/test_asyncio/test_connection_pool.py @@ -4,6 +4,7 @@ import pytest import pytest_asyncio import valkey.asyncio as valkey +from tests.conftest import skip_if_server_version_lt from valkey.asyncio.connection import Connection, to_bool from .compat import aclosing, mock @@ -317,11 +318,13 @@ def test_port(self): assert pool.connection_class == valkey.Connection assert pool.connection_kwargs == {"host": "localhost", "port": 6380} + @skip_if_server_version_lt("6.0.0") def test_username(self): pool = valkey.ConnectionPool.from_url("valkey://myuser:@localhost") assert pool.connection_class == valkey.Connection assert pool.connection_kwargs == {"host": "localhost", "username": "myuser"} + @skip_if_server_version_lt("6.0.0") def test_quoted_username(self): pool = valkey.ConnectionPool.from_url( "valkey://%2Fmyuser%2F%2B name%3D%24+:@localhost" @@ -347,6 +350,7 @@ def test_quoted_password(self): "password": "/mypass/+ word=$+", } + @skip_if_server_version_lt("6.0.0") def test_username_and_password(self): pool = valkey.ConnectionPool.from_url("valkey://myuser:mypass@localhost") assert pool.connection_class == valkey.Connection @@ -473,11 +477,13 @@ def test_defaults(self): assert pool.connection_class == valkey.UnixDomainSocketConnection assert pool.connection_kwargs == {"path": "/socket"} + @skip_if_server_version_lt("6.0.0") def test_username(self): pool = valkey.ConnectionPool.from_url("unix://myuser:@/socket") assert pool.connection_class == valkey.UnixDomainSocketConnection assert pool.connection_kwargs == {"path": "/socket", "username": "myuser"} + @skip_if_server_version_lt("6.0.0") def test_quoted_username(self): pool = valkey.ConnectionPool.from_url( "unix://%2Fmyuser%2F%2B name%3D%24+:@/socket" @@ -581,6 +587,7 @@ async def test_on_connect_error(self): assert not pool._available_connections[0]._reader @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.8") async def test_busy_loading_disconnects_socket(self, r): """ If Valkey raises a LOADING error, the connection should be @@ -592,6 +599,7 @@ async def test_busy_loading_disconnects_socket(self, r): assert not r.connection._reader @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.8") async def test_busy_loading_from_pipeline_immediate_command(self, r): """ BusyLoadingErrors should raise from Pipelines that execute a @@ -608,6 +616,7 @@ async def test_busy_loading_from_pipeline_immediate_command(self, r): assert not pool._available_connections[0]._reader @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.8") async def test_busy_loading_from_pipeline(self, r): """ BusyLoadingErrors should be raised from a pipeline execution @@ -622,6 +631,7 @@ async def test_busy_loading_from_pipeline(self, r): assert len(pool._available_connections) == 1 assert not pool._available_connections[0]._reader + @skip_if_server_version_lt("2.8.8") async def test_read_only_error(self, r): """READONLY errors get turned into ReadOnlyError exceptions""" with pytest.raises(valkey.ReadOnlyError): @@ -666,11 +676,7 @@ async def test_connect_no_auth_supplied_when_required(self, r): """ with pytest.raises(valkey.AuthenticationError): await r.execute_command( - "DEBUG", - "ERROR", - "ERR AUTH called without any password " - "configured for the default user. Are you sure " - "your configuration is correct?", + "DEBUG", "ERROR", "ERR Client sent AUTH, but no password is set" ) async def test_connect_invalid_password_supplied(self, r): diff --git a/tests/test_asyncio/test_pipeline.py b/tests/test_asyncio/test_pipeline.py index d2161d91..5021f91c 100644 --- a/tests/test_asyncio/test_pipeline.py +++ b/tests/test_asyncio/test_pipeline.py @@ -1,5 +1,6 @@ import pytest import valkey +from tests.conftest import skip_if_server_version_lt from .compat import aclosing, mock from .conftest import wait_for_command @@ -392,6 +393,7 @@ async def test_pipeline_get(self, r): assert await pipe.execute() == [b"a1"] @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.0.0") async def test_pipeline_discard(self, r): # empty pipeline should raise an error async with r.pipeline() as pipe: diff --git a/tests/test_asyncio/test_pubsub.py b/tests/test_asyncio/test_pubsub.py index afb6530d..d27e5941 100644 --- a/tests/test_asyncio/test_pubsub.py +++ b/tests/test_asyncio/test_pubsub.py @@ -15,7 +15,7 @@ import pytest import pytest_asyncio import valkey.asyncio as valkey -from tests.conftest import get_protocol_version +from tests.conftest import get_protocol_version, skip_if_server_version_lt from valkey.exceptions import ConnectionError from valkey.typing import EncodableT from valkey.utils import HIREDIS_AVAILABLE @@ -608,6 +608,7 @@ async def test_channel_subscribe(self, r: valkey.Valkey): @pytest.mark.onlynoncluster class TestPubSubSubcommands: @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.0") async def test_pubsub_channels(self, r: valkey.Valkey, pubsub): p = pubsub await p.subscribe("foo", "bar", "baz", "quux") @@ -617,6 +618,7 @@ async def test_pubsub_channels(self, r: valkey.Valkey, pubsub): assert all([channel in await r.pubsub_channels() for channel in expected]) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.0") async def test_pubsub_numsub(self, r: valkey.Valkey): p1 = r.pubsub() await p1.subscribe("foo", "bar", "baz") @@ -636,6 +638,7 @@ async def test_pubsub_numsub(self, r: valkey.Valkey): await p2.aclose() await p3.aclose() + @skip_if_server_version_lt("2.8.0") async def test_pubsub_numpat(self, r: valkey.Valkey): p = r.pubsub() await p.psubscribe("*oo", "*ar", "b*z") @@ -647,6 +650,7 @@ async def test_pubsub_numpat(self, r: valkey.Valkey): @pytest.mark.onlynoncluster class TestPubSubPings: + @skip_if_server_version_lt("3.0.0") async def test_send_pubsub_ping(self, r: valkey.Valkey): p = r.pubsub(ignore_subscribe_messages=True) await p.subscribe("foo") @@ -656,6 +660,7 @@ async def test_send_pubsub_ping(self, r: valkey.Valkey): ) await p.aclose() + @skip_if_server_version_lt("3.0.0") async def test_send_pubsub_ping_message(self, r: valkey.Valkey): p = r.pubsub(ignore_subscribe_messages=True) await p.subscribe("foo") @@ -668,6 +673,7 @@ async def test_send_pubsub_ping_message(self, r: valkey.Valkey): @pytest.mark.onlynoncluster class TestPubSubConnectionKilled: + @skip_if_server_version_lt("3.0.0") async def test_connection_error_raised_when_connection_dies( self, r: valkey.Valkey, pubsub ): diff --git a/tests/test_asyncio/test_scripting.py b/tests/test_asyncio/test_scripting.py index b756b928..42269ad0 100644 --- a/tests/test_asyncio/test_scripting.py +++ b/tests/test_asyncio/test_scripting.py @@ -1,5 +1,6 @@ import pytest import pytest_asyncio +from tests.conftest import skip_if_server_version_lt from valkey import exceptions multiply_script = """ @@ -35,6 +36,7 @@ async def test_eval(self, r): assert await r.eval(multiply_script, 1, "a", 3) == 6 @pytest.mark.asyncio(forbid_global_loop=True) + @skip_if_server_version_lt("6.2.0") async def test_script_flush(self, r): await r.set("a", 2) await r.script_load(multiply_script) diff --git a/tests/test_cluster.py b/tests/test_cluster.py index e9fbaec7..b78418f2 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -45,6 +45,7 @@ _get_client, assert_resp_response, is_resp2_connection, + skip_if_server_version_lt, skip_unless_arch_bits, wait_for_command, ) @@ -1105,6 +1106,7 @@ def test_pubsub_numpat_merge_results(self, r): # subscribed to this channel in the entire cluster assert r.pubsub_numpat(target_nodes="all") == len(nodes) + @skip_if_server_version_lt("2.8.0") def test_cluster_pubsub_channels(self, r): p = r.pubsub() p.subscribe("foo", "bar", "baz", "quux") @@ -1115,6 +1117,7 @@ def test_cluster_pubsub_channels(self, r): [channel in r.pubsub_channels(target_nodes="all") for channel in expected] ) + @skip_if_server_version_lt("2.8.0") def test_cluster_pubsub_numsub(self, r): p1 = r.pubsub() p1.subscribe("foo", "bar", "baz") @@ -1144,6 +1147,7 @@ def test_cluster_slots(self, r): assert cluster_slots.get((0, 8191)) is not None assert cluster_slots.get((0, 8191)).get("primary") == ("127.0.0.1", 7000) + @skip_if_server_version_lt("7.0.0") def test_cluster_shards(self, r): cluster_shards = r.cluster_shards() assert isinstance(cluster_shards, list) @@ -1172,6 +1176,7 @@ def test_cluster_shards(self, r): for attribute in node.keys(): assert attribute in attributes + @skip_if_server_version_lt("7.2.0") def test_cluster_myshardid(self, r): myshardid = r.cluster_myshardid() assert isinstance(myshardid, str) @@ -1182,6 +1187,7 @@ def test_cluster_addslots(self, r): mock_node_resp(node, "OK") assert r.cluster_addslots(node, 1, 2, 3) is True + @skip_if_server_version_lt("7.0.0") def test_cluster_addslotsrange(self, r): node = r.get_random_node() mock_node_resp(node, "OK") @@ -1211,6 +1217,7 @@ def test_cluster_delslots(self): assert node0.valkey_connection.connection.read_response.called assert node1.valkey_connection.connection.read_response.called + @skip_if_server_version_lt("7.0.0") def test_cluster_delslotsrange(self): cluster_slots = [ [ @@ -1399,6 +1406,7 @@ def test_cluster_replicas(self, r): == "r4xfga22229cf3c652b6fca0d09ff69f3e0d4d" ) + @skip_if_server_version_lt("7.0.0") def test_cluster_links(self, r): node = r.get_random_node() res = r.cluster_links(node) @@ -1521,13 +1529,16 @@ def test_time(self, r): assert isinstance(t[0], int) assert isinstance(t[1], int) + @skip_if_server_version_lt("4.0.0") def test_memory_usage(self, r): r.set("foo", "bar") assert isinstance(r.memory_usage("foo"), int) + @skip_if_server_version_lt("4.0.0") def test_memory_malloc_stats(self, r): assert r.memory_malloc_stats() + @skip_if_server_version_lt("4.0.0") def test_memory_stats(self, r): # put a key into the current db to make sure that "db." # has data @@ -1539,10 +1550,12 @@ def test_memory_stats(self, r): if key.startswith("db."): assert isinstance(value, dict) + @skip_if_server_version_lt("4.0.0") def test_memory_help(self, r): with pytest.raises(NotImplementedError): r.memory_help() + @skip_if_server_version_lt("4.0.0") def test_memory_doctor(self, r): with pytest.raises(NotImplementedError): r.memory_doctor() @@ -1555,6 +1568,7 @@ def test_cluster_echo(self, r): node = r.get_primaries()[0] assert r.echo("foo bar", target_nodes=node) == b"foo bar" + @skip_if_server_version_lt("1.0.0") def test_debug_segfault(self, r): with pytest.raises(NotImplementedError): r.debug_segfault() @@ -1572,12 +1586,14 @@ def test_config_resetstat(self, r): ) assert reset_commands_processed < prior_commands_processed + @skip_if_server_version_lt("6.2.0") def test_client_trackinginfo(self, r): node = r.get_primaries()[0] res = r.client_trackinginfo(target_nodes=node) assert len(res) > 2 assert "prefixes" in res or b"prefixes" in res + @skip_if_server_version_lt("2.9.50") def test_client_pause(self, r): node = r.get_primaries()[0] assert r.client_pause(1, target_nodes=node) @@ -1585,13 +1601,16 @@ def test_client_pause(self, r): with pytest.raises(ValkeyError): r.client_pause(timeout="not an integer", target_nodes=node) + @skip_if_server_version_lt("6.2.0") def test_client_unpause(self, r): assert r.client_unpause() + @skip_if_server_version_lt("5.0.0") def test_client_id(self, r): node = r.get_primaries()[0] assert r.client_id(target_nodes=node) > 0 + @skip_if_server_version_lt("5.0.0") def test_client_unblock(self, r): node = r.get_primaries()[0] myid = r.client_id(target_nodes=node) @@ -1599,17 +1618,20 @@ def test_client_unblock(self, r): assert not r.client_unblock(myid, error=True, target_nodes=node) assert not r.client_unblock(myid, error=False, target_nodes=node) + @skip_if_server_version_lt("6.0.0") def test_client_getredir(self, r): node = r.get_primaries()[0] assert isinstance(r.client_getredir(target_nodes=node), int) assert r.client_getredir(target_nodes=node) == -1 + @skip_if_server_version_lt("6.2.0") def test_client_info(self, r): node = r.get_primaries()[0] info = r.client_info(target_nodes=node) assert isinstance(info, dict) assert "addr" in info + @skip_if_server_version_lt("2.6.9") def test_client_kill(self, r, r2): node = r.get_primaries()[0] r.client_setname("valkey-py-c1", target_nodes="all") @@ -1633,11 +1655,13 @@ def test_client_kill(self, r, r2): assert len(clients) == 1 assert clients[0].get("name") == "valkey-py-c1" + @skip_if_server_version_lt("2.6.0") def test_cluster_bitop_not_empty_string(self, r): r["{foo}a"] = "" r.bitop("not", "{foo}r", "{foo}a") assert r.get("{foo}r") is None + @skip_if_server_version_lt("2.6.0") def test_cluster_bitop_not(self, r): test_str = b"\xAA\x00\xFF\x55" correct = ~0xAA00FF55 & 0xFFFFFFFF @@ -1645,6 +1669,7 @@ def test_cluster_bitop_not(self, r): r.bitop("not", "{foo}r", "{foo}a") assert int(binascii.hexlify(r["{foo}r"]), 16) == correct + @skip_if_server_version_lt("2.6.0") def test_cluster_bitop_not_in_place(self, r): test_str = b"\xAA\x00\xFF\x55" correct = ~0xAA00FF55 & 0xFFFFFFFF @@ -1652,6 +1677,7 @@ def test_cluster_bitop_not_in_place(self, r): r.bitop("not", "{foo}a", "{foo}a") assert int(binascii.hexlify(r["{foo}a"]), 16) == correct + @skip_if_server_version_lt("2.6.0") def test_cluster_bitop_single_string(self, r): test_str = b"\x01\x02\xFF" r["{foo}a"] = test_str @@ -1662,6 +1688,7 @@ def test_cluster_bitop_single_string(self, r): assert r["{foo}res2"] == test_str assert r["{foo}res3"] == test_str + @skip_if_server_version_lt("2.6.0") def test_cluster_bitop_string_operands(self, r): r["{foo}a"] = b"\x01\x02\xFF\xFF" r["{foo}b"] = b"\x01\x02\xFF" @@ -1672,6 +1699,7 @@ def test_cluster_bitop_string_operands(self, r): assert int(binascii.hexlify(r["{foo}res2"]), 16) == 0x0102FFFF assert int(binascii.hexlify(r["{foo}res3"]), 16) == 0x000000FF + @skip_if_server_version_lt("6.2.0") def test_cluster_copy(self, r): assert r.copy("{foo}a", "{foo}b") == 0 r.set("{foo}a", "bar") @@ -1679,17 +1707,20 @@ def test_cluster_copy(self, r): assert r.get("{foo}a") == b"bar" assert r.get("{foo}b") == b"bar" + @skip_if_server_version_lt("6.2.0") def test_cluster_copy_and_replace(self, r): r.set("{foo}a", "foo1") r.set("{foo}b", "foo2") assert r.copy("{foo}a", "{foo}b") == 0 assert r.copy("{foo}a", "{foo}b", replace=True) == 1 + @skip_if_server_version_lt("6.2.0") def test_cluster_lmove(self, r): r.rpush("{foo}a", "one", "two", "three", "four") assert r.lmove("{foo}a", "{foo}b") assert r.lmove("{foo}a", "{foo}b", "right", "left") + @skip_if_server_version_lt("6.2.0") def test_cluster_blmove(self, r): r.rpush("{foo}a", "one", "two", "three", "four") assert r.blmove("{foo}a", "{foo}b", 5) @@ -1850,6 +1881,7 @@ def test_cluster_sunionstore(self, r): assert r.sunionstore("{foo}c", "{foo}a", "{foo}b") == 3 assert r.smembers("{foo}c") == {b"1", b"2", b"3"} + @skip_if_server_version_lt("6.2.0") def test_cluster_zdiff(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3}) r.zadd("{foo}b", {"a1": 1, "a2": 2}) @@ -1862,6 +1894,7 @@ def test_cluster_zdiff(self, r): [[b"a3", 3.0]], ) + @skip_if_server_version_lt("6.2.0") def test_cluster_zdiffstore(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3}) r.zadd("{foo}b", {"a1": 1, "a2": 2}) @@ -1870,6 +1903,7 @@ def test_cluster_zdiffstore(self, r): response = r.zrange("{foo}out", 0, -1, withscores=True) assert_resp_response(r, response, [(b"a3", 3.0)], [[b"a3", 3.0]]) + @skip_if_server_version_lt("6.2.0") def test_cluster_zinter(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 1}) r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2}) @@ -1957,6 +1991,7 @@ def test_cluster_zinterstore_with_weight(self, r): [[b"a3", 20.0], [b"a1", 23.0]], ) + @skip_if_server_version_lt("4.9.0") def test_cluster_bzpopmax(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 2}) r.zadd("{foo}b", {"b1": 10, "b2": 20}) @@ -1993,6 +2028,7 @@ def test_cluster_bzpopmax(self, r): [b"{foo}c", b"c1", 100], ) + @skip_if_server_version_lt("4.9.0") def test_cluster_bzpopmin(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 2}) r.zadd("{foo}b", {"b1": 10, "b2": 20}) @@ -2029,6 +2065,7 @@ def test_cluster_bzpopmin(self, r): [b"{foo}c", b"c1", 100], ) + @skip_if_server_version_lt("6.2.0") def test_cluster_zrangestore(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3}) assert r.zrangestore("{foo}b", "{foo}a", 0, 1) @@ -2055,6 +2092,7 @@ def test_cluster_zrangestore(self, r): ) assert r.zrange("{foo}b", 0, -1) == [b"a2"] + @skip_if_server_version_lt("6.2.0") def test_cluster_zunion(self, r): r.zadd("{foo}a", {"a1": 1, "a2": 1, "a3": 1}) r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2}) @@ -2143,6 +2181,7 @@ def test_cluster_zunionstore_with_weight(self, r): [[b"a2", 5.0], [b"a4", 12.0], [b"a3", 20.0], [b"a1", 23.0]], ) + @skip_if_server_version_lt("2.8.9") def test_cluster_pfcount(self, r): members = {b"1", b"2", b"3"} r.pfadd("{foo}a", *members) @@ -2152,6 +2191,7 @@ def test_cluster_pfcount(self, r): assert r.pfcount("{foo}b") == len(members_b) assert r.pfcount("{foo}a", "{foo}b") == len(members_b.union(members)) + @skip_if_server_version_lt("2.8.9") def test_cluster_pfmerge(self, r): mema = {b"1", b"2", b"3"} memb = {b"2", b"3", b"4"} @@ -2170,6 +2210,7 @@ def test_cluster_sort_store(self, r): assert r.lrange("{foo}sorted_values", 0, -1) == [b"1", b"2", b"3"] # GEO COMMANDS + @skip_if_server_version_lt("6.2.0") def test_cluster_geosearchstore(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2188,6 +2229,7 @@ def test_cluster_geosearchstore(self, r): assert r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("6.2.0") def test_geosearchstore_dist(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2207,6 +2249,7 @@ def test_geosearchstore_dist(self, r): # instead of save the geo score, the distance is saved. assert r.zscore("{foo}places_barcelona", "place1") == 88.05060698409301 + @skip_if_server_version_lt("3.2.0") def test_cluster_georadius_store(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2221,6 +2264,7 @@ def test_cluster_georadius_store(self, r): assert r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") def test_cluster_georadius_store_dist(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -2253,6 +2297,7 @@ def test_cluster_keys(self, r): assert set(r.keys(pattern="test*", target_nodes="primaries")) == keys # SCAN COMMANDS + @skip_if_server_version_lt("2.8.0") def test_cluster_scan(self, r): r.set("a", 1) r.set("b", 2) @@ -2271,6 +2316,7 @@ def test_cluster_scan(self, r): assert sorted(cursors.keys()) == sorted(node.name for node in nodes) assert all(cursor == 0 for cursor in cursors.values()) + @skip_if_server_version_lt("6.0.0") def test_cluster_scan_type(self, r): r.sadd("a-set", 1) r.sadd("b-set", 1) @@ -2291,6 +2337,7 @@ def test_cluster_scan_type(self, r): assert sorted(cursors.keys()) == sorted(node.name for node in nodes) assert all(cursor == 0 for cursor in cursors.values()) + @skip_if_server_version_lt("2.8.0") def test_cluster_scan_iter(self, r): keys_all = [] keys_1 = [] @@ -2317,6 +2364,7 @@ def test_cluster_randomkey(self, r): r[key] = 1 assert r.randomkey(target_nodes=node) in (b"{foo}a", b"{foo}b", b"{foo}c") + @skip_if_server_version_lt("6.0.0") def test_acl_log(self, r, request): key = "{cache}:" node = r.get_node_from_key(key) @@ -2370,6 +2418,7 @@ def try_delete_libs(self, r, *lib_names): except Exception: pass + @skip_if_server_version_lt("7.1.140") @pytest.mark.skip def test_tfunction_load_delete(self, r): r.gears_refresh_cluster() @@ -2378,6 +2427,7 @@ def test_tfunction_load_delete(self, r): assert r.tfunction_load(lib_code) assert r.tfunction_delete("lib1") + @skip_if_server_version_lt("7.1.140") @pytest.mark.skip def test_tfunction_list(self, r): r.gears_refresh_cluster() @@ -2401,6 +2451,7 @@ def test_tfunction_list(self, r): assert r.tfunction_delete("lib2") assert r.tfunction_delete("lib3") + @skip_if_server_version_lt("7.1.140") @pytest.mark.skip def test_tfcall(self, r): r.gears_refresh_cluster() diff --git a/tests/test_command_parser.py b/tests/test_command_parser.py index 4165d757..ae2fa8d6 100644 --- a/tests/test_command_parser.py +++ b/tests/test_command_parser.py @@ -1,7 +1,7 @@ import pytest from valkey._parsers import CommandsParser -from .conftest import assert_resp_response +from .conftest import assert_resp_response, skip_if_server_version_lt class TestCommandsParser: @@ -85,6 +85,7 @@ def test_get_moveable_keys(self, r): ) # A bug in redis<7.0 causes this to fail: https://github.com/redis/redis/issues/9493 + @skip_if_server_version_lt("7.0.0") def test_get_eval_keys_with_0_keys(self, r): commands_parser = CommandsParser(r) args = ["EVAL", "return {ARGV[1],ARGV[2]}", 0, "key1", "key2"] diff --git a/tests/test_commands.py b/tests/test_commands.py index 8390bf4d..9f35b5d7 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -24,6 +24,7 @@ assert_resp_response, assert_resp_response_in, is_resp2_connection, + skip_if_server_version_gte, skip_if_server_version_lt, skip_unless_arch_bits, ) @@ -135,16 +136,19 @@ def test_command_on_invalid_key_type(self, r): r["a"] # SERVER INFORMATION + @skip_if_server_version_lt("6.0.0") def test_acl_cat_no_category(self, r): categories = r.acl_cat() assert isinstance(categories, list) assert "read" in categories or b"read" in categories + @skip_if_server_version_lt("6.0.0") def test_acl_cat_with_category(self, r): commands = r.acl_cat("read") assert isinstance(commands, list) assert "get" in commands or b"get" in commands + @skip_if_server_version_lt("7.0.0") def test_acl_dryrun(self, r, request): username = "valkey-py-user" @@ -158,6 +162,7 @@ def teardown(): no_permissions_message = b"user has no permissions to run the" assert no_permissions_message in r.acl_dryrun(username, "get", "key") + @skip_if_server_version_lt("6.0.0") def test_acl_deluser(self, r, request): username = "valkey-py-user" @@ -181,6 +186,7 @@ def teardown(): assert r.acl_getuser(users[3]) is None assert r.acl_getuser(users[4]) is None + @skip_if_server_version_lt("6.0.0") def test_acl_genpass(self, r): password = r.acl_genpass() assert isinstance(password, (str, bytes)) @@ -194,6 +200,7 @@ def test_acl_genpass(self, r): assert isinstance(password, (str, bytes)) assert len(password) == 139 + @skip_if_server_version_lt("7.0.0") def test_acl_getuser_setuser(self, r, request): r.flushall() username = "valkey-py-user" @@ -326,11 +333,13 @@ def teardown(): [{"commands": "-@all +set", "keys": "%W~app*", "channels": ""}], ) + @skip_if_server_version_lt("6.0.0") def test_acl_help(self, r): res = r.acl_help() assert isinstance(res, list) assert len(res) != 0 + @skip_if_server_version_lt("6.0.0") def test_acl_list(self, r, request): username = "valkey-py-user" start = r.acl_list() @@ -344,6 +353,7 @@ def teardown(): users = r.acl_list() assert len(users) == len(start) + 1 + @skip_if_server_version_lt("6.0.0") @pytest.mark.onlynoncluster def test_acl_log(self, r, request): username = "valkey-py-user" @@ -390,6 +400,7 @@ def teardown(): expected.keys(), ) + @skip_if_server_version_lt("6.0.0") def test_acl_setuser_categories_without_prefix_fails(self, r, request): username = "valkey-py-user" @@ -401,6 +412,7 @@ def teardown(): with pytest.raises(exceptions.DataError): r.acl_setuser(username, categories=["list"]) + @skip_if_server_version_lt("6.0.0") def test_acl_setuser_commands_without_prefix_fails(self, r, request): username = "valkey-py-user" @@ -412,6 +424,7 @@ def teardown(): with pytest.raises(exceptions.DataError): r.acl_setuser(username, commands=["get"]) + @skip_if_server_version_lt("6.0.0") def test_acl_setuser_add_passwords_and_nopass_fails(self, r, request): username = "valkey-py-user" @@ -423,11 +436,13 @@ def teardown(): with pytest.raises(exceptions.DataError): r.acl_setuser(username, passwords="+mypass", nopass=True) + @skip_if_server_version_lt("6.0.0") def test_acl_users(self, r): users = r.acl_users() assert isinstance(users, list) assert len(users) > 0 + @skip_if_server_version_lt("6.0.0") def test_acl_whoami(self, r): username = r.acl_whoami() assert isinstance(username, (str, bytes)) @@ -439,12 +454,14 @@ def test_client_list(self, r): assert "addr" in clients[0] @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_client_info(self, r): info = r.client_info() assert isinstance(info, dict) assert "addr" in info @pytest.mark.onlynoncluster + @skip_if_server_version_lt("5.0.0") def test_client_list_types_not_replica(self, r): with pytest.raises(exceptions.ValkeyError): r.client_list(_type="not a client type") @@ -457,6 +474,7 @@ def test_client_list_replica(self, r): assert isinstance(clients, list) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_client_list_client_id(self, r, request): clients = r.client_list() clients = r.client_list(client_id=[clients[0]["id"]]) @@ -471,16 +489,19 @@ def test_client_list_client_id(self, r, request): assert len(clients_listed) > 1 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("5.0.0") def test_client_id(self, r): assert r.client_id() > 0 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_client_trackinginfo(self, r): res = r.client_trackinginfo() assert len(res) > 2 assert "prefixes" in res or b"prefixes" in res @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.0.0") def test_client_tracking(self, r, r2): # simple case assert r.client_tracking_on() @@ -501,6 +522,7 @@ def test_client_tracking(self, r, r2): assert r.client_tracking_on(prefix=["foo", "bar", "blee"]) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("5.0.0") def test_client_unblock(self, r): myid = r.client_id() assert not r.client_unblock(myid) @@ -508,14 +530,17 @@ def test_client_unblock(self, r): assert not r.client_unblock(myid, error=False) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.9") def test_client_getname(self, r): assert r.client_getname() is None @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.9") def test_client_setname(self, r): assert r.client_setname("valkey_py_test") assert_resp_response(r, r.client_getname(), "valkey_py_test", b"valkey_py_test") + @skip_if_server_version_lt("7.2.0") def test_client_setinfo(self, r: valkey.Valkey): r.ping() info = r.client_info() @@ -536,6 +561,7 @@ def test_client_setinfo(self, r: valkey.Valkey): assert info["lib-ver"] == "" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.9") def test_client_kill(self, r, r2): r.client_setname("valkey-py-c1") r2.client_setname("valkey-py-c2") @@ -559,6 +585,7 @@ def test_client_kill(self, r, r2): assert len(clients) == 1 assert clients[0].get("name") == "valkey-py-c1" + @skip_if_server_version_lt("2.8.12") def test_client_kill_filter_invalid_params(self, r): # empty with pytest.raises(exceptions.DataError): @@ -573,6 +600,7 @@ def test_client_kill_filter_invalid_params(self, r): r.client_kill_filter(_type="caster") @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.12") def test_client_kill_filter_by_id(self, r, r2): r.client_setname("valkey-py-c1") r2.client_setname("valkey-py-c2") @@ -598,6 +626,7 @@ def test_client_kill_filter_by_id(self, r, r2): assert clients[0].get("name") == "valkey-py-c1" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.12") def test_client_kill_filter_by_addr(self, r, r2): r.client_setname("valkey-py-c1") r2.client_setname("valkey-py-c2") @@ -622,12 +651,14 @@ def test_client_kill_filter_by_addr(self, r, r2): assert len(clients) == 1 assert clients[0].get("name") == "valkey-py-c1" + @skip_if_server_version_lt("2.6.9") def test_client_list_after_client_setname(self, r): r.client_setname("valkey_py_test") clients = r.client_list() # we don't know which client ours will be assert "valkey_py_test" in [c["name"] for c in clients] + @skip_if_server_version_lt("6.2.0") def test_client_kill_filter_by_laddr(self, r, r2): r.client_setname("valkey-py-c1") r2.client_setname("valkey-py-c2") @@ -643,6 +674,7 @@ def test_client_kill_filter_by_laddr(self, r, r2): client_2_addr = clients_by_name["valkey-py-c2"].get("laddr") assert r.client_kill_filter(laddr=client_2_addr) + @skip_if_server_version_lt("6.0.0") def test_client_kill_filter_by_user(self, r, request): killuser = "user_to_kill" r.acl_setuser( @@ -669,12 +701,14 @@ def test_client_kill_filter_by_maxage(self, r, request): assert len(r.client_list()) == 1 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.9.50") def test_client_pause(self, r): assert r.client_pause(1) assert r.client_pause(timeout=1) with pytest.raises(exceptions.ValkeyError): r.client_pause(timeout="not an integer") + @skip_if_server_version_lt("6.2.0") def test_client_pause_all(self, r, r2): assert r.client_pause(1, all=False) assert r2.set("foo", "bar") @@ -682,16 +716,19 @@ def test_client_pause_all(self, r, r2): assert r.get("foo") == b"bar" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_client_unpause(self, r): assert r.client_unpause() == b"OK" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_client_no_evict(self, r): assert r.client_no_evict("ON") with pytest.raises(TypeError): r.client_no_evict() @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.2.0") def test_client_no_touch(self, r): assert r.client_no_touch("ON") == b"OK" assert r.client_no_touch("OFF") == b"OK" @@ -699,6 +736,7 @@ def test_client_no_touch(self, r): r.client_no_touch() @pytest.mark.onlynoncluster + @skip_if_server_version_lt("3.2.0") def test_client_reply(self, r, r_timeout): assert r_timeout.client_reply("ON") == b"OK" with pytest.raises(exceptions.ValkeyError): @@ -712,10 +750,12 @@ def test_client_reply(self, r, r_timeout): assert r.get("foo") == b"bar" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.0.0") def test_client_getredir(self, r): assert isinstance(r.client_getredir(), int) assert r.client_getredir() == -1 + @skip_if_server_version_lt("6.0.0") def test_hello_notI_implemented(self, r): with pytest.raises(NotImplementedError): r.hello() @@ -726,6 +766,7 @@ def test_config_get(self, r): # # assert 'maxmemory' in data # assert data['maxmemory'].isdigit() + @skip_if_server_version_lt("7.0.0") def test_config_get_multi_params(self, r: valkey.Valkey): res = r.config_get("*max-*-entries*", "maxmemory") assert "maxmemory" in res @@ -746,6 +787,7 @@ def test_config_set(self, r): assert r.config_set("timeout", 0) assert r.config_get()["timeout"] == "0" + @skip_if_server_version_lt("7.0.0") def test_config_set_multi_params(self, r: valkey.Valkey): r.config_set("timeout", 70, "maxmemory", 100) assert r.config_get()["timeout"] == "70" @@ -754,6 +796,7 @@ def test_config_set_multi_params(self, r: valkey.Valkey): assert r.config_get()["timeout"] == "0" assert r.config_get()["maxmemory"] == "0" + @skip_if_server_version_lt("6.0.0") def test_failover(self, r): with pytest.raises(NotImplementedError): r.failover() @@ -778,6 +821,7 @@ def test_info(self, r): assert "valkey_version" in info.keys() @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_info_multi_sections(self, r): res = r.info("clients", "server") assert isinstance(res, dict) @@ -789,6 +833,7 @@ def test_lastsave(self, r): assert isinstance(r.lastsave(), datetime.datetime) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("5.0.0") def test_lolwut(self, r): lolwut = r.lolwut().decode("utf-8") assert "Redis ver." in lolwut @@ -797,6 +842,7 @@ def test_lolwut(self, r): assert "Redis ver." in lolwut @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_reset(self, r): assert_resp_response(r, r.reset(), "RESET", b"RESET") @@ -814,6 +860,7 @@ def test_ping(self, r): def test_quit(self, r): assert r.quit() + @skip_if_server_version_lt("2.8.12") @pytest.mark.onlynoncluster def test_role(self, r): assert r.role()[0] == b"master" @@ -892,6 +939,7 @@ def test_slowlog_length(self, r, slowlog): r.get("foo") assert isinstance(r.slowlog_len(), int) + @skip_if_server_version_lt("2.6.0") def test_time(self, r): t = r.time() assert len(t) == 2 @@ -920,6 +968,7 @@ def test_append(self, r): assert r.append("a", "a2") == 4 assert r["a"] == b"a1a2" + @skip_if_server_version_lt("2.6.0") def test_bitcount(self, r): r.setbit("a", 5, True) assert r.bitcount("a") == 1 @@ -938,6 +987,7 @@ def test_bitcount(self, r): assert r.bitcount("a", -2, -1) == 2 assert r.bitcount("a", 1, 1) == 1 + @skip_if_server_version_lt("7.0.0") def test_bitcount_mode(self, r): r.set("mykey", "foobar") assert r.bitcount("mykey") == 26 @@ -947,12 +997,14 @@ def test_bitcount_mode(self, r): assert r.bitcount("mykey", 5, 30, "but") @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.0") def test_bitop_not_empty_string(self, r): r["a"] = "" r.bitop("not", "r", "a") assert r.get("r") is None @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.0") def test_bitop_not(self, r): test_str = b"\xAA\x00\xFF\x55" correct = ~0xAA00FF55 & 0xFFFFFFFF @@ -961,6 +1013,7 @@ def test_bitop_not(self, r): assert int(binascii.hexlify(r["r"]), 16) == correct @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.0") def test_bitop_not_in_place(self, r): test_str = b"\xAA\x00\xFF\x55" correct = ~0xAA00FF55 & 0xFFFFFFFF @@ -969,6 +1022,7 @@ def test_bitop_not_in_place(self, r): assert int(binascii.hexlify(r["a"]), 16) == correct @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.0") def test_bitop_single_string(self, r): test_str = b"\x01\x02\xFF" r["a"] = test_str @@ -980,6 +1034,7 @@ def test_bitop_single_string(self, r): assert r["res3"] == test_str @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.6.0") def test_bitop_string_operands(self, r): r["a"] = b"\x01\x02\xFF\xFF" r["b"] = b"\x01\x02\xFF" @@ -991,6 +1046,7 @@ def test_bitop_string_operands(self, r): assert int(binascii.hexlify(r["res3"]), 16) == 0x000000FF @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.7") def test_bitpos(self, r): key = "key:bitpos" r.set(key, b"\xff\xf0\x00") @@ -1003,6 +1059,7 @@ def test_bitpos(self, r): r.set(key, b"\x00\x00\x00") assert r.bitpos(key, 1) == -1 + @skip_if_server_version_lt("2.8.7") def test_bitpos_wrong_arguments(self, r): key = "key:bitpos:wrong:args" r.set(key, b"\xff\xf0\x00") @@ -1011,6 +1068,7 @@ def test_bitpos_wrong_arguments(self, r): with pytest.raises(exceptions.ValkeyError): r.bitpos(key, 7) == 12 + @skip_if_server_version_lt("7.0.0") def test_bitpos_mode(self, r): r.set("mykey", b"\x00\xff\xf0") assert r.bitpos("mykey", 1, 0) == 8 @@ -1020,6 +1078,7 @@ def test_bitpos_mode(self, r): r.bitpos("mykey", 1, 7, 15, "bite") @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_copy(self, r): assert r.copy("a", "b") == 0 r.set("a", "foo") @@ -1028,6 +1087,7 @@ def test_copy(self, r): assert r.get("b") == b"foo" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_copy_and_replace(self, r): r.set("a", "foo1") r.set("b", "foo2") @@ -1035,6 +1095,7 @@ def test_copy_and_replace(self, r): assert r.copy("a", "b", replace=True) == 1 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_copy_to_another_database(self, request): r0 = _get_client(valkey.Valkey, request, db=0) r1 = _get_client(valkey.Valkey, request, db=1) @@ -1072,12 +1133,14 @@ def test_delitem(self, r): del r["a"] assert r.get("a") is None + @skip_if_server_version_lt("4.0.0") def test_unlink(self, r): assert r.unlink("a") == 0 r["a"] = "foo" assert r.unlink("a") == 1 assert r.get("a") is None + @skip_if_server_version_lt("4.0.0") def test_unlink_with_multiple_keys(self, r): r["a"] = "foo" r["b"] = "bar" @@ -1086,6 +1149,7 @@ def test_unlink_with_multiple_keys(self, r): assert r.get("b") is None @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_lcs(self, r): r.mset({"foo": "ohmytext", "bar": "mynewtext"}) assert r.lcs("foo", "bar") == b"mytext" @@ -1099,6 +1163,7 @@ def test_lcs(self, r): with pytest.raises(valkey.ResponseError): assert r.lcs("foo", "bar", len=True, idx=True) + @skip_if_server_version_lt("2.6.0") def test_dump_and_restore(self, r): r["a"] = "foo" dumped = r.dump("a") @@ -1106,6 +1171,7 @@ def test_dump_and_restore(self, r): r.restore("a", 0, dumped) assert r["a"] == b"foo" + @skip_if_server_version_lt("3.0.0") def test_dump_and_restore_and_replace(self, r): r["a"] = "bar" dumped = r.dump("a") @@ -1115,6 +1181,7 @@ def test_dump_and_restore_and_replace(self, r): r.restore("a", 0, dumped, replace=True) assert r["a"] == b"bar" + @skip_if_server_version_lt("5.0.0") def test_dump_and_restore_absttl(self, r): r["a"] = "foo" dumped = r.dump("a") @@ -1146,22 +1213,26 @@ def test_expire(self, r): assert r.persist("a") assert r.ttl("a") == -1 + @skip_if_server_version_lt("7.0.0") def test_expire_option_nx(self, r): r.set("key", "val") assert r.expire("key", 100, nx=True) == 1 assert r.expire("key", 500, nx=True) == 0 + @skip_if_server_version_lt("7.0.0") def test_expire_option_xx(self, r): r.set("key", "val") assert r.expire("key", 100, xx=True) == 0 assert r.expire("key", 100) assert r.expire("key", 500, xx=True) == 1 + @skip_if_server_version_lt("7.0.0") def test_expire_option_gt(self, r): r.set("key", "val", 100) assert r.expire("key", 50, gt=True) == 0 assert r.expire("key", 500, gt=True) == 1 + @skip_if_server_version_lt("7.0.0") def test_expire_option_lt(self, r): r.set("key", "val", 100) assert r.expire("key", 50, lt=True) == 1 @@ -1184,11 +1255,13 @@ def test_expireat_unixtime(self, r): assert r.expireat("a", expire_at_seconds) is True assert 0 < r.ttl("a") <= 61 + @skip_if_server_version_lt("7.0.0") def test_expiretime(self, r): r.set("a", "foo") r.expireat("a", 33177117420) assert r.expiretime("a") == 33177117420 + @skip_if_server_version_lt("7.0.0") def test_expireat_option_nx(self, r): assert r.set("key", "val") is True expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) @@ -1196,6 +1269,7 @@ def test_expireat_option_nx(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2) assert r.expireat("key", expire_at, nx=True) is False + @skip_if_server_version_lt("7.0.0") def test_expireat_option_xx(self, r): assert r.set("key", "val") is True expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) @@ -1204,6 +1278,7 @@ def test_expireat_option_xx(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2) assert r.expireat("key", expire_at, xx=True) is True + @skip_if_server_version_lt("7.0.0") def test_expireat_option_gt(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2) assert r.set("key", "val") is True @@ -1213,6 +1288,7 @@ def test_expireat_option_gt(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=3) assert r.expireat("key", expire_at, gt=True) is True + @skip_if_server_version_lt("7.0.0") def test_expireat_option_lt(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2) assert r.set("key", "val") is True @@ -1235,12 +1311,14 @@ def test_get_and_set(self, r): assert r.get("integer") == str(integer).encode() assert r.get("unicode_string").decode("utf-8") == unicode_string + @skip_if_server_version_lt("6.2.0") def test_getdel(self, r): assert r.getdel("a") is None r.set("a", 1) assert r.getdel("a") == b"1" assert r.getdel("a") is None + @skip_if_server_version_lt("6.2.0") def test_getex(self, r): r.set("a", 1) assert r.getex("a") == b"1" @@ -1307,6 +1385,7 @@ def test_incrby(self, r): assert r.incrby("a", 4) == 5 assert r["a"] == b"5" + @skip_if_server_version_lt("2.6.0") def test_incrbyfloat(self, r): assert r.incrbyfloat("a") == 1.0 assert r["a"] == b"1" @@ -1333,12 +1412,14 @@ def test_mget(self, r): assert r.mget("a", "other", "b", "c") == [b"1", None, b"2", b"3"] @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_lmove(self, r): r.rpush("a", "one", "two", "three", "four") assert r.lmove("a", "b") assert r.lmove("a", "b", "right", "left") @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_blmove(self, r): r.rpush("a", "one", "two", "three", "four") assert r.blmove("a", "b", 5) @@ -1361,6 +1442,7 @@ def test_msetnx(self, r): assert r[k] == v assert r.get("d") is None + @skip_if_server_version_lt("2.6.0") def test_pexpire(self, r): assert r.pexpire("a", 60000) is False r["a"] = "foo" @@ -1369,39 +1451,46 @@ def test_pexpire(self, r): assert r.persist("a") assert r.pttl("a") == -1 + @skip_if_server_version_lt("7.0.0") def test_pexpire_option_nx(self, r): assert r.set("key", "val") is True assert r.pexpire("key", 60000, nx=True) is True assert r.pexpire("key", 60000, nx=True) is False + @skip_if_server_version_lt("7.0.0") def test_pexpire_option_xx(self, r): assert r.set("key", "val") is True assert r.pexpire("key", 60000, xx=True) is False assert r.pexpire("key", 60000) is True assert r.pexpire("key", 70000, xx=True) is True + @skip_if_server_version_lt("7.0.0") def test_pexpire_option_gt(self, r): assert r.set("key", "val") is True assert r.pexpire("key", 60000) is True assert r.pexpire("key", 70000, gt=True) is True assert r.pexpire("key", 50000, gt=True) is False + @skip_if_server_version_lt("7.0.0") def test_pexpire_option_lt(self, r): assert r.set("key", "val") is True assert r.pexpire("key", 60000) is True assert r.pexpire("key", 50000, lt=True) is True assert r.pexpire("key", 70000, lt=True) is False + @skip_if_server_version_lt("2.6.0") def test_pexpireat_datetime(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) r["a"] = "foo" assert r.pexpireat("a", expire_at) is True assert 0 < r.pttl("a") <= 61000 + @skip_if_server_version_lt("2.6.0") def test_pexpireat_no_key(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) assert r.pexpireat("a", expire_at) is False + @skip_if_server_version_lt("2.6.0") def test_pexpireat_unixtime(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) r["a"] = "foo" @@ -1409,12 +1498,14 @@ def test_pexpireat_unixtime(self, r): assert r.pexpireat("a", expire_at_milliseconds) is True assert 0 < r.pttl("a") <= 61000 + @skip_if_server_version_lt("7.0.0") def test_pexpireat_option_nx(self, r): assert r.set("key", "val") is True expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) assert r.pexpireat("key", expire_at, nx=True) is True assert r.pexpireat("key", expire_at, nx=True) is False + @skip_if_server_version_lt("7.0.0") def test_pexpireat_option_xx(self, r): assert r.set("key", "val") is True expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) @@ -1422,6 +1513,7 @@ def test_pexpireat_option_xx(self, r): assert r.pexpireat("key", expire_at) is True assert r.pexpireat("key", expire_at, xx=True) is True + @skip_if_server_version_lt("7.0.0") def test_pexpireat_option_gt(self, r): assert r.set("key", "val") is True expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2) @@ -1431,6 +1523,7 @@ def test_pexpireat_option_gt(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=3) assert r.pexpireat("key", expire_at, gt=True) is True + @skip_if_server_version_lt("7.0.0") def test_pexpireat_option_lt(self, r): assert r.set("key", "val") is True expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2) @@ -1440,22 +1533,26 @@ def test_pexpireat_option_lt(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1) assert r.pexpireat("key", expire_at, lt=True) is True + @skip_if_server_version_lt("7.0.0") def test_pexpiretime(self, r): r.set("a", "foo") r.pexpireat("a", 33177117420000) assert r.pexpiretime("a") == 33177117420000 + @skip_if_server_version_lt("2.6.0") def test_psetex(self, r): assert r.psetex("a", 1000, "value") assert r["a"] == b"value" assert 0 < r.pttl("a") <= 1000 + @skip_if_server_version_lt("2.6.0") def test_psetex_timedelta(self, r): expire_at = datetime.timedelta(milliseconds=1000) assert r.psetex("a", expire_at, "value") assert r["a"] == b"value" assert 0 < r.pttl("a") <= 1000 + @skip_if_server_version_lt("2.6.0") def test_pttl(self, r): assert r.pexpire("a", 10000) is False r["a"] = "1" @@ -1464,10 +1561,12 @@ def test_pttl(self, r): assert r.persist("a") assert r.pttl("a") == -1 + @skip_if_server_version_lt("2.8.0") def test_pttl_no_key(self, r): "PTTL on servers 2.8 and after return -2 when the key doesn't exist" assert r.pttl("a") == -2 + @skip_if_server_version_lt("6.2.0") def test_hrandfield(self, r): assert r.hrandfield("key") is None r.hset("key", mapping={"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}) @@ -1502,11 +1601,13 @@ def test_renamenx(self, r): assert r["a"] == b"1" assert r["b"] == b"2" + @skip_if_server_version_lt("2.6.0") def test_set_nx(self, r): assert r.set("a", "1", nx=True) assert not r.set("a", "2", nx=True) assert r["a"] == b"1" + @skip_if_server_version_lt("2.6.0") def test_set_xx(self, r): assert not r.set("a", "1", xx=True) assert r.get("a") is None @@ -1514,6 +1615,7 @@ def test_set_xx(self, r): assert r.set("a", "2", xx=True) assert r.get("a") == b"2" + @skip_if_server_version_lt("2.6.0") def test_set_px(self, r): assert r.set("a", "1", px=10000) assert r["a"] == b"1" @@ -1522,44 +1624,52 @@ def test_set_px(self, r): with pytest.raises(exceptions.DataError): assert r.set("a", "1", px=10.0) + @skip_if_server_version_lt("2.6.0") def test_set_px_timedelta(self, r): expire_at = datetime.timedelta(milliseconds=1000) assert r.set("a", "1", px=expire_at) assert 0 < r.pttl("a") <= 1000 assert 0 < r.ttl("a") <= 1 + @skip_if_server_version_lt("2.6.0") def test_set_ex(self, r): assert r.set("a", "1", ex=10) assert 0 < r.ttl("a") <= 10 with pytest.raises(exceptions.DataError): assert r.set("a", "1", ex=10.0) + @skip_if_server_version_lt("2.6.0") def test_set_ex_str(self, r): assert r.set("a", "1", ex="10") assert 0 < r.ttl("a") <= 10 with pytest.raises(exceptions.DataError): assert r.set("a", "1", ex="10.5") + @skip_if_server_version_lt("2.6.0") def test_set_ex_timedelta(self, r): expire_at = datetime.timedelta(seconds=60) assert r.set("a", "1", ex=expire_at) assert 0 < r.ttl("a") <= 60 + @skip_if_server_version_lt("6.2.0") def test_set_exat_timedelta(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(seconds=10) assert r.set("a", "1", exat=expire_at) assert 0 < r.ttl("a") <= 10 + @skip_if_server_version_lt("6.2.0") def test_set_pxat_timedelta(self, r): expire_at = valkey_server_time(r) + datetime.timedelta(seconds=50) assert r.set("a", "1", pxat=expire_at) assert 0 < r.ttl("a") <= 100 + @skip_if_server_version_lt("2.6.0") def test_set_multipleoptions(self, r): r["a"] = "val" assert r.set("a", "1", xx=True, px=10000) assert 0 < r.ttl("a") <= 10 + @skip_if_server_version_lt("6.0.0") def test_set_keepttl(self, r): r["a"] = "val" assert r.set("a", "1", xx=True, px=10000) @@ -1568,6 +1678,7 @@ def test_set_keepttl(self, r): assert r.get("a") == b"2" assert 0 < r.ttl("a") <= 10 + @skip_if_server_version_lt("6.2.0") def test_set_get(self, r): assert r.set("a", "True", get=True) is None assert r.set("a", "True", get=True) == b"True" @@ -1593,6 +1704,55 @@ def test_setrange(self, r): assert r.setrange("a", 6, "12345") == 11 assert r["a"] == b"abcdef12345" + @skip_if_server_version_lt("6.0.0") + @skip_if_server_version_gte("7.0.0") + def test_stralgo_lcs(self, r): + key1 = "{foo}key1" + key2 = "{foo}key2" + value1 = "ohmytext" + value2 = "mynewtext" + res = "mytext" + + # test LCS of strings + assert r.stralgo("LCS", value1, value2) == res + # test using keys + r.mset({key1: value1, key2: value2}) + assert r.stralgo("LCS", key1, key2, specific_argument="keys") == res + # test other labels + assert r.stralgo("LCS", value1, value2, len=True) == len(res) + assert_resp_response( + r, + r.stralgo("LCS", value1, value2, idx=True), + {"len": len(res), "matches": [[(4, 7), (5, 8)], [(2, 3), (0, 1)]]}, + {"len": len(res), "matches": [[[4, 7], [5, 8]], [[2, 3], [0, 1]]]}, + ) + assert_resp_response( + r, + r.stralgo("LCS", value1, value2, idx=True, withmatchlen=True), + {"len": len(res), "matches": [[4, (4, 7), (5, 8)], [2, (2, 3), (0, 1)]]}, + {"len": len(res), "matches": [[[4, 7], [5, 8], 4], [[2, 3], [0, 1], 2]]}, + ) + assert_resp_response( + r, + r.stralgo( + "LCS", value1, value2, idx=True, withmatchlen=True, minmatchlen=4 + ), + {"len": len(res), "matches": [[4, (4, 7), (5, 8)]]}, + {"len": len(res), "matches": [[[4, 7], [5, 8], 4]]}, + ) + + @skip_if_server_version_lt("6.0.0") + @skip_if_server_version_gte("7.0.0") + def test_stralgo_negative(self, r): + with pytest.raises(exceptions.DataError): + r.stralgo("ISSUB", "value1", "value2") + with pytest.raises(exceptions.DataError): + r.stralgo("LCS", "value1", "value2", len=True, idx=True) + with pytest.raises(exceptions.DataError): + r.stralgo("LCS", "value1", "value2", specific_argument="INT") + with pytest.raises(ValueError): + r.stralgo("LCS", "value1", "value2", idx=True, minmatchlen="one") + def test_strlen(self, r): r["a"] = "foo" assert r.strlen("a") == 3 @@ -1663,6 +1823,7 @@ def test_ttl(self, r): assert r.persist("a") assert r.ttl("a") == -1 + @skip_if_server_version_lt("2.8.0") def test_ttl_nokey(self, r): "TTL on servers 2.8 and after return -2 when the key doesn't exist" assert r.ttl("a") == -2 @@ -1738,6 +1899,7 @@ def test_brpoplpush_empty_string(self, r): assert r.brpoplpush("a", "b") == b"" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_blmpop(self, r): r.rpush("a", "1", "2", "3", "4", "5") res = [b"a", [b"1", b"2"]] @@ -1749,6 +1911,7 @@ def test_blmpop(self, r): assert r.blmpop(1, "2", "foo", "bar", direction="RIGHT") is None @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_lmpop(self, r): r.rpush("foo", "1", "2", "3", "4", "5") result = [b"foo", [b"1", b"2"]] @@ -1782,6 +1945,7 @@ def test_lpop(self, r): assert r.lpop("a") == b"3" assert r.lpop("a") is None + @skip_if_server_version_lt("6.2.0") def test_lpop_count(self, r): r.rpush("a", "1", "2", "3") assert r.lpop("a", 2) == [b"1", b"2"] @@ -1802,6 +1966,7 @@ def test_lpushx(self, r): assert r.lpushx("a", "4") == 4 assert r.lrange("a", 0, -1) == [b"4", b"1", b"2", b"3"] + @skip_if_server_version_lt("4.0.0") def test_lpushx_with_list(self, r): # now with a list r.lpush("somekey", "a") @@ -1846,6 +2011,7 @@ def test_rpop(self, r): assert r.rpop("a") == b"1" assert r.rpop("a") is None + @skip_if_server_version_lt("6.2.0") def test_rpop_count(self, r): r.rpush("a", "1", "2", "3") assert r.rpop("a", 2) == [b"3", b"2"] @@ -1867,6 +2033,7 @@ def test_rpush(self, r): assert r.rpush("a", "3", "4") == 4 assert r.lrange("a", 0, -1) == [b"1", b"2", b"3", b"4"] + @skip_if_server_version_lt("6.0.6") def test_lpos(self, r): assert r.rpush("a", "a", "b", "c", "1", "2", "3", "c", "c") == 8 assert r.lpos("a", "a") == 0 @@ -1907,6 +2074,7 @@ def test_rpushx(self, r): # SCAN COMMANDS @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.0") def test_scan(self, r): r.set("a", 1) r.set("b", 2) @@ -1918,6 +2086,7 @@ def test_scan(self, r): assert set(keys) == {b"a"} @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.0.0") def test_scan_type(self, r): r.sadd("a-set", 1) r.hset("a-hash", "foo", 2) @@ -1926,6 +2095,7 @@ def test_scan_type(self, r): assert set(keys) == {b"a-set"} @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.0") def test_scan_iter(self, r): r.set("a", 1) r.set("b", 2) @@ -1935,6 +2105,7 @@ def test_scan_iter(self, r): keys = list(r.scan_iter(match="a")) assert set(keys) == {b"a"} + @skip_if_server_version_lt("2.8.0") def test_sscan(self, r): r.sadd("a", 1, 2, 3) cursor, members = r.sscan("a") @@ -1943,6 +2114,7 @@ def test_sscan(self, r): _, members = r.sscan("a", match=b"1") assert set(members) == {b"1"} + @skip_if_server_version_lt("2.8.0") def test_sscan_iter(self, r): r.sadd("a", 1, 2, 3) members = list(r.sscan_iter("a")) @@ -1950,6 +2122,7 @@ def test_sscan_iter(self, r): members = list(r.sscan_iter("a", match=b"1")) assert set(members) == {b"1"} + @skip_if_server_version_lt("2.8.0") def test_hscan(self, r): r.hset("a", mapping={"a": 1, "b": 2, "c": 3}) cursor, dic = r.hscan("a") @@ -1971,6 +2144,7 @@ def test_hscan_novalues(self, r): _, keys = r.hscan("a_notset", no_values=True) assert keys == [] + @skip_if_server_version_lt("2.8.0") def test_hscan_iter(self, r): r.hset("a", mapping={"a": 1, "b": 2, "c": 3}) dic = dict(r.hscan_iter("a")) @@ -1990,6 +2164,7 @@ def test_hscan_iter_novalues(self, r): keys = list(r.hscan_iter("a_notset", no_values=True)) assert keys == [] + @skip_if_server_version_lt("2.8.0") def test_zscan(self, r): r.zadd("a", {"a": 1, "b": 2, "c": 3}) cursor, pairs = r.zscan("a") @@ -1998,6 +2173,7 @@ def test_zscan(self, r): _, pairs = r.zscan("a", match="a") assert set(pairs) == {(b"a", 1)} + @skip_if_server_version_lt("2.8.0") def test_zscan_iter(self, r): r.zadd("a", {"a": 1, "b": 2, "c": 3}) pairs = list(r.zscan_iter("a")) @@ -2039,6 +2215,7 @@ def test_sinter(self, r): assert r.sinter("a", "b") == {b"2", b"3"} @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_sintercard(self, r): r.sadd("a", 1, 2, 3) r.sadd("b", 1, 2, 3) @@ -2066,6 +2243,7 @@ def test_smembers(self, r): r.sadd("a", "1", "2", "3") assert r.smembers("a") == {b"1", b"2", b"3"} + @skip_if_server_version_lt("6.2.0") def test_smismember(self, r): r.sadd("a", "1", "2", "3") result_list = [True, False, True, True] @@ -2087,6 +2265,7 @@ def test_spop(self, r): assert value in s assert r.smembers("a") == set(s) - {value} + @skip_if_server_version_lt("3.2.0") def test_spop_multi_value(self, r): s = [b"1", b"2", b"3"] r.sadd("a", *s) @@ -2107,6 +2286,7 @@ def test_srandmember(self, r): r.sadd("a", *s) assert r.srandmember("a") in s + @skip_if_server_version_lt("2.6.0") def test_srandmember_multi_value(self, r): s = [b"1", b"2", b"3"] r.sadd("a", *s) @@ -2133,11 +2313,13 @@ def test_sunionstore(self, r): assert r.sunionstore("c", "a", "b") == 3 assert r.smembers("c") == {b"1", b"2", b"3"} + @skip_if_server_version_lt("1.0.0") def test_debug_segfault(self, r): with pytest.raises(NotImplementedError): r.debug_segfault() @pytest.mark.onlynoncluster + @skip_if_server_version_lt("3.2.0") def test_script_debug(self, r): with pytest.raises(NotImplementedError): r.script_debug() @@ -2205,6 +2387,7 @@ def test_zadd_incr_with_xx(self, r): # valkey-py assert r.zadd("a", {"a1": 1}, xx=True, incr=True) is None + @skip_if_server_version_lt("6.2.0") def test_zadd_gt_lt(self, r): r.zadd("a", {"a": 2}) assert r.zadd("a", {"a": 5}, gt=True, ch=True) == 1 @@ -2235,6 +2418,7 @@ def test_zcount(self, r): assert r.zcount("a", 10, 20) == 0 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_zdiff(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) r.zadd("b", {"a1": 1, "a2": 2}) @@ -2247,6 +2431,7 @@ def test_zdiff(self, r): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_zdiffstore(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) r.zadd("b", {"a1": 1, "a2": 2}) @@ -2266,12 +2451,14 @@ def test_zincrby(self, r): assert r.zscore("a", "a2") == 3.0 assert r.zscore("a", "a3") == 8.0 + @skip_if_server_version_lt("2.8.9") def test_zlexcount(self, r): r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert r.zlexcount("a", "-", "+") == 7 assert r.zlexcount("a", "[b", "[f") == 5 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_zinter(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 1}) r.zadd("b", {"a1": 2, "a2": 2, "a3": 2}) @@ -2310,6 +2497,7 @@ def test_zinter(self, r): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_zintercard(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 1}) r.zadd("b", {"a1": 2, "a2": 2, "a3": 2}) @@ -2369,6 +2557,7 @@ def test_zinterstore_with_weight(self, r): [[b"a3", 20], [b"a1", 23]], ) + @skip_if_server_version_lt("4.9.0") def test_zpopmax(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) assert_resp_response(r, r.zpopmax("a"), [(b"a3", 3)], [b"a3", 3.0]) @@ -2380,6 +2569,7 @@ def test_zpopmax(self, r): [[b"a2", 2], [b"a1", 1]], ) + @skip_if_server_version_lt("4.9.0") def test_zpopmin(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) assert_resp_response(r, r.zpopmin("a"), [(b"a1", 1)], [b"a1", 1.0]) @@ -2391,6 +2581,7 @@ def test_zpopmin(self, r): [[b"a2", 2], [b"a3", 3]], ) + @skip_if_server_version_lt("6.2.0") def test_zrandemember(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5}) assert r.zrandmember("a") is not None @@ -2408,6 +2599,7 @@ def test_zrandemember(self, r): assert len(r.zrandmember("a", -10)) == 10 @pytest.mark.onlynoncluster + @skip_if_server_version_lt("4.9.0") def test_bzpopmax(self, r): r.zadd("a", {"a1": 1, "a2": 2}) r.zadd("b", {"b1": 10, "b2": 20}) @@ -2430,6 +2622,7 @@ def test_bzpopmax(self, r): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("4.9.0") def test_bzpopmin(self, r): r.zadd("a", {"a1": 1, "a2": 2}) r.zadd("b", {"b1": 10, "b2": 20}) @@ -2452,6 +2645,7 @@ def test_bzpopmin(self, r): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_zmpop(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) assert_resp_response( @@ -2471,6 +2665,7 @@ def test_zmpop(self, r): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_bzmpop(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) assert_resp_response( @@ -2527,6 +2722,7 @@ def test_zrange_errors(self, r): with pytest.raises(exceptions.DataError): r.zrange("a", 0, 1, byscore=True, withscores=True, num=2) + @skip_if_server_version_lt("6.2.0") def test_zrange_params(self, r): # bylex r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) @@ -2565,6 +2761,7 @@ def test_zrange_params(self, r): assert r.zrange("a", 0, 1, desc=True) == [b"a5", b"a4"] @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_zrangestore(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3}) assert r.zrangestore("b", "a", 0, 1) @@ -2587,6 +2784,7 @@ def test_zrangestore(self, r): assert r.zrangestore("b", "a", "[a2", "(a3", bylex=True, offset=0, num=1) assert r.zrange("b", 0, -1) == [b"a2"] + @skip_if_server_version_lt("2.8.9") def test_zrangebylex(self, r): r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert r.zrangebylex("a", "-", "[c") == [b"a", b"b", b"c"] @@ -2595,6 +2793,7 @@ def test_zrangebylex(self, r): assert r.zrangebylex("a", "[f", "+") == [b"f", b"g"] assert r.zrangebylex("a", "-", "+", start=3, num=2) == [b"d", b"e"] + @skip_if_server_version_lt("2.9.9") def test_zrevrangebylex(self, r): r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert r.zrevrangebylex("a", "[c", "-") == [b"c", b"b", b"a"] @@ -2628,6 +2827,7 @@ def test_zrank(self, r): assert r.zrank("a", "a2") == 1 assert r.zrank("a", "a6") is None + @skip_if_server_version_lt("7.2.0") def test_zrank_withscore(self, r: valkey.Valkey): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5}) assert r.zrank("a", "a1") == 0 @@ -2648,6 +2848,7 @@ def test_zrem_multiple_keys(self, r): assert r.zrem("a", "a1", "a2") == 2 assert r.zrange("a", 0, 5) == [b"a3"] + @skip_if_server_version_lt("2.8.9") def test_zremrangebylex(self, r): r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0}) assert r.zremrangebylex("a", "-", "[c") == 3 @@ -2721,6 +2922,7 @@ def test_zrevrank(self, r): assert r.zrevrank("a", "a2") == 3 assert r.zrevrank("a", "a6") is None + @skip_if_server_version_lt("7.2.0") def test_zrevrank_withscore(self, r): r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5}) assert r.zrevrank("a", "a1") == 4 @@ -2738,6 +2940,7 @@ def test_zscore(self, r): assert r.zscore("a", "a4") is None @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_zunion(self, r): r.zadd("a", {"a1": 1, "a2": 1, "a3": 1}) r.zadd("b", {"a1": 2, "a2": 2, "a3": 2}) @@ -2824,6 +3027,7 @@ def test_zunionstore_with_weight(self, r): [[b"a2", 5], [b"a4", 12], [b"a3", 20], [b"a1", 23]], ) + @skip_if_server_version_lt("6.1.240") def test_zmscore(self, r): with pytest.raises(exceptions.DataError): r.zmscore("invalid_key", []) @@ -2834,6 +3038,7 @@ def test_zmscore(self, r): assert r.zmscore("a", ["a1", "a2", "a3", "a4"]) == [1.0, 2.0, 3.5, None] # HYPERLOGLOG TESTS + @skip_if_server_version_lt("2.8.9") def test_pfadd(self, r): members = {b"1", b"2", b"3"} assert r.pfadd("a", *members) == 1 @@ -2841,6 +3046,7 @@ def test_pfadd(self, r): assert r.pfcount("a") == len(members) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.9") def test_pfcount(self, r): members = {b"1", b"2", b"3"} r.pfadd("a", *members) @@ -2851,6 +3057,7 @@ def test_pfcount(self, r): assert r.pfcount("a", "b") == len(members_b.union(members)) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.9") def test_pfmerge(self, r): mema = {b"1", b"2", b"3"} memb = {b"2", b"3", b"4"} @@ -2933,6 +3140,7 @@ def test_hincrby(self, r): assert r.hincrby("a", "1", amount=2) == 3 assert r.hincrby("a", "1", amount=-2) == 1 + @skip_if_server_version_lt("2.6.0") def test_hincrbyfloat(self, r): assert r.hincrbyfloat("a", "1") == 1.0 assert r.hincrbyfloat("a", "1") == 2.0 @@ -2978,6 +3186,7 @@ def test_hvals(self, r): remote_vals = r.hvals("a") assert sorted(local_vals) == sorted(remote_vals) + @skip_if_server_version_lt("3.2.0") def test_hstrlen(self, r): r.hset("a", mapping={"1": "22", "2": "333"}) assert r.hstrlen("a", "1") == 2 @@ -3124,6 +3333,7 @@ def test_sort_all_options(self, r): assert num == 4 assert r.lrange("sorted", 0, 10) == [b"vodka", b"milk", b"gin", b"apple juice"] + @skip_if_server_version_lt("7.0.0") @pytest.mark.onlynoncluster def test_sort_ro(self, r): r["score:1"] = 8 @@ -3202,15 +3412,24 @@ def test_cluster_slaves(self, mock_cluster_resp_slaves): assert isinstance(mock_cluster_resp_slaves.cluster("slaves", "nodeid"), dict) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("3.0.0") + @skip_if_server_version_gte("7.0.0") + def test_readwrite(self, r): + assert r.readwrite() + + @pytest.mark.onlynoncluster + @skip_if_server_version_lt("3.0.0") def test_readonly_invalid_cluster_state(self, r): with pytest.raises(exceptions.ValkeyError): r.readonly() @pytest.mark.onlynoncluster + @skip_if_server_version_lt("3.0.0") def test_readonly(self, mock_cluster_resp_ok): assert mock_cluster_resp_ok.readonly() is True # GEO COMMANDS + @skip_if_server_version_lt("3.2.0") def test_geoadd(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3220,6 +3439,7 @@ def test_geoadd(self, r): assert r.geoadd("barcelona", values) == 2 assert r.zcard("barcelona") == 2 + @skip_if_server_version_lt("6.2.0") def test_geoadd_nx(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3235,6 +3455,7 @@ def test_geoadd_nx(self, r): assert r.geoadd("a", values, nx=True) == 1 assert r.zrange("a", 0, -1) == [b"place3", b"place2", b"place1"] + @skip_if_server_version_lt("6.2.0") def test_geoadd_xx(self, r): values = (2.1909389952632, 41.433791470673, "place1") assert r.geoadd("a", values) == 1 @@ -3246,6 +3467,7 @@ def test_geoadd_xx(self, r): assert r.geoadd("a", values, xx=True) == 0 assert r.zrange("a", 0, -1) == [b"place1"] + @skip_if_server_version_lt("6.2.0") def test_geoadd_ch(self, r): values = (2.1909389952632, 41.433791470673, "place1") assert r.geoadd("a", values) == 1 @@ -3257,10 +3479,12 @@ def test_geoadd_ch(self, r): assert r.geoadd("a", values, ch=True) == 2 assert r.zrange("a", 0, -1) == [b"place1", b"place2"] + @skip_if_server_version_lt("3.2.0") def test_geoadd_invalid_params(self, r): with pytest.raises(exceptions.ValkeyError): r.geoadd("barcelona", (1, 2)) + @skip_if_server_version_lt("3.2.0") def test_geodist(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3270,6 +3494,7 @@ def test_geodist(self, r): assert r.geoadd("barcelona", values) == 2 assert r.geodist("barcelona", "place1", "place2") == 3067.4157 + @skip_if_server_version_lt("3.2.0") def test_geodist_units(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3279,15 +3504,18 @@ def test_geodist_units(self, r): r.geoadd("barcelona", values) assert r.geodist("barcelona", "place1", "place2", "km") == 3.0674 + @skip_if_server_version_lt("3.2.0") def test_geodist_missing_one_member(self, r): values = (2.1909389952632, 41.433791470673, "place1") r.geoadd("barcelona", values) assert r.geodist("barcelona", "place1", "missing_member", "km") is None + @skip_if_server_version_lt("3.2.0") def test_geodist_invalid_units(self, r): with pytest.raises(exceptions.ValkeyError): assert r.geodist("x", "y", "z", "inches") + @skip_if_server_version_lt("3.2.0") def test_geohash(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3303,6 +3531,7 @@ def test_geohash(self, r): ) @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") def test_geopos(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3324,9 +3553,16 @@ def test_geopos(self, r): ], ) + @skip_if_server_version_lt("4.0.0") def test_geopos_no_value(self, r): assert r.geopos("barcelona", "place1", "place2") == [None, None] + @skip_if_server_version_lt("3.2.0") + @skip_if_server_version_gte("4.0.0") + def test_old_geopos_no_value(self, r): + assert r.geopos("barcelona", "place1", "place2") == [] + + @skip_if_server_version_lt("6.2.0") def test_geosearch(self, r): values = ( (2.1909389952632, 41.433791470673, "place1") @@ -3357,6 +3593,7 @@ def test_geosearch(self, r): )[0] in [b"place1", b"place3", b"\x80place2"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("6.2.0") def test_geosearch_member(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3393,6 +3630,7 @@ def test_geosearch_member(self, r): ], ] + @skip_if_server_version_lt("6.2.0") def test_geosearch_sort(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3408,6 +3646,7 @@ def test_geosearch_sort(self, r): ) == [b"place2", b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("6.2.0") def test_geosearch_with(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3470,6 +3709,7 @@ def test_geosearch_with(self, r): == [] ) + @skip_if_server_version_lt("6.2.0") def test_geosearch_negative(self, r): # not specifying member nor longitude and latitude with pytest.raises(exceptions.DataError): @@ -3512,6 +3752,7 @@ def test_geosearch_negative(self, r): assert r.geosearch("barcelona", member="place3", radius=100, any=1) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("6.2.0") def test_geosearchstore(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3531,6 +3772,7 @@ def test_geosearchstore(self, r): @pytest.mark.onlynoncluster @skip_unless_arch_bits(64) + @skip_if_server_version_lt("6.2.0") def test_geosearchstore_dist(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3550,11 +3792,13 @@ def test_geosearchstore_dist(self, r): # instead of save the geo score, the distance is saved. assert r.zscore("places_barcelona", "place1") == 88.05060698409301 + @skip_if_server_version_lt("3.2.0") def test_georadius_Issue2609(self, r): # test for issue #2609 (Geo search functions don't work with execute_command) r.geoadd(name="my-key", values=[1, 2, "data"]) assert r.execute_command("GEORADIUS", "my-key", 1, 2, 400, "m") == [b"data"] + @skip_if_server_version_lt("3.2.0") def test_georadius(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3566,6 +3810,7 @@ def test_georadius(self, r): assert r.georadius("barcelona", 2.191, 41.433, 1000) == [b"place1"] assert r.georadius("barcelona", 2.187, 41.406, 1000) == [b"\x80place2"] + @skip_if_server_version_lt("3.2.0") def test_georadius_no_values(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3576,6 +3821,7 @@ def test_georadius_no_values(self, r): r.geoadd("barcelona", values) assert r.georadius("barcelona", 1, 2, 1000) == [] + @skip_if_server_version_lt("3.2.0") def test_georadius_units(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3587,6 +3833,7 @@ def test_georadius_units(self, r): assert r.georadius("barcelona", 2.191, 41.433, 1, unit="km") == [b"place1"] @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") def test_georadius_with(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3641,6 +3888,7 @@ def test_georadius_with(self, r): == [] ) + @skip_if_server_version_lt("6.2.0") def test_georadius_count(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3654,6 +3902,7 @@ def test_georadius_count(self, r): b"place2" ] + @skip_if_server_version_lt("3.2.0") def test_georadius_sort(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3672,6 +3921,7 @@ def test_georadius_sort(self, r): ] @pytest.mark.onlynoncluster + @skip_if_server_version_lt("3.2.0") def test_georadius_store(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3685,6 +3935,7 @@ def test_georadius_store(self, r): @pytest.mark.onlynoncluster @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") def test_georadius_store_dist(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3698,6 +3949,7 @@ def test_georadius_store_dist(self, r): assert r.zscore("places_barcelona", "place1") == 88.05060698409301 @skip_unless_arch_bits(64) + @skip_if_server_version_lt("3.2.0") def test_georadiusmember(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3729,6 +3981,7 @@ def test_georadiusmember(self, r): ], ] + @skip_if_server_version_lt("6.2.0") def test_georadiusmember_count(self, r): values = (2.1909389952632, 41.433791470673, "place1") + ( 2.1873744593677, @@ -3740,6 +3993,7 @@ def test_georadiusmember_count(self, r): b"\x80place2" ] + @skip_if_server_version_lt("5.0.0") def test_xack(self, r): stream = "stream" group = "group" @@ -3760,6 +4014,7 @@ def test_xack(self, r): assert r.xack(stream, group, m1) == 1 assert r.xack(stream, group, m2, m3) == 2 + @skip_if_server_version_lt("5.0.0") def test_xadd(self, r): stream = "stream" message_id = r.xadd(stream, {"foo": "bar"}) @@ -3773,6 +4028,7 @@ def test_xadd(self, r): r.xadd(stream, {"foo": "bar"}, maxlen=2, approximate=False) assert r.xlen(stream) == 2 + @skip_if_server_version_lt("6.2.0") def test_xadd_nomkstream(self, r): # nomkstream option stream = "stream" @@ -3782,6 +4038,7 @@ def test_xadd_nomkstream(self, r): r.xadd(stream, {"some": "other"}, nomkstream=True) assert r.xlen(stream) == 3 + @skip_if_server_version_lt("6.2.0") def test_xadd_minlen_and_limit(self, r): stream = "stream" @@ -3827,12 +4084,14 @@ def test_xadd_minlen_and_limit(self, r): r.xadd(stream, {"foo": "bar"}) assert r.xadd(stream, {"foo": "bar"}, approximate=True, minid=m3) + @skip_if_server_version_lt("7.0.0") def test_xadd_explicit_ms(self, r: valkey.Valkey): stream = "stream" message_id = r.xadd(stream, {"foo": "bar"}, "9999999999999999999-*") ms = message_id[: message_id.index(b"-")] assert ms == b"9999999999999999999" + @skip_if_server_version_lt("7.0.0") def test_xautoclaim(self, r): stream = "stream" group = "group" @@ -3865,6 +4124,7 @@ def test_xautoclaim(self, r): stream, group, consumer1, min_idle_time=0, start_id=message_id2, justid=True ) == [message_id2] + @skip_if_server_version_lt("6.2.0") def test_xautoclaim_negative(self, r): stream = "stream" group = "group" @@ -3876,6 +4136,7 @@ def test_xautoclaim_negative(self, r): with pytest.raises(valkey.DataError): r.xautoclaim(stream, group, consumer, min_idle_time=0, count=-1) + @skip_if_server_version_lt("5.0.0") def test_xclaim(self, r): stream = "stream" group = "group" @@ -3912,6 +4173,7 @@ def test_xclaim(self, r): justid=True, ) == [message_id] + @skip_if_server_version_lt("7.0.0") def test_xclaim_trimmed(self, r): # xclaim should not raise an exception if the item is not there stream = "stream" @@ -3935,6 +4197,7 @@ def test_xclaim_trimmed(self, r): assert len(item) == 1 assert item[0][0] == sid2 + @skip_if_server_version_lt("5.0.0") def test_xdel(self, r): stream = "stream" @@ -3949,6 +4212,7 @@ def test_xdel(self, r): assert r.xdel(stream, m1) == 1 assert r.xdel(stream, m2, m3) == 2 + @skip_if_server_version_lt("7.0.0") def test_xgroup_create(self, r): # tests xgroup_create and xinfo_groups stream = "stream" @@ -3971,6 +4235,7 @@ def test_xgroup_create(self, r): ] assert r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("7.0.0") def test_xgroup_create_mkstream(self, r): # tests xgroup_create and xinfo_groups stream = "stream" @@ -3996,6 +4261,7 @@ def test_xgroup_create_mkstream(self, r): ] assert r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("7.0.0") def test_xgroup_create_entriesread(self, r: valkey.Valkey): stream = "stream" group = "group" @@ -4017,6 +4283,7 @@ def test_xgroup_create_entriesread(self, r: valkey.Valkey): ] assert r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("5.0.0") def test_xgroup_delconsumer(self, r): stream = "stream" group = "group" @@ -4034,6 +4301,7 @@ def test_xgroup_delconsumer(self, r): # deleting the consumer should return 2 pending messages assert r.xgroup_delconsumer(stream, group, consumer) == 2 + @skip_if_server_version_lt("6.2.0") def test_xgroup_createconsumer(self, r): stream = "stream" group = "group" @@ -4049,6 +4317,7 @@ def test_xgroup_createconsumer(self, r): # deleting the consumer should return 2 pending messages assert r.xgroup_delconsumer(stream, group, consumer) == 2 + @skip_if_server_version_lt("5.0.0") def test_xgroup_destroy(self, r): stream = "stream" group = "group" @@ -4060,6 +4329,7 @@ def test_xgroup_destroy(self, r): r.xgroup_create(stream, group, 0) assert r.xgroup_destroy(stream, group) + @skip_if_server_version_lt("7.0.0") def test_xgroup_setid(self, r): stream = "stream" group = "group" @@ -4080,6 +4350,7 @@ def test_xgroup_setid(self, r): ] assert r.xinfo_groups(stream) == expected + @skip_if_server_version_lt("7.2.0") def test_xinfo_consumers(self, r): stream = "stream" group = "group" @@ -4106,6 +4377,7 @@ def test_xinfo_consumers(self, r): assert isinstance(info[1].pop("inactive"), int) assert info == expected + @skip_if_server_version_lt("7.0.0") def test_xinfo_stream(self, r): stream = "stream" m1 = r.xadd(stream, {"foo": "bar"}) @@ -4119,6 +4391,7 @@ def test_xinfo_stream(self, r): assert info["entries-added"] == 2 assert info["recorded-first-entry-id"] == m1 + @skip_if_server_version_lt("6.0.0") def test_xinfo_stream_full(self, r): stream = "stream" group = "group" @@ -4135,6 +4408,7 @@ def test_xinfo_stream_full(self, r): ) assert len(info["groups"]) == 1 + @skip_if_server_version_lt("5.0.0") def test_xlen(self, r): stream = "stream" assert r.xlen(stream) == 0 @@ -4142,6 +4416,7 @@ def test_xlen(self, r): r.xadd(stream, {"foo": "bar"}) assert r.xlen(stream) == 2 + @skip_if_server_version_lt("5.0.0") def test_xpending(self, r): stream = "stream" group = "group" @@ -4170,6 +4445,7 @@ def test_xpending(self, r): } assert r.xpending(stream, group) == expected + @skip_if_server_version_lt("5.0.0") def test_xpending_range(self, r): stream = "stream" group = "group" @@ -4200,6 +4476,7 @@ def test_xpending_range(self, r): assert response[0]["message_id"] == m1 assert response[0]["consumer"] == consumer1.encode() + @skip_if_server_version_lt("6.2.0") def test_xpending_range_idle(self, r): stream = "stream" group = "group" @@ -4240,6 +4517,7 @@ def test_xpending_range_negative(self, r): stream, group, min=None, max=None, count=None, consumername=0 ) + @skip_if_server_version_lt("5.0.0") def test_xrange(self, r): stream = "stream" m1 = r.xadd(stream, {"foo": "bar"}) @@ -4262,6 +4540,7 @@ def get_ids(results): results = r.xrange(stream, max=m2, count=1) assert get_ids(results) == [m1] + @skip_if_server_version_lt("5.0.0") def test_xread(self, r): stream = "stream" m1 = r.xadd(stream, {"foo": "bar"}) @@ -4301,6 +4580,7 @@ def test_xread(self, r): # xread starting at the last message returns an empty list assert_resp_response(r, r.xread(streams={stream: m2}), [], {}) + @skip_if_server_version_lt("5.0.0") def test_xreadgroup(self, r): stream = "stream" group = "group" @@ -4374,6 +4654,7 @@ def test_xreadgroup(self, r): {stream_name: [expected_entries]}, ) + @skip_if_server_version_lt("5.0.0") def test_xrevrange(self, r): stream = "stream" m1 = r.xadd(stream, {"foo": "bar"}) @@ -4396,6 +4677,7 @@ def get_ids(results): results = r.xrevrange(stream, min=m2, count=1) assert get_ids(results) == [m4] + @skip_if_server_version_lt("5.0.0") def test_xtrim(self, r): stream = "stream" @@ -4414,6 +4696,7 @@ def test_xtrim(self, r): # 1 message is trimmed assert r.xtrim(stream, 3, approximate=False) == 1 + @skip_if_server_version_lt("6.2.4") def test_xtrim_minlen_and_length_args(self, r): stream = "stream" @@ -4522,6 +4805,7 @@ def test_bitfield_operations(self, r): ) assert resp == [0, None, 255] + @skip_if_server_version_lt("6.0.0") def test_bitfield_ro(self, r: valkey.Valkey): bf = r.bitfield("a") resp = bf.set("u8", 8, 255).execute() @@ -4534,17 +4818,21 @@ def test_bitfield_ro(self, r: valkey.Valkey): resp = r.bitfield_ro("a", "u8", 0, items) assert resp == [0, 15, 15, 14] + @skip_if_server_version_lt("4.0.0") def test_memory_help(self, r): with pytest.raises(NotImplementedError): r.memory_help() + @skip_if_server_version_lt("4.0.0") def test_memory_doctor(self, r): with pytest.raises(NotImplementedError): r.memory_doctor() + @skip_if_server_version_lt("4.0.0") def test_memory_malloc_stats(self, r): assert r.memory_malloc_stats() + @skip_if_server_version_lt("4.0.0") def test_memory_stats(self, r): # put a key into the current db to make sure that "db." # has data @@ -4556,10 +4844,12 @@ def test_memory_stats(self, r): if key.startswith("db."): assert isinstance(value, dict) + @skip_if_server_version_lt("4.0.0") def test_memory_usage(self, r): r.set("foo", "bar") assert isinstance(r.memory_usage("foo"), int) + @skip_if_server_version_lt("7.0.0") def test_latency_histogram_not_implemented(self, r: valkey.Valkey): with pytest.raises(NotImplementedError): r.latency_histogram() @@ -4581,20 +4871,24 @@ def test_latency_latest(self, r: valkey.Valkey): def test_latency_reset(self, r: valkey.Valkey): assert r.latency_reset() == 0 + @skip_if_server_version_lt("4.0.0") def test_module_list(self, r): assert isinstance(r.module_list(), list) for x in r.module_list(): assert isinstance(x, dict) + @skip_if_server_version_lt("2.8.13") def test_command_count(self, r): res = r.command_count() assert isinstance(res, int) assert res >= 100 + @skip_if_server_version_lt("7.0.0") def test_command_docs(self, r): with pytest.raises(NotImplementedError): r.command_docs("set") + @skip_if_server_version_lt("7.0.0") def test_command_list(self, r: valkey.Valkey): assert len(r.command_list()) > 300 assert len(r.command_list(module="fakemod")) == 0 @@ -4604,6 +4898,7 @@ def test_command_list(self, r: valkey.Valkey): r.command_list(category="list", pattern="l*") @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.13") def test_command_getkeys(self, r): res = r.command_getkeys("MSET", "a", "b", "c", "d", "e", "f") assert_resp_response(r, res, ["a", "c", "e"], [b"a", b"c", b"e"]) @@ -4623,6 +4918,7 @@ def test_command_getkeys(self, r): r, res, ["key1", "key2", "key3"], [b"key1", b"key2", b"key3"] ) + @skip_if_server_version_lt("2.8.13") def test_command(self, r): res = r.command() assert len(res) >= 100 @@ -4631,6 +4927,7 @@ def test_command(self, r): assert "get" in cmds @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_command_getkeysandflags(self, r: valkey.Valkey): assert_resp_response( r, @@ -4646,6 +4943,7 @@ def test_command_getkeysandflags(self, r: valkey.Valkey): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("4.0.0") def test_module(self, r): with pytest.raises(valkey.exceptions.ModuleError) as excinfo: r.module_load("/some/fake/path") @@ -4656,6 +4954,7 @@ def test_module(self, r): assert "Error loading the extension." in str(excinfo.value) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_module_loadex(self, r: valkey.Valkey): with pytest.raises(valkey.exceptions.ModuleError) as excinfo: r.module_loadex("/some/fake/path") @@ -4665,6 +4964,7 @@ def test_module_loadex(self, r: valkey.Valkey): r.module_loadex("/some/fake/path", ["name", "value"], ["arg1", "arg2"]) assert "Error loading the extension." in str(excinfo.value) + @skip_if_server_version_lt("2.6.0") def test_restore(self, r): # standard restore key = "foo" @@ -4689,6 +4989,7 @@ def test_restore(self, r): assert r.restore(key2, 0, dumpdata) assert r.ttl(key2) == -1 + @skip_if_server_version_lt("5.0.0") def test_restore_idletime(self, r): key = "yayakey" r.set(key, "blee!") @@ -4697,6 +4998,7 @@ def test_restore_idletime(self, r): assert r.restore(key, 0, dumpdata, idletime=5) assert r.get(key) == b"blee!" + @skip_if_server_version_lt("5.0.0") def test_restore_frequency(self, r): key = "yayakey" r.set(key, "blee!") @@ -4706,6 +5008,7 @@ def test_restore_frequency(self, r): assert r.get(key) == b"blee!" @pytest.mark.onlynoncluster + @skip_if_server_version_lt("5.0.0") def test_replicaof(self, r): with pytest.raises(valkey.ResponseError): assert r.replicaof("NO ONE") @@ -4716,6 +5019,7 @@ def test_shutdown(self, r: valkey.Valkey): r.execute_command("SHUTDOWN", "NOSAVE") r.execute_command.assert_called_once_with("SHUTDOWN", "NOSAVE") + @skip_if_server_version_lt("7.0.0") def test_shutdown_with_params(self, r: valkey.Valkey): r.execute_command = mock.MagicMock() r.execute_command("SHUTDOWN", "SAVE", "NOW", "FORCE") @@ -4725,6 +5029,7 @@ def test_shutdown_with_params(self, r: valkey.Valkey): @pytest.mark.replica @pytest.mark.xfail(strict=False) + @skip_if_server_version_lt("2.8.0") def test_sync(self, r): r.flushdb() time.sleep(1) @@ -4733,6 +5038,7 @@ def test_sync(self, r): assert b"VALKEY" in res @pytest.mark.replica + @skip_if_server_version_lt("2.8.0") def test_psync(self, r): r2 = valkey.Valkey(port=6380, decode_responses=False) res = r2.psync(r2.client_id(), 1) diff --git a/tests/test_connection.py b/tests/test_connection.py index b4fe7107..9c60aa8f 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -18,6 +18,7 @@ from valkey.retry import Retry from valkey.utils import HIREDIS_AVAILABLE +from .conftest import skip_if_server_version_lt from .mocks import MockSocket @@ -32,6 +33,7 @@ def test_invalid_response(r): assert str(cm.value) == f"Protocol Error: {raw!r}" +@skip_if_server_version_lt("4.0.0") @pytest.mark.valkeymod def test_loading_external_modules(r): def inner(): diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py index 5cb1dba4..07806fa7 100644 --- a/tests/test_connection_pool.py +++ b/tests/test_connection_pool.py @@ -10,7 +10,7 @@ from valkey.connection import to_bool from valkey.utils import SSL_AVAILABLE -from .conftest import _get_client +from .conftest import _get_client, skip_if_server_version_lt from .test_pubsub import wait_for_message @@ -213,11 +213,13 @@ def test_port(self): assert pool.connection_class == valkey.Connection assert pool.connection_kwargs == {"host": "localhost", "port": 6380} + @skip_if_server_version_lt("6.0.0") def test_username(self): pool = valkey.ConnectionPool.from_url("valkey://myuser:@localhost") assert pool.connection_class == valkey.Connection assert pool.connection_kwargs == {"host": "localhost", "username": "myuser"} + @skip_if_server_version_lt("6.0.0") def test_quoted_username(self): pool = valkey.ConnectionPool.from_url( "valkey://%2Fmyuser%2F%2B name%3D%24+:@localhost" @@ -243,6 +245,7 @@ def test_quoted_password(self): "password": "/mypass/+ word=$+", } + @skip_if_server_version_lt("6.0.0") def test_username_and_password(self): pool = valkey.ConnectionPool.from_url("valkey://myuser:mypass@localhost") assert pool.connection_class == valkey.Connection @@ -377,11 +380,13 @@ def test_defaults(self): assert pool.connection_class == valkey.UnixDomainSocketConnection assert pool.connection_kwargs == {"path": "/socket"} + @skip_if_server_version_lt("6.0.0") def test_username(self): pool = valkey.ConnectionPool.from_url("unix://myuser:@/socket") assert pool.connection_class == valkey.UnixDomainSocketConnection assert pool.connection_kwargs == {"path": "/socket", "username": "myuser"} + @skip_if_server_version_lt("6.0.0") def test_quoted_username(self): pool = valkey.ConnectionPool.from_url( "unix://%2Fmyuser%2F%2B name%3D%24+:@/socket" @@ -504,6 +509,7 @@ def test_on_connect_error(self): assert not pool._available_connections[0]._sock @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.8") def test_busy_loading_disconnects_socket(self, r): """ If Valkey raises a LOADING error, the connection should be @@ -514,6 +520,7 @@ def test_busy_loading_disconnects_socket(self, r): assert not r.connection._sock @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.8") def test_busy_loading_from_pipeline_immediate_command(self, r): """ BusyLoadingErrors should raise from Pipelines that execute a @@ -528,6 +535,7 @@ def test_busy_loading_from_pipeline_immediate_command(self, r): assert not pool._available_connections[0]._sock @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.8") def test_busy_loading_from_pipeline(self, r): """ BusyLoadingErrors should be raised from a pipeline execution @@ -542,6 +550,7 @@ def test_busy_loading_from_pipeline(self, r): assert len(pool._available_connections) == 1 assert not pool._available_connections[0]._sock + @skip_if_server_version_lt("2.8.8") def test_read_only_error(self, r): "READONLY errors get turned into ReadOnlyError exceptions" with pytest.raises(valkey.ReadOnlyError): @@ -583,6 +592,13 @@ def test_connect_no_auth_configured(self, r): AuthenticationError should be raised when the server is not configured with auth but credentials are supplied by the user. """ + # Server < 6 + with pytest.raises(valkey.AuthenticationError): + r.execute_command( + "DEBUG", "ERROR", "ERR Client sent AUTH, but no password is set" + ) + + # Server >= 6 with pytest.raises(valkey.AuthenticationError): r.execute_command( "DEBUG", diff --git a/tests/test_function.py b/tests/test_function.py index e406287a..a4637872 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -1,7 +1,7 @@ import pytest from valkey.exceptions import ResponseError -from .conftest import assert_resp_response +from .conftest import assert_resp_response, skip_if_server_version_lt engine = "lua" lib = "mylib" @@ -15,6 +15,7 @@ redis.call('GET', keys[1]) end)" +@skip_if_server_version_lt("7.0.0") class TestFunction: @pytest.fixture(autouse=True) def reset_functions(self, r): diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 4bfc08e0..065f898c 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -4,7 +4,7 @@ import pytest import valkey -from .conftest import wait_for_command +from .conftest import skip_if_server_version_lt, wait_for_command class TestPipeline: @@ -388,6 +388,7 @@ def test_pipeline_with_bitfield(self, r): assert response == [True, [0, 0, 15, 15, 14], b"1"] @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.0.0") def test_pipeline_discard(self, r): # empty pipeline should raise an error with r.pipeline() as pipe: diff --git a/tests/test_pubsub.py b/tests/test_pubsub.py index 3d99e9e1..43b38d00 100644 --- a/tests/test_pubsub.py +++ b/tests/test_pubsub.py @@ -12,7 +12,7 @@ from valkey.exceptions import ConnectionError from valkey.utils import HIREDIS_AVAILABLE -from .conftest import _get_client, is_resp2_connection +from .conftest import _get_client, is_resp2_connection, skip_if_server_version_lt def wait_for_message( @@ -107,11 +107,13 @@ def test_pattern_subscribe_unsubscribe(self, r): self._test_subscribe_unsubscribe(**kwargs) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_shard_channel_subscribe_unsubscribe(self, r): kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel") self._test_subscribe_unsubscribe(**kwargs) @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_shard_channel_subscribe_unsubscribe_cluster(self, r): node_channels = defaultdict(int) p = r.pubsub() @@ -185,6 +187,7 @@ def test_resubscribe_to_patterns_on_reconnection(self, r): self._test_resubscribe_on_reconnection(**kwargs) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_resubscribe_to_shard_channels_on_reconnection(self, r): kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel") self._test_resubscribe_on_reconnection(**kwargs) @@ -245,11 +248,13 @@ def test_subscribe_property_with_patterns(self, r): self._test_subscribed_property(**kwargs) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_subscribe_property_with_shard_channels(self, r): kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel") self._test_subscribed_property(**kwargs) @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_subscribe_property_with_shard_channels_cluster(self, r): p = r.pubsub() keys = ["foo", "bar", "uni" + chr(4456) + "code"] @@ -309,6 +314,7 @@ def test_subscribe_property_with_shard_channels_cluster(self, r): # now we're finally unsubscribed assert p.subscribed is False + @skip_if_server_version_lt("7.0.0") def test_ignore_all_subscribe_messages(self, r): p = r.pubsub(ignore_subscribe_messages=True) @@ -328,6 +334,7 @@ def test_ignore_all_subscribe_messages(self, r): assert wait_for_message(p, func=get_func) is None assert p.subscribed is False + @skip_if_server_version_lt("7.0.0") def test_ignore_individual_subscribe_messages(self, r): p = r.pubsub() @@ -358,6 +365,7 @@ def test_sub_unsub_resub_patterns(self, r): self._test_sub_unsub_resub(**kwargs) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_sub_unsub_resub_shard_channels(self, r): kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel") self._test_sub_unsub_resub(**kwargs) @@ -377,6 +385,7 @@ def _test_sub_unsub_resub( assert p.subscribed is True @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_sub_unsub_resub_shard_channels_cluster(self, r): p = r.pubsub() key = "foo" @@ -404,6 +413,7 @@ def test_sub_unsub_all_resub_patterns(self, r): self._test_sub_unsub_all_resub(**kwargs) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_sub_unsub_all_resub_shard_channels(self, r): kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel") self._test_sub_unsub_all_resub(**kwargs) @@ -423,6 +433,7 @@ def _test_sub_unsub_all_resub( assert p.subscribed is True @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_sub_unsub_all_resub_shard_channels_cluster(self, r): p = r.pubsub() key = "foo" @@ -460,6 +471,7 @@ def test_published_message_to_channel(self, r): assert message == make_message("message", "foo", "test message") @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_published_message_to_shard_channel(self, r): p = r.pubsub() p.ssubscribe("foo") @@ -471,6 +483,7 @@ def test_published_message_to_shard_channel(self, r): assert message == make_message("smessage", "foo", "test message") @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_published_message_to_shard_channel_cluster(self, r): p = r.pubsub() p.ssubscribe("foo") @@ -514,6 +527,7 @@ def test_channel_message_handler(self, r): assert wait_for_message(p) is None assert self.message == make_message("message", "foo", "test message") + @skip_if_server_version_lt("7.0.0") def test_shard_channel_message_handler(self, r): p = r.pubsub(ignore_subscribe_messages=True) p.ssubscribe(foo=self.message_handler) @@ -543,6 +557,7 @@ def test_unicode_channel_message_handler(self, r): assert wait_for_message(p) is None assert self.message == make_message("message", channel, "test message") + @skip_if_server_version_lt("7.0.0") def test_unicode_shard_channel_message_handler(self, r): p = r.pubsub(ignore_subscribe_messages=True) channel = "uni" + chr(4456) + "code" @@ -586,6 +601,7 @@ def test_push_handler(self, r): assert self.message == ["my handler", [b"message", b"foo", b"test message"]] @pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only") + @skip_if_server_version_lt("7.0.0") def test_push_handler_sharded_pubsub(self, r): if is_resp2_connection(r): return @@ -634,6 +650,7 @@ def test_pattern_subscribe_unsubscribe(self, r): p.punsubscribe(self.pattern) assert wait_for_message(p) == self.make_message("punsubscribe", self.pattern, 0) + @skip_if_server_version_lt("7.0.0") def test_shard_channel_subscribe_unsubscribe(self, r): p = r.pubsub() p.ssubscribe(self.channel) @@ -665,6 +682,7 @@ def test_pattern_publish(self, r): "pmessage", self.channel, self.data, pattern=self.pattern ) + @skip_if_server_version_lt("7.0.0") def test_shard_channel_publish(self, r): p = r.pubsub() p.ssubscribe(self.channel) @@ -714,6 +732,7 @@ def test_pattern_message_handler(self, r): "pmessage", self.channel, new_data, pattern=self.pattern ) + @skip_if_server_version_lt("7.0.0") def test_shard_channel_message_handler(self, r): p = r.pubsub(ignore_subscribe_messages=True) p.ssubscribe(**{self.channel: self.message_handler}) @@ -757,6 +776,7 @@ def test_channel_subscribe(self, r): class TestPubSubSubcommands: @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.0") def test_pubsub_channels(self, r): p = r.pubsub() p.subscribe("foo", "bar", "baz", "quux") @@ -766,6 +786,7 @@ def test_pubsub_channels(self, r): assert all([channel in r.pubsub_channels() for channel in expected]) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.0.0") def test_pubsub_shardchannels(self, r): p = r.pubsub() p.ssubscribe("foo", "bar", "baz", "quux") @@ -775,6 +796,7 @@ def test_pubsub_shardchannels(self, r): assert all([channel in r.pubsub_shardchannels() for channel in expected]) @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_pubsub_shardchannels_cluster(self, r): channels = { b"foo": r.get_node_from_key("foo"), @@ -796,6 +818,7 @@ def test_pubsub_shardchannels_cluster(self, r): ) @pytest.mark.onlynoncluster + @skip_if_server_version_lt("2.8.0") def test_pubsub_numsub(self, r): p1 = r.pubsub() p1.subscribe("foo", "bar", "baz") @@ -812,6 +835,7 @@ def test_pubsub_numsub(self, r): channels = [(b"foo", 1), (b"bar", 2), (b"baz", 3)] assert r.pubsub_numsub("foo", "bar", "baz") == channels + @skip_if_server_version_lt("2.8.0") def test_pubsub_numpat(self, r): p = r.pubsub() p.psubscribe("*oo", "*ar", "b*z") @@ -820,6 +844,7 @@ def test_pubsub_numpat(self, r): assert r.pubsub_numpat() == 3 @pytest.mark.onlycluster + @skip_if_server_version_lt("7.0.0") def test_pubsub_shardnumsub(self, r): channels = { b"foo": r.get_node_from_key("foo"), @@ -846,6 +871,7 @@ def test_pubsub_shardnumsub(self, r): class TestPubSubPings: + @skip_if_server_version_lt("3.0.0") def test_send_pubsub_ping(self, r): p = r.pubsub(ignore_subscribe_messages=True) p.subscribe("foo") @@ -854,6 +880,7 @@ def test_send_pubsub_ping(self, r): type="pong", channel=None, data="", pattern=None ) + @skip_if_server_version_lt("3.0.0") def test_send_pubsub_ping_message(self, r): p = r.pubsub(ignore_subscribe_messages=True) p.subscribe("foo") @@ -865,6 +892,7 @@ def test_send_pubsub_ping_message(self, r): @pytest.mark.onlynoncluster class TestPubSubConnectionKilled: + @skip_if_server_version_lt("3.0.0") def test_connection_error_raised_when_connection_dies(self, r): p = r.pubsub() p.subscribe("foo") diff --git a/tests/test_scripting.py b/tests/test_scripting.py index 82a99a37..d697136a 100644 --- a/tests/test_scripting.py +++ b/tests/test_scripting.py @@ -1,5 +1,6 @@ import pytest import valkey +from tests.conftest import skip_if_server_version_lt from valkey import exceptions from valkey.commands.core import Script @@ -63,6 +64,7 @@ def test_eval_multiply(self, r): # 2 * 3 == 6 assert r.eval(multiply_script, 1, "a", 3) == 6 + @skip_if_server_version_lt("7.0.0") def test_eval_ro(self, r): r.set("a", "b") assert r.eval_ro("return redis.call('GET', KEYS[1])", 1, "a") == b"b" @@ -115,6 +117,7 @@ def test_eval_crossslot(self, r): with pytest.raises(exceptions.ValkeyClusterException): r.eval(script, 2, "A{foo}", "B{bar}") + @skip_if_server_version_lt("6.2.0") def test_script_flush_620(self, r): r.set("a", 2) r.script_load(multiply_script) @@ -149,6 +152,7 @@ def test_evalsha(self, r): # 2 * 3 == 6 assert r.evalsha(sha, 1, "a", 3) == 6 + @skip_if_server_version_lt("7.0.0") def test_evalsha_ro(self, r): r.set("a", "b") get_sha = r.script_load("return redis.call('GET', KEYS[1])") diff --git a/valkey/_parsers/base.py b/valkey/_parsers/base.py index a123acbc..0f9b10b1 100644 --- a/valkey/_parsers/base.py +++ b/valkey/_parsers/base.py @@ -35,11 +35,14 @@ "types, can't unload" ) # user send an AUTH cmd to a server without authorization configured -NO_AUTH_SET_ERROR = ( +NO_AUTH_SET_ERROR = { + # Server >= 6.0 "AUTH called without any password " "configured for the default user. Are you sure " - "your configuration is correct?" -) + "your configuration is correct?": AuthenticationError, + # Server < 6.0 + "Client sent AUTH, but no password is set": AuthenticationError, +} class BaseParser(ABC): @@ -59,7 +62,7 @@ class BaseParser(ABC): MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError, NO_SUCH_MODULE_ERROR: ModuleError, MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError, - NO_AUTH_SET_ERROR: AuthenticationError, + **NO_AUTH_SET_ERROR, }, "OOM": OutOfMemoryError, "WRONGPASS": AuthenticationError, diff --git a/valkey/asyncio/client.py b/valkey/asyncio/client.py index 9af8d087..960de835 100644 --- a/valkey/asyncio/client.py +++ b/valkey/asyncio/client.py @@ -171,7 +171,8 @@ class initializer. In the case of conflicting arguments, querystring if auto_close_connection_pool is not None: warnings.warn( DeprecationWarning( - '"auto_close_connection_pool" is deprecated. ' + '"auto_close_connection_pool" is deprecated ' + "since version 5.0.1. " "Please create a ConnectionPool explicitly and " "provide to the Valkey() constructor instead." ) @@ -258,7 +259,8 @@ def __init__( if auto_close_connection_pool is not None: warnings.warn( DeprecationWarning( - '"auto_close_connection_pool" is deprecated. ' + '"auto_close_connection_pool" is deprecated ' + "since version 5.0.1. " "Please create a ConnectionPool explicitly and " "provide to the Valkey() constructor instead." ) @@ -589,7 +591,7 @@ async def aclose(self, close_connection_pool: Optional[bool] = None) -> None: ): await self.connection_pool.disconnect() - @deprecated_function(reason="Use aclose() instead", name="close") + @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close") async def close(self, close_connection_pool: Optional[bool] = None) -> None: """ Alias for aclose(), for backwards compatibility @@ -853,12 +855,12 @@ async def aclose(self): self.patterns = {} self.pending_unsubscribe_patterns = set() - @deprecated_function(reason="Use aclose() instead", name="close") + @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close") async def close(self) -> None: """Alias for aclose(), for backwards compatibility""" await self.aclose() - @deprecated_function(reason="Use aclose() instead", name="reset") + @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="reset") async def reset(self) -> None: """Alias for aclose(), for backwards compatibility""" await self.aclose() diff --git a/valkey/asyncio/cluster.py b/valkey/asyncio/cluster.py index 5ee74621..4e7e3580 100644 --- a/valkey/asyncio/cluster.py +++ b/valkey/asyncio/cluster.py @@ -446,7 +446,7 @@ async def aclose(self) -> None: await self.nodes_manager.aclose() await self.nodes_manager.aclose("startup_nodes") - @deprecated_function(reason="Use aclose() instead", name="close") + @deprecated_function(version="5.0.0", reason="Use aclose() instead", name="close") async def close(self) -> None: """alias for aclose() for backwards compatibility""" await self.aclose() diff --git a/valkey/asyncio/connection.py b/valkey/asyncio/connection.py index 908f0dd9..77b329a8 100644 --- a/valkey/asyncio/connection.py +++ b/valkey/asyncio/connection.py @@ -40,6 +40,7 @@ from valkey.credentials import CredentialProvider, UsernamePasswordCredentialProvider from valkey.exceptions import ( AuthenticationError, + AuthenticationWrongNumberOfArgsError, ConnectionError, DataError, ResponseError, @@ -381,7 +382,16 @@ async def on_connect(self) -> None: # to check the health prior to the AUTH elif auth_args: await self.send_command("AUTH", *auth_args, check_health=False) - auth_response = await self.read_response() + + try: + auth_response = await self.read_response() + except AuthenticationWrongNumberOfArgsError: + # a username and password were specified but the server seems + # to be < 6.0.0 which expects a single password arg. + # retry auth with just the password. + # https://github.com/andymccurdy/redis-py/issues/1274 + await self.send_command("AUTH", auth_args[-1], check_health=False) + auth_response = await self.read_response() if str_if_bytes(auth_response) != "OK": raise AuthenticationError("Invalid Username or Password") diff --git a/valkey/commands/bf/commands.py b/valkey/commands/bf/commands.py index b8d192ff..0e70cd3a 100644 --- a/valkey/commands/bf/commands.py +++ b/valkey/commands/bf/commands.py @@ -332,7 +332,7 @@ def query(self, key, *items): """ # noqa return self.execute_command(TOPK_QUERY, key, *items) - @deprecated_function(reason="deprecated since valkeybloom 2.4.0") + @deprecated_function(version="4.4.0", reason="deprecated since valkeybloom 2.4.0") def count(self, key, *items): """ Return count for one `item` or more from `key`. diff --git a/valkey/commands/core.py b/valkey/commands/core.py index bdf41c77..cd071c31 100644 --- a/valkey/commands/core.py +++ b/valkey/commands/core.py @@ -3855,8 +3855,8 @@ def xpending_range( name: name of the stream. groupname: name of the consumer group. - idle: filter entries by their idle-time, given - in milliseconds (optional). + idle: available from version 6.2. filter entries by their + idle-time, given in milliseconds (optional). min: minimum stream ID. max: maximum stream ID. count: number of messages to return @@ -5364,9 +5364,12 @@ def script_flush( For more information see https://valkey.io/commands/script-flush """ + # Server pre 6 had no sync_type. if sync_type not in ["SYNC", "ASYNC", None]: raise DataError( - "SCRIPT FLUSH defaults to SYNC in valkey, or accepts SYNC/ASYNC." + "SCRIPT FLUSH defaults to SYNC in server version > 6.2, or " + "accepts SYNC/ASYNC. For older versions, " + "of valkey leave as None." ) if sync_type is None: pieces = [] diff --git a/valkey/commands/json/commands.py b/valkey/commands/json/commands.py index 0d493124..3b7ee09a 100644 --- a/valkey/commands/json/commands.py +++ b/valkey/commands/json/commands.py @@ -141,7 +141,7 @@ def numincrby(self, name: str, path: str, number: int) -> str: "JSON.NUMINCRBY", name, str(path), self._encode(number) ) - @deprecated_function(reason="deprecated since redisjson 1.0.0") + @deprecated_function(version="4.0.0", reason="deprecated since redisjson 1.0.0") def nummultby(self, name: str, path: str, number: int) -> str: """Multiply the numeric (integer or floating point) JSON value under ``path`` at key ``name`` with the provided ``number``. @@ -412,14 +412,20 @@ def debug( pieces.append(str(path)) return self.execute_command("JSON.DEBUG", *pieces) - @deprecated_function(reason="redisjson-py supported this, call get directly.") + @deprecated_function( + version="4.0.0", reason="redisjson-py supported this, call get directly." + ) def jsonget(self, *args, **kwargs): return self.get(*args, **kwargs) - @deprecated_function(reason="redisjson-py supported this, call get directly.") + @deprecated_function( + version="4.0.0", reason="redisjson-py supported this, call get directly." + ) def jsonmget(self, *args, **kwargs): return self.mget(*args, **kwargs) - @deprecated_function(reason="redisjson-py supported this, call get directly.") + @deprecated_function( + version="4.0.0", reason="redisjson-py supported this, call get directly." + ) def jsonset(self, *args, **kwargs): return self.set(*args, **kwargs) diff --git a/valkey/commands/search/commands.py b/valkey/commands/search/commands.py index bf97a75a..e16fc9d7 100644 --- a/valkey/commands/search/commands.py +++ b/valkey/commands/search/commands.py @@ -317,7 +317,9 @@ def _add_document_hash( return self.execute_command(*args) - @deprecated_function(reason="deprecated since redisearch 2.0, call hset instead") + @deprecated_function( + version="2.0.0", reason="deprecated since redisearch 2.0, call hset instead" + ) def add_document( self, doc_id: str, @@ -371,7 +373,9 @@ def add_document( **fields, ) - @deprecated_function(reason="deprecated since valkeyearch 2.0, call hset instead") + @deprecated_function( + version="2.0.0", reason="deprecated since valkeyearch 2.0, call hset instead" + ) def add_document_hash(self, doc_id, score=1.0, language=None, replace=False): """ Add a hash document to the index. diff --git a/valkey/connection.py b/valkey/connection.py index 57a9836e..29d3fbb0 100644 --- a/valkey/connection.py +++ b/valkey/connection.py @@ -24,6 +24,7 @@ from .credentials import CredentialProvider, UsernamePasswordCredentialProvider from .exceptions import ( AuthenticationError, + AuthenticationWrongNumberOfArgsError, ChildDeadlockedError, ConnectionError, DataError, @@ -375,7 +376,16 @@ def on_connect(self): # avoid checking health here -- PING will fail if we try # to check the health prior to the AUTH self.send_command("AUTH", *auth_args, check_health=False) - auth_response = self.read_response() + + try: + auth_response = self.read_response() + except AuthenticationWrongNumberOfArgsError: + # a username and password were specified but the server seems + # to be < 6.0.0 which expects a single password arg. + # retry auth with just the password. + # https://github.com/andymccurdy/redis-py/issues/1274 + self.send_command("AUTH", auth_args[-1], check_health=False) + auth_response = self.read_response() if str_if_bytes(auth_response) != "OK": raise AuthenticationError("Invalid Username or Password")