|
| 1 | +import os |
| 2 | +from copy import deepcopy |
| 3 | +from itertools import chain |
| 4 | + |
| 5 | +import pytest |
| 6 | + |
| 7 | +from dvc.dvcfile import PIPELINE_FILE, Dvcfile |
| 8 | +from dvc.serialize import get_params_deps |
| 9 | +from dvc.stage import PipelineStage, create_stage |
| 10 | +from dvc.stage.loader import StageLoader |
| 11 | + |
| 12 | + |
| 13 | +@pytest.fixture |
| 14 | +def stage_data(): |
| 15 | + return {"cmd": "command", "deps": ["foo"], "outs": ["bar"]} |
| 16 | + |
| 17 | + |
| 18 | +@pytest.fixture |
| 19 | +def lock_data(): |
| 20 | + return { |
| 21 | + "cmd": "command", |
| 22 | + "deps": [{"path": "foo", "md5": "foo_checksum"}], |
| 23 | + "outs": [{"path": "bar", "md5": "bar_checksum"}], |
| 24 | + } |
| 25 | + |
| 26 | + |
| 27 | +def test_fill_from_lock_deps_outs(dvc, lock_data): |
| 28 | + stage = create_stage( |
| 29 | + PipelineStage, dvc, PIPELINE_FILE, deps=["foo"], outs=["bar"] |
| 30 | + ) |
| 31 | + |
| 32 | + for item in chain(stage.deps, stage.outs): |
| 33 | + assert not item.checksum and not item.info |
| 34 | + |
| 35 | + StageLoader.fill_from_lock(stage, lock_data) |
| 36 | + |
| 37 | + assert stage.deps[0].info == {"md5": "foo_checksum"} |
| 38 | + assert stage.outs[0].info == {"md5": "bar_checksum"} |
| 39 | + |
| 40 | + |
| 41 | +def test_fill_from_lock_params(dvc, lock_data): |
| 42 | + stage = create_stage( |
| 43 | + PipelineStage, |
| 44 | + dvc, |
| 45 | + PIPELINE_FILE, |
| 46 | + deps=["foo"], |
| 47 | + outs=["bar"], |
| 48 | + params=[ |
| 49 | + "lorem", |
| 50 | + "lorem.ipsum", |
| 51 | + {"myparams.yaml": ["ipsum", "foobar"]}, |
| 52 | + ], |
| 53 | + ) |
| 54 | + lock_data["params"] = { |
| 55 | + "params.yaml": { |
| 56 | + "lorem": "lorem", |
| 57 | + "lorem.ipsum": ["i", "p", "s", "u", "m"], |
| 58 | + }, |
| 59 | + "myparams.yaml": { |
| 60 | + # missing value in lock for `foobar` params |
| 61 | + "ipsum": "ipsum" |
| 62 | + }, |
| 63 | + } |
| 64 | + params_deps = get_params_deps(stage)[0] |
| 65 | + assert set(params_deps[0].params) == {"lorem", "lorem.ipsum"} |
| 66 | + assert set(params_deps[1].params) == {"ipsum", "foobar"} |
| 67 | + assert not params_deps[0].info |
| 68 | + assert not params_deps[1].info |
| 69 | + |
| 70 | + StageLoader.fill_from_lock(stage, lock_data) |
| 71 | + assert params_deps[0].info == lock_data["params"]["params.yaml"] |
| 72 | + assert params_deps[1].info == lock_data["params"]["myparams.yaml"] |
| 73 | + |
| 74 | + |
| 75 | +def test_fill_from_lock_missing_params_section(dvc, lock_data): |
| 76 | + stage = create_stage( |
| 77 | + PipelineStage, |
| 78 | + dvc, |
| 79 | + PIPELINE_FILE, |
| 80 | + deps=["foo"], |
| 81 | + outs=["bar"], |
| 82 | + params=["lorem", "lorem.ipsum", {"myparams.yaml": ["ipsum"]}], |
| 83 | + ) |
| 84 | + params_deps = get_params_deps(stage)[0] |
| 85 | + StageLoader.fill_from_lock(stage, lock_data) |
| 86 | + assert not params_deps[0].info and not params_deps[1].info |
| 87 | + |
| 88 | + |
| 89 | +def test_fill_from_lock_missing_checksums(dvc, lock_data): |
| 90 | + stage = create_stage( |
| 91 | + PipelineStage, |
| 92 | + dvc, |
| 93 | + PIPELINE_FILE, |
| 94 | + deps=["foo", "foo1"], |
| 95 | + outs=["bar", "bar1"], |
| 96 | + ) |
| 97 | + |
| 98 | + StageLoader.fill_from_lock(stage, lock_data) |
| 99 | + |
| 100 | + assert stage.deps[0].info == {"md5": "foo_checksum"} |
| 101 | + assert stage.outs[0].info == {"md5": "bar_checksum"} |
| 102 | + assert not stage.deps[1].checksum and not stage.outs[1].checksum |
| 103 | + |
| 104 | + |
| 105 | +def test_fill_from_lock_use_appropriate_checksum(dvc, lock_data): |
| 106 | + stage = create_stage( |
| 107 | + PipelineStage, |
| 108 | + dvc, |
| 109 | + PIPELINE_FILE, |
| 110 | + deps=["s3://dvc-temp/foo"], |
| 111 | + outs=["bar"], |
| 112 | + ) |
| 113 | + lock_data["deps"] = [ |
| 114 | + {"path": "s3://dvc-temp/foo", "md5": "high five", "etag": "e-tag"} |
| 115 | + ] |
| 116 | + StageLoader.fill_from_lock(stage, lock_data) |
| 117 | + assert stage.deps[0].checksum == "e-tag" |
| 118 | + assert stage.outs[0].checksum == "bar_checksum" |
| 119 | + |
| 120 | + |
| 121 | +def test_fill_from_lock_with_missing_sections(dvc, lock_data): |
| 122 | + stage = create_stage( |
| 123 | + PipelineStage, dvc, PIPELINE_FILE, deps=["foo"], outs=["bar"] |
| 124 | + ) |
| 125 | + lock = deepcopy(lock_data) |
| 126 | + del lock["deps"] |
| 127 | + StageLoader.fill_from_lock(stage, lock) |
| 128 | + assert not stage.deps[0].checksum |
| 129 | + assert stage.outs[0].checksum == "bar_checksum" |
| 130 | + |
| 131 | + lock = deepcopy(lock_data) |
| 132 | + del lock["outs"] |
| 133 | + StageLoader.fill_from_lock(stage, lock) |
| 134 | + assert stage.deps[0].checksum == "foo_checksum" |
| 135 | + assert not stage.outs[0].checksum |
| 136 | + |
| 137 | + |
| 138 | +def test_fill_from_lock_empty_data(dvc): |
| 139 | + stage = create_stage( |
| 140 | + PipelineStage, dvc, PIPELINE_FILE, deps=["foo"], outs=["bar"] |
| 141 | + ) |
| 142 | + StageLoader.fill_from_lock(stage, None) |
| 143 | + assert not stage.deps[0].checksum and not stage.outs[0].checksum |
| 144 | + StageLoader.fill_from_lock(stage, {}) |
| 145 | + assert not stage.deps[0].checksum and not stage.outs[0].checksum |
| 146 | + |
| 147 | + |
| 148 | +def test_load_stage(dvc, stage_data, lock_data): |
| 149 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 150 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) |
| 151 | + |
| 152 | + assert stage.wdir == os.path.abspath(os.curdir) |
| 153 | + assert stage.name == "stage-1" |
| 154 | + assert stage.cmd == "command" |
| 155 | + assert stage.path == os.path.abspath(PIPELINE_FILE) |
| 156 | + assert stage.deps[0].def_path == "foo" |
| 157 | + assert stage.deps[0].checksum == "foo_checksum" |
| 158 | + assert stage.outs[0].def_path == "bar" |
| 159 | + assert stage.outs[0].checksum == "bar_checksum" |
| 160 | + |
| 161 | + |
| 162 | +def test_load_stage_outs_with_flags(dvc, stage_data, lock_data): |
| 163 | + stage_data["outs"] = [{"foo": {"cache": False}}] |
| 164 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 165 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) |
| 166 | + assert stage.outs[0].use_cache is False |
| 167 | + |
| 168 | + |
| 169 | +def test_load_stage_no_lock(dvc, stage_data): |
| 170 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 171 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data) |
| 172 | + assert stage.deps[0].def_path == "foo" and stage.outs[0].def_path == "bar" |
| 173 | + assert not stage.deps[0].checksum |
| 174 | + assert not stage.outs[0].checksum |
| 175 | + |
| 176 | + |
| 177 | +def test_load_stage_with_params(dvc, stage_data, lock_data): |
| 178 | + lock_data["params"] = {"params.yaml": {"lorem": "ipsum"}} |
| 179 | + stage_data["params"] = ["lorem"] |
| 180 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 181 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) |
| 182 | + |
| 183 | + params, deps = get_params_deps(stage) |
| 184 | + assert deps[0].def_path == "foo" and stage.outs[0].def_path == "bar" |
| 185 | + assert params[0].def_path == "params.yaml" |
| 186 | + assert params[0].info == {"lorem": "ipsum"} |
| 187 | + assert deps[0].checksum == "foo_checksum" |
| 188 | + assert stage.outs[0].checksum == "bar_checksum" |
| 189 | + |
| 190 | + |
| 191 | +@pytest.mark.parametrize("typ", ["metrics", "plots"]) |
| 192 | +def test_load_stage_with_metrics_and_plots(dvc, stage_data, lock_data, typ): |
| 193 | + stage_data[typ] = stage_data.pop("outs") |
| 194 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 195 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) |
| 196 | + |
| 197 | + assert stage.outs[0].def_path == "bar" |
| 198 | + assert stage.outs[0].checksum == "bar_checksum" |
| 199 | + |
| 200 | + |
| 201 | +def test_load_changed_command(dvc, stage_data, lock_data): |
| 202 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 203 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data) |
| 204 | + assert not stage.cmd_changed |
| 205 | + assert stage.cmd == "command" |
| 206 | + |
| 207 | + lock_data["cmd"] = "different-command" |
| 208 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) |
| 209 | + assert stage.cmd_changed |
| 210 | + assert stage.cmd == "command" |
| 211 | + |
| 212 | + |
| 213 | +def test_load_stage_wdir_and_path_correctly(dvc, stage_data, lock_data): |
| 214 | + stage_data["wdir"] = "dir" |
| 215 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 216 | + stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) |
| 217 | + |
| 218 | + assert stage.wdir == os.path.abspath("dir") |
| 219 | + assert stage.path == os.path.abspath(PIPELINE_FILE) |
| 220 | + |
| 221 | + |
| 222 | +def test_load_stage_mapping(dvc, stage_data, lock_data): |
| 223 | + dvcfile = Dvcfile(dvc, PIPELINE_FILE) |
| 224 | + loader = StageLoader(dvcfile, {"stage": stage_data}, {"stage": lock_data}) |
| 225 | + assert len(loader) == 1 |
| 226 | + assert "stage" in loader |
| 227 | + assert "stage1" not in loader |
| 228 | + assert loader.keys() == {"stage"} |
| 229 | + assert isinstance(loader["stage"], PipelineStage) |
0 commit comments