Files
dvc/tests/unit/output/test_output.py
T
skshetry 0eebeb9900 tests: fix flaky test in test_get_used_objs (#10739)
Clear logs before assertions so that logs from other tests do not interfere with the test.
Some destructors may also run in between due to GC, so hopefully this will fix that issue too,
or reduce the chances of it happening.

See
https://github.com/iterative/dvc/actions/runs/14873217814/job/41775314725#step:6:97.

```console
>       assert first(caplog.messages) == expected_message
E       assert 'RC: discarding all clients' == "Output 'path... 'stage.dvc'."
E
E         + RC: discarding all clients
E         - Output 'path'(stage: 'stage.dvc') is missing version info. Cache for it will not be collected. Use `dvc repro` to get your pipeline up to date.
E         - You can also use `dvc commit stage.dvc` to associate existing 'path' with stage: 'stage.dvc'.
```
2025-05-07 12:40:51 +05:45

204 lines
6.5 KiB
Python

import logging
import os
import pytest
from funcy import first
from voluptuous import MultipleInvalid, Schema
from dvc.fs import RemoteMissingDepsError
from dvc.ignore import _no_match
from dvc.output import CHECKSUM_SCHEMA, Output
from dvc.stage import Stage
from dvc.utils.fs import remove
def test_save_missing(dvc, mocker):
stage = Stage(dvc)
out = Output(stage, "path", cache=False)
mocker.patch.object(out.fs, "exists", return_value=False)
with pytest.raises(out.DoesNotExistError):
out.save()
@pytest.mark.parametrize(
"value,expected",
[
("", None),
(None, None),
(11111, "11111"),
("11111", "11111"),
("aAaBa", "aaaba"),
(
"3cc286c534a71504476da009ed174423",
"3cc286c534a71504476da009ed174423",
), # md5
(
"d41d8cd98f00b204e9800998ecf8427e-38",
"d41d8cd98f00b204e9800998ecf8427e-38",
), # etag
(
"000002000000000000000000c16859d1d071c6b1ffc9c8557d4909f1",
"000002000000000000000000c16859d1d071c6b1ffc9c8557d4909f1",
), # hdfs checksum
# Not much we can do about hex and oct values without writing our own
# parser. So listing these test cases just to acknowledge this.
# See https://github.com/iterative/dvc/issues/3331.
(0x3451, "13393"),
(0o1244, "676"),
],
)
def test_checksum_schema(value, expected):
assert Schema(CHECKSUM_SCHEMA)(value) == expected
@pytest.mark.parametrize("value", ["1", "11", {}, {"a": "b"}, [], [1, 2]])
def test_checksum_schema_fail(value):
with pytest.raises(MultipleInvalid):
assert Schema(CHECKSUM_SCHEMA)(value)
@pytest.mark.parametrize(
"exists, expected_message",
[
(
False,
(
"Output 'path'(stage: 'stage.dvc') is missing version info. "
"Cache for it will not be collected. "
"Use `dvc repro` to get your pipeline up to date."
),
),
(
True,
(
"Output 'path'(stage: 'stage.dvc') is missing version info. "
"Cache for it will not be collected. "
"Use `dvc repro` to get your pipeline up to date.\n"
"You can also use `dvc commit stage.dvc` to associate "
"existing 'path' with stage: 'stage.dvc'."
),
),
],
)
def test_get_used_objs(exists, expected_message, mocker, caplog):
stage = mocker.MagicMock()
mocker.patch.object(stage, "__str__", return_value="stage: 'stage.dvc'")
mocker.patch.object(stage, "addressing", "stage.dvc")
mocker.patch.object(stage, "wdir", os.getcwd())
mocker.patch.object(stage.repo, "root_dir", os.getcwd())
mocker.patch.object(stage.repo.dvcignore, "is_ignored", return_value=False)
mocker.patch.object(
stage.repo.dvcignore, "check_ignore", return_value=_no_match("path")
)
stage.repo.fs.version_aware = False
stage.repo.fs.PARAM_CHECKSUM = "md5"
output = Output(stage, "path")
mocker.patch.object(output, "use_cache", True)
mocker.patch.object(stage, "is_repo_import", False)
mocker.patch.object(
Output, "exists", new_callable=mocker.PropertyMock
).return_value = exists
caplog.clear()
with caplog.at_level(logging.WARNING, logger="dvc"):
assert output.get_used_objs() == {}
assert first(caplog.messages) == expected_message
def test_remote_missing_dependency_on_dir_pull(tmp_dir, scm, dvc, mocker):
tmp_dir.dvc_gen({"dir": {"subfile": "file2 content"}}, commit="add dir")
with dvc.config.edit() as conf:
conf["remote"]["s3"] = {"url": "s3://bucket/name"}
conf["core"] = {"remote": "s3"}
remove("dir")
remove(dvc.cache.local.path)
mocker.patch(
"dvc.data_cloud.DataCloud.get_remote",
side_effect=RemoteMissingDepsError(dvc.fs, "azure", "azure://", []),
)
with pytest.raises(RemoteMissingDepsError):
dvc.pull()
def test_hash_info_cloud_versioning_dir(mocker):
stage = mocker.MagicMock()
stage.repo.fs.version_aware = False
stage.repo.fs.PARAM_CHECKSUM = "etag"
files = [
{
"size": 3,
"version_id": "WYRG4BglP7pD.gEoJP6a4AqOhl.FRA.h",
"etag": "acbd18db4cc2f85cedef654fccc4a4d8",
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
"relpath": "bar",
},
{
"size": 3,
"version_id": "0vL53tFVY5vVAoJ4HG2jCS1mEcohDPE0",
"etag": "acbd18db4cc2f85cedef654fccc4a4d8",
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
"relpath": "foo",
},
]
out = Output(stage, "path", files=files)
# `hash_info`` and `meta`` constructed from `files``
assert out.hash_info.name == "md5"
assert out.hash_info.value == "77e8000f532886eef8ee1feba82e9bad.dir"
assert out.meta.isdir
assert out.meta.nfiles == 2
assert out.meta.size == 6
def test_dumpd_cloud_versioning_dir(mocker):
stage = mocker.MagicMock()
stage.repo.fs.version_aware = False
stage.repo.fs.PARAM_CHECKSUM = "md5"
files = [
{
"size": 3,
"version_id": "WYRG4BglP7pD.gEoJP6a4AqOhl.FRA.h",
"etag": "acbd18db4cc2f85cedef654fccc4a4d8",
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
"relpath": "bar",
},
{
"size": 3,
"version_id": "0vL53tFVY5vVAoJ4HG2jCS1mEcohDPE0",
"etag": "acbd18db4cc2f85cedef654fccc4a4d8",
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
"relpath": "foo",
},
]
out = Output(stage, "path", files=files)
dumpd = out.dumpd()
assert dumpd == {"path": "path", "hash": "md5", "files": files}
def test_version_aware_is_set_based_on_files(mocker):
import dvc.fs as dvc_fs
get_fs_config = mocker.spy(dvc_fs, "get_fs_config")
stage = mocker.MagicMock()
stage.repo.fs.version_aware = False
stage.repo.fs.PARAM_CHECKSUM = "etag"
files = [
{
"size": 3,
"version_id": "WYRG4BglP7pD.gEoJP6a4AqOhl.FRA.h",
"etag": "acbd18db4cc2f85cedef654fccc4a4d8",
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
"relpath": "bar",
}
]
Output(stage, "path", files=files)
# version_aware is passed as `True` if `files` is present`.
# This will be intentionally ignored in filesystems that don't handle it
# in `_prepare_credentials`.
assert get_fs_config.call_args_list[0][1] == {"url": "path", "version_aware": True}