From e8f6cd7f5ba4d1fe66d5bf4ad189cf91e72bb34f Mon Sep 17 00:00:00 2001 From: Saugat Pachhai Date: Wed, 27 May 2020 09:13:26 +0000 Subject: [PATCH] run: raise error when command is not specified (#3850) * run: raise error when command is not specified * tests: adjust tests/func/plots/test_plots.py * tests: adjust tests/func/plots/test_diff.py * tests: add command to metrics/plots/params * tests: fix test_remote.py * tests: fix test_ls.py * tests: fix test_repro* and test_run* * tests: fix script in test_ls, was failing on Win --- dvc/repo/run.py | 3 + tests/func/metrics/conftest.py | 1 + tests/func/metrics/test_diff.py | 82 +++++----- tests/func/metrics/test_show.py | 42 +++--- tests/func/params/test_diff.py | 16 +- tests/func/params/test_show.py | 20 ++- tests/func/plots/__init__.py | 30 ++++ tests/func/plots/conftest.py | 1 + tests/func/plots/test_diff.py | 28 +++- tests/func/plots/test_plots.py | 226 ++++++++++++++++++---------- tests/func/test_ls.py | 15 +- tests/func/test_remote.py | 6 +- tests/func/test_repro.py | 20 ++- tests/func/test_run_multistage.py | 15 ++ tests/func/test_run_single_stage.py | 51 ++++--- 15 files changed, 374 insertions(+), 182 deletions(-) create mode 100644 tests/func/metrics/conftest.py create mode 100644 tests/func/plots/conftest.py diff --git a/dvc/repo/run.py b/dvc/repo/run.py index b2687746c..374068eac 100644 --- a/dvc/repo/run.py +++ b/dvc/repo/run.py @@ -45,6 +45,9 @@ def run(self, fname=None, no_exec=False, single_stage=False, **kwargs): from dvc.stage import PipelineStage, Stage, create_stage from dvc.dvcfile import Dvcfile, PIPELINE_FILE + if not kwargs.get("cmd"): + raise InvalidArgumentError("command is not specified") + stage_cls = PipelineStage path = PIPELINE_FILE stage_name = kwargs.get("name") diff --git a/tests/func/metrics/conftest.py b/tests/func/metrics/conftest.py new file mode 100644 index 000000000..2e482d908 --- /dev/null +++ b/tests/func/metrics/conftest.py @@ -0,0 +1 @@ +from tests.func.plots import run_copy_metrics # noqa: F401 diff --git a/tests/func/metrics/test_diff.py b/tests/func/metrics/test_diff.py index 0b04e9bef..121c5681a 100644 --- a/tests/func/metrics/test_diff.py +++ b/tests/func/metrics/test_diff.py @@ -3,11 +3,10 @@ import json import yaml -def test_metrics_diff_simple(tmp_dir, scm, dvc): +def test_metrics_diff_simple(tmp_dir, scm, dvc, run_copy_metrics): def _gen(val): - tmp_dir.gen({"m.yaml": str(val)}) - dvc.run(cmd="", metrics=["m.yaml"], single_stage=True) - dvc.scm.add(["m.yaml.dvc"]) + tmp_dir.gen({"m_temp.yaml": str(val)}) + run_copy_metrics("m_temp.yaml", "m.yaml", metrics=["m.yaml"]) dvc.scm.commit(str(val)) _gen(1) @@ -19,13 +18,13 @@ def test_metrics_diff_simple(tmp_dir, scm, dvc): assert dvc.metrics.diff(a_rev="HEAD~2") == expected -def test_metrics_diff_yaml(tmp_dir, scm, dvc): +def test_metrics_diff_yaml(tmp_dir, scm, dvc, run_copy_metrics): def _gen(val): metrics = {"a": {"b": {"c": val, "d": 1, "e": str(val)}}} - tmp_dir.gen({"m.yaml": yaml.dump(metrics)}) - dvc.run(cmd="", metrics=["m.yaml"], single_stage=True) - dvc.scm.add(["m.yaml.dvc"]) - dvc.scm.commit(str(val)) + tmp_dir.gen({"m_temp.yaml": yaml.dump(metrics)}) + run_copy_metrics( + "m_temp.yaml", "m.yaml", metrics=["m.yaml"], commit=str(val) + ) _gen(1) _gen(2) @@ -36,13 +35,13 @@ def test_metrics_diff_yaml(tmp_dir, scm, dvc): assert dvc.metrics.diff(a_rev="HEAD~2") == expected -def test_metrics_diff_json(tmp_dir, scm, dvc): +def test_metrics_diff_json(tmp_dir, scm, dvc, run_copy_metrics): def _gen(val): metrics = {"a": {"b": {"c": val, "d": 1, "e": str(val)}}} - tmp_dir.gen({"m.json": json.dumps(metrics)}) - dvc.run(cmd="", metrics=["m.json"], single_stage=True) - dvc.scm.add(["m.json.dvc"]) - dvc.scm.commit(str(val)) + tmp_dir.gen({"m_temp.json": json.dumps(metrics)}) + run_copy_metrics( + "m_temp.json", "m.json", metrics=["m.json"], commit=str(val) + ) _gen(1) _gen(2) @@ -52,13 +51,13 @@ def test_metrics_diff_json(tmp_dir, scm, dvc): assert dvc.metrics.diff(a_rev="HEAD~2") == expected -def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc): +def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc, run_copy_metrics): def _gen(val): metrics = {"a": {"b": {"c": val, "d": 1, "e": str(val)}}} - tmp_dir.gen({"m.json": json.dumps(metrics)}) - dvc.run(cmd="", metrics=["m.json"], single_stage=True) - dvc.scm.add(["m.json.dvc"]) - dvc.scm.commit(str(val)) + tmp_dir.gen({"m_temp.json": json.dumps(metrics)}) + run_copy_metrics( + "m_temp.json", "m.json", metrics=["m.json"], commit=str(val) + ) _gen(1) _gen(2) @@ -67,12 +66,15 @@ def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc): assert dvc.metrics.diff(a_rev="HEAD~2") == {} -def test_metrics_diff_broken_json(tmp_dir, scm, dvc): +def test_metrics_diff_broken_json(tmp_dir, scm, dvc, run_copy_metrics): metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}} - tmp_dir.gen({"m.json": json.dumps(metrics)}) - dvc.run(cmd="", metrics_no_cache=["m.json"], single_stage=True) - dvc.scm.add(["m.json.dvc", "m.json"]) - dvc.scm.commit("add metrics") + tmp_dir.gen({"m_temp.json": json.dumps(metrics)}) + run_copy_metrics( + "m_temp.json", + "m.json", + metrics_no_cache=["m.json"], + commit="add metrics", + ) (tmp_dir / "m.json").write_text(json.dumps(metrics) + "ma\nlformed\n") @@ -89,10 +91,10 @@ def test_metrics_diff_no_metrics(tmp_dir, scm, dvc): assert dvc.metrics.diff(a_rev="HEAD~1") == {} -def test_metrics_diff_new_metric(tmp_dir, scm, dvc): +def test_metrics_diff_new_metric(tmp_dir, scm, dvc, run_copy_metrics): metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}} - tmp_dir.gen({"m.json": json.dumps(metrics)}) - dvc.run(cmd="", metrics_no_cache=["m.json"], single_stage=True) + tmp_dir.gen({"m_temp.json": json.dumps(metrics)}) + run_copy_metrics("m_temp.json", "m.json", metrics_no_cache=["m.json"]) assert dvc.metrics.diff() == { "m.json": { @@ -102,12 +104,15 @@ def test_metrics_diff_new_metric(tmp_dir, scm, dvc): } -def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc): +def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc, run_copy_metrics): metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}} - tmp_dir.gen({"m.json": json.dumps(metrics)}) - dvc.run(cmd="", metrics_no_cache=["m.json"], single_stage=True) - dvc.scm.add(["m.json.dvc", "m.json"]) - dvc.scm.commit("add metrics") + tmp_dir.gen({"m_temp.json": json.dumps(metrics)}) + run_copy_metrics( + "m_temp.json", + "m.json", + metrics_no_cache=["m.json"], + commit="add metrics", + ) (tmp_dir / "m.json").unlink() @@ -119,11 +124,14 @@ def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc): } -def test_metrics_diff_with_unchanged(tmp_dir, scm, dvc): - tmp_dir.gen("metrics.yaml", "foo: 1\nxyz: 10") - dvc.run(metrics_no_cache=["metrics.yaml"], single_stage=True) - scm.add(["metrics.yaml", "metrics.yaml.dvc"]) - scm.commit("1") +def test_metrics_diff_with_unchanged(tmp_dir, scm, dvc, run_copy_metrics): + tmp_dir.gen("metrics_temp.yaml", "foo: 1\nxyz: 10") + run_copy_metrics( + "metrics_temp.yaml", + "metrics.yaml", + metrics_no_cache=["metrics.yaml"], + commit="1", + ) tmp_dir.scm_gen("metrics.yaml", "foo: 2\nxyz: 10", commit="2") tmp_dir.scm_gen("metrics.yaml", "foo: 3\nxyz: 10", commit="3") diff --git a/tests/func/metrics/test_show.py b/tests/func/metrics/test_show.py index d21d44828..3ff121f9b 100644 --- a/tests/func/metrics/test_show.py +++ b/tests/func/metrics/test_show.py @@ -8,36 +8,44 @@ def test_show_empty(dvc): dvc.metrics.show() -def test_show_simple(tmp_dir, dvc): - tmp_dir.gen("metrics.yaml", "1.1") - dvc.run(metrics=["metrics.yaml"], single_stage=True) +def test_show_simple(tmp_dir, dvc, run_copy_metrics): + tmp_dir.gen("metrics_t.yaml", "1.1") + run_copy_metrics( + "metrics_t.yaml", "metrics.yaml", metrics=["metrics.yaml"], + ) assert dvc.metrics.show() == {"": {"metrics.yaml": 1.1}} -def test_show(tmp_dir, dvc): - tmp_dir.gen("metrics.yaml", "foo: 1.1") - dvc.run(metrics=["metrics.yaml"], single_stage=True) +def test_show(tmp_dir, dvc, run_copy_metrics): + tmp_dir.gen("metrics_t.yaml", "foo: 1.1") + run_copy_metrics( + "metrics_t.yaml", "metrics.yaml", metrics=["metrics.yaml"], + ) assert dvc.metrics.show() == {"": {"metrics.yaml": {"foo": 1.1}}} -def test_show_multiple(tmp_dir, dvc): - tmp_dir.gen("foo", "foo: 1\n") - tmp_dir.gen("baz", "baz: 2\n") - dvc.run(fname="foo.dvc", metrics=["foo"], single_stage=True) - dvc.run(fname="baz.dvc", metrics=["baz"], single_stage=True) +def test_show_multiple(tmp_dir, dvc, run_copy_metrics): + tmp_dir.gen("foo_temp", "foo: 1\n") + tmp_dir.gen("baz_temp", "baz: 2\n") + run_copy_metrics("foo_temp", "foo", fname="foo.dvc", metrics=["foo"]) + run_copy_metrics("baz_temp", "baz", fname="baz.dvc", metrics=["baz"]) assert dvc.metrics.show() == {"": {"foo": {"foo": 1}, "baz": {"baz": 2}}} -def test_show_invalid_metric(tmp_dir, dvc): - tmp_dir.gen("metrics.yaml", "foo:\n- bar\n- baz\nxyz: string") - dvc.run(metrics=["metrics.yaml"], single_stage=True) +def test_show_invalid_metric(tmp_dir, dvc, run_copy_metrics): + tmp_dir.gen("metrics_temp.yaml", "foo:\n- bar\n- baz\nxyz: string") + run_copy_metrics( + "metrics_temp.yaml", "metrics.yaml", metrics=["metrics.yaml"] + ) with pytest.raises(NoMetricsError): dvc.metrics.show() -def test_show_branch(tmp_dir, scm, dvc): - tmp_dir.gen("metrics.yaml", "foo: 1") - dvc.run(metrics_no_cache=["metrics.yaml"], single_stage=True) +def test_show_branch(tmp_dir, scm, dvc, run_copy_metrics): + tmp_dir.gen("metrics_temp.yaml", "foo: 1") + run_copy_metrics( + "metrics_temp.yaml", "metrics.yaml", metrics_no_cache=["metrics.yaml"] + ) scm.add(["metrics.yaml", "metrics.yaml.dvc"]) scm.commit("init") diff --git a/tests/func/params/test_diff.py b/tests/func/params/test_diff.py index d4bc3a2bb..f5ee537fb 100644 --- a/tests/func/params/test_diff.py +++ b/tests/func/params/test_diff.py @@ -4,7 +4,7 @@ def test_diff_no_params(tmp_dir, scm, dvc): def test_diff_no_changes(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("bar") assert dvc.params.diff() == {} @@ -12,7 +12,7 @@ def test_diff_no_changes(tmp_dir, scm, dvc): def test_diff(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("bar") @@ -26,7 +26,7 @@ def test_diff(tmp_dir, scm, dvc): def test_diff_new(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) assert dvc.params.diff() == { "params.yaml": {"foo": {"old": None, "new": "bar"}} @@ -35,7 +35,7 @@ def test_diff_new(tmp_dir, scm, dvc): def test_diff_deleted(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("bar") @@ -48,7 +48,7 @@ def test_diff_deleted(tmp_dir, scm, dvc): def test_diff_deleted_config(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("bar") @@ -61,7 +61,7 @@ def test_diff_deleted_config(tmp_dir, scm, dvc): def test_diff_list(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo:\n- bar\n- baz") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("foo") @@ -76,7 +76,7 @@ def test_diff_list(tmp_dir, scm, dvc): def test_diff_dict(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo:\n bar: baz") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("foo") @@ -89,7 +89,7 @@ def test_diff_dict(tmp_dir, scm, dvc): def test_diff_with_unchanged(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar\nxyz: val") - dvc.run(params=["foo,xyz"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo,xyz"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("bar") diff --git a/tests/func/params/test_show.py b/tests/func/params/test_show.py index 2f903cb5b..f40d0ffe5 100644 --- a/tests/func/params/test_show.py +++ b/tests/func/params/test_show.py @@ -10,14 +10,24 @@ def test_show_empty(dvc): def test_show(tmp_dir, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) assert dvc.params.show() == {"": {"params.yaml": {"foo": "bar"}}} def test_show_multiple(tmp_dir, dvc): tmp_dir.gen("params.yaml", "foo: bar\nbaz: qux\n") - dvc.run(fname="foo.dvc", params=["foo"], single_stage=True) - dvc.run(fname="baz.dvc", params=["baz"], single_stage=True) + dvc.run( + cmd="echo params.yaml", + fname="foo.dvc", + params=["foo"], + single_stage=True, + ) + dvc.run( + cmd="echo params.yaml", + fname="baz.dvc", + params=["baz"], + single_stage=True, + ) assert dvc.params.show() == { "": {"params.yaml": {"foo": "bar", "baz": "qux"}} } @@ -25,13 +35,13 @@ def test_show_multiple(tmp_dir, dvc): def test_show_list(tmp_dir, dvc): tmp_dir.gen("params.yaml", "foo:\n- bar\n- baz\n") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) assert dvc.params.show() == {"": {"params.yaml": {"foo": ["bar", "baz"]}}} def test_show_branch(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") - dvc.run(params=["foo"], single_stage=True) + dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True) scm.add(["params.yaml", "Dvcfile"]) scm.commit("init") diff --git a/tests/func/plots/__init__.py b/tests/func/plots/__init__.py index e69de29bb..abeba7bda 100644 --- a/tests/func/plots/__init__.py +++ b/tests/func/plots/__init__.py @@ -0,0 +1,30 @@ +import os + +import pytest + + +@pytest.fixture +def run_copy_metrics(tmp_dir, run_copy): + def run(file1, file2, commit=None, tag=None, **kwargs): + stage = tmp_dir.dvc.run( + cmd=f"python copy.py {file1} {file2}", + deps=[file1], + single_stage=True, + **kwargs, + ) + + if hasattr(tmp_dir.dvc, "scm"): + files = [stage.path] + files += [ + os.fspath(out.path_info) + for out in stage.outs + if not out.use_cache + ] + tmp_dir.dvc.scm.add(files) + if commit: + tmp_dir.dvc.scm.commit(commit) + if tag: + tmp_dir.dvc.scm.tag(tag) + return stage + + return run diff --git a/tests/func/plots/conftest.py b/tests/func/plots/conftest.py new file mode 100644 index 000000000..7b4cf09a0 --- /dev/null +++ b/tests/func/plots/conftest.py @@ -0,0 +1 @@ +from . import run_copy_metrics # noqa: F401 diff --git a/tests/func/plots/test_diff.py b/tests/func/plots/test_diff.py index e488ec75f..8a8500e58 100644 --- a/tests/func/plots/test_diff.py +++ b/tests/func/plots/test_diff.py @@ -1,20 +1,32 @@ import json -from .test_plots import PlotData, _run_with, _write_json +from .test_plots import PlotData, _write_json -def test_diff_dirty(tmp_dir, scm, dvc): +def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics): metric_1 = [{"y": 2}, {"y": 3}] - _write_json(tmp_dir, metric_1, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="init") + _write_json(tmp_dir, metric_1, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="init", + ) metric_2 = [{"y": 3}, {"y": 5}] - _write_json(tmp_dir, metric_2, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="second") + _write_json(tmp_dir, metric_2, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="second", + ) metric_3 = [{"y": 5}, {"y": 6}] - _write_json(tmp_dir, metric_3, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"]) + _write_json(tmp_dir, metric_3, "metric_t.json") + run_copy_metrics( + "metric_t.json", "metric.json", plots_no_cache=["metric.json"] + ) plot_string = dvc.plots.diff(fields={"y"})["metric.json"] diff --git a/tests/func/plots/test_plots.py b/tests/func/plots/test_plots.py index e8923a437..949c7be79 100644 --- a/tests/func/plots/test_plots.py +++ b/tests/func/plots/test_plots.py @@ -26,20 +26,6 @@ def _remove_whitespace(value): return value.replace(" ", "").replace("\n", "") -def _run_with(tmp_dir, commit=None, tag=None, **kwargs): - stage = tmp_dir.dvc.run(single_stage=True, **kwargs) - if hasattr(tmp_dir.dvc, "scm"): - files = [stage.path] - files += [ - os.fspath(out.path_info) for out in stage.outs if not out.use_cache - ] - tmp_dir.dvc.scm.add(files) - if commit: - tmp_dir.dvc.scm.commit(commit) - if tag: - tmp_dir.dvc.scm.tag(tag) - - def _write_csv(metric, filename, header=True): with open(filename, "w", newline="") as csvobj: if header: @@ -59,11 +45,13 @@ def _write_json(tmp_dir, metric, filename): tmp_dir.gen(filename, json.dumps(metric, sort_keys=True)) -def test_plot_csv_one_column(tmp_dir, scm, dvc): +def test_plot_csv_one_column(tmp_dir, scm, dvc, run_copy_metrics): # no header metric = [{"val": 2}, {"val": 3}] - _write_csv(metric, "metric.csv", header=False) - _run_with(tmp_dir, plots_no_cache=["metric.csv"]) + _write_csv(metric, "metric_t.csv", header=False) + run_copy_metrics( + "metric_t.csv", "metric.csv", plots_no_cache=["metric.csv"] + ) plot_string = dvc.plots.show( csv_header=False, @@ -84,13 +72,15 @@ def test_plot_csv_one_column(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["title"] == "y_title" -def test_plot_csv_multiple_columns(tmp_dir, scm, dvc): +def test_plot_csv_multiple_columns(tmp_dir, scm, dvc, run_copy_metrics): metric = [ OrderedDict([("first_val", 100), ("second_val", 100), ("val", 2)]), OrderedDict([("first_val", 200), ("second_val", 300), ("val", 3)]), ] - _write_csv(metric, "metric.csv") - _run_with(tmp_dir, plots_no_cache=["metric.csv"]) + _write_csv(metric, "metric_t.csv") + run_copy_metrics( + "metric_t.csv", "metric.csv", plots_no_cache=["metric.csv"] + ) plot_string = dvc.plots.show()["metric.csv"] @@ -115,13 +105,15 @@ def test_plot_csv_multiple_columns(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["field"] == "val" -def test_plot_csv_choose_axes(tmp_dir, scm, dvc): +def test_plot_csv_choose_axes(tmp_dir, scm, dvc, run_copy_metrics): metric = [ OrderedDict([("first_val", 100), ("second_val", 100), ("val", 2)]), OrderedDict([("first_val", 200), ("second_val", 300), ("val", 3)]), ] - _write_csv(metric, "metric.csv") - _run_with(tmp_dir, plots_no_cache=["metric.csv"]) + _write_csv(metric, "metric_t.csv") + run_copy_metrics( + "metric_t.csv", "metric.csv", plots_no_cache=["metric.csv"] + ) plot_string = dvc.plots.show(x_field="first_val", y_field="second_val")[ "metric.csv" @@ -146,10 +138,15 @@ def test_plot_csv_choose_axes(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["field"] == "second_val" -def test_plot_json_single_val(tmp_dir, scm, dvc): +def test_plot_json_single_val(tmp_dir, scm, dvc, run_copy_metrics): metric = [{"val": 2}, {"val": 3}] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="first run") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="first run", + ) plot_string = dvc.plots.show()["metric.json"] @@ -162,13 +159,18 @@ def test_plot_json_single_val(tmp_dir, scm, dvc): assert plot_json["encoding"]["y"]["field"] == "val" -def test_plot_json_multiple_val(tmp_dir, scm, dvc): +def test_plot_json_multiple_val(tmp_dir, scm, dvc, run_copy_metrics): metric = [ {"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}, ] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="first run") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="first run", + ) plot_string = dvc.plots.show()["metric.json"] @@ -191,13 +193,18 @@ def test_plot_json_multiple_val(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["field"] == "val" -def test_plot_confusion(tmp_dir, dvc): +def test_plot_confusion(tmp_dir, dvc, run_copy_metrics): confusion_matrix = [ {"predicted": "B", "actual": "A"}, {"predicted": "A", "actual": "A"}, ] - _write_json(tmp_dir, confusion_matrix, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="first run") + _write_json(tmp_dir, confusion_matrix, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="first run", + ) plot_string = dvc.plots.show( template="confusion", x_field="predicted", y_field="actual", @@ -212,21 +219,35 @@ def test_plot_confusion(tmp_dir, dvc): assert plot_content["encoding"]["y"]["field"] == "actual" -def test_plot_multiple_revs_default(tmp_dir, scm, dvc): +def test_plot_multiple_revs_default(tmp_dir, scm, dvc, run_copy_metrics): metric_1 = [{"y": 2}, {"y": 3}] - _write_json(tmp_dir, metric_1, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="init", tag="v1") + _write_json(tmp_dir, metric_1, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="init", + tag="v1", + ) metric_2 = [{"y": 3}, {"y": 5}] - _write_json(tmp_dir, metric_2, "metric.json") - _run_with( - tmp_dir, plots_no_cache=["metric.json"], commit="second", tag="v2" + _write_json(tmp_dir, metric_2, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="second", + tag="v2", ) metric_3 = [{"y": 5}, {"y": 6}] - _write_json(tmp_dir, metric_3, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="third") - + _write_json(tmp_dir, metric_3, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="third", + ) plot_string = dvc.plots.show(fields={"y"}, revs=["HEAD", "v2", "v1"],)[ "metric.json" ] @@ -244,22 +265,31 @@ def test_plot_multiple_revs_default(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["field"] == "y" -def test_plot_multiple_revs(tmp_dir, scm, dvc): +def test_plot_multiple_revs(tmp_dir, scm, dvc, run_copy_metrics): shutil.copy(tmp_dir / ".dvc" / "plots" / "default.json", "template.json") metric_1 = [{"y": 2}, {"y": 3}] - _write_json(tmp_dir, metric_1, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="init", tag="v1") - - metric_2 = [{"y": 3}, {"y": 5}] - _write_json(tmp_dir, metric_2, "metric.json") - _run_with( - tmp_dir, plots_no_cache=["metric.json"], commit="second", tag="v2" + _write_json(tmp_dir, metric_1, "metric_t.json") + stage = run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="init", + tag="v1", ) + metric_2 = [{"y": 3}, {"y": 5}] + _write_json(tmp_dir, metric_2, "metric_t.json") + assert dvc.reproduce(stage.addressing) == [stage] + scm.add(["metric.json", stage.path]) + scm.commit("second") + scm.tag("v2") + metric_3 = [{"y": 5}, {"y": 6}] - _write_json(tmp_dir, metric_3, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="third") + _write_json(tmp_dir, metric_3, "metric_t.json") + assert dvc.reproduce(stage.addressing) == [stage] + scm.add(["metric.json", stage.path]) + scm.commit("third") plot_string = dvc.plots.show( template="template.json", revs=["HEAD", "v2", "v1"], @@ -278,14 +308,17 @@ def test_plot_multiple_revs(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["field"] == "y" -def test_plot_even_if_metric_missing(tmp_dir, scm, dvc, caplog): +def test_plot_even_if_metric_missing( + tmp_dir, scm, dvc, caplog, run_copy_metrics +): tmp_dir.scm_gen("some_file", "content", commit="there is no metric") scm.tag("v1") metric = [{"y": 2}, {"y": 3}] - _write_json(tmp_dir, metric, "metric.json") - _run_with( - tmp_dir, + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", plots_no_cache=["metric.json"], commit="there is metric", tag="v2", @@ -335,10 +368,16 @@ def custom_template(tmp_dir, dvc): return custom_template -def test_custom_template(tmp_dir, scm, dvc, custom_template): +def test_custom_template(tmp_dir, scm, dvc, custom_template, run_copy_metrics): metric = [{"a": 1, "b": 2}, {"a": 2, "b": 3}] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="init", tag="v1") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="init", + tag="v1", + ) plot_string = dvc.plots.show( template=os.fspath(custom_template), x_field="a", y_field="b" @@ -358,15 +397,21 @@ def _replace(path, src, dst): def test_custom_template_with_specified_data( - tmp_dir, scm, dvc, custom_template + tmp_dir, scm, dvc, custom_template, run_copy_metrics ): _replace( custom_template, "DVC_METRIC_DATA", "DVC_METRIC_DATA,metric.json", ) metric = [{"a": 1, "b": 2}, {"a": 2, "b": 3}] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, outs_no_cache=["metric.json"], commit="init", tag="v1") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + outs_no_cache=["metric.json"], + commit="init", + tag="v1", + ) plot_string = dvc.plots.show( template=os.fspath(custom_template), x_field="a", y_field="b", @@ -381,7 +426,9 @@ def test_custom_template_with_specified_data( assert plot_content["encoding"]["y"]["field"] == "b" -def test_plot_override_specified_data_source(tmp_dir, scm, dvc): +def test_plot_override_specified_data_source( + tmp_dir, scm, dvc, run_copy_metrics +): shutil.copy( tmp_dir / ".dvc" / "plots" / "default.json", tmp_dir / "newtemplate.json", @@ -393,8 +440,14 @@ def test_plot_override_specified_data_source(tmp_dir, scm, dvc): ) metric = [{"a": 1, "b": 2}, {"a": 2, "b": 3}] - _write_json(tmp_dir, metric, "metric2.json") - _run_with(tmp_dir, outs_no_cache=["metric2.json"], commit="init", tag="v1") + _write_json(tmp_dir, metric, "metric1.json") + run_copy_metrics( + "metric1.json", + "metric2.json", + outs_no_cache=["metric2.json"], + commit="init", + tag="v1", + ) plot_string = dvc.plots.show( targets=["metric2.json"], template="newtemplate.json", x_field="a" @@ -430,10 +483,18 @@ def test_plot_wrong_metric_type(tmp_dir, scm, dvc): dvc.plots.show(targets=["metric.txt"]) -def test_plot_choose_columns(tmp_dir, scm, dvc, custom_template): +def test_plot_choose_columns( + tmp_dir, scm, dvc, custom_template, run_copy_metrics +): metric = [{"a": 1, "b": 2, "c": 3}, {"a": 2, "b": 3, "c": 4}] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="init", tag="v1") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="init", + tag="v1", + ) plot_string = dvc.plots.show( template=os.fspath(custom_template), @@ -451,10 +512,16 @@ def test_plot_choose_columns(tmp_dir, scm, dvc, custom_template): assert plot_content["encoding"]["y"]["field"] == "c" -def test_plot_default_choose_column(tmp_dir, scm, dvc): +def test_plot_default_choose_column(tmp_dir, scm, dvc, run_copy_metrics): metric = [{"a": 1, "b": 2, "c": 3}, {"a": 2, "b": 3, "c": 4}] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="init", tag="v1") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="init", + tag="v1", + ) plot_string = dvc.plots.show(fields={"b"})["metric.json"] @@ -467,12 +534,14 @@ def test_plot_default_choose_column(tmp_dir, scm, dvc): assert plot_content["encoding"]["y"]["field"] == "b" -def test_plot_yaml(tmp_dir, scm, dvc): +def test_plot_yaml(tmp_dir, scm, dvc, run_copy_metrics): metric = [{"val": 2}, {"val": 3}] - with open("metric.yaml", "w") as fobj: + with open("metric_t.yaml", "w") as fobj: yaml.dump(metric, fobj) - _run_with(tmp_dir, plots_no_cache=["metric.yaml"]) + run_copy_metrics( + "metric_t.yaml", "metric.yaml", plots_no_cache=["metric.yaml"] + ) plot_string = dvc.plots.show()["metric.yaml"] @@ -483,10 +552,15 @@ def test_plot_yaml(tmp_dir, scm, dvc): ] -def test_raise_on_wrong_field(tmp_dir, scm, dvc): +def test_raise_on_wrong_field(tmp_dir, scm, dvc, run_copy_metrics): metric = [{"val": 2}, {"val": 3}] - _write_json(tmp_dir, metric, "metric.json") - _run_with(tmp_dir, plots_no_cache=["metric.json"], commit="first run") + _write_json(tmp_dir, metric, "metric_t.json") + run_copy_metrics( + "metric_t.json", + "metric.json", + plots_no_cache=["metric.json"], + commit="first run", + ) with pytest.raises(NoFieldInDataError): dvc.plots.show("metric.json", x_field="no_val") diff --git a/tests/func/test_ls.py b/tests/func/test_ls.py index 63d56b244..bdf1c8cc0 100644 --- a/tests/func/test_ls.py +++ b/tests/func/test_ls.py @@ -1,5 +1,6 @@ import os import shutil +import textwrap import pytest @@ -29,17 +30,19 @@ def match_files(files, expected_files): def create_dvc_pipeline(tmp_dir, dvc): - script = os.linesep.join( - [ - "from pathlib import Path", - "Path({}).touch()".format(os.path.join("out", "file")), - ] + script = textwrap.dedent( + """\ + import os, sys + f = sys.argv[1] + os.makedirs(os.path.dirname(f)) + open(f, "w+").close() + """ ) tmp_dir.scm_gen({"script.py": script}, commit="init") tmp_dir.dvc_gen({"dep": "content"}, commit="init dvc") dvc.run( **{ - "command": "python script.py", + "cmd": "python script.py {}".format(os.path.join("out", "file")), "outs": [os.path.join("out", "file")], "deps": ["dep"], "fname": "out.dvc", diff --git a/tests/func/test_remote.py b/tests/func/test_remote.py index d4a979aed..5f4a56678 100644 --- a/tests/func/test_remote.py +++ b/tests/func/test_remote.py @@ -239,7 +239,11 @@ def test_external_dir_resource_on_no_cache(tmp_dir, dvc, tmp_path_factory): dvc.cache.local = None with pytest.raises(RemoteCacheRequiredError): - dvc.run(deps=[os.fspath(external_dir)], single_stage=True) + dvc.run( + cmd="echo hello world", + deps=[os.fspath(external_dir)], + single_stage=True, + ) def test_push_order(tmp_dir, dvc, tmp_path_factory, mocker, setup_remote): diff --git a/tests/func/test_repro.py b/tests/func/test_repro.py index 63eaf8225..8ab136a01 100644 --- a/tests/func/test_repro.py +++ b/tests/func/test_repro.py @@ -264,9 +264,11 @@ class TestReproDepDirWithOutputsUnderIt(SingleStageRun, TestDvc): self.assertEqual(len(stages), 1) self.assertTrue(stages[0] is not None) + deps = [self.DATA, self.DATA_SUB] stage = self.dvc.run( + cmd="ls {}".format(" ".join(deps)), fname="dvcfile2.dvc", - deps=[self.DATA, self.DATA_SUB], + deps=deps, single_stage=True, ) self.assertTrue(stage is not None) @@ -405,7 +407,17 @@ class TestReproDryNoExec(TestDvc): self.assertEqual(ret, 0) ret = main( - ["run", "--no-exec", "--single-stage", "-f", DVC_FILE] + deps + [ + "run", + "--no-exec", + "--single-stage", + "-f", + DVC_FILE, + *deps, + "ls {}".format( + " ".join(dep for i, dep in enumerate(deps) if i % 2) + ), + ] ) self.assertEqual(ret, 0) @@ -1465,9 +1477,11 @@ def repro_dir(tmp_dir, dvc, run_copy): stages["dir_file_copy"] = stage last_stage = tmp_dir / "dir" / DVC_FILE + deps = [os.fspath(origin_copy_2), os.fspath(dir_file_copy)] stage = dvc.run( + cmd="echo {}".format(" ".join(deps)), fname=os.fspath(last_stage), - deps=[os.fspath(origin_copy_2), os.fspath(dir_file_copy)], + deps=deps, single_stage=True, ) assert stage is not None diff --git a/tests/func/test_run_multistage.py b/tests/func/test_run_multistage.py index b2cfae5dc..71862411a 100644 --- a/tests/func/test_run_multistage.py +++ b/tests/func/test_run_multistage.py @@ -3,6 +3,8 @@ import os import pytest import yaml +from dvc.exceptions import InvalidArgumentError +from dvc.repo import Repo from dvc.stage.exceptions import DuplicateStageName, InvalidStageName @@ -299,3 +301,16 @@ def test_run_params_no_exec(tmp_dir, dvc): assert data["stages"]["read_params"]["params"] == [ {"params2.yaml": ["lists"]} ] + + +@pytest.mark.parametrize( + "kwargs", + [ + {"outs": ["foo"], "deps": ["bar"]}, + {"outs": ["foo"], "deps": ["bar"], "name": "copy-foo-bar"}, + ], +) +def test_run_without_cmd(kwargs): + with pytest.raises(InvalidArgumentError) as exc: + Repo().run(**kwargs) + assert "command is not specified" == str(exc.value) diff --git a/tests/func/test_run_single_stage.py b/tests/func/test_run_single_stage.py index 2e7956281..362e218e1 100644 --- a/tests/func/test_run_single_stage.py +++ b/tests/func/test_run_single_stage.py @@ -76,7 +76,7 @@ class TestRun(TestDvc): class TestRunEmpty(TestDvc): def test(self): self.dvc.run( - cmd="", + cmd="echo hello world", deps=[], outs=[], outs_no_cache=[], @@ -91,7 +91,7 @@ class TestRunMissingDep(TestDvc): with self.assertRaises(DependencyDoesNotExistError): self.dvc.run( - cmd="", + cmd="command", deps=["non-existing-dep"], outs=[], outs_no_cache=[], @@ -118,7 +118,7 @@ class TestRunCircularDependency(TestDvc): def test(self): with self.assertRaises(CircularDependencyError): self.dvc.run( - cmd="", + cmd="command", deps=[self.FOO], outs=[self.FOO], fname="circular-dependency.dvc", @@ -128,7 +128,7 @@ class TestRunCircularDependency(TestDvc): def test_outs_no_cache(self): with self.assertRaises(CircularDependencyError): self.dvc.run( - cmd="", + cmd="command", deps=[self.FOO], outs_no_cache=[self.FOO], fname="circular-dependency.dvc", @@ -138,7 +138,7 @@ class TestRunCircularDependency(TestDvc): def test_non_normalized_paths(self): with self.assertRaises(CircularDependencyError): self.dvc.run( - cmd="", + cmd="command", deps=["./foo"], outs=["foo"], fname="circular-dependency.dvc", @@ -173,7 +173,7 @@ class TestRunDuplicatedArguments(TestDvc): def test(self): with self.assertRaises(ArgumentDuplicationError): self.dvc.run( - cmd="", + cmd="command", deps=[], outs=[self.FOO, self.FOO], fname="circular-dependency.dvc", @@ -183,7 +183,7 @@ class TestRunDuplicatedArguments(TestDvc): def test_outs_no_cache(self): with self.assertRaises(ArgumentDuplicationError): self.dvc.run( - cmd="", + cmd="command", outs=[self.FOO], outs_no_cache=[self.FOO], fname="circular-dependency.dvc", @@ -193,7 +193,7 @@ class TestRunDuplicatedArguments(TestDvc): def test_non_normalized_paths(self): with self.assertRaises(ArgumentDuplicationError): self.dvc.run( - cmd="", + cmd="command", deps=[], outs=["foo", "./foo"], fname="circular-dependency.dvc", @@ -204,22 +204,30 @@ class TestRunDuplicatedArguments(TestDvc): class TestRunStageInsideOutput(TestDvc): def test_cwd(self): self.dvc.run( - cmd="", deps=[], outs=[self.DATA_DIR], single_stage=True, + cmd=f"mkdir {self.DATA_DIR}", + deps=[], + outs=[self.DATA_DIR], + single_stage=True, ) with self.assertRaises(StagePathAsOutputError): self.dvc.run( - cmd="", + cmd="command", fname=os.path.join(self.DATA_DIR, "inside-cwd.dvc"), single_stage=True, ) def test_file_name(self): - self.dvc.run(cmd="", deps=[], outs=[self.DATA_DIR], single_stage=True) + self.dvc.run( + cmd=f"mkdir {self.DATA_DIR}", + deps=[], + outs=[self.DATA_DIR], + single_stage=True, + ) with self.assertRaises(StagePathAsOutputError): self.dvc.run( - cmd="", + cmd="command", outs=[self.FOO], fname=os.path.join(self.DATA_DIR, "inside-cwd.dvc"), single_stage=True, @@ -229,30 +237,30 @@ class TestRunStageInsideOutput(TestDvc): class TestRunBadCwd(TestDvc): def test(self): with self.assertRaises(StagePathOutsideError): - self.dvc.run(cmd="", wdir=self.mkdtemp(), single_stage=True) + self.dvc.run(cmd="command", wdir=self.mkdtemp(), single_stage=True) def test_same_prefix(self): with self.assertRaises(StagePathOutsideError): path = f"{self._root_dir}-{uuid.uuid4()}" os.mkdir(path) - self.dvc.run(cmd="", wdir=path, single_stage=True) + self.dvc.run(cmd="command", wdir=path, single_stage=True) class TestRunBadWdir(TestDvc): def test(self): with self.assertRaises(StagePathOutsideError): - self.dvc.run(cmd="", wdir=self.mkdtemp(), single_stage=True) + self.dvc.run(cmd="command", wdir=self.mkdtemp(), single_stage=True) def test_same_prefix(self): with self.assertRaises(StagePathOutsideError): path = f"{self._root_dir}-{uuid.uuid4()}" os.mkdir(path) - self.dvc.run(cmd="", wdir=path, single_stage=True) + self.dvc.run(cmd="command", wdir=path, single_stage=True) def test_not_found(self): with self.assertRaises(StagePathNotFoundError): path = os.path.join(self._root_dir, str(uuid.uuid4())) - self.dvc.run(cmd="", wdir=path, single_stage=True) + self.dvc.run(cmd="command", wdir=path, single_stage=True) def test_not_dir(self): with self.assertRaises(StagePathNotDirectoryError): @@ -261,7 +269,7 @@ class TestRunBadWdir(TestDvc): path = os.path.join(path, str(uuid.uuid4())) open(path, "a").close() self.dvc.run( - cmd="", wdir=path, single_stage=True, + cmd="command", wdir=path, single_stage=True, ) @@ -269,7 +277,7 @@ class TestRunBadName(TestDvc): def test(self): with self.assertRaises(StagePathOutsideError): self.dvc.run( - cmd="", + cmd="command", fname=os.path.join(self.mkdtemp(), self.FOO + DVC_FILE_SUFFIX), single_stage=True, ) @@ -279,7 +287,7 @@ class TestRunBadName(TestDvc): path = f"{self._root_dir}-{uuid.uuid4()}" os.mkdir(path) self.dvc.run( - cmd="", + cmd="command", fname=os.path.join(path, self.FOO + DVC_FILE_SUFFIX), single_stage=True, ) @@ -288,7 +296,7 @@ class TestRunBadName(TestDvc): with self.assertRaises(StagePathNotFoundError): path = os.path.join(self._root_dir, str(uuid.uuid4())) self.dvc.run( - cmd="", + cmd="command", fname=os.path.join(path, self.FOO + DVC_FILE_SUFFIX), single_stage=True, ) @@ -574,6 +582,7 @@ class TestCmdRunOverwrite(TestDvc): "out.dvc", "-d", self.BAR, + f"cat {self.BAR}", ] ) self.assertEqual(ret, 0)