exp: Drop init. (#9269)

Drop `exp init`.

Closes #9267
This commit is contained in:
David de la Iglesia Castro
2023-05-19 14:39:16 +05:45
committed by GitHub
parent 3959c51373
commit cdba65f27e
7 changed files with 1 additions and 1531 deletions
-500
View File
@@ -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 -205
View File
@@ -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
-144
View File
@@ -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}]: "