Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions tests/tasks/test_arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,27 @@ def test_task_arch_layered_template_synthesizes_with_layers(
assert layer in contents


def test_task_arch_uses_importlinter_sidecar_config(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
""".importlinter sidecar in project root suppresses bundled --config."""
from interlocks.tasks import arch as arch_mod

proj = tmp_path / "proj"
proj.mkdir()
(proj / "pyproject.toml").write_text("[project]\nname='x'\n", encoding="utf-8")
(proj / ".importlinter").write_text(
"[importlinter]\nroot_package = x\n\n[contracts:test]\nname = test\ntype = forbidden\n"
"source_modules = x\nforbidden_modules = tests\n",
encoding="utf-8",
)
_stub_load_config(monkeypatch, proj, proj / "src", proj / "tests")
task = arch_mod.task_arch()
assert task is not None
assert task.description == "Architecture (import-linter)"
assert "--config" not in task.cmd


def test_task_arch_layered_skips_when_no_layers_defined(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
Expand Down Expand Up @@ -280,3 +301,41 @@ 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_task_arch_uses_import_linter_pin_override(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""[tool.interlocks.tools] import-linter override must appear in the uvx --from spec.

Uses a user [tool.importlinter] section to take the simpler user-contracts path
(no temp INI needed) and keep setup minimal — only the pin is under test.
"""
from interlocks.defaults.tools import default_pin
from interlocks.tasks import arch as arch_mod

custom_pin = "2.0.0"
proj = tmp_path / "proj"
proj.mkdir()
(proj / "pyproject.toml").write_text(
textwrap.dedent(f"""\
[project]
name = "archpin"
version = "0.0.0"

[tool.importlinter]
root_package = "archpin"

[tool.interlocks.tools]
import-linter = "{custom_pin}"
"""),
encoding="utf-8",
)
monkeypatch.chdir(proj)
task = arch_mod.task_arch()
assert task is not None
assert f"import-linter=={custom_pin}" in task.cmd
assert f"import-linter=={default_pin('import-linter')}" not in task.cmd
18 changes: 18 additions & 0 deletions tests/tasks/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,24 @@ def test_coverage_omits_rcfile_with_coveragerc_sidecar(
assert _rcfile_flag(_coverage_run_cmd(task.pre_cmds)) is None


def test_coverage_still_injects_rcfile_when_only_ruff_section_present(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.ruff] must NOT suppress coverage --rcfile (cross-tool isolation)."""
from interlocks.tasks.coverage import task_coverage

(tmp_path / "pyproject.toml").write_text(
_BARE_PYPROJECT + "\n[tool.ruff]\nline-length = 99\n",
encoding="utf-8",
)
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(sys, "argv", ["interlocks", "coverage"])
task = task_coverage()
flag = _rcfile_flag(task.cmd)
assert flag is not None, "[tool.ruff] must NOT suppress coverage --rcfile"
assert Path(flag.split("=", 1)[1]).name == "coveragerc"


def test_coverage_uv_injects_coverage_without_project_dependency(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
Expand Down
31 changes: 31 additions & 0 deletions tests/tasks/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,34 @@ def test_format_injects_bundled_config_in_bare_project(
cmd = task_format().cmd
assert "--config" in cmd
assert Path(cmd[cmd.index("--config") + 1]).name == "ruff.toml"


def test_format_omits_config_when_project_has_ruff_sidecar(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""ruff.toml sidecar: task_format must NOT pass --config."""
from interlocks.tasks.format import task_format

(tmp_path / "pyproject.toml").write_text(
"[project]\nname='bare'\nversion='0.0.0'\n", encoding="utf-8"
)
(tmp_path / "ruff.toml").write_text("line-length = 99\n", encoding="utf-8")
monkeypatch.chdir(tmp_path)
assert "--config" not in task_format().cmd


def test_format_still_injects_config_when_only_basedpyright_section_present(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.basedpyright] must NOT suppress ruff --config for format (cross-tool isolation)."""
from interlocks.tasks.format import task_format

(tmp_path / "pyproject.toml").write_text(
"[project]\nname='bare'\nversion='0.0.0'\n\n"
"[tool.basedpyright]\ntypeCheckingMode = 'standard'\n",
encoding="utf-8",
)
monkeypatch.chdir(tmp_path)
cmd = task_format().cmd
assert "--config" in cmd
assert Path(cmd[cmd.index("--config") + 1]).name == "ruff.toml"
26 changes: 26 additions & 0 deletions tests/tasks/test_format_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,29 @@ def test_format_check_injects_bundled_config_in_bare_project(
cmd = task_format_check().cmd
assert "--config" in cmd
assert Path(cmd[cmd.index("--config") + 1]).name == "ruff.toml"


def test_format_check_omits_config_when_project_has_tool_ruff(
tmp_project: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.ruff] in project pyproject: task_format_check must NOT pass --config."""
from interlocks.tasks.format_check import task_format_check

monkeypatch.chdir(tmp_project)
assert "--config" not in task_format_check().cmd


def test_format_check_still_injects_config_when_only_coverage_section_present(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.coverage] must NOT suppress ruff --config for format-check (cross-tool isolation)."""
from interlocks.tasks.format_check import task_format_check

(tmp_path / "pyproject.toml").write_text(
"[project]\nname='bare'\nversion='0.0.0'\n\n[tool.coverage.run]\nbranch = true\n",
encoding="utf-8",
)
monkeypatch.chdir(tmp_path)
cmd = task_format_check().cmd
assert "--config" in cmd
assert Path(cmd[cmd.index("--config") + 1]).name == "ruff.toml"
57 changes: 57 additions & 0 deletions tests/tasks/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,60 @@ 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


def test_lint_omits_config_when_project_has_dot_ruff_sidecar(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Project with .ruff.toml sidecar: task_lint must NOT pass --config."""
from interlocks.tasks.lint import task_lint

(tmp_path / "pyproject.toml").write_text(_BARE_PYPROJECT, encoding="utf-8")
(tmp_path / ".ruff.toml").write_text("line-length = 99\n", encoding="utf-8")
monkeypatch.chdir(tmp_path)
assert "--config" not in task_lint().cmd


def test_lint_still_injects_config_when_only_basedpyright_section_present(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.basedpyright] must NOT suppress ruff --config (cross-tool isolation)."""
from interlocks.tasks.lint import task_lint

(tmp_path / "pyproject.toml").write_text(
_BARE_PYPROJECT + "\n[tool.basedpyright]\ntypeCheckingMode = 'standard'\n",
encoding="utf-8",
)
monkeypatch.chdir(tmp_path)
cmd = task_lint().cmd
assert "--config" in cmd
assert Path(cmd[cmd.index("--config") + 1]).name == "ruff.toml"


# ─────────────── tool pin propagation ──────────────────────────────


def test_lint_task_uses_ruff_pin_override(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""[tool.interlocks.tools] ruff override must appear in the uvx --from spec."""
from interlocks.defaults.tools import default_pin
from interlocks.tasks.lint import task_lint

custom_pin = "0.1.0"
(tmp_path / "pyproject.toml").write_text(
textwrap.dedent(f"""\
[project]
name = "ruffpin"
version = "0.0.0"

[tool.ruff]
target-version = "py311"

[tool.interlocks.tools]
ruff = "{custom_pin}"
"""),
encoding="utf-8",
)
monkeypatch.chdir(tmp_path)
cmd = task_lint().cmd
assert f"ruff=={custom_pin}" in cmd
assert f"ruff=={default_pin('ruff')}" not in cmd
53 changes: 53 additions & 0 deletions tests/tasks/test_typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,25 @@ def test_typecheck_omits_config_when_project_has_pyrightconfig_sidecar(
assert "--project" not in task_typecheck().cmd


def test_typecheck_still_injects_config_when_only_ruff_section_present(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.ruff] must NOT suppress basedpyright --project (cross-tool isolation)."""
from interlocks.tasks.typecheck import task_typecheck

(tmp_path / "pyproject.toml").write_text(
_BARE_PYPROJECT + "\n[tool.ruff]\nline-length = 99\n",
encoding="utf-8",
)
pkg = tmp_path / "interlocks"
pkg.mkdir()
(pkg / "__init__.py").write_text("", encoding="utf-8")
monkeypatch.chdir(tmp_path)
cmd = task_typecheck().cmd
assert "--project" in cmd
assert Path(cmd[cmd.index("--project") + 1]).name == "pyrightconfig.json"


# ─────────────── target venv pythonpath ─────────────────────


Expand Down Expand Up @@ -263,6 +282,40 @@ def test_typecheck_pyright_sidecar_omits_project_but_keeps_pythonpath(
assert cmd[cmd.index("--pythonpath") + 1] == str(python)


# ─────────────── tool pin propagation ──────────────────────────────


def test_typecheck_task_uses_basedpyright_pin_override(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""[tool.interlocks.tools] basedpyright override must appear in the uvx --from spec."""
from interlocks.defaults.tools import default_pin
from interlocks.tasks.typecheck import task_typecheck

custom_pin = "1.0.0"
(tmp_path / "pyproject.toml").write_text(
textwrap.dedent(f"""\
[project]
name = "pyrightpin"
version = "0.0.0"

[tool.basedpyright]
typeCheckingMode = "standard"

[tool.interlocks.tools]
basedpyright = "{custom_pin}"
"""),
encoding="utf-8",
)
pkg = tmp_path / "pyrightpin"
pkg.mkdir()
(pkg / "__init__.py").write_text("", encoding="utf-8")
monkeypatch.chdir(tmp_path)
cmd = task_typecheck().cmd
assert f"basedpyright=={custom_pin}" in cmd
assert f"basedpyright=={default_pin('basedpyright')}" not in cmd


@pytest.mark.slow
def test_typecheck_resolves_imports_from_target_venv(
tmp_path: Path,
Expand Down
Loading
Loading