mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
* plots: allow dir plots * first scenario * separate dvclive tests * rename index to step * another test * refactor * its ok to have step in data * sort plots * introduce --logs * add logs to run commands * variable name refactoring * fix logs option * fixup * cmd: run: fix unit tests * plots: modify: fix tests * improve dir handling * fixup * dvclive: logs summary * api: introduce logs summarization method * handle no repo case * fix granular dir plots * api: dvclive: simplify * dvclive: roll back logs flag * cmd: introduce logs * api: adjust to logs * test: dvclive fix * rename logs to dvclive * live: command: add uninitialized * outs: dvclive type * dvclive: support providing config via evn variables * dvclive: treat as metrics * dvclive integration: add support for summary on/off * dvc integration: add temporary test for configuration export * remove requirements * cmd: introduce logs * rename logs to dvclive * cmd: introduce logs * remove logs leftowvers * dvclive: env: adjust to chekpoints env variables * command: live: remove unnecessary help * fix linter suggestions * type hinting * review refactor * replace dvclive with live * stage: loading: move live loading to separate method * stage: serialize: allow only single live output * live: add show/diff * stage: serialize: roll back single live enforcing * fixup * rebase fixups * output: live: force single instance * live: command: add unit tests * Update dvc/output/base.py Co-authored-by: Ruslan Kuprieiev <[email protected]> * Update dvc/stage/__init__.py Co-authored-by: Ruslan Kuprieiev <[email protected]> * Update dvc/stage/serialize.py Co-authored-by: Ruslan Kuprieiev <[email protected]> * review v2 * live: fix summary tracking by experiments * naming fixups * api: live: add tests * tests: api: separate live tests Co-authored-by: Ruslan Kuprieiev <[email protected]>
830 lines
24 KiB
Python
830 lines
24 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
import shutil
|
|
from collections import OrderedDict
|
|
|
|
import pytest
|
|
|
|
from dvc.exceptions import (
|
|
MetricDoesNotExistError,
|
|
NoMetricsFoundError,
|
|
NoMetricsParsedError,
|
|
OverlappingOutputPathsError,
|
|
)
|
|
from dvc.main import main
|
|
from dvc.path_info import PathInfo
|
|
from dvc.repo import Repo
|
|
from dvc.repo.plots.data import (
|
|
JSONPlotData,
|
|
PlotData,
|
|
PlotMetricTypeError,
|
|
YAMLPlotData,
|
|
)
|
|
from dvc.repo.plots.template import (
|
|
BadTemplateError,
|
|
NoFieldInDataError,
|
|
TemplateNotFoundError,
|
|
)
|
|
from dvc.utils.fs import remove
|
|
from dvc.utils.serialize import dump_yaml, dumps_yaml, modify_yaml
|
|
from tests.func.plots.utils import _write_csv, _write_json
|
|
|
|
|
|
def test_plot_csv_one_column(tmp_dir, scm, dvc, run_copy_metrics):
|
|
# no header
|
|
metric = [{"val": 2}, {"val": 3}]
|
|
_write_csv(metric, "metric_t.csv", header=False)
|
|
run_copy_metrics(
|
|
"metric_t.csv", "metric.csv", plots_no_cache=["metric.csv"]
|
|
)
|
|
|
|
props = {
|
|
"header": False,
|
|
"x_label": "x_title",
|
|
"y_label": "y_title",
|
|
"title": "mytitle",
|
|
}
|
|
plot_string = dvc.plots.show(props=props)["metric.csv"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["title"] == "mytitle"
|
|
assert plot_content["data"]["values"] == [
|
|
{"0": "2", PlotData.INDEX_FIELD: 0, "rev": "workspace"},
|
|
{"0": "3", PlotData.INDEX_FIELD: 1, "rev": "workspace"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "0"
|
|
assert plot_content["encoding"]["x"]["title"] == "x_title"
|
|
assert plot_content["encoding"]["y"]["title"] == "y_title"
|
|
|
|
|
|
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_t.csv")
|
|
run_copy_metrics(
|
|
"metric_t.csv", "metric.csv", plots_no_cache=["metric.csv"]
|
|
)
|
|
|
|
plot_string = dvc.plots.show()["metric.csv"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{
|
|
"val": "2",
|
|
PlotData.INDEX_FIELD: 0,
|
|
"rev": "workspace",
|
|
"first_val": "100",
|
|
"second_val": "100",
|
|
},
|
|
{
|
|
"val": "3",
|
|
PlotData.INDEX_FIELD: 1,
|
|
"rev": "workspace",
|
|
"first_val": "200",
|
|
"second_val": "300",
|
|
},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "val"
|
|
|
|
|
|
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_t.csv")
|
|
run_copy_metrics(
|
|
"metric_t.csv", "metric.csv", plots_no_cache=["metric.csv"]
|
|
)
|
|
|
|
props = {"x": "first_val", "y": "second_val"}
|
|
plot_string = dvc.plots.show(props=props)["metric.csv"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{
|
|
"val": "2",
|
|
"rev": "workspace",
|
|
"first_val": "100",
|
|
"second_val": "100",
|
|
},
|
|
{
|
|
"val": "3",
|
|
"rev": "workspace",
|
|
"first_val": "200",
|
|
"second_val": "300",
|
|
},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == "first_val"
|
|
assert plot_content["encoding"]["y"]["field"] == "second_val"
|
|
|
|
|
|
def test_plot_json_single_val(tmp_dir, scm, dvc, run_copy_metrics):
|
|
metric = [{"val": 2}, {"val": 3}]
|
|
_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"]
|
|
|
|
plot_json = json.loads(plot_string)
|
|
assert plot_json["data"]["values"] == [
|
|
{"val": 2, PlotData.INDEX_FIELD: 0, "rev": "workspace"},
|
|
{"val": 3, PlotData.INDEX_FIELD: 1, "rev": "workspace"},
|
|
]
|
|
assert plot_json["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_json["encoding"]["y"]["field"] == "val"
|
|
|
|
|
|
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_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"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{
|
|
"val": 2,
|
|
PlotData.INDEX_FIELD: 0,
|
|
"first_val": 100,
|
|
"rev": "workspace",
|
|
},
|
|
{
|
|
"val": 3,
|
|
PlotData.INDEX_FIELD: 1,
|
|
"first_val": 200,
|
|
"rev": "workspace",
|
|
},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "val"
|
|
|
|
|
|
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_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="first run",
|
|
)
|
|
|
|
props = {"template": "confusion", "x": "predicted", "y": "actual"}
|
|
plot_string = dvc.plots.show(props=props)["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"predicted": "B", "actual": "A", "rev": "workspace"},
|
|
{"predicted": "A", "actual": "A", "rev": "workspace"},
|
|
]
|
|
assert plot_content["spec"]["transform"][0]["groupby"] == [
|
|
"actual",
|
|
"predicted",
|
|
]
|
|
assert plot_content["spec"]["encoding"]["x"]["field"] == "predicted"
|
|
assert plot_content["spec"]["encoding"]["y"]["field"] == "actual"
|
|
|
|
|
|
def test_plot_confusion_normalized(tmp_dir, dvc, run_copy_metrics):
|
|
confusion_matrix = [
|
|
{"predicted": "B", "actual": "A"},
|
|
{"predicted": "A", "actual": "A"},
|
|
]
|
|
_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",
|
|
)
|
|
|
|
props = {
|
|
"template": "confusion_normalized",
|
|
"x": "predicted",
|
|
"y": "actual",
|
|
}
|
|
plot_string = dvc.plots.show(props=props)["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"predicted": "B", "actual": "A", "rev": "workspace"},
|
|
{"predicted": "A", "actual": "A", "rev": "workspace"},
|
|
]
|
|
assert plot_content["spec"]["transform"][0]["groupby"] == [
|
|
"actual",
|
|
"predicted",
|
|
]
|
|
assert plot_content["spec"]["transform"][1]["groupby"] == ["rev", "actual"]
|
|
assert plot_content["spec"]["encoding"]["x"]["field"] == "predicted"
|
|
assert plot_content["spec"]["encoding"]["y"]["field"] == "actual"
|
|
|
|
|
|
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_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_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_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="third",
|
|
)
|
|
plot_string = dvc.plots.show(
|
|
revs=["HEAD", "v2", "v1"], props={"fields": {"y"}}
|
|
)["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"y": 5, PlotData.INDEX_FIELD: 0, "rev": "HEAD"},
|
|
{"y": 6, PlotData.INDEX_FIELD: 1, "rev": "HEAD"},
|
|
{"y": 3, PlotData.INDEX_FIELD: 0, "rev": "v2"},
|
|
{"y": 5, PlotData.INDEX_FIELD: 1, "rev": "v2"},
|
|
{"y": 2, PlotData.INDEX_FIELD: 0, "rev": "v1"},
|
|
{"y": 3, PlotData.INDEX_FIELD: 1, "rev": "v1"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "y"
|
|
|
|
|
|
def test_plot_multiple_revs(tmp_dir, scm, dvc, run_copy_metrics):
|
|
templates_dir = dvc.plots.templates.templates_dir
|
|
shutil.copy(
|
|
os.path.join(templates_dir, "default.json"),
|
|
os.path.join(templates_dir, "template.json"),
|
|
)
|
|
|
|
metric_1 = [{"y": 2}, {"y": 3}]
|
|
_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_t.json")
|
|
assert dvc.reproduce(stage.addressing) == [stage]
|
|
scm.add(["metric.json", stage.path])
|
|
scm.commit("third")
|
|
|
|
props = {"template": "template.json"}
|
|
plot_string = dvc.plots.show(revs=["HEAD", "v2", "v1"], props=props)[
|
|
"metric.json"
|
|
]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"y": 5, PlotData.INDEX_FIELD: 0, "rev": "HEAD"},
|
|
{"y": 6, PlotData.INDEX_FIELD: 1, "rev": "HEAD"},
|
|
{"y": 3, PlotData.INDEX_FIELD: 0, "rev": "v2"},
|
|
{"y": 5, PlotData.INDEX_FIELD: 1, "rev": "v2"},
|
|
{"y": 2, PlotData.INDEX_FIELD: 0, "rev": "v1"},
|
|
{"y": 3, PlotData.INDEX_FIELD: 1, "rev": "v1"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "y"
|
|
|
|
|
|
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_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="there is metric",
|
|
tag="v2",
|
|
)
|
|
|
|
caplog.clear()
|
|
with caplog.at_level(logging.WARNING, "dvc"):
|
|
plots = dvc.plots.show(revs=["v1", "v2"], targets=["metric.json"])
|
|
assert "'metric.json' was not found at: 'v1'." in caplog.text
|
|
|
|
plot_content = json.loads(plots["metric.json"])
|
|
assert plot_content["data"]["values"] == [
|
|
{"y": 2, PlotData.INDEX_FIELD: 0, "rev": "v2"},
|
|
{"y": 3, PlotData.INDEX_FIELD: 1, "rev": "v2"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "y"
|
|
|
|
|
|
def test_plot_cache_missing(tmp_dir, scm, dvc, caplog, run_copy_metrics):
|
|
metric = [{"y": 2}, {"y": 3}]
|
|
_write_json(tmp_dir, metric, "metric_t.json")
|
|
stage = run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots=["metric.json"],
|
|
commit="there is metric",
|
|
)
|
|
scm.tag("v1")
|
|
|
|
# Make a different plot and then remove its datafile
|
|
metric = [{"y": 3}, {"y": 4}]
|
|
_write_json(tmp_dir, metric, "metric_t.json")
|
|
stage = run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots=["metric.json"],
|
|
commit="there is an another metric",
|
|
)
|
|
scm.tag("v2")
|
|
remove(stage.outs[0].fspath)
|
|
remove(stage.outs[0].cache_path)
|
|
|
|
plots = dvc.plots.show(revs=["v1", "v2"], targets=["metric.json"])
|
|
plot_content = json.loads(plots["metric.json"])
|
|
assert plot_content["data"]["values"] == [
|
|
{"y": 2, PlotData.INDEX_FIELD: 0, "rev": "v1"},
|
|
{"y": 3, PlotData.INDEX_FIELD: 1, "rev": "v1"},
|
|
]
|
|
|
|
|
|
def test_throw_on_no_metric_at_all(tmp_dir, scm, dvc, caplog):
|
|
tmp_dir.scm_gen("some_file", "content", commit="there is no metric")
|
|
scm.tag("v1")
|
|
|
|
tmp_dir.gen("some_file", "make repo dirty")
|
|
|
|
caplog.clear()
|
|
with pytest.raises(MetricDoesNotExistError) as error, caplog.at_level(
|
|
logging.WARNING, "dvc"
|
|
):
|
|
dvc.plots.show(targets="plot.json", revs=["v1"])
|
|
|
|
# do not warn if none found
|
|
assert len(caplog.messages) == 0
|
|
|
|
assert str(error.value) == "'plot.json' does not exist."
|
|
|
|
|
|
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_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="init",
|
|
tag="v1",
|
|
)
|
|
|
|
props = {"template": os.fspath(custom_template), "x": "a", "y": "b"}
|
|
plot_string = dvc.plots.show(props=props)["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"a": 1, "b": 2, "rev": "workspace"},
|
|
{"a": 2, "b": 3, "rev": "workspace"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == "a"
|
|
assert plot_content["encoding"]["y"]["field"] == "b"
|
|
|
|
|
|
def _replace(path, src, dst):
|
|
path.write_text(path.read_text().replace(src, dst))
|
|
|
|
|
|
def test_should_raise_on_no_template(tmp_dir, dvc, run_copy_metrics):
|
|
metric = [{"val": 2}, {"val": 3}]
|
|
_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(TemplateNotFoundError):
|
|
props = {"template": "non_existing_template.json"}
|
|
dvc.plots.show("metric.json", props=props)
|
|
|
|
|
|
def test_bad_template(tmp_dir, dvc, run_copy_metrics):
|
|
metric = [{"val": 2}, {"val": 3}]
|
|
_write_json(tmp_dir, metric, "metric_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="first run",
|
|
)
|
|
|
|
tmp_dir.gen("template.json", json.dumps({"a": "b", "c": "d"}))
|
|
|
|
with pytest.raises(BadTemplateError):
|
|
props = {"template": "template.json"}
|
|
dvc.plots.show("metric.json", props=props)
|
|
|
|
|
|
def test_plot_wrong_metric_type(tmp_dir, scm, dvc, run_copy_metrics):
|
|
tmp_dir.gen("metric_t.txt", "some text")
|
|
run_copy_metrics(
|
|
"metric_t.txt",
|
|
"metric.txt",
|
|
plots_no_cache=["metric.txt"],
|
|
commit="add text metric",
|
|
)
|
|
|
|
with pytest.raises(PlotMetricTypeError):
|
|
dvc.plots.show(targets=["metric.txt"])
|
|
|
|
|
|
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_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="init",
|
|
tag="v1",
|
|
)
|
|
|
|
props = {
|
|
"template": os.fspath(custom_template),
|
|
"fields": {"b", "c"},
|
|
"x": "b",
|
|
"y": "c",
|
|
}
|
|
plot_string = dvc.plots.show(props=props)["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"b": 2, "c": 3, "rev": "workspace"},
|
|
{"b": 3, "c": 4, "rev": "workspace"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == "b"
|
|
assert plot_content["encoding"]["y"]["field"] == "c"
|
|
|
|
|
|
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_t.json")
|
|
run_copy_metrics(
|
|
"metric_t.json",
|
|
"metric.json",
|
|
plots_no_cache=["metric.json"],
|
|
commit="init",
|
|
tag="v1",
|
|
)
|
|
|
|
plot_string = dvc.plots.show(props={"fields": {"b"}})["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{PlotData.INDEX_FIELD: 0, "b": 2, "rev": "workspace"},
|
|
{PlotData.INDEX_FIELD: 1, "b": 3, "rev": "workspace"},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "b"
|
|
|
|
|
|
def test_plot_yaml(tmp_dir, scm, dvc, run_copy_metrics):
|
|
metric = [{"val": 2}, {"val": 3}]
|
|
dump_yaml("metric_t.yaml", metric)
|
|
run_copy_metrics(
|
|
"metric_t.yaml", "metric.yaml", plots_no_cache=["metric.yaml"]
|
|
)
|
|
|
|
plot_string = dvc.plots.show()["metric.yaml"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{"val": 2, PlotData.INDEX_FIELD: 0, "rev": "workspace"},
|
|
{"val": 3, PlotData.INDEX_FIELD: 1, "rev": "workspace"},
|
|
]
|
|
|
|
|
|
def test_raise_on_wrong_field(tmp_dir, scm, dvc, run_copy_metrics):
|
|
metric = [{"val": 2}, {"val": 3}]
|
|
_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", props={"x": "no_val"})
|
|
|
|
with pytest.raises(NoFieldInDataError):
|
|
dvc.plots.show("metric.json", props={"y": "no_val"})
|
|
|
|
|
|
def test_load_metric_from_dict_json(tmp_dir):
|
|
metric = [{"acccuracy": 1, "loss": 2}, {"accuracy": 3, "loss": 4}]
|
|
dmetric = {"train": metric}
|
|
|
|
plot_data = JSONPlotData("-", "revision", json.dumps(dmetric))
|
|
|
|
expected = metric
|
|
for d in expected:
|
|
d["rev"] = "revision"
|
|
|
|
assert list(map(dict, plot_data.to_datapoints())) == expected
|
|
|
|
|
|
def test_load_metric_from_dict_yaml(tmp_dir):
|
|
metric = [{"acccuracy": 1, "loss": 2}, {"accuracy": 3, "loss": 4}]
|
|
dmetric = {"train": metric}
|
|
|
|
plot_data = YAMLPlotData("-", "revision", dumps_yaml(dmetric))
|
|
|
|
expected = metric
|
|
for d in expected:
|
|
d["rev"] = "revision"
|
|
|
|
assert list(map(dict, plot_data.to_datapoints())) == expected
|
|
|
|
|
|
def test_multiple_plots(tmp_dir, scm, dvc, run_copy_metrics):
|
|
metric1 = [
|
|
OrderedDict([("first_val", 100), ("second_val", 100), ("val", 2)]),
|
|
OrderedDict([("first_val", 200), ("second_val", 300), ("val", 3)]),
|
|
]
|
|
metric2 = [
|
|
OrderedDict([("first_val", 100), ("second_val", 100), ("val", 2)]),
|
|
OrderedDict([("first_val", 200), ("second_val", 300), ("val", 3)]),
|
|
]
|
|
_write_csv(metric1, "metric_t1.csv")
|
|
_write_json(tmp_dir, metric2, "metric_t2.json")
|
|
run_copy_metrics(
|
|
"metric_t1.csv", "metric1.csv", plots_no_cache=["metric1.csv"]
|
|
)
|
|
run_copy_metrics(
|
|
"metric_t2.json", "metric2.json", plots_no_cache=["metric2.json"]
|
|
)
|
|
|
|
assert len(dvc.plots.show().keys()) == 2
|
|
|
|
|
|
@pytest.mark.parametrize("use_dvc", [True, False])
|
|
def test_show_non_plot(tmp_dir, scm, use_dvc):
|
|
metric = [
|
|
{"first_val": 100, "val": 2},
|
|
{"first_val": 200, "val": 3},
|
|
]
|
|
_write_json(tmp_dir, metric, "metric.json")
|
|
|
|
if use_dvc:
|
|
dvc = Repo.init()
|
|
else:
|
|
dvc = Repo(uninitialized=True)
|
|
|
|
plot_string = dvc.plots.show(targets=["metric.json"])["metric.json"]
|
|
|
|
plot_content = json.loads(plot_string)
|
|
assert plot_content["data"]["values"] == [
|
|
{
|
|
"val": 2,
|
|
PlotData.INDEX_FIELD: 0,
|
|
"first_val": 100,
|
|
"rev": "workspace",
|
|
},
|
|
{
|
|
"val": 3,
|
|
PlotData.INDEX_FIELD: 1,
|
|
"first_val": 200,
|
|
"rev": "workspace",
|
|
},
|
|
]
|
|
assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
|
|
assert plot_content["encoding"]["y"]["field"] == "val"
|
|
|
|
if not use_dvc:
|
|
assert not (tmp_dir / ".dvc").exists()
|
|
|
|
|
|
def test_show_non_plot_and_plot_with_params(
|
|
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_copy_metrics(
|
|
"metric.json", "metric2.json", plots_no_cache=["metric2.json"]
|
|
)
|
|
|
|
dvc.plots.modify("metric2.json", props={"title": "TITLE"})
|
|
result = dvc.plots.show(targets=["metric.json", "metric2.json"])
|
|
|
|
plot_content = json.loads(result["metric.json"])
|
|
plot2_content = json.loads(result["metric2.json"])
|
|
|
|
assert plot2_content["title"] == "TITLE"
|
|
|
|
assert plot_content != plot2_content
|
|
plot_content.pop("title")
|
|
plot2_content.pop("title")
|
|
assert plot_content == plot2_content
|
|
|
|
|
|
def test_show_no_repo(tmp_dir):
|
|
metric = [
|
|
{"first_val": 100, "val": 2},
|
|
{"first_val": 200, "val": 3},
|
|
]
|
|
_write_json(tmp_dir, metric, "metric.json")
|
|
|
|
dvc = Repo(uninitialized=True)
|
|
|
|
dvc.plots.show(["metric.json"])
|
|
|
|
|
|
def test_show_from_subdir(tmp_dir, dvc, caplog):
|
|
subdir = tmp_dir / "subdir"
|
|
|
|
subdir.mkdir()
|
|
metric = [
|
|
{"first_val": 100, "val": 2},
|
|
{"first_val": 200, "val": 3},
|
|
]
|
|
_write_json(subdir, metric, "metric.json")
|
|
|
|
with subdir.chdir(), caplog.at_level(logging.INFO, "dvc"):
|
|
assert main(["plots", "show", "metric.json"]) == 0
|
|
|
|
assert f"file://{str(subdir)}" in caplog.text
|
|
assert (subdir / "plots.html").exists()
|
|
|
|
|
|
def test_show_malformed_plots(tmp_dir, scm, dvc, caplog):
|
|
tmp_dir.gen("plot.json", '[{"m":1]')
|
|
|
|
with pytest.raises(NoMetricsParsedError):
|
|
dvc.plots.show(targets=["plot.json"])
|
|
|
|
|
|
def test_plots_show_no_target(tmp_dir, dvc):
|
|
with pytest.raises(MetricDoesNotExistError):
|
|
dvc.plots.show(targets=["plot.json"])
|
|
|
|
|
|
def test_show_no_plots_files(tmp_dir, dvc, caplog):
|
|
with pytest.raises(NoMetricsFoundError):
|
|
dvc.plots.show()
|
|
|
|
|
|
@pytest.mark.parametrize("clear_before_run", [True, False])
|
|
def test_plots_show_overlap(tmp_dir, dvc, run_copy_metrics, clear_before_run):
|
|
data_dir = PathInfo("data")
|
|
(tmp_dir / data_dir).mkdir()
|
|
|
|
dump_yaml(data_dir / "m1_temp.yaml", {"a": {"b": {"c": 2, "d": 1}}})
|
|
run_copy_metrics(
|
|
str(data_dir / "m1_temp.yaml"),
|
|
str(data_dir / "m1.yaml"),
|
|
single_stage=False,
|
|
commit="add m1",
|
|
name="cp-m1",
|
|
plots=[str(data_dir / "m1.yaml")],
|
|
)
|
|
with modify_yaml("dvc.yaml") as d:
|
|
# trying to make an output overlaps error
|
|
d["stages"]["corrupted-stage"] = {
|
|
"cmd": "mkdir data",
|
|
"outs": ["data"],
|
|
}
|
|
|
|
# running by clearing and not clearing stuffs
|
|
# so as it works even for optimized cases
|
|
if clear_before_run:
|
|
remove(data_dir)
|
|
remove(dvc.cache.local.cache_dir)
|
|
|
|
dvc._reset()
|
|
|
|
with pytest.raises(OverlappingOutputPathsError):
|
|
dvc.plots.show()
|
|
|
|
|
|
def test_dir_plots(tmp_dir, dvc, run_copy_metrics):
|
|
subdir = tmp_dir / "subdir"
|
|
subdir.mkdir()
|
|
|
|
metric = [
|
|
{"first_val": 100, "val": 2},
|
|
{"first_val": 200, "val": 3},
|
|
]
|
|
|
|
fname = "file.json"
|
|
_write_json(tmp_dir, metric, fname)
|
|
|
|
p1 = os.path.join("subdir", "p1.json")
|
|
p2 = os.path.join("subdir", "p2.json")
|
|
tmp_dir.dvc.run(
|
|
cmd=(
|
|
f"mkdir subdir && python copy.py {fname} {p1} && "
|
|
f"python copy.py {fname} {p2}"
|
|
),
|
|
deps=[fname],
|
|
single_stage=False,
|
|
plots=["subdir"],
|
|
name="copy_double",
|
|
)
|
|
dvc.plots.modify("subdir", {"title": "TITLE"})
|
|
|
|
result = dvc.plots.show()
|
|
p1_content = json.loads(result[p1])
|
|
p2_content = json.loads(result[p2])
|
|
|
|
assert p1_content["title"] == p2_content["title"] == "TITLE"
|
|
|
|
|
|
def test_show_dir_plots(tmp_dir, dvc, run_copy_metrics):
|
|
subdir = tmp_dir / "subdir"
|
|
subdir.mkdir()
|
|
metric = [
|
|
{"first_val": 100, "val": 2},
|
|
{"first_val": 200, "val": 3},
|
|
]
|
|
|
|
fname = "file.json"
|
|
_write_json(tmp_dir, metric, fname)
|
|
|
|
p1 = os.path.join("subdir", "p1.json")
|
|
p2 = os.path.join("subdir", "p2.json")
|
|
tmp_dir.dvc.run(
|
|
cmd=(
|
|
f"mkdir subdir && python copy.py {fname} {p1} && "
|
|
f"python copy.py {fname} {p2}"
|
|
),
|
|
deps=[fname],
|
|
single_stage=False,
|
|
plots=["subdir"],
|
|
name="copy_double",
|
|
)
|
|
|
|
result = dvc.plots.show(targets=["subdir"])
|
|
p1_content = json.loads(result[p1])
|
|
p2_content = json.loads(result[p2])
|
|
|
|
assert p1_content == p2_content
|
|
|
|
result = dvc.plots.show(targets=[p1])
|
|
assert set(result.keys()) == {p1}
|