diff --git a/dvc/testing/matchers.py b/dvc/testing/matchers.py index 7941c1074..106055ac7 100644 --- a/dvc/testing/matchers.py +++ b/dvc/testing/matchers.py @@ -1,5 +1,5 @@ from collections.abc import Iterable, Mapping -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union if TYPE_CHECKING: import builtins @@ -68,8 +68,37 @@ class attrs: # noqa: N801, PLW1641 return all(getattr(other, name) == v for name, v in self.attribs.items()) +class instance_of: # noqa: N801, PLW1641 + def __init__(self, expected_type: Union[Any, tuple[Any, ...]]) -> None: + self.expected_type = expected_type + + def __repr__(self) -> str: + if isinstance(self.expected_type, tuple): + inner = f"({', '.join(t.__name__ for t in self.expected_type)})" + else: + inner = self.expected_type.__name__ + return f"{self.__class__.__name__}({inner})" + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.expected_type) + + +class any_of: # noqa: N801, PLW1641 + def __init__(self, *items: Any) -> None: + self.items = sorted(items) + + def __repr__(self) -> str: + inner = ", ".join(map(repr, self.items)) + return f"any_of({inner})" + + def __eq__(self, other: object) -> bool: + return other in self.items + + __all__ = [ + "any_of", "attrs", "dict", + "instance_of", "unordered", ] diff --git a/tests/func/metrics/test_diff.py b/tests/func/metrics/test_diff.py index 79d3ae6cc..a839e4dde 100644 --- a/tests/func/metrics/test_diff.py +++ b/tests/func/metrics/test_diff.py @@ -4,6 +4,7 @@ from os.path import join import pytest from dvc.cli import main +from dvc.testing import matchers as M from dvc.utils import relpath from dvc.utils.serialize import JSONFileCorruptedError @@ -95,7 +96,7 @@ def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc, run_copy_metrics): assert dvc.metrics.diff(a_rev="HEAD~2") == {} -def test_metrics_diff_broken_json(M, tmp_dir, scm, dvc, run_copy_metrics): +def test_metrics_diff_broken_json(tmp_dir, scm, dvc, run_copy_metrics): metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}} (tmp_dir / "m_temp.json").dump(metrics) run_copy_metrics( @@ -143,7 +144,7 @@ def test_metrics_diff_new_metric(tmp_dir, scm, dvc, run_copy_metrics): } -def test_metrics_diff_deleted_metric(M, tmp_dir, scm, dvc, run_copy_metrics): +def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc, run_copy_metrics): metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}} (tmp_dir / "m_temp.json").dump(metrics) run_copy_metrics( diff --git a/tests/func/metrics/test_show.py b/tests/func/metrics/test_show.py index 5ae6cc316..000eaeab2 100644 --- a/tests/func/metrics/test_show.py +++ b/tests/func/metrics/test_show.py @@ -11,6 +11,7 @@ from dvc.dvcfile import PROJECT_FILE from dvc.exceptions import OverlappingOutputPathsError from dvc.repo import Repo from dvc.repo.metrics.show import FileResult, Result +from dvc.testing import matchers as M from dvc.utils.fs import remove from dvc.utils.serialize import JSONFileCorruptedError from dvc_data.index import DataIndexDirError @@ -246,7 +247,7 @@ def test_show_malformed_metric(tmp_dir, scm, dvc, caplog): ) -def test_metrics_show_no_target(M, tmp_dir, dvc, capsys): +def test_metrics_show_no_target(tmp_dir, dvc, capsys): assert dvc.metrics.show(targets=["metrics.json"]) == { "": {"data": {"metrics.json": {"error": M.instance_of(FileNotFoundError)}}} } @@ -356,7 +357,7 @@ def test_top_level_parametrized(tmp_dir, dvc): } -def test_metric_in_a_tracked_directory_with_missing_dir_file(M, tmp_dir, dvc): +def test_metric_in_a_tracked_directory_with_missing_dir_file(tmp_dir, dvc): tmp_dir.dvc_gen({"dir": {"file": "2"}}) (tmp_dir / "dvc.yaml").dump({"metrics": [join("dir", "file")]}) shutil.rmtree(tmp_dir / "dir") # remove from workspace diff --git a/tests/func/params/test_diff.py b/tests/func/params/test_diff.py index c32d5b40d..d646a95d7 100644 --- a/tests/func/params/test_diff.py +++ b/tests/func/params/test_diff.py @@ -2,6 +2,7 @@ from os.path import join import pytest +from dvc.testing import matchers as M from dvc.utils import relpath @@ -54,7 +55,7 @@ def test_diff_new(tmp_dir, scm, dvc): } -def test_diff_deleted(M, tmp_dir, scm, dvc): +def test_diff_deleted(tmp_dir, scm, dvc): tmp_dir.gen("params.yaml", "foo: bar") dvc.run(cmd="echo params.yaml", params=["foo"], name="echo-params") scm.add(["params.yaml", "Dvcfile"]) diff --git a/tests/func/params/test_show.py b/tests/func/params/test_show.py index 145d40901..8c46edd74 100644 --- a/tests/func/params/test_show.py +++ b/tests/func/params/test_show.py @@ -6,6 +6,7 @@ import pytest from dvc.dvcfile import PROJECT_FILE from dvc.repo import Repo from dvc.repo.metrics.show import FileResult, Result +from dvc.testing import matchers as M from dvc_data.index import DataIndexDirError @@ -197,7 +198,7 @@ def test_top_level_parametrized(tmp_dir, dvc): } -def test_param_in_a_tracked_directory_with_missing_dir_file(M, tmp_dir, dvc): +def test_param_in_a_tracked_directory_with_missing_dir_file(tmp_dir, dvc): tmp_dir.dvc_gen({"dir": {"file": "2"}}) (tmp_dir / "dvc.yaml").dump({"params": [join("dir", "file")]}) shutil.rmtree(tmp_dir / "dir") # remove from workspace diff --git a/tests/func/repro/test_repro.py b/tests/func/repro/test_repro.py index 4266ad2ca..6806f62fa 100644 --- a/tests/func/repro/test_repro.py +++ b/tests/func/repro/test_repro.py @@ -15,6 +15,7 @@ from dvc.output import Output from dvc.stage import PipelineStage, Stage from dvc.stage.cache import RunCacheNotSupported from dvc.stage.exceptions import StageFileDoesNotExistError, StageNotFound +from dvc.testing import matchers as M from dvc.utils.fs import remove from dvc.utils.serialize import modify_yaml from dvc_data.hashfile.hash import file_md5 @@ -55,7 +56,7 @@ def test_repro_frozen(tmp_dir, dvc, run_copy): assert stages == [data_stage, stage0] -def test_downstream(M, tmp_dir, dvc): +def test_downstream(tmp_dir, dvc): # The dependency graph should look like this: # # E diff --git a/tests/func/test_data_status.py b/tests/func/test_data_status.py index e07d5f5e3..a73ebedc4 100644 --- a/tests/func/test_data_status.py +++ b/tests/func/test_data_status.py @@ -577,7 +577,7 @@ def test_root_from_file_to_dir(tmp_dir, dvc, scm): } -def test_empty_dir(tmp_dir, scm, dvc, M): +def test_empty_dir(tmp_dir, scm, dvc): # regression testing for https://github.com/treeverse/dvc/issues/8958 tmp_dir.dvc_gen({"data": {"foo": "foo"}}) remove("data") diff --git a/tests/func/test_import_db.py b/tests/func/test_import_db.py index f4ddae530..d50c82404 100644 --- a/tests/func/test_import_db.py +++ b/tests/func/test_import_db.py @@ -6,6 +6,8 @@ import pandas as pd import pytest from funcy import compact +from dvc.testing import matchers as M + @pytest.fixture def db_path(tmp_dir): @@ -46,7 +48,7 @@ def load_data(file, output_format): ({"table": "model"}, "model"), ], ) -def test(M, tmp_dir, scm, dvc, db_connection, seed_db, output_format, args, file_name): +def test(tmp_dir, scm, dvc, db_connection, seed_db, output_format, args, file_name): seed_db(values=range(5)) if output_format == "json": file_size = 96, 192 diff --git a/tests/func/test_ls.py b/tests/func/test_ls.py index 9b4258268..4507d72c6 100644 --- a/tests/func/test_ls.py +++ b/tests/func/test_ls.py @@ -10,6 +10,7 @@ from dvc.fs import MemoryFileSystem from dvc.repo import Repo from dvc.repo.ls import _ls_tree, ls_tree from dvc.scm import CloneError +from dvc.testing import matchers as M FS_STRUCTURE = { "README.md": "content", @@ -460,7 +461,7 @@ def test_ls_shows_pipeline_tracked_outs(tmp_dir, dvc, scm, run_copy): match_files(files, ((("bar",), True),)) -def test_ls_granular(erepo_dir, M): +def test_ls_granular(erepo_dir): with erepo_dir.chdir(): erepo_dir.dvc_gen( { @@ -624,7 +625,7 @@ def test_subrepo(request, dvc_top_level, erepo_type): assert _list_files(subrepo, "dvc_dir") == {"lorem"} -def test_broken_symlink(tmp_dir, dvc, M): +def test_broken_symlink(tmp_dir, dvc): from dvc.fs import system tmp_dir.gen("file", "content") @@ -654,7 +655,7 @@ def test_broken_symlink(tmp_dir, dvc, M): ] -def test_ls_broken_dir(tmp_dir, dvc, M): +def test_ls_broken_dir(tmp_dir, dvc): from dvc_data.index import DataIndexDirError tmp_dir.dvc_gen( @@ -836,7 +837,7 @@ def _simplify_tree(files): return ret -def test_ls_tree(M, tmp_dir, scm, dvc): +def test_ls_tree(tmp_dir, scm, dvc): tmp_dir.scm_gen(FS_STRUCTURE, commit="init") tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc") @@ -887,7 +888,7 @@ def test_ls_tree(M, tmp_dir, scm, dvc): } -def test_ls_tree_dvc_only(M, tmp_dir, scm, dvc): +def test_ls_tree_dvc_only(tmp_dir, scm, dvc): tmp_dir.scm_gen(FS_STRUCTURE, commit="init") tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc") @@ -905,7 +906,7 @@ def test_ls_tree_dvc_only(M, tmp_dir, scm, dvc): assert _simplify_tree(files) == expected -def test_ls_tree_maxdepth(M, tmp_dir, scm, dvc): +def test_ls_tree_maxdepth(tmp_dir, scm, dvc): tmp_dir.scm_gen(FS_STRUCTURE, commit="init") tmp_dir.dvc_gen(DVC_STRUCTURE, commit="dvc") diff --git a/tests/func/test_virtual_directory.py b/tests/func/test_virtual_directory.py index 3c67db96b..24ee560dd 100644 --- a/tests/func/test_virtual_directory.py +++ b/tests/func/test_virtual_directory.py @@ -166,7 +166,7 @@ def test_virtual_update_noop(tmp_dir, dvc, remote): assert not dvc.push() -def test_partial_checkout_and_update(M, tmp_dir, dvc, remote): +def test_partial_checkout_and_update(tmp_dir, dvc, remote): dir1 = {f"{i}.txt": f"dir1 {i}" for i in range(10)} dir2 = {f"{i}.txt": f"dir2 {i}" for i in range(10)} tmp_dir.gen({"dir": {"foo": "foo", "subdir": dir1, "subdir2": dir2}}) diff --git a/tests/unit/command/test_get_url.py b/tests/unit/command/test_get_url.py index 5ffe7957f..fcbeaaaa2 100644 --- a/tests/unit/command/test_get_url.py +++ b/tests/unit/command/test_get_url.py @@ -1,9 +1,10 @@ from dvc.cli import parse_args from dvc.commands.get_url import CmdGetUrl from dvc.config import Config +from dvc.testing import matchers as M -def test_get_url(mocker, M): +def test_get_url(mocker): cli_args = parse_args(["get-url", "src", "out", "-j", "5"]) assert cli_args.func == CmdGetUrl diff --git a/tests/unit/command/test_ls_url.py b/tests/unit/command/test_ls_url.py index 33dde6f06..4f5bdf3fd 100644 --- a/tests/unit/command/test_ls_url.py +++ b/tests/unit/command/test_ls_url.py @@ -2,9 +2,10 @@ from dvc.cli import parse_args from dvc.commands.ls_url import CmdListUrl from dvc.config import Config from dvc.fs import LocalFileSystem +from dvc.testing import matchers as M -def test_ls_url(mocker, M): +def test_ls_url(mocker): cli_args = parse_args(["ls-url", "src"]) assert cli_args.func == CmdListUrl cmd = cli_args.func(cli_args) @@ -21,7 +22,7 @@ def test_ls_url(mocker, M): ) -def test_recursive(mocker, M): +def test_recursive(mocker): cli_args = parse_args(["ls-url", "-R", "-L", "2", "src"]) assert cli_args.func == CmdListUrl cmd = cli_args.func(cli_args) @@ -34,7 +35,7 @@ def test_recursive(mocker, M): ) -def test_tree(mocker, M): +def test_tree(mocker): cli_args = parse_args(["ls-url", "--tree", "--level", "2", "src"]) assert cli_args.func == CmdListUrl cmd = cli_args.func(cli_args) diff --git a/tests/unit/render/test_match.py b/tests/unit/render/test_match.py index aae4ae210..d3b3a445c 100644 --- a/tests/unit/render/test_match.py +++ b/tests/unit/render/test_match.py @@ -4,6 +4,7 @@ from funcy import set_in from dvc.render import FIELD, FILENAME, REVISION from dvc.render.converter.vega import VegaConverter from dvc.render.match import PlotsData, _squash_plots_properties, match_defs_renderers +from dvc.testing import matchers as M @pytest.mark.parametrize( @@ -118,7 +119,7 @@ def test_group_definitions(data, expected): assert grouped == expected -def test_match_renderers(M): +def test_match_renderers(): data = { "v1": { "definitions": { @@ -187,7 +188,7 @@ def test_match_renderers(M): assert not renderer_with_errors.definition_errors -def test_flat_datapoints_errors_are_caught(M, mocker): +def test_flat_datapoints_errors_are_caught(mocker): d = {} d = set_in( d, diff --git a/tests/unit/repo/test_reproduce.py b/tests/unit/repo/test_reproduce.py index 77e7f8152..2daa1fcbc 100644 --- a/tests/unit/repo/test_reproduce.py +++ b/tests/unit/repo/test_reproduce.py @@ -4,6 +4,7 @@ from networkx import DiGraph from networkx.utils import graphs_equal from dvc.repo.reproduce import get_active_graph, plan_repro +from dvc.testing import matchers as M def test_active_graph(mocker): @@ -26,7 +27,7 @@ def test_active_graph(mocker): assert not graphs_equal(g, active) -def test_repro_plan(M): +def test_repro_plan(): r""" 1 / \