utils/home_paths/tests/test_check.py
Krzysztof kuhy Rudnicki b42737f834 Gate home-relative paths against the ~ layout
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 17:10:20 +02:00

181 lines
6.2 KiB
Python

"""The ~-layout gate: what it must catch, and what it must not.
The "must not" half carries the weight. A gate that fires on prose is a gate
that gets disabled: the first run of this checker returned 189 findings
across ~/src, and every single one outside `todo_desktop.dart` was either a
comment, a macOS branch, vendored third-party code, or a deliberate test
fixture. Each exclusion below is one of those, pinned so it cannot creep
back in.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from home_paths.check import ALLOW_MARKER, main, scan_file, scan_text
def _segments(text: str, name: str = "sample.py") -> list[str]:
return [f.segment for f in scan_text(Path(name), text)]
# --- the bug this gate exists for -------------------------------------------
def test_catches_the_todo_desktop_regression() -> None:
"""The exact line that served a stale backlog for a day."""
line = "backlogPath: _argValue(args, '--backlog-path') ?? p.join(home, 'todo', 'BACKLOG.md'),"
assert _segments(line, "todo_desktop.dart") == ["todo"]
def test_passes_the_fixed_form() -> None:
assert _segments("p.join(home, 'src', 'todo', 'BACKLOG.md')", "x.dart") == []
@pytest.mark.parametrize(
("line", "segment"),
[
('Path.home() / "kuhylog"', "kuhylog"),
('Path.home().joinpath("kuhylog")', "kuhylog"),
("os.path.join(os.environ['HOME'], 'utils')", "utils"),
('expanduser("~/screen-locker")', "screen-locker"),
('filepath.Join(home, "octoforge")', "octoforge"),
("path.join(os.homedir(), 'signal-bot')", "signal-bot"),
('final dir = Directory("$home/todo");', "todo"),
('BACKUP="${HOME}/phone_backups"', "phone_backups"),
('f"{home}/scanlation-eval"', "scanlation-eval"),
],
)
def test_every_construction_spelling(line: str, segment: str) -> None:
"""The rewriter's blind spot was *how* the path was spelled, not which."""
assert _segments(line) == [segment]
@pytest.mark.parametrize(
"bucket", ["src", "vendor", "data", "sdk", "media", "Downloads", "Documents"]
)
def test_allowed_buckets_pass(bucket: str) -> None:
assert _segments(f'Path.home() / "{bucket}" / "thing"') == []
def test_dotfiles_are_not_our_business() -> None:
assert _segments('Path.home() / ".config" / "todo"') == []
assert _segments("p.join(home, '.local', 'share', 'todo')", "a.dart") == []
# --- the noise it must not make ---------------------------------------------
def test_comments_are_not_findings() -> None:
"""60% of the first run. `~/utils.` in English is a sentence."""
assert (
_segments("# Thin delegate to the shared gate in $HOME/utils, which owns it")
== []
)
assert _segments("// see ${HOME}/todo for the note format", "a.dart") == []
def test_placeholder_segments_are_not_findings() -> None:
"""Docstrings explaining the path grammar say ``$HOME/x``."""
assert (
_segments('"""One regex covers ``$HOME/x``, ``${HOME}/x`` and more."""') == []
)
def test_other_os_home_layouts_are_not_findings() -> None:
assert _segments('Path.home() / "Library" / "Logs"') == []
assert _segments('Path.home() / "AppData" / "Roaming"') == []
def test_third_party_and_test_trees_are_skipped(tmp_path: Path) -> None:
bad = "p.join(home, 'todo', 'x')"
for rel in (
"mcp-servers/a.py",
"vendor/b.py",
"repo/tests/c.py",
"repo/test_d.py",
"repo/e_test.py",
):
target = tmp_path / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(bad)
assert scan_file(target) == [], rel
plain = tmp_path / "repo" / "real.py"
plain.write_text(bad)
assert [f.segment for f in scan_file(plain)] == ["todo"]
def test_allow_marker_is_an_escape_hatch() -> None:
assert _segments(f"x = home + '/todo' # {ALLOW_MARKER}: migration note") == []
def test_non_code_and_unreadable_files_yield_nothing(tmp_path: Path) -> None:
(tmp_path / "notes.md").write_text("p.join(home, 'todo')")
assert scan_file(tmp_path / "notes.md") == []
assert scan_file(tmp_path / "missing.py") == []
binary = tmp_path / "blob.py"
binary.write_bytes(b"\xff\xfe p.join(home, 'todo')")
assert scan_file(binary) == []
# --- CLI ---------------------------------------------------------------------
def test_cli_exit_codes(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
clean = tmp_path / "ok.py"
clean.write_text('Path.home() / "src" / "todo"')
dirty = tmp_path / "bad.py"
dirty.write_text('Path.home() / "todo"')
assert main([]) == 2
assert main([str(clean)]) == 0
assert main([str(dirty)]) == 1
out = capsys.readouterr().out
assert "builds ~/todo" in out and "Did you mean ~/src/todo?" in out
assert main(["--all", str(tmp_path)]) == 1
clean_dir = tmp_path / "clean"
clean_dir.mkdir()
(clean_dir / "f.py").write_text("x = 1")
assert main(["--all", str(clean_dir)]) == 0
def test_render_falls_back_to_absolute_path_outside_root(tmp_path: Path) -> None:
dirty = tmp_path / "bad.py"
dirty.write_text('Path.home() / "todo"')
(finding,) = scan_file(dirty)
assert finding.render(Path("/nowhere")).startswith(str(dirty))
assert finding.render(tmp_path).startswith("bad.py:")
def test_walk_prunes_caches_third_party_and_tests(tmp_path: Path) -> None:
"""`--all` must not descend into build output or vendored trees.
Without this the gate reports on code nobody here owns, which is how the
first run produced 18 findings about macOS `~/Library` inside a vendored
Unity MCP server.
"""
bad = 'Path.home() / "todo"'
for rel in (
"build/out.py",
"node_modules/pkg/index.js",
".venv/lib/thing.py",
"vendor/clone/x.py",
"pkg/tests/t.py",
"pkg/test_helper.py",
"pkg/thing_test.py",
"notes.md",
):
target = tmp_path / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(bad)
real = tmp_path / "pkg" / "real.py"
real.write_text(bad)
assert main(["--all", str(tmp_path)]) == 1
from home_paths.check import walk
assert [p.name for p in walk(tmp_path)] == ["real.py"]