From 81d6b33bc5aa65bc76617b52ba5e33de04ce3144 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 ++++++++++++++--------------- test/integration/play/test_play.py | 161 +++++++++++--------------- 4 files changed, 158 insertions(+), 188 deletions(-) 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_play.py b/test/integration/play/test_play.py index 1b88e80ab..88a839d01 100644 --- a/test/integration/play/test_play.py +++ b/test/integration/play/test_play.py @@ -21,14 +21,6 @@ def test_instance(request, tmp_path): return inst -def test_play_unset_arg(tt_cmd, tmp_path): - # Testing with unset uri and .xlog or .snap file. - cmd = [tt_cmd, "play"] - rc, output = run_command_and_get_output(cmd, cwd=tmp_path) - assert rc == 1 - assert re.search(r"required to specify an URI and at least one .xlog or .snap file", output) - - def test_play_non_existent_uri(tt_cmd, tmp_path): # Testing with non-existent uri. cmd = [tt_cmd, "play", "127.0.0.1:0", "_"] @@ -37,100 +29,85 @@ def test_play_non_existent_uri(tt_cmd, tmp_path): assert re.search(r"no connection to the host", output) -def test_play_non_existent_file(tt_cmd, tmp_path, test_instance): - # Run play with non-existent file. - cmd = [tt_cmd, "play", "127.0.0.1:" + test_instance.port, "path-to-non-existent-file"] - rc, output = run_command_and_get_output(cmd, cwd=tmp_path) - assert rc == 1 - assert re.search(r"No such file or directory", output) - - -def test_play_test_remote_instance(tt_cmd, test_instance): +@pytest.mark.parametrize("args, play_error", [ + ( + # Testing with unset uri and .xlog or .snap file. + "", + "required to specify an URI and at least one .xlog or .snap file", + ), + ( + "path-to-non-existent-file", + "No such file or directory", + ), + ( + ("test.xlog", "--timestamp=abcdef", "--space=999"), + 'failed to parse a timestamp: parsing time "abcdef"', + ), + ( + ("test.xlog", "--timestamp=2024-11-14T14:02:36.abc", "--space=999"), + 'failed to parse a timestamp: parsing time "2024-11-14T14:02:36.abc"', + ), +]) +def test_play_test_remote_instance_timestamp_failed(tt_cmd, test_instance, args, play_error): # Play .xlog file to the remote instance. - cmd = [tt_cmd, "play", "127.0.0.1:" + test_instance.port, "test.xlog", "--space=999"] - rc, output = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) - assert rc == 0 - assert re.search(r"Play result: completed successfully", output) - - # Testing played .xlog file from the remote instance. - cmd = [tt_cmd, "cat", "00000000000000000000.xlog", "--space=999"] - rc, output = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) - assert rc == 0 - assert re.search(r"space_id: 999", output) - assert re.search(r"[1, 'Roxette', 1986]", output) - assert re.search(r"[2, 'Scorpions', 2015]", output) - assert re.search(r"[3, 'Ace of Base', 1993]", output) - - -TEST_PLAY_TIMESTAMP_PARAMS_CCONFIG = ("input, play_result, found, not_found") - - -def make_test_play_timestamp_param( - input="", - play_result=0, - found={}, - not_found={}, -): - return pytest.param(input, play_result, found, not_found) + cmd = [tt_cmd, "play", f"127.0.0.1:{test_instance.port}"] + cmd.extend(args) + rc, play_output = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) + assert rc == 1 + assert play_error in play_output -@pytest.mark.parametrize(TEST_PLAY_TIMESTAMP_PARAMS_CCONFIG, [ - make_test_play_timestamp_param( - input="abcdef", - play_result=1, - found={"failed to parse a timestamp: parsing time \"abcdef\""}, +@pytest.mark.parametrize("args, found, not_found", [ + ( + # Play .xlog file to the remote instance. + ("test.xlog", "--space=999"), + {"space_id: 999", "[1, 'Roxette', 1986]", "[2, 'Scorpions', 2015]", + "[3, 'Ace of Base', 1993]"}, + (), ), - make_test_play_timestamp_param( - input="2024-11-14T14:02:36.abc", - play_result=1, - found={"failed to parse a timestamp: parsing time \"2024-11-14T14:02:36.abc\""}, + ( + # Testing timestamp default value. + ("test.xlog", "--timestamp=", "--space=999"), + {"[3, 'Ace of Base', 1993]"}, + (), ), - make_test_play_timestamp_param( - input="", - play_result=0, - found={"[3, 'Ace of Base', 1993]"}, + ( + ("test.xlog", "--timestamp=1651130533.1534", "--space=999"), + {"space_id: 999", "[1, 'Roxette', 1986]", "[2, 'Scorpions', 2015]"}, + {"Ace of Base"}, ), - make_test_play_timestamp_param( - input="1651130533.1534", - play_result=0, - found={"space_id: 999", - "[1, 'Roxette', 1986]", - "[2, 'Scorpions', 2015]"}, - not_found={"Ace of Base"}, + ( + ("test.xlog", "--timestamp=1651130533.1534", "--space=999"), + {"space_id: 999", "[1, 'Roxette', 1986]", "[2, 'Scorpions', 2015]"}, + {"Ace of Base"}, ), - make_test_play_timestamp_param( - input="2022-04-28T07:22:13.1534+00:00", - play_result=0, - found={"space_id: 999", - "[1, 'Roxette', 1986]", - "[2, 'Scorpions', 2015]"}, - not_found={"Ace of Base"}, + ( + ("test.xlog", "--timestamp=2022-04-28T07:22:12+00:00", "--space=999"), + {"space_id: 999", "[1, 'Roxette', 1986]"}, + {"Scorpions", "Ace of Base"}, ), - make_test_play_timestamp_param( - input="2022-04-28T07:22:12+00:00", - play_result=0, - found={"space_id: 999", - "[1, 'Roxette', 1986]"}, - not_found={"Scorpions", - "Ace of Base"}, + ( + # Testing with multiple files specified + ("test.xlog", "test.snap"), + {"space_id: 999", "[1, 'Roxette', 1986]"}, + (), ), ]) -def test_play_test_remote_instance_timestamp(tt_cmd, test_instance, input, - play_result, found, not_found): +def test_play_remote_instance_timestamp_valid(tt_cmd, test_instance, + args, found, not_found): # Play .xlog file to the remote instance. - cmd = [tt_cmd, "play", "127.0.0.1:" + test_instance.port, "test.xlog", - "--timestamp={0}".format(input), "--space=999"] - rc, output = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) - assert rc == play_result - if play_result == 0: - # Testing played .xlog file from the remote instance. - cmd = [tt_cmd, "cat", "00000000000000000000.xlog", "--space=999"] - rc, output = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) - assert rc == 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) + cmd = [tt_cmd, "play", f"127.0.0.1:{test_instance.port}"] + cmd.extend(args) + rc, _ = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) + assert rc == 0 + # Testing played .xlog file from the remote instance. + cmd = [tt_cmd, "cat", "00000000000000000000.xlog", "--space=999"] + rc, cat_output = run_command_and_get_output(cmd, cwd=test_instance._tmpdir) + assert rc == 0 + for item in found: + assert item in cat_output + for item in not_found: + assert item not in cat_output @pytest.mark.parametrize("opts", [ @@ -153,7 +130,7 @@ def test_play_wrong_creds(tt_cmd, tmp_path, opts, test_instance): if "flags" in opts: cmd.extend(opts["flags"]) - rc, output = run_command_and_get_output(cmd, cwd=tmp_path, env=env) + rc, _ = run_command_and_get_output(cmd, cwd=tmp_path, env=env) assert rc != 0