diff --git a/.flake8 b/.flake8 index d23e3e158..879ffb212 100644 --- a/.flake8 +++ b/.flake8 @@ -13,6 +13,7 @@ ignore= B028 max_line_length = 79 max-complexity = 10 -select = B,C,E,F,W,T4,B902,T,P +select = B,C,E,F,W,T4,B902,T,P,TYP show_source = true count = true +min_python_version = 3.8.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b74d89df9..3f8c9ec9f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,6 +60,7 @@ repos: - flake8-comprehensions - flake8-debugger - flake8-string-format + - flake8-typing-imports - repo: local hooks: - id: mypy diff --git a/dvc/dependency/param.py b/dvc/dependency/param.py index d38a0af32..99e3603af 100644 --- a/dvc/dependency/param.py +++ b/dvc/dependency/param.py @@ -130,8 +130,7 @@ class ParamsDependency(Dependency): and list(actual[param]) == info[param] ): continue - else: - st = "modified" + st = "modified" else: continue diff --git a/dvc/output.py b/dvc/output.py index 945904932..a382d8ef7 100644 --- a/dvc/output.py +++ b/dvc/output.py @@ -1274,7 +1274,8 @@ class Output: if not self.obj or not other.hash_info.isdir: return other_obj = other.obj if other.obj is not None else other.get_obj() - assert isinstance(self.obj, Tree) and isinstance(other_obj, Tree) + assert isinstance(self.obj, Tree) + assert isinstance(other_obj, Tree) updated = update_meta(self.obj, other_obj) assert updated.hash_info == self.obj.hash_info self.obj = updated diff --git a/dvc/repo/index.py b/dvc/repo/index.py index 776798671..2819891fc 100644 --- a/dvc/repo/index.py +++ b/dvc/repo/index.py @@ -504,9 +504,8 @@ def build_data_index( # NOTE: whether the root will be returned by build_entries # depends on the filesystem (e.g. local doesn't, but s3 does). continue - else: - entry.key = key + entry.key + entry.key = key + entry.key data.add(entry) if compute_hash: diff --git a/dvc/stage/loader.py b/dvc/stage/loader.py index 0fc5a3743..872af8d0c 100644 --- a/dvc/stage/loader.py +++ b/dvc/stage/loader.py @@ -92,7 +92,8 @@ class StageLoader(Mapping): cls, dvcfile: "ProjectFile", name, stage_data, lock_data=None ): assert all([name, dvcfile, dvcfile.repo, dvcfile.path]) - assert stage_data and isinstance(stage_data, dict) + assert stage_data + assert isinstance(stage_data, dict) path, wdir = resolve_paths( dvcfile.repo.fs, dvcfile.path, stage_data.get(Stage.PARAM_WDIR) diff --git a/dvc/testing/path_info.py b/dvc/testing/path_info.py index d3a4291dc..4f65751c9 100644 --- a/dvc/testing/path_info.py +++ b/dvc/testing/path_info.py @@ -129,7 +129,9 @@ class URLInfo(_BasePath): def __init__(self, url): p = urlparse(url) - assert not p.query and not p.params and not p.fragment + assert not p.query + assert not p.params + assert not p.fragment assert p.password is None self._fill_parts(p.scheme, p.hostname, p.username, p.port, p.path) diff --git a/pyproject.toml b/pyproject.toml index 9670cf96a..d1c4cc094 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -161,8 +161,8 @@ line_length = 79 line-length = 79 target-version = "py38" show-source = true -ignore = ["S101", "SIM105", "SIM108", "SIM110", "SIM111", "SIM117", "PLR2004", "RUF005"] -select = ["F", "E", "W", "C90", "UP", "YTT", "S", "BLE", "B", "A", "C4", "T10", "ISC", "ICN", "G", "INP", "PIE", "T20", "PT003", "PT008", "PT009", "PT010", "PT015", "PT016", "PT017", "PT020", "PT021", "PT024", "PT025", "PT026", "Q", "RET501", "RET504", "SIM", "TID", "TCH", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "TRY002", "TRY004", "TRY201", "TRY400", "RUF"] +ignore = ["S101", "PT004", "PT007", "PT019", "SIM105", "SIM108", "SIM110", "SIM111", "SIM117", "TRY003", "TRY200", "TRY300", "TRY301", "PLR2004", "RUF005"] +select = ["F", "E", "W", "C90", "UP", "YTT", "S", "BLE", "B", "A", "C4", "T10", "ISC", "ICN", "G", "INP", "PIE", "T20", "PT", "Q", "RET501", "RET504", "SIM", "TID", "TCH", "ARG", "PGH", "PLC", "PLE", "PLR", "PLW", "TRY", "RUF"] # external flake8 codes that should be preserved external = ["B301", "B024", "C901", "E302", "F401", "W601"] @@ -174,6 +174,7 @@ external = ["B301", "B024", "C901", "E302", "F401", "W601"] fixture-parentheses = false mark-parentheses = false parametrize-names-type = "csv" +raises-extend-require-match-for = ["dvc.exceptions.DvcException", "dvc.scm.SCMError", "scmrepo.exceptions.SCMError"] [tool.ruff.flake8-tidy-imports] [tool.ruff.flake8-tidy-imports.banned-api] diff --git a/tests/func/experiments/test_checkpoints.py b/tests/func/experiments/test_checkpoints.py index 5b3b2bf82..ad89a1c09 100644 --- a/tests/func/experiments/test_checkpoints.py +++ b/tests/func/experiments/test_checkpoints.py @@ -10,7 +10,7 @@ from dvc.repo.experiments.exceptions import MultipleBranchError from dvc.repo.experiments.executor.base import BaseExecutor from dvc.repo.experiments.refs import EXEC_APPLY, EXEC_CHECKPOINT from dvc.repo.experiments.utils import exp_refs_by_rev -from dvc.scm import InvalidRemoteSCMRepo +from dvc.scm import InvalidRemoteSCMRepo, RevError @pytest.mark.parametrize("links", ["reflink,copy", "hardlink,symlink"]) @@ -54,7 +54,7 @@ def test_resume_checkpoint( checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace ) - with pytest.raises(DvcException): + with pytest.raises(RevError, match="unknown Git revision 'abc1234'"): dvc.experiments.run( checkpoint_stage.addressing, checkpoint_resume="abc1234", @@ -177,7 +177,9 @@ def test_resume_non_head_checkpoint( rev = list(scm.branch_revs(checkpoint_head, orig_head))[-1] dvc.experiments.apply(rev) - with pytest.raises(DvcException): + with pytest.raises( + DvcException, match="Nothing to do for unchanged checkpoint" + ): dvc.experiments.run(checkpoint_stage.addressing, tmp_dir=not workspace) results = dvc.experiments.run( diff --git a/tests/func/experiments/test_experiments.py b/tests/func/experiments/test_experiments.py index 84281b9a7..4caf3ce94 100644 --- a/tests/func/experiments/test_experiments.py +++ b/tests/func/experiments/test_experiments.py @@ -32,7 +32,8 @@ def test_new_simple(tmp_dir, scm, dvc, exp_stage, mocker, name, workspace): ) exp = first(results) ref_info = first(exp_refs_by_rev(scm, exp)) - assert ref_info and ref_info.baseline_sha == baseline + assert ref_info + assert ref_info.baseline_sha == baseline new_mock.assert_called_once() fs = scm.get_fs(exp) @@ -131,8 +132,8 @@ def test_apply(tmp_dir, scm, dvc, exp_stage): with pytest.raises(ApplyConflictError): dvc.experiments.apply(exp_b, force=False) # failed apply should revert everything to prior state - assert (tmp_dir / "params.yaml").read_text().strip() == "foo: 2" - assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 2" + assert (tmp_dir / "params.yaml").read_text().strip() == "foo: 2" + assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 2" dvc.experiments.apply("foo") assert (tmp_dir / "params.yaml").read_text().strip() == "foo: 3" diff --git a/tests/func/experiments/test_save.py b/tests/func/experiments/test_save.py index 1f43b85d6..faf3af601 100644 --- a/tests/func/experiments/test_save.py +++ b/tests/func/experiments/test_save.py @@ -13,7 +13,6 @@ from dvc.scm import resolve_rev def modified_exp_stage(exp_stage, tmp_dir): with open(tmp_dir / "copy.py", "a", encoding="utf-8") as fh: fh.write("\n# dummy change") - yield def test_exp_save_unchanged(tmp_dir, dvc, scm, exp_stage): @@ -26,7 +25,8 @@ def test_exp_save(tmp_dir, dvc, scm, exp_stage, name, modified_exp_stage): exp = dvc.experiments.save(name=name) ref_info = first(exp_refs_by_rev(scm, exp)) - assert ref_info and ref_info.baseline_sha == baseline + assert ref_info + assert ref_info.baseline_sha == baseline exp_name = name if name else ref_info.name assert dvc.experiments.get_exact_name([exp])[exp] == exp_name diff --git a/tests/func/machine/conftest.py b/tests/func/machine/conftest.py index 6ea95ae71..011936c7a 100644 --- a/tests/func/machine/conftest.py +++ b/tests/func/machine/conftest.py @@ -35,7 +35,7 @@ TEST_INSTANCE = { @pytest.fixture def machine_config(tmp_dir): (tmp_dir / ".dvc" / "config").write_text(BASIC_CONFIG) - yield BASIC_CONFIG + return BASIC_CONFIG @pytest.fixture @@ -52,4 +52,4 @@ def machine_instance(tmp_dir, dvc, mocker): "tpi.terraform.TerraformBackend.instances", mocker.MagicMock(side_effect=mock_instances), ) - yield TEST_INSTANCE + return TEST_INSTANCE diff --git a/tests/func/parsing/test_resolver.py b/tests/func/parsing/test_resolver.py index 6bb733e39..dd151e402 100644 --- a/tests/func/parsing/test_resolver.py +++ b/tests/func/parsing/test_resolver.py @@ -163,10 +163,9 @@ def test_vars_already_loaded_message(tmp_dir, dvc, local, vars_): else: d["stages"]["build"]["vars"] = vars_ - with pytest.raises(ResolveError) as exc_info: + with pytest.raises(ResolveError) as exc_info: # noqa: PT012 resolver = DataResolver(dvc, tmp_dir.fs_path, d) resolver.resolve() - assert "partially" in str(exc_info.value) diff --git a/tests/func/test_add.py b/tests/func/test_add.py index 029979754..6997688ce 100644 --- a/tests/func/test_add.py +++ b/tests/func/test_add.py @@ -13,6 +13,7 @@ import dvc as dvc_module import dvc_data from dvc.annotations import Annotation from dvc.cli import main +from dvc.config import ConfigError from dvc.dvcfile import DVC_FILE_SUFFIX from dvc.exceptions import ( DvcException, @@ -97,7 +98,7 @@ def test_add_unicode(tmp_dir, dvc): def test_add_unsupported_file(dvc): - with pytest.raises(DvcException): + with pytest.raises(ConfigError, match="Unsupported URL type"): dvc.add("unsupported://unsupported") @@ -810,7 +811,8 @@ def test_add_symlink_dir(make_tmp_dir, tmp_dir, dvc, external): (tmp_dir / "dir").symlink_to(target) - with pytest.raises(DvcException): + msg = "Cannot add files inside symlinked directories to DVC" + with pytest.raises(DvcException, match=msg): dvc.add("dir") @@ -826,7 +828,8 @@ def test_add_file_in_symlink_dir(make_tmp_dir, tmp_dir, dvc, external): (tmp_dir / "dir").symlink_to(target) - with pytest.raises(DvcException): + msg = "Cannot add files inside symlinked directories to DVC" + with pytest.raises(DvcException, match=msg): dvc.add(os.path.join("dir", "foo")) @@ -895,7 +898,7 @@ def test_add_long_fname(tmp_dir, dvc): # nothing we can do in this case, as the resulting dvcfile # will definitely exceed NAME_MAX - with pytest.raises(OSError) as info: + with pytest.raises(OSError, match=f"File name too long: .*{name}") as info: dvc.add(os.path.join("data", name)) assert info.value.errno == errno.ENAMETOOLONG @@ -939,8 +942,8 @@ def test_add_to_remote_absolute(tmp_dir, make_tmp_dir, dvc, remote): assert not os.path.exists(tmp_foo) assert foo.read_text() == "foo" + tmp_bar = tmp_abs_dir / "bar" with pytest.raises(StageExternalOutputsError): - tmp_bar = tmp_abs_dir / "bar" dvc.add(str(tmp_foo), out=str(tmp_bar), to_remote=True) @@ -1108,9 +1111,8 @@ def test_add_does_not_remove_stage_file_on_failure( side_effect=DvcException(exc_msg), ) - with pytest.raises(DvcException) as exc_info: + with pytest.raises(DvcException, match=exc_msg): dvc.add("foo") - assert str(exc_info.value) == exc_msg assert (tmp_dir / "foo.dvc").exists() assert (tmp_dir / stage.path).read_text() == dvcfile_contents diff --git a/tests/func/test_analytics.py b/tests/func/test_analytics.py index e92110250..c122f61fd 100644 --- a/tests/func/test_analytics.py +++ b/tests/func/test_analytics.py @@ -31,8 +31,9 @@ def mock_daemon(mocker): def func(argv): return main(["daemon", *argv]) - m = mocker.patch("dvc.daemon.daemon", mocker.MagicMock(side_effect=func)) - yield m + return mocker.patch( + "dvc.daemon.daemon", mocker.MagicMock(side_effect=func) + ) def test_collect_and_send_report(mocker, dvc, mock_daemon): diff --git a/tests/func/test_cli.py b/tests/func/test_cli.py index 57af7d771..612e1e91e 100644 --- a/tests/func/test_cli.py +++ b/tests/func/test_cli.py @@ -13,7 +13,7 @@ from dvc.commands.remove import CmdRemove from dvc.commands.repro import CmdRepro from dvc.commands.run import CmdRun from dvc.commands.status import CmdDataStatus -from dvc.exceptions import DvcException +from dvc.exceptions import NotDvcRepoError def test_argparse(dvc): @@ -176,7 +176,7 @@ def test_find_root(dvc): cd = os.path.pardir args = A() - with pytest.raises(DvcException): + with pytest.raises(NotDvcRepoError): Cmd(args) @@ -192,7 +192,7 @@ def test_cd(dvc): parent_dir = os.path.realpath(os.path.pardir) args = A() - with pytest.raises(DvcException): + with pytest.raises(NotDvcRepoError): Cmd(args) current_dir = os.path.realpath(os.path.curdir) assert parent_dir == current_dir diff --git a/tests/func/test_config.py b/tests/func/test_config.py index e2a48208f..8dfd01371 100644 --- a/tests/func/test_config.py +++ b/tests/func/test_config.py @@ -218,7 +218,9 @@ def test_list_bad_args(tmp_dir, dvc, caplog, args): def test_set_invalid_key(dvc): - with pytest.raises(ConfigError, match=r"extra keys not allowed"): + with pytest.raises( # noqa: PT012 + ConfigError, match=r"extra keys not allowed" + ): with dvc.config.edit() as conf: conf["core"]["invalid_key"] = "value" @@ -227,7 +229,7 @@ def test_merging_two_levels(dvc): with dvc.config.edit() as conf: conf["remote"]["test"] = {"url": "ssh://example.com"} - with pytest.raises( + with pytest.raises( # noqa: PT012 ConfigError, match=r"expected 'url' for dictionary value" ): with dvc.config.edit("global") as conf: diff --git a/tests/func/test_ignore.py b/tests/func/test_ignore.py index 804e27059..49cf9dce5 100644 --- a/tests/func/test_ignore.py +++ b/tests/func/test_ignore.py @@ -98,7 +98,8 @@ def test_rename_ignored_file(tmp_dir, dvc): shutil.move("dir/ignored", "dir/ignored_new") new_mtime, new_size = get_mtime_and_size("dir", dvc.fs, dvc.dvcignore) - assert new_mtime == mtime and new_size == size + assert new_mtime == mtime + assert new_size == size def test_rename_file(tmp_dir, dvc): @@ -108,7 +109,8 @@ def test_rename_file(tmp_dir, dvc): shutil.move("dir/foo", "dir/foo_new") new_mtime, new_size = get_mtime_and_size("dir", dvc.fs, dvc.dvcignore) - assert new_mtime != mtime and new_size == size + assert new_mtime != mtime + assert new_size == size def test_remove_ignored_file(tmp_dir, dvc): @@ -119,9 +121,7 @@ def test_remove_ignored_file(tmp_dir, dvc): mtime, size = get_mtime_and_size("dir", dvc.fs, dvc.dvcignore) os.remove("dir/ignored") - new_mtime, new_size = get_mtime_and_size("dir", dvc.fs, dvc.dvcignore) - - assert new_mtime == mtime and new_size == size + assert get_mtime_and_size("dir", dvc.fs, dvc.dvcignore) == (mtime, size) def test_remove_file(tmp_dir, dvc): @@ -131,7 +131,8 @@ def test_remove_file(tmp_dir, dvc): os.remove("dir/foo") new_mtime, new_size = get_mtime_and_size("dir", dvc.fs, dvc.dvcignore) - assert new_mtime != mtime and new_size != size + assert new_mtime != mtime + assert new_size != size def test_dvcignore_in_out_dir(tmp_dir, dvc): diff --git a/tests/func/test_install.py b/tests/func/test_install.py index a4a800c9d..f4f5626d9 100644 --- a/tests/func/test_install.py +++ b/tests/func/test_install.py @@ -40,7 +40,7 @@ class TestInstall: def test_fail_if_hook_exists(self, scm, dvc): self._hook("post-checkout").write_text("hook content") - with pytest.raises(DvcException) as exc_info: + with pytest.raises(DvcException) as exc_info: # noqa: PT011 dvc.install() assert escape_ansi(str(exc_info.value)) == ( diff --git a/tests/func/test_lock.py b/tests/func/test_lock.py index 10d7899a6..8049c085e 100644 --- a/tests/func/test_lock.py +++ b/tests/func/test_lock.py @@ -25,15 +25,18 @@ def test_unlock_lock_failed(tmp_dir, dvc, mocker): # It's a common scenario now to have lock unlocked and locked back (e.g. in # repro of a stage) in with. We should see LockError exception here. - with pytest.raises(LockError), lock: + with lock: lock.unlock() lock_ext.lock() # imitate an exernal process had time to lock it - lock.lock() + with pytest.raises(LockError): + lock.lock() def test_unlock_unlocked_raises(): lock = Lock("lock") - with pytest.raises(DvcException): + with pytest.raises( + DvcException, match="Unlock called on an unlocked lock" + ): lock.unlock() diff --git a/tests/func/test_lockfile.py b/tests/func/test_lockfile.py index 899f96fb5..1f148b03d 100644 --- a/tests/func/test_lockfile.py +++ b/tests/func/test_lockfile.py @@ -178,7 +178,7 @@ def v1_repo_lock(tmp_dir, dvc): dvc.run(cmd="echo foo", name="foo", no_exec=True) dvc.run(cmd="echo bar>bar.txt", outs=["bar.txt"], name="bar", no_exec=True) (tmp_dir / "dvc.lock").dump(v1_lockdata) - yield v1_lockdata + return v1_lockdata def test_can_read_v1_lockfile(tmp_dir, dvc, v1_repo_lock): diff --git a/tests/func/test_move.py b/tests/func/test_move.py index acd554a2a..305b2e04a 100644 --- a/tests/func/test_move.py +++ b/tests/func/test_move.py @@ -4,7 +4,7 @@ import textwrap import pytest from dvc.cli import main -from dvc.exceptions import DvcException, MoveNotDataSourceError +from dvc.exceptions import MoveNotDataSourceError, OutputNotFoundError def test_move(tmp_dir, dvc): @@ -16,7 +16,7 @@ def test_move(tmp_dir, dvc): def test_move_non_existent_file(dvc): - with pytest.raises(DvcException): + with pytest.raises(OutputNotFoundError): dvc.move("non_existent_file", "dst") diff --git a/tests/func/test_remote.py b/tests/func/test_remote.py index 7f3b1af1d..28297cc51 100644 --- a/tests/func/test_remote.py +++ b/tests/func/test_remote.py @@ -243,9 +243,9 @@ def test_raise_on_too_many_open_files( side_effect=OSError(errno.EMFILE, "Too many open files"), ) - with pytest.raises(OSError) as e: + with pytest.raises(OSError, match="Too many open files") as e: dvc.push() - assert e.errno == errno.EMFILE + assert e.value.errno == errno.EMFILE def test_modify_missing_remote(tmp_dir, dvc): diff --git a/tests/func/test_repro.py b/tests/func/test_repro.py index b8b271d4c..9784caeda 100644 --- a/tests/func/test_repro.py +++ b/tests/func/test_repro.py @@ -543,17 +543,23 @@ def test_repro_frozen(tmp_dir, dvc, copy_script, run_stage): assert len(stages) == 3 -def test_freeze_non_existing(dvc): +@pytest.mark.parametrize( + "target", + [ + "Dvcfile", + "pipelines.yaml", + "pipelines.yaml:name", + "Dvcfile:name", + "stage.dvc", + "stage.dvc:name", + "not-existing-stage.json", + ], +) +def test_freeze_non_existing(dvc, target): with pytest.raises(StageFileDoesNotExistError): - dvc.freeze("Dvcfile") - dvc.freeze("pipelines.yaml") - dvc.freeze("pipelines.yaml:name") - dvc.freeze("Dvcfile:name") - dvc.freeze("stage.dvc") - dvc.freeze("stage.dvc:name") - dvc.freeze("not-existing-stage.json") + dvc.freeze(target) - ret = main(["freeze", "non-existing-stage"]) + ret = main(["freeze", target]) assert ret != 0 @@ -998,7 +1004,7 @@ def repro_dir(tmp_dir, dvc, run_copy): is not None ) - yield stages + return stages def _rewrite_file(path_elements, new_content): diff --git a/tests/func/test_repro_multistage.py b/tests/func/test_repro_multistage.py index 154546fa7..955f84df3 100644 --- a/tests/func/test_repro_multistage.py +++ b/tests/func/test_repro_multistage.py @@ -9,15 +9,14 @@ from dvc.cli import main from dvc.dvcfile import LOCK_FILE, PROJECT_FILE from dvc.exceptions import CyclicGraphError, ReproductionError from dvc.stage import PipelineStage +from dvc.stage.exceptions import StageNotFound def test_non_existing_stage_name(tmp_dir, dvc, run_copy): - from dvc.exceptions import DvcException - tmp_dir.gen("file1", "file1") run_copy("file1", "file2", name="copy-file1-file2") - with pytest.raises(DvcException): + with pytest.raises(StageNotFound): dvc.freeze(":copy-file1-file3") assert main(["freeze", ":copy-file1-file3"]) != 0 @@ -90,20 +89,16 @@ def test_downstream(tmp_dir, dvc): ) assert len(evaluation) == 3 - assert ( - isinstance(evaluation[0], PipelineStage) - and evaluation[0].relpath == PROJECT_FILE - and evaluation[0].name == "B-gen" - ) - assert ( - isinstance(evaluation[1], PipelineStage) - and evaluation[1].relpath == PROJECT_FILE - and evaluation[1].name == "D-gen" - ) - assert ( - not isinstance(evaluation[2], PipelineStage) - and evaluation[2].relpath == "E.dvc" - ) + assert isinstance(evaluation[0], PipelineStage) + assert evaluation[0].relpath == PROJECT_FILE + assert evaluation[0].name == "B-gen" + + assert isinstance(evaluation[1], PipelineStage) + assert evaluation[1].relpath == PROJECT_FILE + assert evaluation[1].name == "D-gen" + + assert not isinstance(evaluation[2], PipelineStage) + assert evaluation[2].relpath == "E.dvc" # B, C should be run (in any order) before D # See https://github.com/iterative/dvc/issues/3602 @@ -112,11 +107,10 @@ def test_downstream(tmp_dir, dvc): ) assert len(evaluation) == 5 - assert ( - isinstance(evaluation[0], PipelineStage) - and evaluation[0].relpath == PROJECT_FILE - and evaluation[0].name == "A-gen" - ) + assert isinstance(evaluation[0], PipelineStage) + assert evaluation[0].relpath == PROJECT_FILE + assert evaluation[0].name == "A-gen" + names = set() for stage in evaluation[1:3]: if isinstance(stage, PipelineStage): @@ -125,15 +119,13 @@ def test_downstream(tmp_dir, dvc): else: names.add(stage.relpath) assert names == {"B-gen", "C.dvc"} - assert ( - isinstance(evaluation[3], PipelineStage) - and evaluation[3].relpath == PROJECT_FILE - and evaluation[3].name == "D-gen" - ) - assert ( - not isinstance(evaluation[4], PipelineStage) - and evaluation[4].relpath == "E.dvc" - ) + + assert isinstance(evaluation[3], PipelineStage) + assert evaluation[3].relpath == PROJECT_FILE + assert evaluation[3].name == "D-gen" + + assert not isinstance(evaluation[4], PipelineStage) + assert evaluation[4].relpath == "E.dvc" def test_repro_when_cmd_changes(tmp_dir, dvc, run_copy, mocker): @@ -308,11 +300,9 @@ def test_repro_when_lockfile_gets_deleted(tmp_dir, dvc, copy_script): assert not dvc.reproduce(":run-copy") os.unlink(LOCK_FILE) stages = dvc.reproduce(":run-copy") - assert ( - stages - and stages[0].relpath == PROJECT_FILE - and stages[0].name == "run-copy" - ) + assert stages + assert stages[0].relpath == PROJECT_FILE + assert stages[0].name == "run-copy" def test_cyclic_graph_error(tmp_dir, dvc, run_copy): diff --git a/tests/func/test_run_cache.py b/tests/func/test_run_cache.py index 0a64ad1f4..ed5d88092 100644 --- a/tests/func/test_run_cache.py +++ b/tests/func/test_run_cache.py @@ -36,7 +36,8 @@ def test_restore(tmp_dir, dvc, run_copy, mocker): mock_restore.assert_called_once_with(stage, dry=False) mock_run.assert_not_called() - assert (tmp_dir / "bar").exists() and not (tmp_dir / "foo").unlink() + assert (tmp_dir / "bar").exists() + assert not (tmp_dir / "foo").unlink() assert (tmp_dir / LOCK_FILE).exists() @@ -119,7 +120,8 @@ def test_memory_for_multiple_runs_of_same_stage( assert (tmp_dir / "bar").read_text() == "foo" mock_run.assert_not_called() mock_restore.assert_called_once_with(stage, dry=False) - assert (tmp_dir / "bar").exists() and not (tmp_dir / "foo").unlink() + assert (tmp_dir / "bar").exists() + assert not (tmp_dir / "foo").unlink() assert (tmp_dir / LOCK_FILE).exists() @@ -180,5 +182,6 @@ def test_restore_pull(tmp_dir, dvc, run_copy, mocker, local_remote): mock_restore.assert_called_once_with(stage, pull=True, dry=False) mock_run.assert_not_called() assert mock_checkout.call_count == 2 - assert (tmp_dir / "bar").exists() and not (tmp_dir / "foo").unlink() + assert (tmp_dir / "bar").exists() + assert not (tmp_dir / "foo").unlink() assert (tmp_dir / LOCK_FILE).exists() diff --git a/tests/func/test_run_single_stage.py b/tests/func/test_run_single_stage.py index 3cea63f29..97a3d85e5 100644 --- a/tests/func/test_run_single_stage.py +++ b/tests/func/test_run_single_stage.py @@ -238,9 +238,9 @@ class TestRunBadCwd: dvc.run(cmd="command", wdir=make_tmp_dir("tmp"), single_stage=True) def test_same_prefix(self, tmp_dir, dvc): + path = f"{tmp_dir}-{uuid.uuid4()}" + os.mkdir(path) with pytest.raises(StagePathOutsideError): - path = f"{tmp_dir}-{uuid.uuid4()}" - os.mkdir(path) dvc.run(cmd="command", wdir=path, single_stage=True) @@ -250,22 +250,22 @@ class TestRunBadWdir: dvc.run(cmd="command", wdir=make_tmp_dir("tmp"), single_stage=True) def test_same_prefix(self, tmp_dir, dvc): + path = f"{tmp_dir}-{uuid.uuid4()}" + os.mkdir(path) with pytest.raises(StagePathOutsideError): - path = f"{tmp_dir}-{uuid.uuid4()}" - os.mkdir(path) dvc.run(cmd="command", wdir=path, single_stage=True) def test_not_found(self, tmp_dir, dvc): + path = os.path.join(tmp_dir, str(uuid.uuid4())) with pytest.raises(StagePathNotFoundError): - path = os.path.join(tmp_dir, str(uuid.uuid4())) dvc.run(cmd="command", wdir=path, single_stage=True) def test_not_dir(self, tmp_dir, dvc): + path = tmp_dir / str(uuid.uuid4()) + path.mkdir() + path = path / str(uuid.uuid4()) + path.touch() with pytest.raises(StagePathNotDirectoryError): - path = tmp_dir / str(uuid.uuid4()) - path.mkdir() - path = path / str(uuid.uuid4()) - path.touch() dvc.run(cmd="command", wdir=os.fspath(path), single_stage=True) @@ -279,9 +279,9 @@ class TestRunBadName: ) def test_same_prefix(self, tmp_dir, dvc): + path = f"{tmp_dir}-{uuid.uuid4()}" + os.mkdir(path) with pytest.raises(StagePathOutsideError): - path = f"{tmp_dir}-{uuid.uuid4()}" - os.mkdir(path) dvc.run( cmd="command", fname=os.path.join(path, "foo.dvc"), @@ -289,8 +289,8 @@ class TestRunBadName: ) def test_not_found(self, tmp_dir, dvc): + path = os.path.join(tmp_dir, str(uuid.uuid4())) with pytest.raises(StagePathNotFoundError): - path = os.path.join(tmp_dir, str(uuid.uuid4())) dvc.run( cmd="command", fname=os.path.join(path, "foo.dvc"), diff --git a/tests/func/test_scm.py b/tests/func/test_scm.py index abf3d2bcd..f7a227557 100644 --- a/tests/func/test_scm.py +++ b/tests/func/test_scm.py @@ -16,7 +16,7 @@ def test_init_git(tmp_dir): def test_init_no_git(tmp_dir): - with pytest.raises(SCMError): + with pytest.raises(SCMError, match=r".* is not a git repository"): SCM(os.fspath(tmp_dir)) diff --git a/tests/remotes/git_server.py b/tests/remotes/git_server.py index d57377de1..402146beb 100644 --- a/tests/remotes/git_server.py +++ b/tests/remotes/git_server.py @@ -44,7 +44,7 @@ def git_server(test_config, docker_compose, docker_services): def git_ssh_connection(git_server): from sshfs import SSHFileSystem - yield SSHFileSystem( + return SSHFileSystem( host=git_server["host"], port=git_server["port"], username=TEST_SSH_USER, diff --git a/tests/unit/command/test_data_status.py b/tests/unit/command/test_data_status.py index 8635f3da1..6c66c811f 100644 --- a/tests/unit/command/test_data_status.py +++ b/tests/unit/command/test_data_status.py @@ -12,7 +12,7 @@ from tests.func.parsing.test_errors import escape_ansi @pytest.fixture def mocked_status(): - yield Status( + return Status( not_in_cache=["notincache"], committed={ "added": ["dir/bar", "dir/foo"], diff --git a/tests/unit/command/test_plots.py b/tests/unit/command/test_plots.py index 9d007c3fb..68255ec92 100644 --- a/tests/unit/command/test_plots.py +++ b/tests/unit/command/test_plots.py @@ -11,7 +11,7 @@ from dvc.commands.plots import CmdPlotsDiff, CmdPlotsShow, CmdPlotsTemplates @pytest.fixture def plots_data(): - yield { + return { "revision": { "sources": { "data": { diff --git a/tests/unit/dependency/test_params.py b/tests/unit/dependency/test_params.py index de95d46b9..a99e24a18 100644 --- a/tests/unit/dependency/test_params.py +++ b/tests/unit/dependency/test_params.py @@ -71,9 +71,21 @@ def test_loads_params_without_any_specific_targets(dvc): assert not deps[2].hash_info -@pytest.mark.parametrize("params", [[3], [{"b_file": "cat"}]]) -def test_params_error(dvc, params): - with pytest.raises(ValueError): +@pytest.mark.parametrize( + "params, errmsg", + [ + ([3], "Only list of str/dict is supported. Got: 'int'"), + ( + [{"b_file": "cat"}], + ( + "Expected list of params for custom params file " + "'b_file', got 'str'." + ), + ), + ], +) +def test_params_error(dvc, params, errmsg): + with pytest.raises(ValueError, match=errmsg): loads_params(Stage(dvc), params) diff --git a/tests/unit/fs/test_dvc_info.py b/tests/unit/fs/test_dvc_info.py index fc8a0a3a2..528611a09 100644 --- a/tests/unit/fs/test_dvc_info.py +++ b/tests/unit/fs/test_dvc_info.py @@ -38,7 +38,7 @@ def dvcfs(tmp_dir, dvc, scm): tmp_dir.scm_gen(fs_structure, commit="repo init") tmp_dir.dvc_gen(dvc_structure, commit="use dvc") - yield DVCFileSystem(repo=dvc, subrepos=True) + return DVCFileSystem(repo=dvc, subrepos=True) def test_info_not_existing(dvcfs): diff --git a/tests/unit/output/test_load.py b/tests/unit/output/test_load.py index 613c09271..8a7cc3d63 100644 --- a/tests/unit/output/test_load.py +++ b/tests/unit/output/test_load.py @@ -58,11 +58,14 @@ def test_load_from_pipeline_accumulates_flag(dvc): for out in outs: assert isinstance(out, Output) assert isinstance(out.fs, LocalFileSystem) - assert not out.plot and not out.metric + assert not out.plot + assert not out.metric assert not out.hash_info - assert outs[0].use_cache and not outs[0].persist - assert not outs[1].use_cache and outs[1].persist + assert outs[0].use_cache + assert not outs[0].persist + assert not outs[1].use_cache + assert outs[1].persist def test_load_remote_files_from_pipeline(dvc): @@ -72,7 +75,8 @@ def test_load_remote_files_from_pipeline(dvc): ) assert isinstance(out, Output) assert isinstance(out.fs, S3FileSystem) - assert not out.plot and out.metric + assert not out.plot + assert out.metric assert not out.persist assert not out.hash_info @@ -89,16 +93,23 @@ def test_load_remote(dvc): @pytest.mark.parametrize("typ", [None, "", "illegal"]) def test_load_from_pipeline_error_on_typ(dvc, typ): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match=f"'{typ}' key is not allowed for pipeline files." + ): output.load_from_pipeline(Stage(dvc), ["file1"], typ) @pytest.mark.parametrize("key", [3, "list".split()]) def test_load_from_pipeline_illegal_type(dvc, key): stage = Stage(dvc) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match=f"'{type(key).__name__}' not supported." + ): output.load_from_pipeline(stage, [key], "outs") - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match=f"Expected dict for 'key', got: '{type(key).__name__}'", + ): output.load_from_pipeline(stage, [{"key": key}], "outs") @@ -121,11 +132,13 @@ def test_plots_load_from_pipeline(dvc): assert isinstance(outs[0], Output) assert isinstance(outs[0].fs, LocalFileSystem) assert outs[0].use_cache - assert outs[0].plot is True and not outs[0].metric + assert outs[0].plot is True + assert not outs[0].metric assert not outs[0].persist assert isinstance(outs[1], Output) assert isinstance(outs[1].fs, LocalFileSystem) assert not outs[1].use_cache - assert outs[1].plot == {"x": 3} and not outs[1].metric + assert outs[1].plot == {"x": 3} + assert not outs[1].metric assert outs[1].persist diff --git a/tests/unit/render/test_vega_converter.py b/tests/unit/render/test_vega_converter.py index 3bd01d9a5..d2cc0126a 100644 --- a/tests/unit/render/test_vega_converter.py +++ b/tests/unit/render/test_vega_converter.py @@ -4,7 +4,7 @@ import pytest from dvc.exceptions import DvcException from dvc.render import VERSION_FIELD -from dvc.render.converter.vega import VegaConverter, _lists +from dvc.render.converter.vega import FieldNotFoundError, VegaConverter, _lists @pytest.mark.parametrize( @@ -395,7 +395,7 @@ def test_convert( @pytest.mark.parametrize( - "input_data,properties", + "input_data,properties,exc", [ pytest.param( { @@ -408,6 +408,7 @@ def test_convert( "f2": {"metric": [{"v2": 0.1}]}, }, {"x": {"f": "v"}, "y": {"f2": "v2"}}, + DvcException, id="unequal_datapoints", ), pytest.param( @@ -425,13 +426,14 @@ def test_convert( }, }, {"x": {"f": "v", "f2": "v3"}, "y": {"f": "v2"}}, + FieldNotFoundError, id="unequal_x_y", ), ], ) -def test_convert_fail(input_data, properties): +def test_convert_fail(input_data, properties, exc): converter = VegaConverter("f", input_data, properties) - with pytest.raises(DvcException): + with pytest.raises(exc): converter.flat_datapoints("r") diff --git a/tests/unit/repo/experiments/queue/test_celery.py b/tests/unit/repo/experiments/queue/test_celery.py index c867dce32..74bfa91df 100644 --- a/tests/unit/repo/experiments/queue/test_celery.py +++ b/tests/unit/repo/experiments/queue/test_celery.py @@ -187,7 +187,7 @@ def test_queue_iter_done_task(test_queue, mocker, status): ] elif status == "SUCCESS": - with pytest.raises(DvcException): + with pytest.raises(DvcException, match="Invalid experiment"): assert list(test_queue.iter_success()) diff --git a/tests/unit/repo/experiments/test_executor_status.py b/tests/unit/repo/experiments/test_executor_status.py index 9d91d7223..b086b75e8 100644 --- a/tests/unit/repo/experiments/test_executor_status.py +++ b/tests/unit/repo/experiments/test_executor_status.py @@ -2,7 +2,7 @@ import os import pytest -from dvc.exceptions import DvcException +from dvc.exceptions import ReproductionError from dvc.repo.experiments.executor.base import ExecutorInfo, TaskStatus from dvc.repo.experiments.queue.tasks import ( cleanup_exp, @@ -100,7 +100,7 @@ def test_workspace_executor_failed_status( infofile = queue.get_infofile_path(name) rev = entry.stash_rev - with pytest.raises(DvcException): + with pytest.raises(ReproductionError): executor.reproduce( info=executor.info, rev=rev, diff --git a/tests/unit/repo/test_scm_context.py b/tests/unit/repo/test_scm_context.py index 8e53ddffd..546be9b33 100644 --- a/tests/unit/repo/test_scm_context.py +++ b/tests/unit/repo/test_scm_context.py @@ -80,7 +80,7 @@ def test_scm_context_clears_ignores_on_error(scm_context): class CustomException(Exception): pass - with pytest.raises(CustomException), scm_context(): + with pytest.raises(CustomException), scm_context(): # noqa: PT012 scm_context.ignore("foo") assert scm_context.ignored_paths == ["foo"] raise CustomException @@ -113,7 +113,8 @@ def test_scm_context_remind_to_track(caplog, scm_context): else: assert "To track the changes with git, run:" in caplog.text match = re.search(r"git add(?: (('.*?')|(\S+)))*", caplog.text) - assert match and set(match.groups()) == {"'lorem ipsum'", "foo"} + assert match + assert set(match.groups()) == {"'lorem ipsum'", "foo"} def test_scm_context_remind_disable(caplog, scm_context): diff --git a/tests/unit/stage/test_loader_pipeline_file.py b/tests/unit/stage/test_loader_pipeline_file.py index 8fdb42910..e5fd93bd8 100644 --- a/tests/unit/stage/test_loader_pipeline_file.py +++ b/tests/unit/stage/test_loader_pipeline_file.py @@ -104,7 +104,8 @@ def test_fill_from_lock_missing_params_section(dvc, lock_data): ) params_deps = split_params_deps(stage)[0] StageLoader.fill_from_lock(stage, lock_data) - assert not params_deps[0].hash_info and not params_deps[1].hash_info + assert not params_deps[0].hash_info + assert not params_deps[1].hash_info def test_fill_from_lock_missing_checksums(dvc, lock_data): @@ -120,7 +121,8 @@ def test_fill_from_lock_missing_checksums(dvc, lock_data): assert stage.deps[0].hash_info == HashInfo("md5", "foo_checksum") assert stage.outs[0].hash_info == HashInfo("md5", "bar_checksum") - assert not stage.deps[1].hash_info and not stage.outs[1].hash_info + assert not stage.deps[1].hash_info + assert not stage.outs[1].hash_info def test_fill_from_lock_use_appropriate_checksum(dvc, lock_data): @@ -159,9 +161,11 @@ def test_fill_from_lock_empty_data(dvc): PipelineStage, dvc, PROJECT_FILE, deps=["foo"], outs=["bar"] ) StageLoader.fill_from_lock(stage, None) - assert not stage.deps[0].hash_info and not stage.outs[0].hash_info + assert not stage.deps[0].hash_info + assert not stage.outs[0].hash_info StageLoader.fill_from_lock(stage, {}) - assert not stage.deps[0].hash_info and not stage.outs[0].hash_info + assert not stage.deps[0].hash_info + assert not stage.outs[0].hash_info def test_load_stage(dvc, stage_data, lock_data): @@ -195,7 +199,8 @@ def test_load_stage_outs_with_flags(dvc, stage_data, lock_data): def test_load_stage_no_lock(dvc, stage_data): dvcfile = load_file(dvc, PROJECT_FILE) stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data) - assert stage.deps[0].def_path == "foo" and stage.outs[0].def_path == "bar" + assert stage.deps[0].def_path == "foo" + assert stage.outs[0].def_path == "bar" assert not stage.deps[0].hash_info assert not stage.outs[0].hash_info @@ -207,7 +212,8 @@ def test_load_stage_with_params(dvc, stage_data, lock_data): stage = StageLoader.load_stage(dvcfile, "stage-1", stage_data, lock_data) params, deps = split_params_deps(stage) - assert deps[0].def_path == "foo" and stage.outs[0].def_path == "bar" + assert deps[0].def_path == "foo" + assert stage.outs[0].def_path == "bar" assert params[0].def_path == "params.yaml" assert params[0].hash_info == HashInfo("params", {"lorem": "ipsum"}) assert deps[0].hash_info == HashInfo("md5", "foo_checksum") diff --git a/tests/unit/stage/test_stage.py b/tests/unit/stage/test_stage.py index 2da313322..3585543f7 100644 --- a/tests/unit/stage/test_stage.py +++ b/tests/unit/stage/test_stage.py @@ -137,7 +137,8 @@ def test_env(dvc, mocker): def mock_read_env(out, **kwargs): return {"foo": str(out)} - with pytest.raises(DvcException) as exc: - mocker.patch.object(stage, "_read_env", mock_read_env) + mocker.patch.object(stage, "_read_env", mock_read_env) + with pytest.raises( + DvcException, match="Conflicting values for env variable" + ): _ = stage.env() - assert exc.value == "Conflicting values for env variable" diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index 7f7eac1ac..ef54604e5 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -6,10 +6,10 @@ from dvc import api def test_open_raises_error_if_no_context(tmp_dir, dvc): tmp_dir.dvc_gen("foo", "foo-text") + fd = api.open("foo") with pytest.raises( AttributeError, match="should be used in a with statement." ): - fd = api.open("foo") fd.read() diff --git a/tests/unit/test_context.py b/tests/unit/test_context.py index a86088498..bc10628d5 100644 --- a/tests/unit/test_context.py +++ b/tests/unit/test_context.py @@ -164,11 +164,13 @@ def test_select_unwrap(): assert context.select("foo", unwrap=True) == "foo" node = context.select("dct", unwrap=True) - assert isinstance(node, dict) and recurse_not_a_node(node) + assert isinstance(node, dict) + assert recurse_not_a_node(node) assert node == {"foo": "bar"} node = context.select("lst", unwrap=True) - assert isinstance(node, list) and recurse_not_a_node(node) + assert isinstance(node, list) + assert recurse_not_a_node(node) assert node == [1, 2, 3] @@ -336,25 +338,19 @@ def test_track_from_multiple_files(tmp_dir): with context.track() as tracked: context.select("Train") - assert not ( - key_tracked(tracked, path1, "Train") - or key_tracked(tracked, path2, "Train") - ) + assert not key_tracked(tracked, path1, "Train") + assert not key_tracked(tracked, path2, "Train") context.select("Train.us") - assert not ( - key_tracked(tracked, path1, "Train.us") - or key_tracked(tracked, path2, "Train.us") - ) + assert not key_tracked(tracked, path1, "Train.us") + assert not key_tracked(tracked, path2, "Train.us") context.select("Train.us.lr") - assert key_tracked(tracked, path1, "Train.us.lr") and not key_tracked( - tracked, path2, "Train.us.lr" - ) + assert key_tracked(tracked, path1, "Train.us.lr") + assert not key_tracked(tracked, path2, "Train.us.lr") context.select("Train.us.layers") - assert not key_tracked( - tracked, path1, "Train.us.layers" - ) and key_tracked(tracked, path2, "Train.us.layers") + assert not key_tracked(tracked, path1, "Train.us.layers") + assert key_tracked(tracked, path2, "Train.us.layers") context = Context.clone(context) assert not context._tracked_data @@ -363,19 +359,15 @@ def test_track_from_multiple_files(tmp_dir): context["us"] = context["Train"]["us"] with context.track() as tracked: context.select("us") - assert not ( - key_tracked(tracked, path1, "Train.us") - or key_tracked(tracked, path2, "Train.us") - ) + assert not key_tracked(tracked, path1, "Train.us") + assert not key_tracked(tracked, path2, "Train.us") context.select("us.lr") - assert key_tracked(tracked, path1, "Train.us.lr") and not key_tracked( - tracked, path2, "Train.us.lr" - ) + assert key_tracked(tracked, path1, "Train.us.lr") + assert not key_tracked(tracked, path2, "Train.us.lr") context.select("Train.us.layers") - assert not key_tracked( - tracked, path1, "Train.us.layers" - ) and key_tracked(tracked, path2, "Train.us.layers") + assert not key_tracked(tracked, path1, "Train.us.layers") + assert key_tracked(tracked, path2, "Train.us.layers") def test_node_value(): diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py index f7a6324bb..fd4825a41 100644 --- a/tests/unit/test_logger.py +++ b/tests/unit/test_logger.py @@ -20,12 +20,12 @@ colors = { } -@pytest.fixture() +@pytest.fixture def dt(mocker): mocker.patch( "time.time", return_value=time.mktime(datetime(2020, 2, 2).timetuple()) ) - yield "2020-02-02 00:00:00,000" + return "2020-02-02 00:00:00,000" class TestColorFormatter: diff --git a/tests/unit/test_scm.py b/tests/unit/test_scm.py index 5ed73734c..8f4368c01 100644 --- a/tests/unit/test_scm.py +++ b/tests/unit/test_scm.py @@ -5,5 +5,5 @@ from dvc.scm import resolve_rev def test_resolve_rev_empty_git_repo(scm): - with pytest.raises(DvcException): + with pytest.raises(DvcException, match="unknown Git revision 'HEAD'"): resolve_rev(scm, "HEAD") diff --git a/tests/unit/ui/test_prompt.py b/tests/unit/ui/test_prompt.py index ad9bc7514..30dd59437 100644 --- a/tests/unit/ui/test_prompt.py +++ b/tests/unit/ui/test_prompt.py @@ -16,8 +16,8 @@ def test_prompt_interrupt_message_not_interleaved( prompt = "should print exception in the next line" console = RichConsole(file=io.StringIO()) + mocker.patch.object(console, "input", side_effect=side_effect) with pytest.raises(side_effect): - mocker.patch.object(console, "input", side_effect=side_effect) prompt_cls.ask(prompt=prompt, console=console) # as we are raising side_effect on the input, unfortunately we cannot # assert for the prompt string, only the newline. diff --git a/tests/unit/utils/test_executors.py b/tests/unit/utils/test_executors.py index 46a073c61..ea28224ec 100644 --- a/tests/unit/utils/test_executors.py +++ b/tests/unit/utils/test_executors.py @@ -49,7 +49,7 @@ def test_cancel_futures(wait, cancel_futures): def test_cancel_on_error_context_manager(mocker): executor = ThreadPoolExecutor(max_workers=2, cancel_on_error=True) spy = mocker.spy(executor, "shutdown") - with pytest.raises(RuntimeError), executor: + with pytest.raises(RuntimeError), executor: # noqa: PT012 future1 = executor.submit(operator.mul, 2, 21) future2 = executor.submit(time.sleep, 0.1) future3 = executor.submit(raiser(RuntimeError), "This is an error") diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index e0b3ef836..7745a8758 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -142,9 +142,9 @@ def test_parse_target(inp, out, default): def test_hint_on_lockfile(): - with pytest.raises(Exception) as exc: + with pytest.raises(Exception, match="Did you mean: `dvc.yaml:name`?") as e: assert parse_target("dvc.lock:name") - assert "dvc.yaml:name" in str(exc.value) + assert "dvc.yaml:name" in str(e.value) @pytest.mark.parametrize(