From 51cc817f8e2bab01ee028fbc1a0029a3f314d576 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 4 Jul 2022 09:59:01 +0200 Subject: Merging upstream version 1.12.0. Signed-off-by: Daniel Baumann --- tests/cli_tests/test_command_input.py | 1 + tests/conftest.py | 17 ++++ tests/unittests/command_parse/test_base_token.py | 24 ++++++ tests/unittests/command_parse/test_connection.py | 25 +++++- .../unittests/command_parse/test_generic_parse.py | 39 +++++++++ tests/unittests/command_parse/test_geo.py | 36 +++++---- tests/unittests/command_parse/test_list_parse.py | 35 ++++++++ tests/unittests/command_parse/test_server.py | 71 +++++++++++++++++ tests/unittests/test_client.py | 47 ++++++++++- tests/unittests/test_completers.py | 93 ++++++++++++++++++++-- 10 files changed, 363 insertions(+), 25 deletions(-) (limited to 'tests') diff --git a/tests/cli_tests/test_command_input.py b/tests/cli_tests/test_command_input.py index f70ee3c..75917cb 100644 --- a/tests/cli_tests/test_command_input.py +++ b/tests/cli_tests/test_command_input.py @@ -61,6 +61,7 @@ def test_hello_command_is_not_supported(cli): cli.expect("IRedis currently not support RESP3") +@pytest.mark.xfail(reason="unstable, maybe due to github action's signal handling") def test_abort_reading_connection(cli): cli.sendline("blpop mylist 30") cli.send(chr(3)) diff --git a/tests/conftest.py b/tests/conftest.py index 4d7d643..b70bf95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import os +import re import tempfile from textwrap import dedent @@ -17,6 +18,22 @@ TIMEOUT = 2 HISTORY_FILE = ".iredis_history" +@pytest.fixture +def token_should_match(): + def match_func(token, tomatch): + assert re.fullmatch(token, tomatch) is not None + + return match_func + + +@pytest.fixture +def token_should_not_match(): + def match_func(token, tomatch): + assert re.fullmatch(token, tomatch) is None + + return match_func + + @pytest.fixture def judge_command(): def judge_command_func(input_text, expect): diff --git a/tests/unittests/command_parse/test_base_token.py b/tests/unittests/command_parse/test_base_token.py index b04722e..545bc92 100644 --- a/tests/unittests/command_parse/test_base_token.py +++ b/tests/unittests/command_parse/test_base_token.py @@ -54,3 +54,27 @@ def test_command_with_key_in_quotes(judge_command): judge_command( 'cluster keyslot "mykey "', {"command": "cluster keyslot", "key": '"mykey "'} ) + + +def test_timeout(token_should_match, token_should_not_match): + from iredis.redis_grammar import TIMEOUT + + token_should_match(TIMEOUT, "1.1") + token_should_match(TIMEOUT, "1.0") + token_should_match(TIMEOUT, ".1") + token_should_match(TIMEOUT, "123123.1123") + token_should_not_match(TIMEOUT, "1.") + token_should_not_match(TIMEOUT, ".") + token_should_not_match(TIMEOUT, ".a") + + +def test_lr_const(token_should_match, token_should_not_match): + from iredis.redis_grammar import LR_CONST + + token_should_match(LR_CONST, "left") + token_should_match(LR_CONST, "right") + token_should_match(LR_CONST, "LEFT") + token_should_match(LR_CONST, "RIGHT") + token_should_not_match(LR_CONST, "righ") + token_should_not_match(LR_CONST, "ab") + token_should_not_match(LR_CONST, "123") diff --git a/tests/unittests/command_parse/test_connection.py b/tests/unittests/command_parse/test_connection.py index 8952e53..2a1de26 100644 --- a/tests/unittests/command_parse/test_connection.py +++ b/tests/unittests/command_parse/test_connection.py @@ -71,7 +71,7 @@ def test_client_tracking(judge_command): "command": "CLIENT TRACKING", "on_off": "ON", "prefix_const": "PREFIX", - "prefix": "foo", + "prefixes": "foo", }, ) judge_command( @@ -80,7 +80,7 @@ def test_client_tracking(judge_command): "command": "CLIENT TRACKING", "on_off": "ON", "prefix_const": "PREFIX", - "prefix": "foo", + "prefixes": "foo", }, ) judge_command( @@ -89,9 +89,28 @@ def test_client_tracking(judge_command): "command": "CLIENT TRACKING", "on_off": "ON", "prefix_const": "PREFIX", - "prefix": "foo", + "prefixes": "foo", "bcast_const": "BCAST", "noloop_const": "NOLOOP", "optin_const": "OPTIN", }, ) + judge_command( + "CLIENT TRACKING ON PREFIX foo bar ok BCAST NOLOOP OPTIN", + { + "command": "CLIENT TRACKING", + "on_off": "ON", + "prefix_const": "PREFIX", + "prefixes": "foo bar ok", + "bcast_const": "BCAST", + "noloop_const": "NOLOOP", + "optin_const": "OPTIN", + }, + ) + + +def test_client_pause(judge_command): + judge_command( + "CLIENT PAUSE 20 WRITE", + {"command": "CLIENT PAUSE", "timeout": "20", "pause_type": "WRITE"}, + ) diff --git a/tests/unittests/command_parse/test_generic_parse.py b/tests/unittests/command_parse/test_generic_parse.py index f41d887..6c4b0f3 100644 --- a/tests/unittests/command_parse/test_generic_parse.py +++ b/tests/unittests/command_parse/test_generic_parse.py @@ -177,3 +177,42 @@ def test_restore(judge_command): "value": '"\n\x17\x17\x00\x00\x00\x12\x00\x00\x00\x03\x00\x00\xc0\x01\x00\x04\xc0\x02\x00\x04\xc0\x03\x00\xff\x04\x00u#<\xc0;.\xe9\xdd"', # noqa }, ) + + +def test_copy(judge_command): + judge_command( + "COPY foo bar DB 3 REPLACE", + { + "command": "COPY", + "key": ["foo", "bar"], + "db_const": "DB", + "index": "3", + "replace_const": "REPLACE", + }, + ) + judge_command( + "COPY foo bar REPLACE", + {"command": "COPY", "key": ["foo", "bar"], "replace_const": "REPLACE"}, + ) + judge_command("COPY foo bar", {"command": "COPY", "key": ["foo", "bar"]}) + + +def test_getex(judge_command): + judge_command("GETEX foo", {"command": "GETEX", "key": "foo"}) + judge_command( + "GETEX bar ex 5", + {"command": "GETEX", "key": "bar", "expiration": "ex", "millisecond": "5"}, + ) + judge_command( + "GETEX bar px 5", + {"command": "GETEX", "key": "bar", "expiration": "px", "millisecond": "5"}, + ) + judge_command( + "GETEX bar pxat 5", + {"command": "GETEX", "key": "bar", "pxat_const": "pxat", "timestampms": "5"}, + ) + judge_command( + "GETEX bar exat 5", + {"command": "GETEX", "key": "bar", "exat_const": "exat", "timestamp": "5"}, + ) + judge_command("GETEX bar ex 5 exat 5", None) diff --git a/tests/unittests/command_parse/test_geo.py b/tests/unittests/command_parse/test_geo.py index 8a8d4c7..8502696 100644 --- a/tests/unittests/command_parse/test_geo.py +++ b/tests/unittests/command_parse/test_geo.py @@ -9,31 +9,37 @@ def test_geoadd(judge_command): "member": '"Catania"', }, ) + judge_command( + 'GEOADD Sicily NX CH 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"', + { + "command": "GEOADD", + "condition": "NX", + "changed": "CH", + "key": "Sicily", + "longitude": "15.087269", + "latitude": "37.502669", + "member": '"Catania"', + }, + ) -def test_georadiusbymember(judge_command): +def test_geosearch(judge_command): judge_command( - "GEORADIUSBYMEMBER Sicily Agrigento 100 km", + "GEOSEARCH Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC WITHCOORD WITHDIST", { - "command": "GEORADIUSBYMEMBER", + "command": "GEOSEARCH", "key": "Sicily", - "member": "Agrigento", - "float": "100", - "distunit": "km", + "any": "FROMLONLAT 15 37 BYBOX 400 400 km ASC WITHCOORD WITHDIST", }, ) -def test_georadius(judge_command): +def test_geosearchstore(judge_command): judge_command( - "GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD ", + "GEOSEARCHSTORE key2 Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC COUNT 3 STOREDIST", { - "command": "GEORADIUS", - "key": "Sicily", - "longitude": "15", - "latitude": "37", - "float": "200", - "distunit": "km", - "geochoice": "WITHCOORD", + "command": "GEOSEARCHSTORE", + "key": ["Sicily", "key2"], + "any": "FROMLONLAT 15 37 BYBOX 400 400 km ASC COUNT 3 STOREDIST", }, ) diff --git a/tests/unittests/command_parse/test_list_parse.py b/tests/unittests/command_parse/test_list_parse.py index e60d7a2..69a294e 100644 --- a/tests/unittests/command_parse/test_list_parse.py +++ b/tests/unittests/command_parse/test_list_parse.py @@ -41,6 +41,18 @@ def test_brpoplpush(judge_command): judge_command("BRPOPLPUSH list1 list2 -1", None) +def test_brpoplpush_with_double_timeout(judge_command): + judge_command( + "BRPOPLPUSH list1 list2 10.0", + {"command": "BRPOPLPUSH", "key": "list1", "newkey": "list2", "timeout": "10.0"}, + ) + judge_command( + "BRPOPLPUSH list1 list2 .2", + {"command": "BRPOPLPUSH", "key": "list1", "newkey": "list2", "timeout": ".2"}, + ) + judge_command("BRPOPLPUSH list1 list2 12.", None) + + def test_linsert(judge_command): judge_command( 'LINSERT mylist BEFORE "World" "There"', @@ -106,3 +118,26 @@ def test_lpos(judge_command): "rank": "-1", }, ) + + +def test_blmove(judge_command): + judge_command( + "blmove list1 list2 left right 1.2", + { + "command": "blmove", + "key": ["list1", "list2"], + "lr_const": ["left", "right"], + "timeout": "1.2", + }, + ) + judge_command( + "blmove list1 list2 right right .2", + { + "command": "blmove", + "key": ["list1", "list2"], + "lr_const": ["right", "right"], + "timeout": ".2", + }, + ) + judge_command("blmove list1 list2 right right", None) + judge_command("blmove list1 right right 1", None) diff --git a/tests/unittests/command_parse/test_server.py b/tests/unittests/command_parse/test_server.py index 5129e5f..5ecccbf 100644 --- a/tests/unittests/command_parse/test_server.py +++ b/tests/unittests/command_parse/test_server.py @@ -31,6 +31,20 @@ def test_client_list(judge_command): {"command": "client list", "type_const": "TYPE", "conntype": "REPLICA"}, ) + judge_command( + "client list TYPE REPLICA id 1 2 3", + { + "command": "client list", + "type_const": "TYPE", + "conntype": "REPLICA", + "clientids": "1 2 3", + }, + ) + judge_command( + "client list ID 1 2 3", + {"command": "client list", "clientids": "1 2 3"}, + ) + def test_configset(judge_command): judge_command( @@ -71,6 +85,18 @@ def test_client_kill(judge_command): "CLIENT KILL 127.0.0.1:12345 ", {"command": "CLIENT KILL", "ip_port": "127.0.0.1:12345"}, ) + judge_command( + "CLIENT KILL ADDR 127.0.0.1:12345 ", + {"command": "CLIENT KILL", "ip_port": "127.0.0.1:12345", "addr": "ADDR"}, + ) + judge_command( + "CLIENT KILL LADDR 127.0.0.1:12345 ", + {"command": "CLIENT KILL", "ip_port": "127.0.0.1:12345", "laddr": "LADDR"}, + ) + judge_command( + "CLIENT KILL USER myuser", + {"command": "CLIENT KILL", "const_user": "USER", "username": "myuser"}, + ) judge_command( "CLIENT KILL id 123455 type pubsub skipme no", { @@ -199,3 +225,48 @@ def test_acl_setuser(judge_command): def test_acl_getuser(judge_command): judge_command("acl getuser alan", {"command": "acl getuser", "username": "alan"}) judge_command("acl getuser", None) + + +def test_failover(judge_command): + judge_command( + "failover to 10.0.0.5 7379 abort timeout 101", + { + "command": "failover", + "to_const": "to", + "host": "10.0.0.5", + "port": "7379", + "abort_const": "abort", + "timeout_const": "timeout", + "millisecond": "101", + }, + ) + judge_command( + "failover abort timeout 101", + { + "command": "failover", + "abort_const": "abort", + "timeout_const": "timeout", + "millisecond": "101", + }, + ) + judge_command( + "failover timeout 101", + { + "command": "failover", + "timeout_const": "timeout", + "millisecond": "101", + }, + ) + judge_command( + "failover to 10.0.0.5 7379 force abort timeout 101", + { + "command": "failover", + "to_const": "to", + "force": "force", + "host": "10.0.0.5", + "port": "7379", + "abort_const": "abort", + "timeout_const": "timeout", + "millisecond": "101", + }, + ) diff --git a/tests/unittests/test_client.py b/tests/unittests/test_client.py index f27587b..5c6628f 100644 --- a/tests/unittests/test_client.py +++ b/tests/unittests/test_client.py @@ -33,7 +33,7 @@ def test_send_command(_input, command_name, expect_args): client = Client("127.0.0.1", "6379", None) client.execute = MagicMock() next(client.send_command(_input, None)) - args, kwargs = client.execute.call_args + args, _ = client.execute.call_args assert args == (command_name, *expect_args) @@ -511,7 +511,7 @@ def test_reissue_command_on_redis_cluster_with_password_in_dsn( assert call_args[0].password == "bar" -def test_version_parse(iredis_client): +def test_version_parse_for_auth(iredis_client): """ fix: https://github.com/laixintao/iredis/issues/418 """ @@ -521,3 +521,46 @@ def test_version_parse(iredis_client): assert command2syntax["AUTH"] == "command_password" iredis_client.auth_compat("5.0.14.1") assert command2syntax["AUTH"] == "command_password" + + +@pytest.mark.parametrize( + "info, version", + [ + ( + ( + "# Server\r\nredis_version:df--128-NOTFOUND\r\n" + "redis_mode:standalone\r\narch_bits:64" + ), + "df--128-NOTFOUND", + ), + ( + ( + "# Server\r\nredis_version:6.2.5\r\n" + "redis_git_sha1:00000000\r\n" + "redis_git_dirty:0\r\n" + "redis_build_id:915e5480613bc9b6\r\n" + "redis_mode:standalone " + ), + "6.2.5", + ), + ( + ( + "# Server\r\nredis_version:5.0.14.1\r\n" + "redis_git_sha1:00000000\r\nredis_git_dirty:0\r\n" + "redis_build_id:915e5480613bc9b6\r\n" + "redis_mode:standalone " + ), + "5.0.14.1", + ), + ], +) +def test_version_path(info, version): + with patch("iredis.client.config") as mock_config: + mock_config.no_info = True + mock_config.pager = "less" + mock_config.version = "5.0.0" + with patch("iredis.client.Client.execute") as mock_execute: + mock_execute.return_value = info + client = Client("127.0.0.1", "6379", None) + client.get_server_info() + assert mock_config.version == version diff --git a/tests/unittests/test_completers.py b/tests/unittests/test_completers.py index d0344fd..5441b0e 100644 --- a/tests/unittests/test_completers.py +++ b/tests/unittests/test_completers.py @@ -183,7 +183,7 @@ def test_group_completer(): @patch("iredis.completers.pendulum.now") def test_timestamp_completer_humanize_time_completion(fake_now): fake_now.return_value = pendulum.from_timestamp(1578487013) - c = TimestampCompleter() + c = TimestampCompleter(is_milliseconds=True, future_time=False) fake_document = MagicMock() fake_document.text = fake_document.text_before_cursor = "30" @@ -260,8 +260,87 @@ def test_timestamp_completer_humanize_time_completion(fake_now): ] +@patch("iredis.completers.pendulum.now") +def test_timestamp_completer_humanize_time_completion_seconds(fake_now): + fake_now.return_value = pendulum.from_timestamp(1578487013) + c = TimestampCompleter(is_milliseconds=False, future_time=False) + + fake_document = MagicMock() + fake_document.text = fake_document.text_before_cursor = "30" + completions = list(c.get_completions(fake_document, None)) + + assert completions == [ + Completion( + text="1575895013", + start_position=-2, + display=FormattedText([("", "1575895013")]), + display_meta="30 days ago (2019-12-09 12:36:53)", + ), + Completion( + text="1578379013", + start_position=-2, + display=FormattedText([("", "1578379013")]), + display_meta="30 hours ago (2020-01-07 06:36:53)", + ), + Completion( + text="1578485213", + start_position=-2, + display=FormattedText([("", "1578485213")]), + display_meta="30 minutes ago (2020-01-08 12:06:53)", + ), + Completion( + text="1578486983", + start_position=-2, + display=FormattedText([("", "1578486983")]), + display_meta="30 seconds ago (2020-01-08 12:36:23)", + ), + ] + + +@patch("iredis.completers.pendulum.now") +def test_timestamp_completer_humanize_time_completion_seconds_future_time(fake_now): + fake_now.return_value = pendulum.from_timestamp(1578487013) + c = TimestampCompleter(is_milliseconds=False, future_time=True) + + fake_document = MagicMock() + fake_document.text = fake_document.text_before_cursor = "30" + completions = list(c.get_completions(fake_document, None)) + + print(completions) + for c in completions: + print(c.text) + print(c.display) + print(c.display_meta) + assert completions == [ + Completion( + text="1578487043", + start_position=-2, + display=FormattedText([("", "1578487043")]), + display_meta="30 seconds later (2020-01-08 12:37:23)", + ), + Completion( + text="1578488813", + start_position=-2, + display=FormattedText([("", "1578488813")]), + display_meta="30 minutes later (2020-01-08 13:06:53)", + ), + Completion( + text="1578595013", + start_position=-2, + display=FormattedText([("", "1578595013")]), + display_meta="30 hours later (2020-01-09 18:36:53)", + ), + Completion( + text="1581079013", + start_position=-2, + display=FormattedText([("", "1581079013")]), + display_meta="30 days later (2020-02-07 12:36:53)", + ), + ] + + def test_timestamp_completer_datetime_format_time_completion(): - c = TimestampCompleter() + c = TimestampCompleter(is_milliseconds=True, future_time=False) fake_document = MagicMock() fake_document.text = fake_document.text_before_cursor = "2020-02-07" completions = list(c.get_completions(fake_document, None)) @@ -299,14 +378,18 @@ def test_completion_casing(): completion.text for completion in c.get_completions(fake_document, None) ] == [ "get", + "getex", "getset", + "getdel", "getbit", "geopos", "geoadd", "geohash", "geodist", "getrange", + "geosearch", "georadius", + "geosearchstore", "georadiusbymember", ] @@ -314,19 +397,19 @@ def test_completion_casing(): fake_document.text = fake_document.text_before_cursor = "GET" assert [ completion.text for completion in c.get_completions(fake_document, None) - ] == ["GET", "GETSET", "GETBIT", "GETRANGE"] + ] == ["GET", "GETEX", "GETSET", "GETDEL", "GETBIT", "GETRANGE"] c = IRedisCompleter(completion_casing="upper") fake_document.text = fake_document.text_before_cursor = "get" assert [ completion.text for completion in c.get_completions(fake_document, None) - ] == ["GET", "GETSET", "GETBIT", "GETRANGE"] + ] == ["GET", "GETEX", "GETSET", "GETDEL", "GETBIT", "GETRANGE"] c = IRedisCompleter(completion_casing="lower") fake_document.text = fake_document.text_before_cursor = "GET" assert [ completion.text for completion in c.get_completions(fake_document, None) - ] == ["get", "getset", "getbit", "getrange"] + ] == ["get", "getex", "getset", "getdel", "getbit", "getrange"] def test_username_completer(): -- cgit v1.2.3