Skip to content
Merged
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
34 changes: 33 additions & 1 deletion scripts/check-doc-count.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ def _resolve_root() -> Path:
# the reader straight into a second failure. A hint is only worth printing if it
# runs; keep the spelling here and let tests/test_check_doc_count.py prove the
# other sites agree with it.
#
# Scope of that measurement, added 2026-09-13 (cyc20260913-122923) after walking
# into it: "this form exits 0" is a property of a **synced** checkout. The
# measurement above was taken in the main clone, which is synced. In a fresh
# worktree the same spelling exits 2 without measuring anything, because
# `uv run --no-sync` has created an empty `.venv` there and both `python` and
# `python3` resolve to it - so the spelling is necessary but not sufficient, and
# the hint it appears in must not be printed when the environment, not the
# interpreter choice, is what is missing. See the pytest-missing branch in
# `measured_count`.
INVOCATION = "uv run --no-sync python3 scripts/check-doc-count.py"

# The stored form, as it appeared in Agent.md: the command followed by the count
Expand Down Expand Up @@ -185,9 +195,31 @@ def measured_count() -> int:
"(its output could not be decoded)"
)
if proc.returncode != 0:
detail = (proc.stdout[-2000:] + proc.stderr[-2000:]).strip()
# Two causes, two remedies - and the second one is *not* "use the right
# interpreter". Measured 2026-09-13 (cyc20260913-122923) in a fresh
# review worktree: `uv run --no-sync` there had produced an empty `.venv`
# (`site-packages` holding only `_virtualenv.pth` and `_virtualenv.py`),
# and `python` and `python3` both resolve to it - so the invocation this
# tool prints as its own remedy failed with byte-identical output, rc 2,
# sending the reader in a circle. Reading that output as a wrong
# interpreter is the misdiagnosis: there is no interpreter in this
# checkout that has pytest, so advising a different one cannot help.
if "No module named pytest" in detail:
raise DocCountError(
"pytest is not installed in the interpreter running this tool "
f"({sys.executable}), so no test was collected:\n"
+ detail
+ "\n\nThis is an unsynced checkout, not a wrong-interpreter "
"problem - a fresh worktree or clone gets an empty `.venv`, and "
"`python` and `python3` both resolve to it, so re-running "
f"`{INVOCATION}` here fails identically. Run `uv sync` in this "
"checkout first, or run this tool from a checkout whose "
"environment is already synced."
)
raise DocCountError(
f"pytest --collect-only failed (rc={proc.returncode}):\n"
+ (proc.stdout[-2000:] + proc.stderr[-2000:]).strip()
+ detail
+ "\n\nhint: run this with the project interpreter, e.g."
f" `{INVOCATION} --measure`"
)
Expand Down
50 changes: 50 additions & 0 deletions tests/test_check_doc_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,56 @@ class _Proc:
assert f"`{mod.INVOCATION} --measure`" in str(excinfo.value)


def test_a_missing_pytest_is_diagnosed_as_an_unsynced_checkout(monkeypatch) -> None:
"""The remedy must not be the command that just failed.

Measured state this pins (2026-09-13, `cyc20260913-122923`): in a fresh
review worktree the tool printed its own `INVOCATION` as the fix, and running
that spelling produced byte-identical output, rc 2 - `uv run --no-sync` had
left an empty `.venv` there and both `python` and `python3` resolve to it, so
"use the project interpreter" is a circle. The message must name the
environment instead.
"""
fresh = _load_module()

class _Proc:
returncode = 1
stdout = ""
stderr = "/some/checkout/.venv/bin/python3: No module named pytest\n"

monkeypatch.setattr(fresh.subprocess, "run", lambda *a, **k: _Proc())
with pytest.raises(fresh.DocCountError) as excinfo:
fresh.measured_count()
message = str(excinfo.value)
assert "No module named pytest" in message
assert "/some/checkout/.venv/bin/python3" in message, "the child's own words"
assert "unsynced" in message, "the cause, named"
assert "uv sync" in message, "a remedy that can actually work here"


def test_a_real_collection_failure_keeps_the_invocation_hint(monkeypatch) -> None:
"""The other cause of the same non-zero exit: pytest ran, and it failed.

Without this arm, treating every non-zero collection as an unsynced checkout
would be green - which would take the invocation hint away from the case it
was written for.
"""
fresh = _load_module()

class _Proc:
returncode = 2
stdout = "ERROR: file or directory not found: tests/\n"
stderr = ""

monkeypatch.setattr(fresh.subprocess, "run", lambda *a, **k: _Proc())
with pytest.raises(fresh.DocCountError) as excinfo:
fresh.measured_count()
message = str(excinfo.value)
assert f"`{fresh.INVOCATION} --measure`" in message
assert "uv sync" not in message
assert "unsynced" not in message


# --- which tree was scanned --------------------------------------------------


Expand Down
Loading