exp: use new collection in show/cli commands

- Updates 'dvc exp show --json' to use new data format
This commit is contained in:
Peter Rowlands
2023-04-20 16:58:32 +09:00
committed by Peter Rowlands (변기호)
parent 58f74ae543
commit ec090c5680
8 changed files with 673 additions and 1788 deletions
+5 -8
View File
@@ -255,17 +255,14 @@ def test_auto_push_self_remote(
def test_tmp_dir_failed_checkpoint(tmp_dir, scm, dvc, failed_checkpoint_stage, caplog):
baseline = scm.get_rev()
results = dvc.experiments.run(
dvc.experiments.run(
failed_checkpoint_stage.addressing, params=["foo=2"], tmp_dir=True
)
exp = first(results)
result = dvc.experiments.show()[baseline]
# Assert 4 rows: baseline, 2 checkpoints, and final commit
assert len(result) == 4
assert exp in result
result = dvc.experiments.show()[1]
assert len(result.experiments) == 1
# Assert 2 checkpoints and final commit
assert len(result.experiments[0]) == 3
assert (
"Checkpoint stage 'failed-checkpoint-file' was interrupted remaining "
"stages in pipeline will not be reproduced." in caplog.text
+287 -464
View File
@@ -1,21 +1,17 @@
import logging
import os
import random
from datetime import datetime
from unittest.mock import ANY
import pytest
from funcy import first, get_in
from funcy import first
from scmrepo.exceptions import SCMError
from dvc.cli import main
from dvc.repo.experiments.executor.base import BaseExecutor, ExecutorInfo, TaskStatus
from dvc.repo.experiments.queue.base import QueueEntry
from dvc.repo.experiments.refs import CELERY_STASH, ExpRefInfo
from dvc.repo.experiments.show import _CachedError
from dvc.repo.experiments.refs import CELERY_STASH
from dvc.repo.experiments.utils import EXEC_PID_DIR, EXEC_TMP_DIR, exp_refs_by_rev
from dvc.utils import relpath
from dvc.utils.serialize import YAMLFileCorruptedError
LOCK_CONTENTS = {
"read": {
@@ -41,42 +37,73 @@ def make_executor_info(**kwargs):
return ExecutorInfo(**kwargs)
def make_executor(local=None, **kwargs):
if local:
local_executor = {
"root": ANY,
"log": ANY,
"pid": ANY,
"returncode": ANY,
"task_id": ANY,
}
local_executor.update(local)
else:
local_executor = ANY
data = {
"state": ANY,
"local": local_executor,
"name": ANY,
}
data.update(kwargs)
return data
def make_data(params=None, **kwargs):
params = {"data": params or {"foo": 1}}
data = {
"rev": ANY,
"deps": {
"copy.py": {
"hash": ANY,
"size": ANY,
"nfiles": None,
}
},
"metrics": {"metrics.yaml": params},
"outs": {},
"params": {"params.yaml": params},
"timestamp": ANY,
"meta": ANY,
}
data.update(kwargs)
return data
@pytest.mark.vscode
def test_show_branch_and_tag_name(tmp_dir, scm, dvc, exp_stage):
with tmp_dir.branch("new/branch", new=True):
tmp_dir.scm_gen("branch", "branch", "commit")
branch_rev = scm.get_rev()
result = dvc.experiments.show(all_branches=True)
assert result[branch_rev]["baseline"]["data"]["name"] == "new/branch"
expected = [None, "master", "new/branch"]
assert [exp.name for exp in result] == expected
scm.tag("new/tag")
tag_rev = scm.get_rev()
result = dvc.experiments.show(all_tags=True)
assert result[tag_rev]["baseline"]["data"]["name"] == "new/tag"
with scm.detach_head(tag_rev):
result = dvc.experiments.show(all_tags=True)
expected = [None, "new/tag"]
assert [exp.name for exp in result] == expected
@pytest.mark.vscode
def test_show_simple(tmp_dir, scm, dvc, exp_stage):
assert dvc.experiments.show()["workspace"] == {
"baseline": {
"data": {
"rev": "workspace",
"deps": {
"copy.py": {
"hash": ANY,
"size": ANY,
"nfiles": None,
}
},
"metrics": {"metrics.yaml": {"data": {"foo": 1}}},
"outs": {},
"params": {"params.yaml": {"data": {"foo": 1}}},
"status": "Success",
"executor": None,
"timestamp": None,
}
}
assert dvc.experiments.show()[0].dumpd() == {
"rev": "workspace",
"name": None,
"data": make_data(rev="workspace"),
"error": None,
"experiments": None,
}
@@ -88,39 +115,33 @@ def test_show_experiment(tmp_dir, scm, dvc, exp_stage, workspace):
scm.gitpython.repo.rev_parse(baseline_rev).committed_date
)
dvc.experiments.run(exp_stage.addressing, params=["foo=2"], tmp_dir=not workspace)
exp_rev = first(
dvc.experiments.run(
exp_stage.addressing, params=["foo=2"], tmp_dir=not workspace
)
)
results = dvc.experiments.show()
expected_baseline = {
"data": {
"rev": ANY,
"deps": {
"copy.py": {
"hash": ANY,
"size": ANY,
"nfiles": None,
}
},
"metrics": {"metrics.yaml": {"data": {"foo": 1}}},
"outs": {},
"params": {"params.yaml": {"data": {"foo": 1}}},
"status": "Success",
"executor": None,
"timestamp": timestamp,
"name": "master",
}
assert results[1].dumpd() == {
"rev": baseline_rev,
"name": "master",
"data": make_data(rev=baseline_rev, timestamp=timestamp),
"error": None,
"experiments": [
{
"revs": [
{
"rev": exp_rev,
"name": ANY,
"data": make_data(rev=exp_rev, params={"foo": 2}),
"error": None,
"experiments": None,
}
],
"executor": None,
"name": ANY,
}
],
}
expected_params = {"data": {"foo": 2}}
assert set(results.keys()) == {"workspace", baseline_rev}
experiments = results[baseline_rev]
assert len(experiments) == 2
for rev, exp in experiments.items():
if rev == "baseline":
assert exp == expected_baseline
else:
assert exp["data"]["metrics"]["metrics.yaml"] == expected_params
assert exp["data"]["params"]["params.yaml"] == expected_params
@pytest.mark.vscode
@@ -132,12 +153,28 @@ def test_show_queued(tmp_dir, scm, dvc, exp_stage):
)
exp_rev = dvc.experiments.scm.resolve_rev(f"{CELERY_STASH}@{{0}}")
results = dvc.experiments.show()[baseline_rev]
assert len(results) == 2
exp = results[exp_rev]["data"]
assert exp["name"] == "test_name"
assert exp["status"] == "Queued"
assert exp["params"]["params.yaml"] == {"data": {"foo": 2}}
results = dvc.experiments.show()
assert results[1].dumpd() == {
"rev": baseline_rev,
"name": "master",
"data": make_data(rev=baseline_rev),
"error": None,
"experiments": [
{
"revs": [
{
"rev": exp_rev,
"name": "test_name",
"data": make_data(rev=exp_rev, params={"foo": 2}, metrics=ANY),
"error": None,
"experiments": None,
}
],
"executor": make_executor(state="queued"),
"name": "test_name",
}
],
}
# test that only queued experiments for the current baseline are returned
tmp_dir.gen("foo", "foo")
@@ -148,99 +185,85 @@ def test_show_queued(tmp_dir, scm, dvc, exp_stage):
dvc.experiments.run(exp_stage.addressing, params=["foo=3"], queue=True)
exp_rev = dvc.experiments.scm.resolve_rev(f"{CELERY_STASH}@{{0}}")
results = dvc.experiments.show()[new_rev]
assert len(results) == 2
exp = results[exp_rev]["data"]
assert exp["status"] == "Queued"
assert exp["params"]["params.yaml"] == {"data": {"foo": 3}}
results = dvc.experiments.show()
assert results[1].dumpd() == {
"rev": new_rev,
"name": "master",
"data": make_data(rev=new_rev),
"error": None,
"experiments": [
{
"revs": [
{
"rev": exp_rev,
"name": ANY,
"data": make_data(rev=exp_rev, params={"foo": 3}, metrics=ANY),
"error": None,
"experiments": None,
}
],
"executor": make_executor(state="queued"),
"name": ANY,
}
],
}
@pytest.mark.vscode
def test_show_failed_experiment(tmp_dir, scm, dvc, failed_exp_stage, test_queue):
baseline_rev = scm.get_rev()
timestamp = datetime.fromtimestamp(
scm.gitpython.repo.rev_parse(baseline_rev).committed_date
)
dvc.experiments.run(
failed_exp_stage.addressing, params=["foo=2"], queue=True, name="show-failed"
)
dvc.experiments.run(failed_exp_stage.addressing, params=["foo=2"], queue=True)
exp_rev = dvc.experiments.scm.resolve_rev(f"{CELERY_STASH}@{{0}}")
dvc.experiments.run(run_all=True)
test_queue.wait(["show-failed"])
experiments = dvc.experiments.show()[baseline_rev]
expected_baseline = {
"data": {
"rev": ANY,
"deps": {
"copy.py": {
"hash": ANY,
"size": ANY,
"nfiles": None,
}
},
"metrics": {},
"outs": {},
"params": {"params.yaml": {"data": {"foo": 1}}},
"status": "Success",
"executor": None,
"timestamp": timestamp,
"name": "master",
}
results = dvc.experiments.show()
assert results[1].dumpd() == {
"rev": baseline_rev,
"name": "master",
"data": make_data(rev=baseline_rev, metrics=ANY),
"error": None,
"experiments": [
{
"revs": [
{
"rev": exp_rev,
"name": ANY,
"data": make_data(rev=exp_rev, params={"foo": 2}, metrics=ANY),
"error": {"msg": "Experiment run failed", "type": ANY},
"experiments": None,
}
],
"executor": make_executor(
state="failed",
local={"returncode": 255},
),
"name": ANY,
}
],
}
expected_failed = {
"data": {
"rev": ANY,
"timestamp": ANY,
"params": {"params.yaml": {"data": {"foo": 2}}},
"deps": {"copy.py": {"hash": None, "size": None, "nfiles": None}},
"metrics": {},
"outs": {},
"status": "Failed",
"executor": None,
"error": {
"msg": "Experiment run failed.",
"type": "",
},
"name": ANY,
}
}
assert len(experiments) == 2
for rev, exp in experiments.items():
if rev == "baseline":
assert exp == expected_baseline
else:
assert rev == exp_rev
assert exp == expected_failed
@pytest.mark.vscode
@pytest.mark.parametrize("workspace", [True, False])
def test_show_checkpoint(tmp_dir, scm, dvc, checkpoint_stage, capsys, workspace):
baseline_rev = scm.get_rev()
results = dvc.experiments.run(
checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
)
exp_rev = first(results)
results = dvc.experiments.show()[baseline_rev]
# Assert 4 rows: baseline, 2 checkpoints, and final commit
assert len(results) == checkpoint_stage.iterations + 2
checkpoints = []
for rev, exp in results.items():
if rev != "baseline":
checkpoints.append(rev)
assert exp["data"]["checkpoint_tip"] == exp_rev
baseline = dvc.experiments.show()[1]
assert baseline.data.meta.get("has_checkpoints")
checkpoints = first(baseline.experiments)
# 2 checkpoints + final commit
assert len(checkpoints) == checkpoint_stage.iterations + 1
assert first(checkpoints).rev == exp_rev
capsys.readouterr()
assert main(["exp", "show", "--no-pager"]) == 0
cap = capsys.readouterr()
for i, rev in enumerate(checkpoints):
for i, exp in enumerate(checkpoints):
rev = exp.rev
if i == 0:
name = dvc.experiments.get_exact_name([rev])[rev]
name = f"{rev[:7]} [{name}]"
@@ -254,44 +277,6 @@ def test_show_checkpoint(tmp_dir, scm, dvc, checkpoint_stage, capsys, workspace)
assert f"{fs} {name}" in cap.out
@pytest.mark.vscode
@pytest.mark.parametrize("workspace", [True, False])
def test_show_checkpoint_branch(tmp_dir, scm, dvc, checkpoint_stage, capsys, workspace):
results = dvc.experiments.run(
checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
)
branch_rev = first(results)
if not workspace:
dvc.experiments.apply(branch_rev)
results = dvc.experiments.run(
checkpoint_stage.addressing,
checkpoint_resume=branch_rev,
tmp_dir=not workspace,
)
checkpoint_a = first(results)
dvc.experiments.apply(branch_rev)
results = dvc.experiments.run(
checkpoint_stage.addressing,
checkpoint_resume=branch_rev,
params=["foo=100"],
tmp_dir=not workspace,
)
checkpoint_b = first(results)
capsys.readouterr()
assert main(["exp", "show", "--no-pager"]) == 0
cap = capsys.readouterr()
for rev in (checkpoint_a, checkpoint_b):
ref = dvc.experiments.get_branch_by_rev(rev)
ref_info = ExpRefInfo.from_ref(ref)
name = f"{rev[:7]} [{ref_info.name}]"
assert f"╓ {name}" in cap.out
assert f"({branch_rev[:7]})" in cap.out
def test_show_filter(
tmp_dir,
scm,
@@ -373,19 +358,21 @@ def test_show_multiple_commits(tmp_dir, scm, dvc, exp_stage):
dvc.experiments.show(num=-2)
expected = {"workspace", init_rev, next_rev}
expected = ["workspace", next_rev, init_rev]
results = dvc.experiments.show(num=2)
assert set(results.keys()) == expected
assert [exp.rev for exp in results] == expected
expected = {"workspace"} | set(scm.branch_revs("master"))
expected = ["workspace", *scm.branch_revs("master")]
results = dvc.experiments.show(all_commits=True)
assert set(results.keys()) == expected
assert [exp.rev for exp in results] == expected
results = dvc.experiments.show(num=100)
assert set(results.keys()) == expected
assert [exp.rev for exp in results] == expected
def test_show_sort(tmp_dir, scm, dvc, exp_stage, caplog):
dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
with caplog.at_level(logging.ERROR):
assert main(["exp", "show", "--no-pager", "--sort-by=bar"]) != 0
assert "Unknown sort column" in caplog.text
@@ -408,14 +395,20 @@ def test_show_sort(tmp_dir, scm, dvc, exp_stage, caplog):
(TaskStatus.FAILED, False),
],
)
def test_show_running_workspace(
def test_show_running(
tmp_dir, scm, dvc, exp_stage, capsys, caplog, status, pid_exists, mocker
):
from dvc.rwlock import RWLOCK_FILE
from dvc_task.proc.process import ProcessInfo
baseline_rev = scm.get_rev()
pid_dir = os.path.join(dvc.tmp_dir, EXEC_TMP_DIR, EXEC_PID_DIR)
lock_file = relpath(os.path.join(dvc.tmp_dir, RWLOCK_FILE), str(tmp_dir))
info = make_executor_info(location=BaseExecutor.DEFAULT_LOCATION, status=status)
info = make_executor_info(
location=BaseExecutor.DEFAULT_LOCATION,
status=status,
baseline_rev=baseline_rev,
)
pidfile = os.path.join(
pid_dir,
"workspace",
@@ -425,195 +418,42 @@ def test_show_running_workspace(
(tmp_dir / pidfile).dump_json(info.asdict())
(tmp_dir / lock_file).dump_json(LOCK_CONTENTS)
mocker.patch.object(ProcessInfo, "load", return_value=mocker.Mock(pid=123))
mocker.patch("psutil.pid_exists", return_value=pid_exists)
assert dvc.experiments.show().get("workspace") == {
"baseline": {
"data": {
"rev": "workspace",
"deps": {
"copy.py": {
"hash": ANY,
"size": ANY,
"nfiles": None,
}
},
"metrics": {"metrics.yaml": {"data": {"foo": 1}}},
"params": {"params.yaml": {"data": {"foo": 1}}},
"outs": {},
"status": (
"Running"
if status == TaskStatus.RUNNING and pid_exists
else "Success"
),
"executor": (
info.location
if status == TaskStatus.RUNNING and pid_exists
else None
),
"timestamp": None,
tempdir_active = mocker.spy(dvc.experiments.tempdir_queue, "collect_active_data")
celery_active = mocker.spy(dvc.experiments.celery_queue, "collect_active_data")
results = dvc.experiments.show()
assert results[1].dumpd() == {
"rev": ANY,
"name": "master",
"data": make_data(),
"error": None,
"experiments": [
{
"revs": ANY,
"executor": make_executor(state="running"),
"name": ANY,
}
}
]
if pid_exists
else None,
}
capsys.readouterr()
assert main(["exp", "show", "--csv"]) == 0
cap = capsys.readouterr()
if status == TaskStatus.RUNNING:
if pid_exists:
assert "Running" in cap.out
assert info.location in cap.out
else:
cmd = LOCK_CONTENTS["read"]["data/MNIST"][0]["cmd"]
pid = LOCK_CONTENTS["read"]["data/MNIST"][0]["pid"]
assert (
f"Process '{cmd}' with (Pid {pid}), in RWLock-file "
f"'{lock_file}' had been killed." in caplog.text
)
assert f"Delete corrupted RWLock-file '{lock_file}'" in caplog.text
def test_show_running_tempdir(tmp_dir, scm, dvc, exp_stage, mocker):
baseline_rev = scm.get_rev()
run_results = dvc.experiments.run(
exp_stage.addressing, params=["foo=2"], tmp_dir=True
)
exp_rev = first(run_results)
exp_ref = first(exp_refs_by_rev(scm, exp_rev))
queue = dvc.experiments.tempdir_queue
stash_rev = "abc123"
entries = [
QueueEntry(
str(tmp_dir / ".dvc" / "tmp" / "foo"),
str(tmp_dir / ".dvc" / "tmp" / "foo"),
str(exp_ref),
stash_rev,
exp_ref.baseline_sha,
None,
exp_ref.name,
None,
)
]
mocker.patch.object(
dvc.experiments.tempdir_queue,
"iter_active",
return_value=entries,
)
info = make_executor_info(location=BaseExecutor.DEFAULT_LOCATION)
pidfile = queue.get_infofile_path(stash_rev)
os.makedirs(os.path.dirname(pidfile), exist_ok=True)
(tmp_dir / pidfile).dump_json(info.asdict())
mock_fetch = mocker.patch.object(
dvc.experiments.tempdir_queue,
"get_running_exps",
return_value={exp_rev: info.asdict()},
)
results = dvc.experiments.show()
mock_fetch.assert_has_calls(
[mocker.call(True)],
)
exp_data = get_in(results, [baseline_rev, exp_rev, "data"])
assert exp_data["status"] == "Running"
assert exp_data["executor"] == info.location
assert results["workspace"]["baseline"]["data"]["status"] == "Success"
def test_show_running_celery(tmp_dir, scm, dvc, exp_stage, mocker):
baseline_rev = scm.get_rev()
dvc.experiments.run(exp_stage.addressing, params=["foo=2"], queue=True)
exp_rev = dvc.experiments.scm.resolve_rev(f"{CELERY_STASH}@{{0}}")
queue = dvc.experiments.celery_queue
entries = list(queue.iter_queued())
mocker.patch.object(
dvc.experiments.celery_queue,
"iter_active",
return_value=entries,
)
info = make_executor_info(location=BaseExecutor.DEFAULT_LOCATION)
pidfile = queue.get_infofile_path(entries[0].stash_rev)
os.makedirs(os.path.dirname(pidfile), exist_ok=True)
(tmp_dir / pidfile).dump_json(info.asdict())
results = dvc.experiments.show()
exp_data = get_in(results, [baseline_rev, exp_rev, "data"])
assert exp_data["status"] == "Running"
assert exp_data["executor"] == info.location
assert results["workspace"]["baseline"]["data"]["status"] == "Success"
def test_show_running_checkpoint(
tmp_dir, scm, dvc, checkpoint_stage, mocker, test_queue
):
from dvc.repo.experiments.executor.local import TempDirExecutor
baseline_rev = scm.get_rev()
dvc.experiments.run(
checkpoint_stage.addressing, params=["foo=2"], queue=True, name="foo"
)
queue = dvc.experiments.celery_queue
entries = list(queue.iter_queued())
run_results = dvc.experiments.run(run_all=True)
test_queue.wait(["foo"])
checkpoint_rev = first(run_results)
exp_ref = first(exp_refs_by_rev(scm, checkpoint_rev))
mocker.patch.object(
dvc.experiments.celery_queue,
"iter_active",
return_value=entries,
)
mocker.patch.object(
dvc.experiments.celery_queue,
"iter_failed",
return_value=[],
)
pidfile = queue.get_infofile_path(entries[0].stash_rev)
info = make_executor_info(
git_url="foo.git",
baseline_rev=baseline_rev,
location=TempDirExecutor.DEFAULT_LOCATION,
status=TaskStatus.RUNNING,
)
os.makedirs(os.path.dirname(pidfile), exist_ok=True)
(tmp_dir / pidfile).dump_json(info.asdict())
mocker.patch.object(BaseExecutor, "fetch_exps", return_value=[str(exp_ref)])
results = dvc.experiments.show()
checkpoint_res = get_in(results, [baseline_rev, checkpoint_rev, "data"])
assert checkpoint_res["status"] == "Running"
assert checkpoint_res["executor"] == info.location
assert results["workspace"]["baseline"]["data"]["status"] == "Success"
tempdir_active.assert_called_once()
celery_active.assert_called_once()
def test_show_with_broken_repo(tmp_dir, scm, dvc, exp_stage, caplog):
baseline_rev = scm.get_rev()
exp1 = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
exp2 = dvc.experiments.run(exp_stage.addressing, params=["foo=3"])
dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
with open("dvc.yaml", "a", encoding="utf-8") as fd:
fd.write("breaking the yaml!")
result = dvc.experiments.show()
rev1 = first(exp1)
rev2 = first(exp2)
results = dvc.experiments.show()
assert results[0].error
assert results[0].error.type == "YAMLSyntaxError"
baseline = result[baseline_rev]
paths = ["data", "params", "params.yaml"]
assert get_in(baseline[rev1], paths) == {"data": {"foo": 2}}
assert get_in(baseline[rev2], paths) == {"data": {"foo": 3}}
paths = ["workspace", "baseline", "error"]
assert isinstance(get_in(result, paths), (_CachedError, YAMLFileCorruptedError))
assert "YAML file structure is corrupted" in str(get_in(result, paths))
for exp_range in results[1].experiments:
assert not any(exp.error for exp in exp_range)
def test_show_csv(tmp_dir, scm, dvc, exp_stage, capsys):
@@ -646,8 +486,8 @@ def test_show_csv(tmp_dir, scm, dvc, exp_stage, capsys):
assert "metrics.yaml:foo,params.yaml:foo,copy.py" in cap.out
assert f",workspace,baseline,,,3,3,{data_hash}" in cap.out
assert (
"master,{},baseline,{},,1,1,{}".format(
baseline_rev[:7], _get_rev_isotimestamp(baseline_rev), data_hash
",master,baseline,{},,1,1,{}".format(
_get_rev_isotimestamp(baseline_rev), data_hash
)
in cap.out
)
@@ -810,35 +650,52 @@ def test_show_outs(tmp_dir, dvc, scm, erepo_dir, copy_script):
scm.commit("init")
outs = dvc.experiments.show()["workspace"]["baseline"]["data"]["outs"]
assert outs == {
"out": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": False,
}
results = dvc.experiments.show()
assert results[0].dumpd() == {
"rev": "workspace",
"name": None,
"data": make_data(
params=ANY,
outs={
"out": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": False,
}
},
),
"error": None,
"experiments": None,
}
tmp_dir.dvc_gen("out_add", "foo", commit="dvc add output")
outs = dvc.experiments.show()["workspace"]["baseline"]["data"]["outs"]
assert outs == {
"out": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": False,
},
"out_add": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": True,
},
results = dvc.experiments.show()
assert results[0].dumpd() == {
"rev": "workspace",
"name": None,
"data": make_data(
params=ANY,
outs={
"out": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": False,
},
"out_add": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": True,
},
},
),
"error": None,
"experiments": None,
}
with erepo_dir.chdir():
@@ -846,29 +703,38 @@ def test_show_outs(tmp_dir, dvc, scm, erepo_dir, copy_script):
dvc.imp(os.fspath(erepo_dir), "out", "out_imported")
outs = dvc.experiments.show()["workspace"]["baseline"]["data"]["outs"]
assert outs == {
"out": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": False,
},
"out_add": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": True,
},
"out_imported": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": True,
},
results = dvc.experiments.show()
assert results[0].dumpd() == {
"rev": "workspace",
"name": None,
"data": make_data(
params=ANY,
outs={
"out": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": False,
},
"out_add": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": True,
},
"out_imported": {
"hash": ANY,
"size": ANY,
"nfiles": None,
"use_cache": True,
"is_data_source": True,
},
},
),
"error": None,
"experiments": None,
}
@@ -927,12 +793,7 @@ def test_metrics_renaming(tmp_dir, dvc, scm, capsys, copy_script):
scm.gitpython.repo.rev_parse(rev).committed_date
).isoformat()
assert (
"master,{},baseline,{},,1,,1".format(
scores_rev[:7], _get_rev_isotimestamp(scores_rev)
)
in cap.out
)
assert f",master,baseline,{_get_rev_isotimestamp(scores_rev)},,1,,1" in cap.out
assert (
",{},baseline,{},,,1,1".format(
metrics_rev[:7], _get_rev_isotimestamp(metrics_rev)
@@ -961,8 +822,6 @@ def test_show_sorted_deps(tmp_dir, dvc, scm, capsys):
@pytest.mark.vscode
def test_show_queued_error(tmp_dir, scm, dvc, exp_stage, mocker):
baseline_rev = scm.get_rev()
dvc.experiments.run(
exp_stage.addressing, params=["foo=2"], queue=True, name="test_name"
)
@@ -983,17 +842,16 @@ def test_show_queued_error(tmp_dir, scm, dvc, exp_stage, mocker):
side_effect=mocker.MagicMock(side_effect=resolve_commit),
)
results = dvc.experiments.show()[baseline_rev]
results = dvc.experiments.show()[1].experiments
assert len(results) == 2
exp_2 = results[exp_rev_2]["data"]
assert exp_2["status"] == "Queued"
assert exp_2["params"]["params.yaml"] == {"data": {"foo": 2}}
queued = results[0]
assert queued.executor.state == "queued"
errored = results[1]
assert errored.revs[0].error
@pytest.mark.vscode
def test_show_completed_error(tmp_dir, scm, dvc, exp_stage, mocker):
baseline_rev = scm.get_rev()
result_2 = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
exp_rev_2 = first(result_2)
commit_2 = scm.resolve_commit(exp_rev_2)
@@ -1010,49 +868,14 @@ def test_show_completed_error(tmp_dir, scm, dvc, exp_stage, mocker):
"resolve_commit",
side_effect=mocker.MagicMock(side_effect=resolve_commit),
)
experiments = dvc.experiments.show()[baseline_rev]
assert len(experiments) == 2
assert exp_rev_2 in experiments
@pytest.mark.vscode
def test_show_checkpoint_error(tmp_dir, scm, dvc, checkpoint_stage, mocker):
baseline_rev = scm.get_rev()
results = dvc.experiments.run(checkpoint_stage.addressing, params=["foo=2"])
exp_rev = first(results)
exp_ref = str(first(exp_refs_by_rev(scm, exp_rev)))
results = dvc.experiments.show()[baseline_rev]
# Assert 4 rows: baseline, 2 checkpoints, and final commit
assert len(results) == checkpoint_stage.iterations + 2
checkpoints = {}
for rev in results:
if rev != "baseline":
checkpoints[rev] = scm.resolve_commit(rev)
checkpoints[exp_ref] = scm.resolve_commit(exp_ref)
checkpoints[baseline_rev] = scm.resolve_commit(baseline_rev)
failed_rev = random.choice(list(checkpoints.keys()))
def resolve_commit(rev):
if rev == failed_rev:
raise SCMError
return checkpoints[rev]
mocker.patch.object(
scm,
"resolve_commit",
side_effect=mocker.MagicMock(side_effect=resolve_commit),
)
results = dvc.experiments.show(force=True)[baseline_rev]
results = dvc.experiments.show()[1].experiments
assert len(results) == 1
assert not results[0].revs[0].error
@pytest.mark.vscode
def test_show_baseline_error(tmp_dir, scm, dvc, exp_stage, mocker):
baseline_rev = scm.get_rev()
branch = scm.active_branch()
result_2 = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
exp_rev_2 = first(result_2)
@@ -1068,7 +891,7 @@ def test_show_baseline_error(tmp_dir, scm, dvc, exp_stage, mocker):
"resolve_commit",
side_effect=mocker.MagicMock(side_effect=resolve_commit),
)
experiments = dvc.experiments.show()[baseline_rev]
assert len(experiments) == 1
assert experiments["baseline"]["data"] == {"name": branch}
assert isinstance(experiments["baseline"]["error"], (SCMError, _CachedError))
results = dvc.experiments.show()
assert results[1].error
assert len(results[1].experiments) == 1
+1 -306
View File
@@ -1,7 +1,4 @@
import csv
import pathlib
import textwrap
from datetime import datetime
import pytest
@@ -18,7 +15,7 @@ from dvc.commands.experiments.push import CmdExperimentsPush
from dvc.commands.experiments.remove import CmdExperimentsRemove
from dvc.commands.experiments.run import CmdExperimentsRun
from dvc.commands.experiments.save import CmdExperimentsSave
from dvc.commands.experiments.show import CmdExperimentsShow, show_experiments
from dvc.commands.experiments.show import CmdExperimentsShow
from dvc.exceptions import InvalidArgumentError
from dvc.repo import Repo
from tests.utils import ANY
@@ -422,279 +419,6 @@ def test_experiments_remove_invalid(dvc, scm, mocker, capsys, caplog):
)
def test_show_experiments_csv(capsys):
all_experiments = {
"workspace": {
"baseline": {
"data": {
"timestamp": None,
"params": {
"params.yaml": {
"data": {
"featurize": {
"max_features": 3000,
"ngrams": 1,
},
"parent": 20170428,
"train": {
"n_est": 100,
"min_split": 36,
},
}
}
},
"status": "Success",
"executor": None,
"metrics": {
"scores.json": {
"data": {
"featurize": {
"max_features": 3000,
"ngrams": 1,
},
"avg_prec": 0.5843640011189556,
"roc_auc": 0.9544670443829399,
}
}
},
}
}
},
"b05eecc666734e899f79af228ff49a7ae5a18cc0": {
"baseline": {
"data": {
"timestamp": datetime(2021, 8, 2, 16, 48, 14),
"params": {
"params.yaml": {
"data": {
"featurize": {
"max_features": 3000,
"ngrams": 1,
},
"parent": 20170428,
"train": {
"n_est": 100,
"min_split": 2,
},
}
}
},
"status": "Success",
"executor": None,
"metrics": {
"scores.json": {
"data": {
"featurize": {
"max_features": 3000,
"ngrams": 1,
},
"avg_prec": 0.5325162867864254,
"roc_auc": 0.9106964878520005,
}
}
},
"name": "master",
}
},
"ae99936461d6c3092934160f8beafe66a294f98d": {
"data": {
"timestamp": datetime(2021, 8, 31, 14, 56, 55),
"params": {
"params.yaml": {
"data": {
"featurize": {
"max_features": 3000,
"ngrams": 1,
},
"parent": 20170428,
"train": {
"n_est": 100,
"min_split": 36,
},
}
}
},
"status": "Running",
"executor": None,
"metrics": {
"scores.json": {
"data": {
"featurize": {
"max_features": 3000,
"ngrams": 1,
},
"avg_prec": 0.5843640011189556,
"roc_auc": 0.9544670443829399,
}
}
},
"name": "exp-44136",
}
},
},
}
show_experiments(all_experiments, precision=None, fill_value="", iso=True, csv=True)
cap = capsys.readouterr()
assert (
"Experiment,rev,typ,Created,parent,State,scores.json:"
"featurize.max_features,scores.json:featurize.ngrams,"
"avg_prec,roc_auc,params.yaml:featurize.max_features,"
"params.yaml:featurize.ngrams,params.yaml:parent,"
"train.n_est,train.min_split" in cap.out
)
assert (
",workspace,baseline,,,,3000,1,0.5843640011189556,0.9544670443829399,"
"3000,1,20170428,100,36" in cap.out
)
assert (
"master,b05eecc,baseline,2021-08-02T16:48:14,,,3000,1,"
"0.5325162867864254,0.9106964878520005,3000,1,20170428,100,2" in cap.out
)
assert (
"exp-44136,ae99936,branch_base,2021-08-31T14:56:55,,Running,"
"3000,1,0.5843640011189556,0.9544670443829399,3000,1,20170428,100,36" in cap.out
)
def test_show_experiments_md(capsys):
all_experiments = {
"workspace": {
"baseline": {
"data": {
"timestamp": None,
"params": {"params.yaml": {"data": {"foo": 1}}},
"status": "Success",
"executor": None,
"metrics": {"scores.json": {"data": {"bar": 0.9544670443829399}}},
}
}
},
}
show_experiments(
all_experiments, precision=None, fill_value="", iso=True, markdown=True
)
cap = capsys.readouterr()
assert cap.out == textwrap.dedent(
"""\
| Experiment | Created | bar | foo |
|--------------|-----------|--------------------|-------|
| workspace | | 0.9544670443829399 | 1 |\n
"""
)
@pytest.mark.parametrize("sort_order", ["asc", "desc"])
def test_show_experiments_sort_by(capsys, sort_order):
sort_experiments = {
"workspace": {
"baseline": {
"data": {
"timestamp": None,
"params": {
"params.yaml": {
"data": {
"foo": 1,
}
}
},
"status": "Success",
"executor": None,
"metrics": {},
}
}
},
"233b132676792d89e848e5c9c12e408d7efde78a": {
"baseline": {
"data": {
"timestamp": datetime(2021, 8, 2, 16, 48, 14),
"params": {
"params.yaml": {
"data": {
"foo": 0,
}
}
},
"status": "Success",
"executor": None,
"metrics": {},
"name": "master",
}
},
"fad0a94": {
"data": {
"timestamp": datetime(2021, 8, 31, 14, 56, 55),
"params": {
"params.yaml": {
"data": {
"foo": 1,
}
}
},
"status": "Success",
"executor": None,
"metrics": {},
"name": "exp-89140",
}
},
"60fcda8": {
"data": {
"timestamp": datetime(2021, 8, 31, 14, 56, 55),
"params": {
"params.yaml": {
"data": {
"foo": 2,
}
}
},
"status": "Success",
"executor": None,
"metrics": {},
"name": "exp-43537",
}
},
"a7e9aaf": {
"data": {
"timestamp": datetime(2021, 8, 31, 14, 56, 55),
"params": {
"params.yaml": {
"data": {
"foo": 0,
}
}
},
"status": "Success",
"executor": None,
"metrics": {},
"name": "exp-4f89e",
}
},
},
}
show_experiments(
sort_experiments,
precision=None,
fill_value="",
iso=True,
csv=True,
sort_by="foo",
sort_order=sort_order,
)
cap = capsys.readouterr()
rows = list(csv.reader(cap.out.strip().split("\n")))
# [3:] To skip header, workspace and baseline(master)
# which are not affected by order
params = tuple(int(row[-1]) for row in rows[3:])
if sort_order == "asc":
assert params == (0, 1, 2)
else:
assert params == (2, 1, 0)
@pytest.mark.parametrize("extra_args", [(), ("--run",)])
def test_experiments_init(dvc, scm, mocker, capsys, extra_args):
stage = mocker.Mock(outs=[], addressing="train")
@@ -893,35 +617,6 @@ def test_experiments_init_displays_output_on_no_run(dvc, mocker, capsys, args):
assert out.splitlines() == expected_lines
def test_show_experiments_pcp(tmp_dir, mocker):
all_experiments = {
"workspace": {
"baseline": {
"data": {
"timestamp": None,
"params": {"params.yaml": {"data": {"foo": 1}}},
"status": "Success",
"executor": None,
"metrics": {"scores.json": {"data": {"bar": 0.9544670443829399}}},
}
}
},
}
experiments_table = mocker.patch("dvc.commands.experiments.show.experiments_table")
td = experiments_table.return_value
show_experiments(all_experiments, pcp=True)
td.drop.assert_called_with("Created")
td.dropna.assert_called_with("rows", how="all", subset=set())
td.drop_duplicates.assert_called_with("rows", subset=set())
kwargs = td.to_parallel_coordinates.call_args[1]
assert kwargs["output_path"] == str(tmp_dir / "dvc_plots" / "index.html")
assert kwargs["color_by"] == "Experiment"
def test_experiments_save(dvc, scm, mocker):
cli_args = parse_args(["exp", "save", "--name", "exp-name", "--force"])
assert cli_args.func == CmdExperimentsSave
-217
View File
@@ -1,217 +0,0 @@
import pytest
from funcy import first
from dvc.repo.experiments.show import (
ExpStatus,
get_branch_names,
move_properties_to_head,
update_names,
)
def test_get_show_branch(tmp_dir, scm, dvc, exp_stage):
new_branch = "new"
baseline = scm.get_rev()
base_branch = scm.active_branch()
results = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
exp_a = first(results)
dvc.experiments.branch(exp_a, new_branch)
scm.checkout(new_branch, force=True)
result = {
"workspace": {"baseline": {"data": {}}},
exp_a: {"baseline": {"data": {}}},
baseline: {"baseline": {"data": {}}, exp_a: {"data": {}}},
}
branch_names = get_branch_names(scm, ["workspace", exp_a, baseline])
assert branch_names == {exp_a: new_branch, baseline: base_branch}
update_names(dvc, branch_names, result)
assert result[exp_a]["baseline"]["data"] == {"name": new_branch}
assert result[baseline]["baseline"]["data"] == {"name": base_branch}
assert result[baseline][exp_a]["data"] == {"name": new_branch}
@pytest.mark.parametrize(
"res_in,res_out",
[
(
{
"1": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"2": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "a",
}
},
"3": {
"data": {
"checkpoint_tip": "3",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"4": {
"data": {
"checkpoint_tip": "3",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "b",
}
},
},
{
"1": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "a",
}
},
"2": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"3": {
"data": {
"checkpoint_tip": "3",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "b",
}
},
"4": {
"data": {
"checkpoint_tip": "3",
"executor": None,
"status": ExpStatus.Success.name,
}
},
},
),
(
{
"0": {"error": {"msg": "something"}},
"1": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"2": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "a",
}
},
"3": {"data": {"name": "b"}},
},
{
"0": {"error": {"msg": "something"}},
"1": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "a",
}
},
"2": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"3": {"data": {"name": "b"}},
},
),
(
{
"1": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"2": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "a",
}
},
"3": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "b",
}
},
"4": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
}
},
},
{
"1": {
"data": {
"checkpoint_tip": "1",
"executor": "local",
"status": ExpStatus.Running.name,
"name": "a",
}
},
"2": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
"3": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
"name": "b",
}
},
"4": {
"data": {
"checkpoint_tip": "1",
"executor": None,
"status": ExpStatus.Success.name,
}
},
},
),
],
)
def test_move_properties_to_head(res_in, res_out):
move_properties_to_head({"baseline": res_in})
assert res_in == res_out