diff --git a/tests/tasks/test_arch.py b/tests/tasks/test_arch.py index d640cb5..dfe7fa1 100644 --- a/tests/tasks/test_arch.py +++ b/tests/tasks/test_arch.py @@ -280,3 +280,32 @@ def test_task_arch_layered_skips_when_no_layers_defined( out = capsys.readouterr().out assert "layered template selected" in out assert "arch_layers" in out + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_arch_import_linter_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] import-linter` override replaces bundled pin in uvx argv.""" + from interlocks.tasks import arch as arch_mod + + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.0" + + [tool.importlinter] + root_package = "pin_probe" + + [tool.interlocks.tools] + import-linter = "9.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + task = arch_mod.task_arch() + assert task is not None, "expected a Task (user importlinter config present)" + assert "import-linter==9.99.0" in task.cmd diff --git a/tests/tasks/test_audit.py b/tests/tasks/test_audit.py index bc595bc..5e311a5 100644 --- a/tests/tasks/test_audit.py +++ b/tests/tasks/test_audit.py @@ -186,3 +186,27 @@ def test_audit_network_skip_clean_run_prints_ok( audit_mod.cmd_audit(allow_network_skip=True) assert "no known vulnerabilities" in capsys.readouterr().out.lower() + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_audit_pip_audit_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] pip-audit` override replaces the bundled pin in uvx argv.""" + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.1" + dependencies = ["requests"] + + [tool.interlocks.tools] + pip-audit = "9.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + task = audit_mod.task_audit() + assert "pip-audit==9.99.0" in task.cmd diff --git a/tests/tasks/test_complexity.py b/tests/tasks/test_complexity.py index bc170bd..e643f65 100644 --- a/tests/tasks/test_complexity.py +++ b/tests/tasks/test_complexity.py @@ -119,3 +119,28 @@ def test_complexity_honors_tool_interlock_overrides( assert _flag_value(cmd, "-C") == "20" assert _flag_value(cmd, "-a") == "5" assert _flag_value(cmd, "-L") == "150" + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_complexity_lizard_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] lizard` override replaces the bundled pin in uvx argv.""" + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.0" + + [tool.interlocks.tools] + lizard = "9.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + from interlocks.tasks.complexity import task_complexity + + cmd = task_complexity().cmd + assert "lizard==9.99.0" in cmd diff --git a/tests/tasks/test_deps.py b/tests/tasks/test_deps.py index 548332a..d2d4380 100644 --- a/tests/tasks/test_deps.py +++ b/tests/tasks/test_deps.py @@ -90,3 +90,28 @@ def fake_run(task: Task, **_: object) -> None: assert "--known-first-party" in cmd kfp_idx = cmd.index("--known-first-party") assert cmd[kfp_idx + 1] == "interlocks" + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_deps_deptry_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] deptry` override replaces the bundled pin in uvx argv.""" + from interlocks.tasks.deps import task_deps + + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.0" + + [tool.interlocks.tools] + deptry = "9.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + cmd = task_deps().cmd + assert "deptry==9.99.0" in cmd diff --git a/tests/tasks/test_lint.py b/tests/tasks/test_lint.py index bd728ff..8ab918e 100644 --- a/tests/tasks/test_lint.py +++ b/tests/tasks/test_lint.py @@ -118,3 +118,28 @@ def test_lint_omits_config_when_project_has_ruff_sidecar( (tmp_path / "ruff.toml").write_text("line-length = 99\n", encoding="utf-8") monkeypatch.chdir(tmp_path) assert "--config" not in task_lint().cmd + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_lint_ruff_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] ruff` override replaces the bundled pin in the uvx argv.""" + from interlocks.tasks.lint import task_lint + + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.0" + + [tool.interlocks.tools] + ruff = "0.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + cmd = task_lint().cmd + assert "ruff==0.99.0" in cmd diff --git a/tests/tasks/test_mutation.py b/tests/tasks/test_mutation.py index 1e2df58..5411689 100644 --- a/tests/tasks/test_mutation.py +++ b/tests/tasks/test_mutation.py @@ -593,3 +593,40 @@ def test_pulse_silent_when_verbose(tmp_path: Path, monkeypatch: pytest.MonkeyPat out = buf.getvalue() assert "\r" not in out + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_mutation_mutmut_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] interlocks-mutmut` override replaces bundled pin in uv run argv.""" + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.0" + + [tool.interlocks.tools] + interlocks-mutmut = "9.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + + captured_argv: list[list[str]] = [] + + def _spy_run(argv: list[str], _timeout: int) -> tuple[bool, Path]: + captured_argv.append(argv) + return True, tmp_path / ".interlocks" / "mutation.log" + + monkeypatch.setattr(mutation_mod, "_run_mutmut", _spy_run) + monkeypatch.setattr(mutation_mod, "coverage_line_rate", lambda: 1.0) + monkeypatch.setattr(mutation_mod, "read_mutation_summary", lambda: None) + monkeypatch.setattr(sys, "argv", ["interlocks", "mutation", "--min-coverage=0"]) + + cmd_mutation() + + assert captured_argv, "_run_mutmut was not called" + assert "interlocks-mutmut==9.99.0" in captured_argv[0] diff --git a/tests/tasks/test_typecheck.py b/tests/tasks/test_typecheck.py index ed31bf4..1e74483 100644 --- a/tests/tasks/test_typecheck.py +++ b/tests/tasks/test_typecheck.py @@ -314,3 +314,29 @@ def test_typecheck_resolves_imports_from_target_venv( out = capsys.readouterr().out assert "[typecheck]" in out assert "ok" in out + + +# ─────────────── tool pin propagation ─────────────────────────────── + + +def test_typecheck_basedpyright_pin_override_flows_into_cmd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] basedpyright` override replaces the bundled pin in uvx argv.""" + from interlocks.tasks.typecheck import task_typecheck + + (tmp_path / "pyproject.toml").write_text( + textwrap.dedent("""\ + [project] + name = "pin-probe" + version = "0.0.0" + requires-python = ">=3.11" + + [tool.interlocks.tools] + basedpyright = "9.99.0" + """), + encoding="utf-8", + ) + monkeypatch.chdir(tmp_path) + cmd = task_typecheck().cmd + assert "basedpyright==9.99.0" in cmd diff --git a/tests/test_config.py b/tests/test_config.py index d7c57d3..a0345bf 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -204,6 +204,32 @@ def test_tool_version_ignores_non_string_override( assert load_config().tool_version("ruff") == DEFAULTS["ruff"] +def test_coverage_pin_override_flows_into_invoker_prefix( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """`[tool.interlocks.tools] coverage` override flows into the uv run --with spec.""" + from interlocks.config import coverage_invoker_prefix + + _setup_pyproject( + tmp_path, + monkeypatch, + """ + [project] + name = "pin-probe" + version = "0.0.0" + + [tool.interlocks] + test_invoker = "uv" + + [tool.interlocks.tools] + coverage = "9.99.0" + """, + ) + cfg = load_config() + prefix = coverage_invoker_prefix(cfg) + assert "coverage==9.99.0" in prefix + + def test_skip_project_policy_resolves_labels( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: