mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
@@ -1,500 +0,0 @@
|
||||
import io
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.cli import main
|
||||
from dvc.commands.experiments.init import CmdExperimentsInit
|
||||
from dvc.repo.experiments.init import init
|
||||
from dvc.stage.exceptions import DuplicateStageName
|
||||
|
||||
# the tests may hang on prompts on failure
|
||||
pytestmark = pytest.mark.timeout(3, func_only=True)
|
||||
|
||||
|
||||
@pytest.mark.timeout(5, func_only=True)
|
||||
def test_init_simple(tmp_dir, scm, dvc, capsys):
|
||||
tmp_dir.gen(
|
||||
{
|
||||
CmdExperimentsInit.CODE: {"copy.py": ""},
|
||||
"data": "data",
|
||||
"params.yaml": '{"foo": 1}',
|
||||
"plots": {},
|
||||
}
|
||||
)
|
||||
code_path = os.path.join(CmdExperimentsInit.CODE, "copy.py")
|
||||
script = f"python {code_path}"
|
||||
|
||||
capsys.readouterr()
|
||||
assert main(["exp", "init", script]) == 0
|
||||
out, err = capsys.readouterr()
|
||||
assert not err
|
||||
assert (
|
||||
"Creating train stage in dvc.yaml\n\n"
|
||||
"Ensure your experiment command creates metrics.json, plots and models"
|
||||
'.\nYou can now run your experiment using "dvc exp run".' in out
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": script,
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [{"metrics.json": {"cache": False}}],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [{"plots": {"cache": False}}],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert (tmp_dir / "data").read_text() == "data"
|
||||
assert (tmp_dir / "src").is_dir()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interactive", [True, False])
|
||||
def test_when_stage_already_exists_with_same_name(tmp_dir, dvc, interactive):
|
||||
(tmp_dir / "dvc.yaml").dump({"stages": {"train": {"cmd": "test"}}})
|
||||
with pytest.raises(DuplicateStageName) as exc:
|
||||
init(
|
||||
dvc,
|
||||
interactive=interactive,
|
||||
overrides={"cmd": "true"},
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
)
|
||||
assert (
|
||||
str(exc.value)
|
||||
== "Stage 'train' already exists in 'dvc.yaml'. Use '--force' to overwrite."
|
||||
)
|
||||
|
||||
|
||||
def test_when_stage_force_if_already_exists(tmp_dir, dvc):
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
(tmp_dir / "dvc.yaml").dump({"stages": {"train": {"cmd": "test"}}})
|
||||
init(
|
||||
dvc,
|
||||
force=True,
|
||||
overrides={"cmd": "true"},
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
)
|
||||
d = (tmp_dir / "dvc.yaml").parse()
|
||||
assert d["stages"]["train"]["cmd"] == "true"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interactive", [True, False])
|
||||
def test_creates_params_file_by_default(tmp_dir, dvc, interactive):
|
||||
init(
|
||||
dvc,
|
||||
interactive=interactive,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides={"cmd": "cmd"},
|
||||
stream=io.StringIO(""),
|
||||
)
|
||||
|
||||
assert (tmp_dir / "params.yaml").is_file()
|
||||
assert (tmp_dir / "params.yaml").parse() == {}
|
||||
|
||||
|
||||
def test_with_a_custom_name(tmp_dir, dvc):
|
||||
init(dvc, name="custom", overrides={"cmd": "cmd"})
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {"stages": {"custom": {"cmd": "cmd"}}}
|
||||
|
||||
|
||||
def test_init_with_no_defaults_non_interactive(tmp_dir, scm, dvc):
|
||||
init(dvc, defaults={}, overrides={"cmd": "python script.py"})
|
||||
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {"train": {"cmd": "python script.py"}}
|
||||
}
|
||||
scm._reset()
|
||||
assert not (tmp_dir / "dvc.lock").exists()
|
||||
assert scm.is_tracked("dvc.yaml")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"extra_overrides, inp",
|
||||
[
|
||||
({"cmd": "cmd"}, io.StringIO()),
|
||||
({}, io.StringIO("cmd")),
|
||||
],
|
||||
)
|
||||
def test_init_interactive_when_no_path_prompts_need_to_be_asked(
|
||||
tmp_dir, dvc, extra_overrides, inp
|
||||
):
|
||||
"""When we pass everything that's required of, it should not prompt us."""
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
init(
|
||||
dvc,
|
||||
interactive=True,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides={**CmdExperimentsInit.DEFAULTS, **extra_overrides},
|
||||
stream=inp,
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "cmd",
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [
|
||||
{"metrics.json": {"cache": False}},
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{"plots": {"cache": False}},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert (tmp_dir / "src").is_dir()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
|
||||
|
||||
def test_when_params_is_omitted_in_interactive_mode(tmp_dir, scm, dvc):
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
inp = io.StringIO("python script.py\nscript.py\ndata\nn")
|
||||
|
||||
init(dvc, interactive=True, stream=inp, defaults=CmdExperimentsInit.DEFAULTS)
|
||||
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "python script.py",
|
||||
"deps": ["data", "script.py"],
|
||||
"metrics": [{"metrics.json": {"cache": False}}],
|
||||
"outs": ["models"],
|
||||
"plots": [{"plots": {"cache": False}}],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert not (tmp_dir / "dvc.lock").exists()
|
||||
assert not (tmp_dir / "script.py").read_text()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
scm._reset()
|
||||
assert scm.is_tracked("dvc.yaml")
|
||||
assert not scm.is_tracked("params.yaml")
|
||||
assert scm.is_tracked(".gitignore")
|
||||
assert scm.is_ignored("models")
|
||||
|
||||
|
||||
def test_init_interactive_params_validation(tmp_dir, dvc, capsys):
|
||||
tmp_dir.gen({"data": {"foo": "foo"}})
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
inp = io.StringIO("python script.py\nscript.py\ndata\ndata")
|
||||
|
||||
init(dvc, stream=inp, interactive=True, defaults=CmdExperimentsInit.DEFAULTS)
|
||||
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "python script.py",
|
||||
"deps": ["data", "script.py"],
|
||||
"metrics": [{"metrics.json": {"cache": False}}],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [{"plots": {"cache": False}}],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert not (tmp_dir / "script.py").read_text()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert not out
|
||||
assert (
|
||||
"Path to a parameters file [params.yaml, n to omit]: "
|
||||
"'data' is a directory. "
|
||||
"Please retry with an existing parameters file.\n"
|
||||
"Path to a parameters file [params.yaml, n to omit]:" in err
|
||||
)
|
||||
|
||||
|
||||
def test_init_with_no_defaults_interactive(tmp_dir, dvc):
|
||||
inp = io.StringIO("script.py\ndata\nn\nmodel\nmetric\nn\n")
|
||||
init(
|
||||
dvc,
|
||||
defaults={},
|
||||
overrides={"cmd": "python script.py"},
|
||||
interactive=True,
|
||||
stream=inp,
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "python script.py",
|
||||
"deps": ["data", "script.py"],
|
||||
"metrics": [{"metric": {"cache": False}}],
|
||||
"outs": ["model"],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert not (tmp_dir / "script.py").read_text()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"interactive, overrides, inp",
|
||||
[
|
||||
(False, {"cmd": "python script.py", "code": "script.py"}, None),
|
||||
(
|
||||
True,
|
||||
{},
|
||||
io.StringIO(
|
||||
"python script.py\n"
|
||||
"script.py\n"
|
||||
"data\n"
|
||||
"params.yaml\n"
|
||||
"models\n"
|
||||
"metrics.json\n"
|
||||
"plots\n"
|
||||
"y"
|
||||
),
|
||||
),
|
||||
],
|
||||
ids=["non-interactive", "interactive"],
|
||||
)
|
||||
def test_init_default(tmp_dir, scm, dvc, interactive, overrides, inp, capsys):
|
||||
(tmp_dir / "params.yaml").dump({"foo": {"bar": 1}})
|
||||
|
||||
init(
|
||||
dvc,
|
||||
interactive=interactive,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides=overrides,
|
||||
stream=inp,
|
||||
)
|
||||
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "python script.py",
|
||||
"deps": ["data", "script.py"],
|
||||
"metrics": [{"metrics.json": {"cache": False}}],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [{"plots": {"cache": False}}],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert not (tmp_dir / "dvc.lock").exists()
|
||||
assert not (tmp_dir / "script.py").read_text()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
scm._reset()
|
||||
assert scm.is_tracked("dvc.yaml")
|
||||
assert scm.is_tracked("params.yaml")
|
||||
assert scm.is_tracked(".gitignore")
|
||||
assert scm.is_ignored("models")
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
assert not out
|
||||
if interactive:
|
||||
assert "'script.py' does not exist, the file will be created." in err
|
||||
assert "'data' does not exist, the directory will be created." in err
|
||||
|
||||
|
||||
@pytest.mark.timeout(5, func_only=True)
|
||||
@pytest.mark.parametrize(
|
||||
"interactive, overrides, inp",
|
||||
[
|
||||
(False, {"cmd": "python script.py", "code": "script.py"}, None),
|
||||
(
|
||||
True,
|
||||
{},
|
||||
io.StringIO("python script.py\nscript.py\ndata\nparams.yaml\nmodels\ny"),
|
||||
),
|
||||
(
|
||||
True,
|
||||
{"cmd": "python script.py"},
|
||||
io.StringIO("script.py\ndata\nparams.yaml\nmodels\ny"),
|
||||
),
|
||||
(
|
||||
True,
|
||||
{"cmd": "python script.py", "models": "models"},
|
||||
io.StringIO("script.py\ndata\nparams.yaml\ny"),
|
||||
),
|
||||
],
|
||||
ids=[
|
||||
"non-interactive",
|
||||
"interactive",
|
||||
"interactive-cmd-provided",
|
||||
"interactive-cmd-models-provided",
|
||||
],
|
||||
)
|
||||
def test_init_interactive_live(tmp_dir, scm, dvc, interactive, overrides, inp, capsys):
|
||||
overrides["live"] = "dvclive"
|
||||
|
||||
(tmp_dir / "params.yaml").dump({"foo": {"bar": 1}})
|
||||
|
||||
init(
|
||||
dvc,
|
||||
interactive=interactive,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides=overrides,
|
||||
stream=inp,
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "python script.py",
|
||||
"deps": ["data", "script.py"],
|
||||
"metrics": [
|
||||
{os.path.join("dvclive", "metrics.json"): {"cache": False}}
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [{os.path.join("dvclive", "plots"): {"cache": False}}],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert not (tmp_dir / "dvc.lock").exists()
|
||||
assert not (tmp_dir / "script.py").read_text()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
scm._reset()
|
||||
assert scm.is_tracked("dvc.yaml")
|
||||
assert scm.is_tracked("params.yaml")
|
||||
assert scm.is_tracked(".gitignore")
|
||||
assert scm.is_ignored("models")
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
assert not out
|
||||
if interactive:
|
||||
assert "'script.py' does not exist, the file will be created." in err
|
||||
assert "'data' does not exist, the directory will be created." in err
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"interactive, inp",
|
||||
[
|
||||
(False, None),
|
||||
(True, io.StringIO()),
|
||||
],
|
||||
)
|
||||
def test_init_with_type_checkpoint_and_models_plots_provided(
|
||||
tmp_dir, dvc, interactive, inp
|
||||
):
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
init(
|
||||
dvc,
|
||||
type="checkpoint",
|
||||
interactive=interactive,
|
||||
stream=inp,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides={"cmd": "cmd", "metrics": "m", "plots": "p"},
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "cmd",
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [
|
||||
{"m": {"cache": False, "persist": True}},
|
||||
],
|
||||
"outs": [{"models": {"checkpoint": True}}],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{"p": {"cache": False, "persist": True}},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert (tmp_dir / "src").is_dir()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"interactive, inp",
|
||||
[
|
||||
(False, None),
|
||||
(True, io.StringIO()),
|
||||
],
|
||||
)
|
||||
def test_init_with_type_default_and_live_provided(tmp_dir, dvc, interactive, inp):
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
init(
|
||||
dvc,
|
||||
interactive=interactive,
|
||||
stream=inp,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides={"cmd": "cmd", "live": "live"},
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "cmd",
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [
|
||||
{os.path.join("live", "metrics.json"): {"cache": False}},
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{os.path.join("live", "plots"): {"cache": False}},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert (tmp_dir / "src").is_dir()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"interactive, inp",
|
||||
[
|
||||
(False, None),
|
||||
(True, io.StringIO()),
|
||||
],
|
||||
)
|
||||
def test_init_with_live_and_metrics_plots_provided(tmp_dir, dvc, interactive, inp):
|
||||
(tmp_dir / "params.yaml").dump({"foo": 1})
|
||||
init(
|
||||
dvc,
|
||||
interactive=interactive,
|
||||
stream=inp,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides={
|
||||
"cmd": "cmd",
|
||||
"live": "live",
|
||||
"metrics": "metrics.json",
|
||||
"plots": "plots",
|
||||
},
|
||||
)
|
||||
assert (tmp_dir / "dvc.yaml").parse() == {
|
||||
"stages": {
|
||||
"train": {
|
||||
"cmd": "cmd",
|
||||
"deps": ["data", "src"],
|
||||
"metrics": [
|
||||
{os.path.join("live", "metrics.json"): {"cache": False}},
|
||||
{"metrics.json": {"cache": False}},
|
||||
],
|
||||
"outs": ["models"],
|
||||
"params": [{"params.yaml": None}],
|
||||
"plots": [
|
||||
{os.path.join("live", "plots"): {"cache": False}},
|
||||
{"plots": {"cache": False}},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
assert (tmp_dir / "src").is_dir()
|
||||
assert (tmp_dir / "data").is_dir()
|
||||
|
||||
|
||||
def test_gen_output_dirs(tmp_dir, dvc):
|
||||
init(
|
||||
dvc,
|
||||
defaults=CmdExperimentsInit.DEFAULTS,
|
||||
overrides={
|
||||
"cmd": "cmd",
|
||||
"models": "models/predict.h5",
|
||||
"metrics": "eval/scores.json",
|
||||
"plots": "eval/plots",
|
||||
"live": "eval/live",
|
||||
},
|
||||
)
|
||||
|
||||
assert (tmp_dir / "models").is_dir()
|
||||
assert (tmp_dir / "eval").is_dir()
|
||||
assert (tmp_dir / "eval/plots").is_dir()
|
||||
assert (tmp_dir / "eval/live").is_dir()
|
||||
assert not (tmp_dir / "models/predict.h5").exists()
|
||||
assert not (tmp_dir / "eval/scores.json").exists()
|
||||
@@ -1,14 +1,11 @@
|
||||
import pathlib
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.cli import DvcParserError, parse_args
|
||||
from dvc.cli import parse_args
|
||||
from dvc.commands.experiments.apply import CmdExperimentsApply
|
||||
from dvc.commands.experiments.branch import CmdExperimentsBranch
|
||||
from dvc.commands.experiments.clean import CmdExperimentsClean
|
||||
from dvc.commands.experiments.diff import CmdExperimentsDiff
|
||||
from dvc.commands.experiments.gc import CmdExperimentsGC
|
||||
from dvc.commands.experiments.init import CmdExperimentsInit
|
||||
from dvc.commands.experiments.ls import CmdExperimentsList
|
||||
from dvc.commands.experiments.pull import CmdExperimentsPull
|
||||
from dvc.commands.experiments.push import CmdExperimentsPush
|
||||
@@ -17,9 +14,6 @@ from dvc.commands.experiments.run import CmdExperimentsRun
|
||||
from dvc.commands.experiments.save import CmdExperimentsSave
|
||||
from dvc.commands.experiments.show import CmdExperimentsShow
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.repo import Repo
|
||||
from tests.utils import ANY
|
||||
from tests.utils.asserts import called_once_with_subset
|
||||
|
||||
from .test_repro import common_arguments as repro_arguments
|
||||
|
||||
@@ -420,204 +414,6 @@ def test_experiments_remove_invalid(dvc, scm, mocker, capsys, caplog):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("extra_args", [(), ("--run",)])
|
||||
def test_experiments_init(dvc, scm, mocker, capsys, extra_args):
|
||||
stage = mocker.Mock(outs=[], addressing="train")
|
||||
m = mocker.patch("dvc.repo.experiments.init.init", return_value=(stage, [], []))
|
||||
runner = mocker.patch("dvc.repo.experiments.run.run", return_value=0)
|
||||
cli_args = parse_args(["exp", "init", *extra_args, "cmd"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
|
||||
assert isinstance(cmd, CmdExperimentsInit)
|
||||
assert cmd.run() == 0
|
||||
m.assert_called_once_with(
|
||||
ANY(Repo),
|
||||
name="train",
|
||||
type="default",
|
||||
defaults={
|
||||
"code": "src",
|
||||
"models": "models",
|
||||
"data": "data",
|
||||
"metrics": "metrics.json",
|
||||
"params": "params.yaml",
|
||||
"plots": "plots",
|
||||
},
|
||||
overrides={"cmd": "cmd"},
|
||||
interactive=False,
|
||||
force=False,
|
||||
)
|
||||
|
||||
if extra_args:
|
||||
# `parse_args` creates a new `Repo` object
|
||||
runner.assert_called_once_with(ANY(Repo), targets=["train"])
|
||||
|
||||
|
||||
def test_experiments_init_config(dvc, scm, mocker):
|
||||
with dvc.config.edit() as conf:
|
||||
conf["exp"] = {"code": "new_src", "models": "new_models"}
|
||||
|
||||
stage = mocker.Mock(outs=[])
|
||||
m = mocker.patch("dvc.repo.experiments.init.init", return_value=(stage, [], []))
|
||||
cli_args = parse_args(["exp", "init", "cmd"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
|
||||
assert isinstance(cmd, CmdExperimentsInit)
|
||||
assert cmd.run() == 0
|
||||
|
||||
m.assert_called_once_with(
|
||||
ANY(Repo),
|
||||
name="train",
|
||||
type="default",
|
||||
defaults={
|
||||
"code": "new_src",
|
||||
"models": "new_models",
|
||||
"data": "data",
|
||||
"metrics": "metrics.json",
|
||||
"params": "params.yaml",
|
||||
"plots": "plots",
|
||||
},
|
||||
overrides={"cmd": "cmd"},
|
||||
interactive=False,
|
||||
force=False,
|
||||
)
|
||||
|
||||
|
||||
def test_experiments_init_explicit(dvc, mocker):
|
||||
stage = mocker.Mock(outs=[])
|
||||
m = mocker.patch("dvc.repo.experiments.init.init", return_value=(stage, [], []))
|
||||
cli_args = parse_args(["exp", "init", "--explicit", "cmd"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
|
||||
assert cmd.run() == 0
|
||||
m.assert_called_once_with(
|
||||
ANY(Repo),
|
||||
name="train",
|
||||
type="default",
|
||||
defaults={},
|
||||
overrides={"cmd": "cmd"},
|
||||
interactive=False,
|
||||
force=False,
|
||||
)
|
||||
|
||||
|
||||
def test_experiments_init_cmd_required_for_non_interactive_mode(dvc):
|
||||
cli_args = parse_args(["exp", "init"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
assert isinstance(cmd, CmdExperimentsInit)
|
||||
|
||||
with pytest.raises(InvalidArgumentError) as exc:
|
||||
cmd.run()
|
||||
assert str(exc.value) == "command is not specified"
|
||||
|
||||
|
||||
def test_experiments_init_cmd_not_required_for_interactive_mode(dvc, mocker):
|
||||
cli_args = parse_args(["exp", "init", "--interactive"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
assert isinstance(cmd, CmdExperimentsInit)
|
||||
|
||||
stage = mocker.Mock(outs=[])
|
||||
m = mocker.patch("dvc.repo.experiments.init.init", return_value=(stage, [], []))
|
||||
assert cmd.run() == 0
|
||||
assert called_once_with_subset(m, ANY(Repo), interactive=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"extra_args, expected_kw",
|
||||
[
|
||||
(["--type", "default"], {"type": "default", "name": "train"}),
|
||||
(["--type", "checkpoint"], {"type": "checkpoint", "name": "train"}),
|
||||
(["--force"], {"force": True, "name": "train"}),
|
||||
(
|
||||
["--name", "name", "--type", "checkpoint"],
|
||||
{"name": "name", "type": "checkpoint"},
|
||||
),
|
||||
(
|
||||
[
|
||||
"--plots",
|
||||
"p",
|
||||
"--models",
|
||||
"m",
|
||||
"--code",
|
||||
"c",
|
||||
"--metrics",
|
||||
"m.json",
|
||||
"--params",
|
||||
"p.yaml",
|
||||
"--data",
|
||||
"d",
|
||||
"--live",
|
||||
"live",
|
||||
],
|
||||
{
|
||||
"name": "train",
|
||||
"overrides": {
|
||||
"plots": "p",
|
||||
"models": "m",
|
||||
"code": "c",
|
||||
"metrics": "m.json",
|
||||
"params": "p.yaml",
|
||||
"data": "d",
|
||||
"live": "live",
|
||||
"cmd": "cmd",
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_experiments_init_extra_args(extra_args, expected_kw, mocker):
|
||||
cli_args = parse_args(["exp", "init", *extra_args, "cmd"])
|
||||
cmd = cli_args.func(cli_args)
|
||||
assert isinstance(cmd, CmdExperimentsInit)
|
||||
|
||||
stage = mocker.Mock(outs=[])
|
||||
m = mocker.patch("dvc.repo.experiments.init.init", return_value=(stage, [], []))
|
||||
assert cmd.run() == 0
|
||||
assert called_once_with_subset(m, ANY(Repo), **expected_kw)
|
||||
|
||||
|
||||
def test_experiments_init_type_invalid_choice():
|
||||
with pytest.raises(DvcParserError):
|
||||
parse_args(["exp", "init", "--type=invalid", "cmd"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("args", [[], ["--run"]])
|
||||
def test_experiments_init_displays_output_on_no_run(dvc, mocker, capsys, args):
|
||||
model_dir = pathlib.Path("models")
|
||||
model_path = str(model_dir / "predict.h5")
|
||||
stage = dvc.stage.create(
|
||||
name="train",
|
||||
cmd=["cmd"],
|
||||
deps=["code", "data"],
|
||||
params=["params.yaml"],
|
||||
outs=["metrics.json", "plots", model_path],
|
||||
)
|
||||
mocker.patch(
|
||||
"dvc.repo.experiments.init.init",
|
||||
return_value=(stage, stage.deps, [model_dir]),
|
||||
)
|
||||
mocker.patch("dvc.repo.experiments.run.run", return_value=0)
|
||||
cli_args = parse_args(["exp", "init", "cmd", *args])
|
||||
cmd = cli_args.func(cli_args)
|
||||
assert cmd.run() == 0
|
||||
|
||||
expected_lines = [
|
||||
"Creating dependencies: code, data and params.yaml",
|
||||
"Creating output directories: models",
|
||||
"Creating train stage in dvc.yaml",
|
||||
"",
|
||||
"Ensure your experiment command creates metrics.json, plots and ",
|
||||
f"{model_path}.",
|
||||
]
|
||||
if not cli_args.run:
|
||||
expected_lines += [
|
||||
'You can now run your experiment using "dvc exp run".',
|
||||
]
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert not err
|
||||
assert out.splitlines() == expected_lines
|
||||
|
||||
|
||||
def test_experiments_save(dvc, scm, mocker):
|
||||
cli_args = parse_args(["exp", "save", "--name", "exp-name", "--force"])
|
||||
assert cli_args.func == CmdExperimentsSave
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
import io
|
||||
from unittest.mock import call
|
||||
|
||||
import pytest
|
||||
from rich.console import Console as RichConsole
|
||||
|
||||
from dvc.ui.prompt import Confirm, InvalidResponse, Prompt
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side_effect", [EOFError, KeyboardInterrupt])
|
||||
@pytest.mark.parametrize("prompt_cls", [Prompt, Confirm])
|
||||
def test_prompt_interrupt_message_not_interleaved(prompt_cls, side_effect, mocker):
|
||||
"""Test that the interrupt message is not interleaved with the prompt."""
|
||||
prompt = "should print exception in the next line"
|
||||
console = RichConsole(file=io.StringIO())
|
||||
|
||||
mocker.patch.object(console, "input", side_effect=side_effect)
|
||||
with pytest.raises(side_effect):
|
||||
prompt_cls.ask(prompt=prompt, console=console)
|
||||
# as we are raising side_effect on the input, unfortunately we cannot
|
||||
# assert for the prompt string, only the newline.
|
||||
assert console.file.getvalue() == "\n"
|
||||
|
||||
|
||||
def test_prompt_str():
|
||||
console = RichConsole(file=io.StringIO())
|
||||
assert (
|
||||
Prompt.ask("What is your name?", console=console, stream=io.StringIO("foo"))
|
||||
== "foo"
|
||||
)
|
||||
assert console.file.getvalue() == "What is your name?: "
|
||||
|
||||
|
||||
def test_prompt_disallows_empty_response(mocker):
|
||||
console = RichConsole(file=io.StringIO())
|
||||
Prompt.prompt_(
|
||||
"What is your name?",
|
||||
console=console,
|
||||
stream=io.StringIO("\nfoo"),
|
||||
)
|
||||
expected = (
|
||||
"What is your name?: Response required. Please try again.\nWhat is your name?: "
|
||||
)
|
||||
assert console.file.getvalue() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"default, brack",
|
||||
[
|
||||
("default", "[default, n to omit]"),
|
||||
(None, "[n to omit]"),
|
||||
],
|
||||
)
|
||||
def test_prompt_with_conditional_skip(mocker, default, brack):
|
||||
console = RichConsole(file=io.StringIO())
|
||||
mocked_validator = mocker.MagicMock(return_value="something")
|
||||
assert (
|
||||
Prompt.prompt_(
|
||||
"What is your name?",
|
||||
console=console,
|
||||
allow_omission=True,
|
||||
stream=io.StringIO("n\n"),
|
||||
default=default,
|
||||
validator=mocked_validator,
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert console.file.getvalue() == f"What is your name? {brack}: "
|
||||
mocked_validator.assert_not_called()
|
||||
|
||||
|
||||
def test_prompt_retries_on_invalid_response_from_validator(mocker):
|
||||
console = RichConsole(file=io.StringIO())
|
||||
mocked_validator = mocker.MagicMock(
|
||||
side_effect=[InvalidResponse("it is a number"), "foo"]
|
||||
)
|
||||
assert (
|
||||
Prompt.ask(
|
||||
"what is your name?",
|
||||
console=console,
|
||||
stream=io.StringIO("3\nfoo"),
|
||||
validator=mocked_validator,
|
||||
)
|
||||
== "foo"
|
||||
)
|
||||
mocked_validator.assert_has_calls([call("3"), call("foo")])
|
||||
|
||||
expected = "what is your name?: it is a number\nwhat is your name?: "
|
||||
assert console.file.getvalue() == expected
|
||||
|
||||
|
||||
def test_prompt_shows_message_from_validator_response(mocker):
|
||||
console = RichConsole(file=io.StringIO())
|
||||
mocked_validator = mocker.MagicMock(
|
||||
return_value=("foo@email", "failed to send a verification email")
|
||||
)
|
||||
assert (
|
||||
Prompt.ask(
|
||||
"what is your email?",
|
||||
console=console,
|
||||
stream=io.StringIO("foo@email"),
|
||||
validator=mocked_validator,
|
||||
)
|
||||
== "foo@email"
|
||||
)
|
||||
mocked_validator.assert_has_calls([call("foo@email")])
|
||||
|
||||
expected = "what is your email?: failed to send a verification email\n"
|
||||
assert console.file.getvalue() == expected
|
||||
|
||||
|
||||
def test_prompt_default(mocker):
|
||||
console = RichConsole(file=io.StringIO())
|
||||
mocked_validator = mocker.MagicMock(return_value="default")
|
||||
assert (
|
||||
Prompt.ask(
|
||||
"What is your name?",
|
||||
console=console,
|
||||
stream=io.StringIO(""),
|
||||
default="default",
|
||||
validator=mocked_validator,
|
||||
)
|
||||
== "default"
|
||||
)
|
||||
assert console.file.getvalue() == "What is your name? [default]: "
|
||||
mocked_validator.assert_has_calls([call("default")])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"default, inner", [(True, "Y/n"), (False, "y/N"), (Ellipsis, "y/n")]
|
||||
)
|
||||
def test_prompt_confirm(default, inner):
|
||||
console = RichConsole(file=io.StringIO())
|
||||
assert (
|
||||
Confirm.ask(
|
||||
"exit?",
|
||||
console=console,
|
||||
stream=io.StringIO("y\n"),
|
||||
default=default,
|
||||
)
|
||||
is True
|
||||
)
|
||||
output = console.file.getvalue()
|
||||
assert output == f"exit? [{inner}]: "
|
||||
Reference in New Issue
Block a user