From 084955fd034d4b2fd5f0cfd083f2d8563f0bf1bc Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Wed, 20 Nov 2024 14:24:40 +0300 Subject: [PATCH] cat/play: tip that multiple files can be processed Closes #1018 Closes #1019 --- cli/cmd/cat.go | 4 +- cli/cmd/play.go | 4 +- test/integration/cat/test_cat.py | 177 +++++++++--------- .../play/test_file/remote_instance_cfg.lua | 1 + .../play/test_file/timestamp/create_space.lua | 3 + .../play/test_file/timestamp/get_data.lua | 1 + .../play/test_file/timestamp/timestamp.snap | Bin 0 -> 5052 bytes .../play/test_file/timestamp/timestamp.xlog | Bin 0 -> 688 bytes test/integration/play/test_play.py | 157 ++++++---------- 9 files changed, 156 insertions(+), 191 deletions(-) create mode 100644 test/integration/play/test_file/timestamp/create_space.lua create mode 100644 test/integration/play/test_file/timestamp/get_data.lua create mode 100644 test/integration/play/test_file/timestamp/timestamp.snap create mode 100644 test/integration/play/test_file/timestamp/timestamp.xlog diff --git a/cli/cmd/cat.go b/cli/cmd/cat.go index a3730b541..3c1a41adc 100644 --- a/cli/cmd/cat.go +++ b/cli/cmd/cat.go @@ -33,7 +33,7 @@ var catFlags = checkpoint.Opts{ func NewCatCmd() *cobra.Command { var catCmd = &cobra.Command{ Use: "cat ...", - Short: "Print into stdout the contents of .snap/.xlog files", + Short: "Print into stdout the contents of .snap/.xlog FILE(s)", Run: func(cmd *cobra.Command, args []string) { cmdCtx.CommandName = cmd.Name() err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo, @@ -41,7 +41,7 @@ func NewCatCmd() *cobra.Command { util.HandleCmdErr(cmd, err) }, Example: "tt cat /path/to/xlog --timestamp 2024-11-13T14:02:36.818700000+00:00\n" + - " tt cat /path/to/snap --timestamp=1731592956.818", + " tt cat /path/to/file.xlog /path/to/file.snap --timestamp=1731592956.818", } catCmd.Flags().Uint64Var(&catFlags.To, "to", catFlags.To, diff --git a/cli/cmd/play.go b/cli/cmd/play.go index 932cf26d2..ca8ccad63 100644 --- a/cli/cmd/play.go +++ b/cli/cmd/play.go @@ -40,7 +40,7 @@ var ( func NewPlayCmd() *cobra.Command { var playCmd = &cobra.Command{ Use: "play ...", - Short: "Play the contents of .snap/.xlog files to another Tarantool instance", + Short: "Play the contents of .snap/.xlog FILE(s) to another Tarantool instance", Run: func(cmd *cobra.Command, args []string) { cmdCtx.CommandName = cmd.Name() err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo, @@ -48,7 +48,7 @@ func NewPlayCmd() *cobra.Command { util.HandleCmdErr(cmd, err) }, Example: "tt play uri /path/to/xlog --timestamp 2024-11-13T14:02:36.818700000+00:00\n" + - " tt play uri /path/to/xlog --timestamp=1731592956.818", + " tt play uri /path/to/file.xlog /path/to/file.snap --timestamp=1731592956.818", } playCmd.Flags().StringVarP(&playUsername, "username", "u", "", "username") diff --git a/test/integration/cat/test_cat.py b/test/integration/cat/test_cat.py index 9075302f5..7de279b99 100644 --- a/test/integration/cat/test_cat.py +++ b/test/integration/cat/test_cat.py @@ -1,5 +1,6 @@ +import datetime +import io import os -import re import shutil import pytest @@ -7,116 +8,108 @@ from utils import run_command_and_get_output -def test_cat_unset_arg(tt_cmd, tmp_path): - # Testing with unset .xlog or .snap file. - cmd = [tt_cmd, "cat"] - rc, output = run_command_and_get_output(cmd, cwd=tmp_path) - assert rc == 1 - assert re.search(r"it is required to specify at least one .xlog or .snap file", output) - +@pytest.mark.parametrize("args, found", [ + ( + # Testing with unset .xlog or .snap file. + (), + "it is required to specify at least one .xlog or .snap file", + ), + ( + "path-to-non-existent-file", + "No such file or directory", + ), +]) +def test_cat_args_tests_failed(tt_cmd, tmp_path, args, found): + # Copy the .xlog file to the "run" directory. + test_xlog_file = os.path.join(os.path.dirname(__file__), "test_file", "test.xlog") + test_snap_file = os.path.join(os.path.dirname(__file__), "test_file", "test.snap") + shutil.copy(test_xlog_file, tmp_path) + shutil.copy(test_snap_file, tmp_path) -def test_cat_non_existent_file(tt_cmd, tmp_path): - # Testing with non-existent .xlog or .snap file. - cmd = [tt_cmd, "cat", "path-to-non-existent-file"] + cmd = [tt_cmd, "cat"] + cmd.extend(args) rc, output = run_command_and_get_output(cmd, cwd=tmp_path) assert rc == 1 - assert re.search(r"No such file or directory", output) + assert found in output -def test_cat_snap_file(tt_cmd, tmp_path): - # Copy the .snap file to the "run" directory. - test_app_path = os.path.join(os.path.dirname(__file__), "test_file", "test.snap") - shutil.copy(test_app_path, tmp_path) +@pytest.mark.parametrize("args, found", [ + ( + ("test.snap", "--show-system", "--space=320", "--space=296", "--from=423", "--to=513"), + ("lsn: 423", "lsn: 512", "space_id: 320", "space_id: 296"), + ), + ( + ("test.xlog", "--show-system", "--replica=1"), + ("replica_id: 1"), + ), + ( + ("test.xlog", "test.snap"), + ('Result of cat: the file "test.xlog" is processed below', + 'Result of cat: the file "test.snap" is processed below'), + ), +]) +def test_cat_args_tests_successed(tt_cmd, tmp_path, args, found): + # Copy the .xlog file to the "run" directory. + test_xlog_file = os.path.join(os.path.dirname(__file__), "test_file", "test.xlog") + test_snap_file = os.path.join(os.path.dirname(__file__), "test_file", "test.snap") + shutil.copy(test_xlog_file, tmp_path) + shutil.copy(test_snap_file, tmp_path) - # Testing .snap file. - cmd = [ - tt_cmd, "cat", "test.snap", "--show-system", - "--space=320", "--space=296", "--from=423", "--to=513" - ] + cmd = [tt_cmd, "cat"] + cmd.extend(args) rc, output = run_command_and_get_output(cmd, cwd=tmp_path) assert rc == 0 - assert re.search(r"lsn: 423", output) - assert re.search(r"lsn: 512", output) - assert re.search(r"space_id: 320", output) - assert re.search(r"space_id: 296", output) + for item in found: + assert item in output -def test_cat_xlog_file(tt_cmd, tmp_path): +@pytest.mark.parametrize("input, error", [ + ( + "abcdef", + 'failed to parse a timestamp: parsing time "abcdef"', + ), + ( + "2024-11-14T14:02:36.abc", + 'failed to parse a timestamp: parsing time "2024-11-14T14:02:36.abc"', + ), +]) +def test_cat_test_timestamp_failed(tt_cmd, tmp_path, input, error): # Copy the .xlog file to the "run" directory. - test_app_path = os.path.join(os.path.dirname(__file__), "test_file", "test.xlog") + test_app_path = os.path.join(os.path.dirname(__file__), "test_file", "timestamp.xlog") shutil.copy(test_app_path, tmp_path) - # Testing .xlog file. - cmd = [tt_cmd, "cat", "test.xlog", "--show-system", "--replica=1"] + cmd = [tt_cmd, "cat", "timestamp.xlog", f"--timestamp={input}"] rc, output = run_command_and_get_output(cmd, cwd=tmp_path) - assert rc == 0 - assert re.search(r"replica_id: 1", output) - - -TEST_CAT_TIMESTAMP_PARAMS_CCONFIG = ("input, cat_result, found, not_found") - - -def make_test_cat_timestamp_param( - input="", - cat_result=0, - found={}, - not_found={}, -): - return pytest.param(input, cat_result, found, not_found) + assert rc == 1 + assert error in output -@pytest.mark.parametrize(TEST_CAT_TIMESTAMP_PARAMS_CCONFIG, [ - make_test_cat_timestamp_param( - input="abcdef", - cat_result=1, - found={"failed to parse a timestamp: parsing time \"abcdef\""}, - ), - make_test_cat_timestamp_param( - input="2024-11-14T14:02:36.abc", - cat_result=1, - found={"failed to parse a timestamp: parsing time \"2024-11-14T14:02:36.abc\""}, - ), - make_test_cat_timestamp_param( - input="", - cat_result=0, - found={"lsn: 12"}, - ), - make_test_cat_timestamp_param( - input="1731592956.8182", - cat_result=0, - found={"lsn: 6", - "timestamp: 1731592956.8181"}, - not_found={"lsn: 8", - "timestamp: 1731592956.8184"}, - ), - make_test_cat_timestamp_param( - input="2024-11-14T14:02:36.818299999Z", - cat_result=0, - found={"lsn: 6", - "timestamp: 1731592956.8181"}, - not_found={"lsn: 8", - "timestamp: 1731592956.8184"}, - ), - make_test_cat_timestamp_param( - input="2024-11-14T14:02:35+00:00", - cat_result=0, - not_found={"lsn: 6", - "timestamp: 1731592956.8181", - "lsn: 8", - "timestamp: 1731592956.8184"}, - ), +@pytest.mark.parametrize("input", [ + 1731592956.1182, + 1731592956.8182, + "2024-11-14T14:02:36.818+00:00", + "2024-11-14T14:02:35+00:00", ]) -def test_cat_test_remote_instance_timestamp(tt_cmd, tmp_path, input, - cat_result, found, not_found): +def test_cat_test_timestamp_successed(tt_cmd, tmp_path, input): # Copy the .xlog file to the "run" directory. test_app_path = os.path.join(os.path.dirname(__file__), "test_file", "timestamp.xlog") shutil.copy(test_app_path, tmp_path) - cmd = [tt_cmd, "cat", "timestamp.xlog", "--timestamp={0}".format(input)] + cmd = [tt_cmd, "cat", "timestamp.xlog", f"--timestamp={input}"] rc, output = run_command_and_get_output(cmd, cwd=tmp_path) - assert rc == cat_result - if cat_result == 0: - for item in found: - assert re.search(r"{0}".format(item), output) - for item in not_found: - assert not re.search(r"{0}".format(item), output) + assert rc == 0 + + # Convert input to timestamp + input_ts = 0 + if type(input) is float or type(input) is int: + input_ts = float(input) + if type(input) is str: + input_ts = float(datetime.datetime.fromisoformat(input).timestamp()) + + # Compare input value and record's timestamp + buf = io.StringIO(output) + while (line := buf.readline()) != "": + if "timestamp:" in line: + index = line.find(':') + record_ts = line[index+1:].strip() + assert input_ts > float(record_ts) diff --git a/test/integration/play/test_file/remote_instance_cfg.lua b/test/integration/play/test_file/remote_instance_cfg.lua index 0cdd86e14..74b373d0e 100644 --- a/test/integration/play/test_file/remote_instance_cfg.lua +++ b/test/integration/play/test_file/remote_instance_cfg.lua @@ -22,6 +22,7 @@ local function configure_instance() tester:create_index('primary', {type = 'tree', parts = {'id'}}) box.schema.user.grant('guest', 'read,write', 'space', 'tester') box.schema.user.create('test_user', { password = 'secret' }) + box.schema.user.grant('test_user', 'execute', 'universe') box.schema.user.grant('test_user', 'super') end diff --git a/test/integration/play/test_file/timestamp/create_space.lua b/test/integration/play/test_file/timestamp/create_space.lua new file mode 100644 index 000000000..5bfdaaf64 --- /dev/null +++ b/test/integration/play/test_file/timestamp/create_space.lua @@ -0,0 +1,3 @@ +box.schema.space.create('test', { id = 512 }) + +return box.space.test:create_index('0') diff --git a/test/integration/play/test_file/timestamp/get_data.lua b/test/integration/play/test_file/timestamp/get_data.lua new file mode 100644 index 000000000..4eb64d943 --- /dev/null +++ b/test/integration/play/test_file/timestamp/get_data.lua @@ -0,0 +1 @@ +return box.space.test:select() diff --git a/test/integration/play/test_file/timestamp/timestamp.snap b/test/integration/play/test_file/timestamp/timestamp.snap new file mode 100644 index 0000000000000000000000000000000000000000..84c8fc7c0e950a302db19364c1f2d49dc94bac57 GIT binary patch literal 5052 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 S(72#kP!vTDDO~~85UuS&sIXlC literal 0 HcmV?d00001 diff --git a/test/integration/play/test_file/timestamp/timestamp.xlog b/test/integration/play/test_file/timestamp/timestamp.xlog new file mode 100644 index 0000000000000000000000000000000000000000..eb1452baaa6f814de3126cf85e25c370004e088e GIT binary patch literal 688 zcmY+=%_{_P9LMqB%svlAIe5rnHaD4V*4Wvd$$`{LDN!UOWA?WXVX!+>3bmvhT-d~k zgFGflLR=h_1MQwTC>OC9cEBn3|zul~<&2EU1fEkhPF3G=(%t7#)>0L6l;VNIWD(4hjW{dF^v+lKH-oNo5qn)X-T{DgG!oFpsrY(H8mP3b@kx3;C9J9A- zP(d|%n&~Y^aqG6J_ws*>MgAOv0M|ylvov-RJkS7`WdPrQy@qJ)B6x5cu!9A>yz+5M zV>iJ=I$);*@K3IALt_uY!xeyCPQbrgm31`s5O`v}(U&)VPcxB*+q s>TMcx1miQnUJu}w=uw!)B?Ko_z&