PR Gates / CI image pin matches docker/ci (pull_request) Successful in 10s
PR Gates / Docs update reminder (pull_request) Successful in 13s
PR Gates / decisions lifecycle (pull_request) Successful in 18s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 11s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 25s
review-verdict/h10 Awaiting review verdict for b0f42f1
Review verdict / Set review-verdict status (pull_request_target) Successful in 10s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 5m38s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m35s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m11s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m50s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 5s
`testing.guard-derives-population-from-source` (#774) says a completeness guard derives its population from an authoritative source, and its worked examples are an enum and the generated OpenAPI document. It was silent on the commonest population in our own guards — files in a directory — and every one of them answered with a filesystem walk. A walk is not an authoritative source: it reports build output, generated shims and editor droppings, and it differs per machine. #778 measured the cost by getting the same population wrong three times in one PR while implementing the milestone that exists to prevent it. The worst shape was `Path.rglob` enumerating `.husky/_/` — 17 shims `npm ci` writes, gitignored and untracked — which made that guard RED on every developer checkout and GREEN in CI, whose `script-tests` job never runs `npm ci`. Only that one guard was fixed then. This audits the rest. CONVERTED (a completeness claim over tracked files): * `test_guard_inventory.py` — its `.husky/` walk excluded `.husky/_/` only because `_` is a directory, so the obvious "make it recursive" edit would have reintroduced #778's defect in the repo's own model guard. Both halves are gated on the index: the callers, and the `scripts/…` paths they name — the second was left on `Path.exists()` in the first cut and found by cold review. * `test_hook_fire_log.py` — an untracked scratch `.sh` in `.claude/hooks/` was demanded to carry instrumentation. * `test_ci_image_pin_population.py` — and `*.yaml` added: Gitea accepts both spellings, so a `.yaml` workflow adopting the toolchain image was structurally invisible while the test read as covering every workflow. * `test_remote_state_inventory.py` — folded onto the shared derivation, so the rule has one implementation rather than two. ASSESSED AND RECORDED, not silently skipped: * `test_ci_release_path_scan_job.py::_repo_copy` — not a completeness claim, but it takes its file LIST from the index anyway for hermeticity, since `copytree` copied untracked files and `__pycache__` into a tree whose behaviour the probes measure. Content still comes from the working tree, and the copy is NOT a git repo, so neither file that step runs may use the helper — written down because `MARKED_JOBS` is left open as a residual gap, which invites editing exactly that file. * `test_ci_dropped_step_guard.py` — no filesystem population at all; its members come from the parsed workflow. * The decisions corpus keeps its walks and is recorded as unexamined rather than cleared. This is not "replace every glob". `scripts/tests/tracked_files.py` is the single derivation. `test_guard_populations_derive_from_git.py` proves it in two directions, which are complements rather than a second opinion — measured both ways: * REMOVAL, exhaustively: every member of every registered derivation is dropped from the index in turn and must disappear while still on disk. One victim was not enough — `derived_guard_files` unions four contributors, so a mutant putting only one back on a walk passed. This catches a hardcoded `.exists()` admit and memoisation, which the other direction cannot. * The CALL LOG: a derivation may READ a file but must not LIST a directory. This catches an append-only source that yields nothing on this machine — #778's shape — which removal cannot see, because it has nothing to remove. Its limits are stated in one place and are true in both directions. Both the population and the proofs are registered against a hand-written scope mirror that carries its own equality check, and the import matcher's 16 forms are pinned as a table so the next edit cannot silently re-open one. The mutation this guard declares in `mutation_manifest.py` (#790) is the real defect cold review found in its own first round. Docs: `testing.guard-derives-population-from-source` gains the file-population case and why the disk is not authoritative; `docs/guard-inventory.md` carries the per-guard audit table, including the two no-change verdicts and the decisions corpus recorded as unexamined. Seven rounds of independent cold review (Codex GPT-5.6 and Opus, alternating) — the per-round findings and their measurements are in the PR. fixes #806 Decisions-Edit: yes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
603 lines
33 KiB
Python
603 lines
33 KiB
Python
"""No file population in this repo admits a file git does not track (ersatztv#806).
|
|
|
|
The regression for #778's third defect, hoisted to cover every guard that shares the mechanism
|
|
rather than being copied into each of them. `Path.rglob` enumerated `.husky/_/` — 17 husky shims
|
|
generated by `npm ci`, gitignored and untracked — so `test_remote_state_inventory.py` was RED on
|
|
every developer checkout and GREEN in CI, whose `script-tests` job checks out and pip-installs but
|
|
never runs `npm ci`. A guard that fails everywhere except where it runs trains its readers to ignore
|
|
it, and it did so on the artifact whose entire thesis is population correctness.
|
|
|
|
TWO PROOFS, because they fail differently and either alone leaves a hole.
|
|
|
|
* `test_the_primitive_REALLY_excludes_an_untracked_file` builds a throwaway git repo, commits one
|
|
file, leaves an identical sibling untracked, and runs the real derivation against it. It proves
|
|
the mechanism by EXECUTING it rather than by recognising its shape — no monkeypatching, no
|
|
stand-in for git. Nothing here is a claim about `git ls-files`; it is `git ls-files`.
|
|
* `test_no_derivation_admits_an_untracked_file` narrows the tracked set under each real derivation
|
|
and requires the dropped member to vanish from the population even though the file is still on
|
|
disk and still matches the scope. That is the property stated over the ACTUAL guards, so a
|
|
future refactor that quietly reintroduces a filesystem walk in any one of them fails here rather
|
|
than on somebody's laptop.
|
|
|
|
`DERIVATIONS` is the reason this file is not four near-copies: a guard that starts deriving a file
|
|
population registers here, and both proofs cover it for free. The register is hand-written and that
|
|
is a SCOPE decision, not a population one — per `testing.guard-derives-population-from-source`, a
|
|
scope mirroring an authoritative source needs its own equality check, and
|
|
`test_every_index_derived_module_is_registered` is it: it reads which modules import the shared
|
|
helper and demands each one appear below, so adding a fifth derivation and forgetting this file is
|
|
red rather than silently uncovered.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import glob
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from scripts.tests import test_ci_image_pin_population as image_pin
|
|
from scripts.tests import test_guard_inventory as guard_inventory
|
|
from scripts.tests import test_hook_fire_log as hook_fire
|
|
from scripts.tests import test_remote_state_inventory as remote_state
|
|
from scripts.tests import tracked_files
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
TESTS_DIR = REPO_ROOT / "scripts" / "tests"
|
|
|
|
|
|
class _EmptyScan:
|
|
"""An exhausted ITERATOR that is also a context manager, standing in for `os.scandir`.
|
|
|
|
Both halves are load-bearing and each was missing in turn. `os.scandir` is used as
|
|
`with os.scandir(...) as it`, so a bare iterator broke the context-manager protocol; and
|
|
`os.walk` does `entry = next(scandir_it)` on the result, so an ITERABLE defining only
|
|
`__iter__` broke that. Either way the enumeration assertion still fired with the right message,
|
|
but the report also carried a TypeError about the harness — and a finding that arrives beside a
|
|
harness error invites doubting the finding rather than the code.
|
|
"""
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
raise StopIteration
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *_exc):
|
|
return False
|
|
|
|
def close(self):
|
|
return None
|
|
|
|
|
|
def _as_relative_strings(members) -> set[str]:
|
|
"""Derivations return either repo-relative strings or absolute `Path`s; compare on one form.
|
|
|
|
Iterating into a set is also what DRAINS a derivation that returns a generator, which the
|
|
enumeration proof depends on — see its call site.
|
|
|
|
An unexpected member type is REPORTED rather than stringified. `str(m)` on anything at all meant
|
|
a derivation yielding, say, nested generators produced plausible-looking members and compared
|
|
equal to nothing, which is a population check passing over data it did not understand.
|
|
"""
|
|
out = set()
|
|
for m in members:
|
|
assert isinstance(m, str | Path), (
|
|
f"a derivation yielded {type(m).__name__} ({m!r}); populations here are repo-relative "
|
|
"strings or absolute Paths, and stringifying anything else would compare a plausible "
|
|
"value against a set that can never contain it."
|
|
)
|
|
out.add(str(Path(m).relative_to(REPO_ROOT)) if isinstance(m, Path) else str(m))
|
|
return out
|
|
|
|
|
|
# (label, callable, a floor below which the derivation has plainly broken). `test_hook_fire_log`
|
|
# floors the same population at the same number for its own coverage assertions; that is not a
|
|
# duplicate guard masking another, because the two protect different consumers from going
|
|
# vacuous — delete this one and THIS file's proofs iterate over nothing while reporting success.
|
|
DERIVATIONS = (
|
|
("test_guard_inventory.derived_guard_files", guard_inventory.derived_guard_files, 25),
|
|
("test_hook_fire_log.hook_scripts", hook_fire.hook_scripts, 10),
|
|
("test_ci_image_pin_population.workflow_files", image_pin.workflow_files, 5),
|
|
("test_remote_state_inventory.derived_population", remote_state.derived_population, 40),
|
|
)
|
|
|
|
# The floor matters only to the anti-vacuity test; the two property tests take the pair, so an
|
|
# unused parameter cannot drift into looking like an assertion they make.
|
|
_IDS = [d[0] for d in DERIVATIONS]
|
|
_PAIRS = [(label, derive) for label, derive, _ in DERIVATIONS]
|
|
|
|
# Modules that import the shared derivation WITHOUT deriving a guard population. Kept here, beside
|
|
# DERIVATIONS, so adding one is an edit to this file that a reviewer sees.
|
|
POPULATION_EXEMPT = {
|
|
# Uses the index to assemble a HERMETIC tmp fixture copy; nothing in it asserts membership.
|
|
"test_ci_release_path_scan_job.py": "index-derived fixture copy, not a population",
|
|
}
|
|
|
|
_HELPER = "scripts.tests.tracked_files"
|
|
_PACKAGE = ["scripts", "tests"]
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# ANTI-VACUITY FIRST — every assertion below compares sets, and a derivation that collapsed to
|
|
# nothing would satisfy all of them while proving nothing.
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive", "floor"), DERIVATIONS, ids=_IDS)
|
|
def test_each_derivation_found_something(label, derive, floor):
|
|
members = _as_relative_strings(derive())
|
|
assert len(members) >= floor, (
|
|
f"{label} derived only {len(members)} members, below its floor of {floor} — the derivation "
|
|
"is broken, not the repo, and every set comparison built on it is vacuous."
|
|
)
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# PROOF 1 — the primitive, executed against a real git repo rather than described
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_the_primitive_REALLY_excludes_an_untracked_file(tmp_path, monkeypatch):
|
|
"""A tracked and an untracked file, identical in name shape and both on disk. Only one is in.
|
|
|
|
Run rather than reasoned about. This class of defect is produced by arguments about what a
|
|
traversal WOULD enumerate, and such arguments are locally convincing whether or not they are
|
|
right; only executing the traversal distinguishes the two.
|
|
"""
|
|
repo = tmp_path / "repo"
|
|
(repo / ".claude" / "hooks").mkdir(parents=True)
|
|
(repo / ".claude" / "hooks" / "committed.sh").write_text("#!/bin/sh\n")
|
|
(repo / ".claude" / "hooks" / "untracked.sh").write_text("#!/bin/sh\n")
|
|
|
|
def git(*args):
|
|
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
|
|
|
|
git("init", "-q")
|
|
git("config", "user.email", "guard@example.invalid")
|
|
git("config", "user.name", "guard")
|
|
git("add", ".claude/hooks/committed.sh")
|
|
git("commit", "-qm", "one tracked hook")
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
found = tracked_files.tracked_children(".claude/hooks", ("*.sh",))
|
|
|
|
assert (repo / ".claude" / "hooks" / "untracked.sh").is_file(), (
|
|
"the untracked file must still be on disk, or this proves nothing about the index winning over the filesystem"
|
|
)
|
|
assert found == {".claude/hooks/committed.sh"}, (
|
|
f"the derivation returned {sorted(found)}. A filesystem walk returns both files here; only "
|
|
"the index distinguishes them, and that difference is the entire point of ersatztv#806."
|
|
)
|
|
|
|
|
|
def test_the_primitive_does_not_recurse_into_an_untracked_subdirectory(tmp_path, monkeypatch):
|
|
"""`.husky/_/` in miniature — the shape that made #778 red on every checkout.
|
|
|
|
Even a TRACKED nested file must stay out: `tracked_children` is direct-children-only by design,
|
|
and recursion is what dragged the shims in. Proving it with a tracked file makes the assertion
|
|
about the traversal rather than about the index, so the two properties cannot mask each other.
|
|
"""
|
|
repo = tmp_path / "repo"
|
|
(repo / ".husky" / "_").mkdir(parents=True)
|
|
(repo / ".husky" / "pre-commit").write_text("#!/bin/sh\n")
|
|
(repo / ".husky" / "_" / "husky.sh").write_text("#!/bin/sh\n")
|
|
|
|
def git(*args):
|
|
subprocess.run(["git", "-C", str(repo), *args], check=True, capture_output=True)
|
|
|
|
git("init", "-q")
|
|
git("config", "user.email", "guard@example.invalid")
|
|
git("config", "user.name", "guard")
|
|
git("add", "-A")
|
|
git("commit", "-qm", "husky plus a nested shim, both tracked")
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
assert tracked_files.tracked_children(".husky", ("*",)) == {".husky/pre-commit"}, (
|
|
"a nested file entered a flat population — this is the `.husky/_/` shape, and it was red on "
|
|
"every developer checkout the last time it shipped"
|
|
)
|
|
|
|
|
|
def test_an_empty_index_FAILS_LOUDLY_rather_than_reporting_an_empty_population(tmp_path, monkeypatch):
|
|
"""The floor under every floor. A silent empty population is how a completeness guard reports
|
|
total coverage having examined nothing, which is the failure mode this repo has shipped twice
|
|
(#631, #751)."""
|
|
repo = tmp_path / "repo"
|
|
repo.mkdir()
|
|
subprocess.run(["git", "-C", str(repo), "init", "-q"], check=True, capture_output=True)
|
|
|
|
monkeypatch.setattr(tracked_files, "REPO_ROOT", repo)
|
|
with pytest.raises(AssertionError, match="reported nothing"):
|
|
tracked_files.tracked_children(".claude/hooks", ("*.sh",))
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# PROOF 2 — the property, over the real guards
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive"), _PAIRS, ids=_IDS)
|
|
def test_no_derivation_admits_an_untracked_file(label, derive):
|
|
"""Narrow the index, leave the disk alone, and require the member to disappear — for EVERY
|
|
member, one at a time.
|
|
|
|
EXHAUSTIVE RATHER THAN ONE VICTIM, and the difference is not thoroughness for its own sake.
|
|
`derived_guard_files` unions four sources; one victim is always drawn from whichever sorts
|
|
first, so a mutant putting only the third source back on a filesystem walk passes while the
|
|
proof reports on all four. A sample cannot see the source it did not draw from — this file
|
|
applying `testing.guard-derives-population-from-source` to itself.
|
|
|
|
The floors cannot substitute, and the numbers say why. Reproduce with:
|
|
|
|
PYTHONPATH=. python3 -c "from scripts.tests import test_guard_inventory as g; \
|
|
print(len(g.derived_guard_files()))"
|
|
|
|
61 members on 2026-08-22; suppressing a single contributor leaves 39 (`scripts/tests`), 48
|
|
(hooks) or 58 (husky), all far above the anti-vacuity floor of 25. The figures move whenever a
|
|
guard is added — they were 60/39/47/57 one commit earlier — so read them as an illustration of
|
|
the GAP, not as values to assert against. A floor tight enough to catch a lost source
|
|
would go red every time a guard is legitimately deleted, which is the wrong instrument.
|
|
|
|
Its own `monkeypatch` context, never the shared fixture instance: the function-scoped fixture is
|
|
the same object `conftest.py`'s autouse `isolate_hook_fire_log` patched, so calling `undo()` on
|
|
it here also unsets `ETV_HOOK_FIRE_LOG_DIR` and silently re-points a later hook-driving test at
|
|
the PRODUCTION log — the #776 isolation disarmed from inside the file that argues for proofs.
|
|
|
|
WHAT REMOVAL CANNOT SEE, so it is not read as more than it is: a source contributing ONLY
|
|
untracked members has nothing here to remove, and #778's defect was exactly that shape (an
|
|
`rglob` over `.husky/_/` adds 17 untracked members and removes none). That direction is
|
|
`test_no_derivation_ENUMERATES_the_filesystem` below; the two are complements, not duplicates.
|
|
"""
|
|
before = _as_relative_strings(derive())
|
|
assert before, f"{label} derived nothing; there is no victim to remove"
|
|
|
|
real = tracked_files._git_ls_files()
|
|
survivors = []
|
|
absent = []
|
|
with pytest.MonkeyPatch.context() as m:
|
|
for victim in sorted(before):
|
|
if not (REPO_ROOT / victim).is_file():
|
|
absent.append(victim)
|
|
continue
|
|
m.setattr(tracked_files, "_git_ls_files", lambda v=victim: [p for p in real if p != v])
|
|
if victim in _as_relative_strings(derive()):
|
|
survivors.append(victim)
|
|
|
|
assert not absent, (
|
|
f"{label} contains {absent}, which git tracks but are not on disk. The proof below asserts "
|
|
"that the INDEX decides while the file is still present; it cannot mean that for a member "
|
|
"that is missing, so this is reported rather than skipped."
|
|
)
|
|
assert not survivors, (
|
|
f"{label} still contains {survivors} after git stopped tracking them. Every one of those is "
|
|
"still on disk, so the derivation is reading the filesystem for that member and untracked "
|
|
"build output can redden it on a developer checkout while CI stays green (ersatztv#778)."
|
|
)
|
|
|
|
|
|
# The directory-listing APIs a Python file population is realistically written with. NOT every way a
|
|
# process can list a directory — `subprocess.run(["ls"])`, a module-level alias captured before the
|
|
# patch, and any C-level call all walk straight past this, all three verified by cold review. That
|
|
# bounds what the check below can claim, and the docstring says so rather than implying a sandbox.
|
|
# Reading a file stays allowed: `derived_guard_files` must read workflow bodies.
|
|
_ENUMERATORS = (
|
|
(Path, "glob"),
|
|
(Path, "rglob"),
|
|
(Path, "iterdir"),
|
|
(Path, "walk"),
|
|
(os, "listdir"),
|
|
(os, "walk"),
|
|
(os, "scandir"),
|
|
(glob, "glob"),
|
|
(glob, "iglob"),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive"), _PAIRS, ids=_IDS)
|
|
def test_no_derivation_ENUMERATES_the_filesystem(label, derive):
|
|
"""The ADD direction, without arranging any state: a derivation may READ files, but it may not
|
|
LIST a directory while it runs.
|
|
|
|
WHY THE ADD DIRECTION NEEDS ITS OWN TEST. Removing members from the index cannot see a source
|
|
that contributes ONLY untracked members: it adds and never takes away, so nothing of its is
|
|
available to remove. `.husky/_/` is that shape exactly — 17 shims `npm ci` writes, none ever in
|
|
the index — and it is the shape #778 shipped. Scope the claim precisely: a source appending
|
|
`(REPO_ROOT / ".husky" / "_").rglob("*")` leaves the removal proof green ON A MACHINE WHERE THAT
|
|
DIRECTORY IS ABSENT, which is the `script-tests` checkout. Where the shims exist, the appended
|
|
members are present and removal reddens too. The blind spot is an append-only source that yields
|
|
nothing HERE — which is exactly the CI shape, and exactly where a guard going quiet matters.
|
|
|
|
WHY THE PROPERTY IS "DOES NOT ENUMERATE" RATHER THAN "DOES NOT RETURN AN UNTRACKED FILE". Both
|
|
obvious formulations of the latter are machine-dependent, which is the very fault #806 exists to
|
|
remove:
|
|
|
|
* creating real probe files in the checkout and requiring they not enter. That needs a
|
|
`test_*.py` probe to reach the `scripts/tests` scope — a file pytest COLLECTS mid-session —
|
|
and its parametrised names collide across `-n auto` workers and concurrent sessions,
|
|
`finally` does not survive SIGKILL, and a concurrent `git add -A` can stage one. Defects in
|
|
the test, not in the thing tested.
|
|
* neutralising the shared primitive and requiring the derivation to go empty. That misses the
|
|
`.husky/_/` source on any machine where `.husky/_/` does not exist — which is every CI
|
|
checkout of `script-tests`, which never runs `npm ci`. Green where it runs, red only on a
|
|
laptop: the inverted asymmetry again, inside the proof written to abolish it.
|
|
|
|
Watching for the CALL needs no arranged state: an `rglob` issued while deriving is caught even
|
|
where the directory it walks is empty, because the evidence is the call rather than what it
|
|
returned. Reading is untouched, so a derivation may still parse the workflow bodies it scrapes
|
|
for referenced scripts.
|
|
|
|
WHAT IT DOES NOT COVER. The boundary is not "synchronous", which is what two earlier drafts
|
|
said and what measurement disproved — a thread that outlives the `derive()` call but finishes
|
|
while its result is being drained IS caught, as is a `__del__` firing during that drain. The
|
|
boundary is mechanical rather than temporal: a call to one of the SPIES is observed, wherever and
|
|
whenever it happens in this process before the assertion below. "While the patch is active"
|
|
under-claims it — the spy appends to a list that outlives the patch, so a reference captured
|
|
during the window and invoked after it still records. What decides observation is whether the
|
|
call goes through a spy, not when. The result is drained while the patch is installed, so a lazy
|
|
generator is reached.
|
|
|
|
NOT REACHED, because no spy was ever installed on that path. Enumeration HOISTED TO MODULE SCOPE
|
|
runs at import, before this test exists — the likeliest instance rather than a contrivance, since
|
|
`test_ci_image_pin_population.py` already precomputes `_DOC` that way — as does an `atexit` hook,
|
|
and a cached property warmed by the baseline call below. `from os import listdir` binds the real
|
|
function before any patch; `from os import walk` IS caught (it routes through the patched
|
|
`os.scandir`) and so is `from glob import glob` (through the patched `glob.iglob`). And anything
|
|
listing in ANOTHER PROCESS — a deliberate `subprocess.run(["ls"])`, or a forked child. This is a
|
|
regression guard against the shapes that arrive by accident, not a sandbox.
|
|
|
|
It cuts the other way too: any spy call at all reddens this test, so unrelated background thread
|
|
activity touching a patched name during the window would too. Nothing in this suite does that
|
|
today, and the report names the call, so a false red would be diagnosable rather than
|
|
mysterious.
|
|
|
|
It also cannot see a derivation that admits a HARDCODED path without listing anything
|
|
(`if (REPO_ROOT / "x.sh").exists(): add`) — listing is the commonest way to discover an untracked
|
|
member, not the only one. The removal proof above catches that shape, and catches memoisation,
|
|
which this one cannot: the baseline call below warms any cache outside the patch. The two are
|
|
complements.
|
|
"""
|
|
assert derive(), f"{label} derived nothing; this proof needs a baseline"
|
|
|
|
calls: list[str] = []
|
|
|
|
def _spy(what):
|
|
# RECORDS and returns empty rather than raising. Raising made the assertion "did an
|
|
# exception reach us", which a derivation defeats by catching it: a `try: ... except
|
|
# Exception: return set()` around an `rglob` enumerated the filesystem and this test passed,
|
|
# measured. The evidence is the CALL, so the call is what is asserted on.
|
|
def spy(*_args, **_kwargs):
|
|
calls.append(what)
|
|
# Iterator AND context manager: `os.scandir` is used as `with os.scandir(...) as it`,
|
|
# and a bare iterator made the failure report carry a test-induced TypeError about the
|
|
# context manager protocol alongside the real finding. The assertion fired correctly
|
|
# either way, but a report that blames the harness invites doubting the finding.
|
|
return _EmptyScan()
|
|
|
|
return spy
|
|
|
|
failure = None
|
|
with pytest.MonkeyPatch.context() as m:
|
|
for owner, name in _ENUMERATORS:
|
|
m.setattr(owner, name, _spy(f"{getattr(owner, '__name__', owner)}.{name}"), raising=False)
|
|
try:
|
|
# DRAINED inside the context, never `derive()` discarded. A derivation returning a lazy
|
|
# generator does its work when the caller drains it, so discarding the result moved the
|
|
# whole walk outside the patch: a generator yielding the index population and then
|
|
# appending `.husky/_` passed here and admitted 17 untracked shims on a checkout where
|
|
# that directory exists. `_as_relative_strings` is what drains it — it iterates into a
|
|
# set — so the call must stay here rather than being hoisted out or wrapped in something
|
|
# lazier.
|
|
_as_relative_strings(derive())
|
|
except BaseException as exc: # re-raised below, after the evidence has been judged
|
|
failure = exc
|
|
|
|
assert not calls, (
|
|
f"{label} enumerated the filesystem via {sorted(set(calls))} while deriving its population. "
|
|
"Directory listings report build output, generated shims and editor droppings, and differ "
|
|
"between the CI checkout and a developer's, so the member set stops being a property of the "
|
|
f"repo (ersatztv#778, #806)." + (f" It also raised: {failure!r}" if failure is not None else "")
|
|
)
|
|
if failure is not None:
|
|
raise failure
|
|
|
|
|
|
@pytest.mark.parametrize(("label", "derive"), _PAIRS, ids=_IDS)
|
|
def test_every_derived_member_is_tracked(label, derive):
|
|
"""The same property as an invariant over the real tree, which is the form that catches a
|
|
refactor going back to a filesystem walk without also touching this file."""
|
|
tracked = set(tracked_files._git_ls_files())
|
|
stray = sorted(m for m in _as_relative_strings(derive()) if m not in tracked)
|
|
assert not stray, f"{label} contains untracked path(s): {stray}"
|
|
|
|
|
|
# ------------------------------------------------------------------------------------------------
|
|
# THE SCOPE MIRROR ABOVE IS ITSELF CHECKED
|
|
# ------------------------------------------------------------------------------------------------
|
|
|
|
|
|
def _modules_importing_the_helper() -> set[str]:
|
|
"""Which `scripts/tests/test_*.py` import the shared derivation, by PARSING them.
|
|
|
|
`ast` rather than a substring scan, and the distinction is the point rather than tidiness. A
|
|
substring scan over source both EVADES and FALSELY FIRES here: `import scripts.tests.
|
|
tracked_files as tf` and `from scripts.tests import tracked_files as tf` escape a scan for
|
|
`"tracked_files import"`, while a comment merely citing `scripts/tests/tracked_files.py` matches
|
|
a scan for `"tracked_files."` and would redden a correct tree over prose.
|
|
`docs/decisions/records/testing/guard-derives-population-from-source.md` records why patching
|
|
such a predicate does not converge: it is not a parser. Python ships the parser, and a comment
|
|
is not a node at all.
|
|
|
|
WHAT THE PARSE DOES NOT REACH, stated rather than implied by the word "parsing": STATIC import
|
|
statements naming the helper. `importlib.import_module("scripts.tests.tracked_files")`, a
|
|
re-export through `scripts/tests/__init__.py`, and `from scripts.tests import *` are invisible,
|
|
verified by executing each. (`from scripts.tests.tracked_files import *` IS seen — it names the
|
|
module.) Those sit inside the same residual as a module that derives a population without the
|
|
helper at all — the residual named below — and no mechanical check closes it.
|
|
|
|
The FILE LIST is a filesystem walk on purpose, and it is not the defect this file forbids: it is
|
|
a superset check over what pytest itself collects, so an untracked stray `test_x.py` here makes
|
|
the guard MORE demanding, never blind. Using the index would let an unstaged new guard escape
|
|
registration, which is the wrong direction for a check about coverage.
|
|
"""
|
|
found = set()
|
|
for path in sorted(TESTS_DIR.glob("test_*.py")):
|
|
for node in ast.walk(ast.parse(path.read_text())):
|
|
# RESOLVE the name to an absolute module and compare exactly. Testing `base ==
|
|
# "tracked_files"` handled `from .tracked_files import x` but silently missed
|
|
# `from ..tests.tracked_files import x`, which resolves to the same helper — a false
|
|
# NEGATIVE, the direction that lets a module adopt the helper and escape registration.
|
|
# A suffix match instead over-accepts `from unrelated.package import tracked_files`,
|
|
# reddening a correct tree over a module this repo does not own. Resolution is the only
|
|
# form with neither failure: these files live in `scripts.tests`, so level 1 resolves to
|
|
# `scripts.tests` and level 2 to `scripts` — see the guard below for why there is no
|
|
# level 3.
|
|
if isinstance(node, ast.Import):
|
|
hit = any(a.name == _HELPER for a in node.names)
|
|
elif isinstance(node, ast.ImportFrom):
|
|
if node.level > len(_PACKAGE):
|
|
# Beyond the top-level package: Python raises ImportError for this, so it cannot
|
|
# be an import of the helper. Guarded explicitly because `_PACKAGE[:negative]`
|
|
# silently WRAPS — level 4 produced the same prefix as level 2 — which pinned an
|
|
# unimportable form as a valid detection. Valid levels here are exactly 1 and 2:
|
|
# 1 resolves to `scripts.tests`, 2 to `scripts`, and 3 or more is beyond the
|
|
# top-level package, which Python refuses.
|
|
continue
|
|
prefix = _PACKAGE[: len(_PACKAGE) - (node.level - 1)] if node.level else []
|
|
base_parts = prefix + ([node.module] if node.module else [])
|
|
base = ".".join(base_parts)
|
|
hit = base == _HELPER or any(f"{base}.{a.name}" == _HELPER for a in node.names)
|
|
else:
|
|
continue
|
|
if hit:
|
|
found.add(path.name)
|
|
break
|
|
return found
|
|
|
|
|
|
# (source, should the matcher see it). Every row is a form that has actually been mis-classified; the
|
|
# table is here so the next edit to `_modules_importing_the_helper` cannot re-open one silently.
|
|
# A false NEGATIVE lets a module adopt the helper and escape registration; a false POSITIVE reddens
|
|
# a correct tree over a module this repo does not own. Both directions are pinned.
|
|
_IMPORT_FORMS = (
|
|
("import scripts.tests.tracked_files as tf", True),
|
|
("from scripts.tests import tracked_files as tf", True),
|
|
("from scripts.tests.tracked_files import tracked_paths", True),
|
|
("from scripts.tests.tracked_files import *", True),
|
|
("from . import tracked_files", True),
|
|
("from .tracked_files import tracked_paths", True),
|
|
("from ..tests.tracked_files import tracked_paths", True),
|
|
# Beyond the top-level package from `scripts.tests`: Python raises ImportError, so there is
|
|
# nothing to detect. Pinned False so the negative-slicing wrap that once made it look
|
|
# detectable cannot come back.
|
|
("from ...scripts.tests.tracked_files import tracked_paths", False),
|
|
# Witnesses the negative-slice wrap specifically: without the level guard this one
|
|
# resolves through `scripts` and matches.
|
|
("from ....tests.tracked_files import tracked_paths", False),
|
|
("def f():\n from scripts.tests import tracked_files\n return tracked_files", True),
|
|
("from unrelated.package import tracked_files", False),
|
|
("from ..something import tracked_files", False),
|
|
("from .something import tracked_files", False),
|
|
("from ..other.tracked_files import x", False),
|
|
("# see scripts/tests/tracked_files.py for the rationale\nimport re", False),
|
|
# Pins PARSER VISIBILITY, not importability: executing this line really does import the helper.
|
|
# The row records that a static parse cannot see it — a known gap, pinned so it is not a
|
|
# surprise — and closing it would mean updating this row, which is the intended friction.
|
|
("import importlib\nimportlib.import_module('scripts.tests.tracked_files')", False),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(("source", "expected"), _IMPORT_FORMS, ids=[s.splitlines()[0][:48] for s, _ in _IMPORT_FORMS])
|
|
def test_the_import_matcher_classifies_every_reviewed_form(source, expected, monkeypatch):
|
|
class _Fake:
|
|
name = "test_probe.py"
|
|
|
|
def read_text(self):
|
|
return source
|
|
|
|
def __lt__(self, other):
|
|
return True
|
|
|
|
class _Dir:
|
|
def glob(self, _pattern):
|
|
return [_Fake()]
|
|
|
|
monkeypatch.setattr(sys.modules[__name__], "TESTS_DIR", _Dir())
|
|
seen = "test_probe.py" in _modules_importing_the_helper()
|
|
assert seen is expected, f"the import matcher {'missed' if expected else 'falsely matched'} this form:\n{source}"
|
|
|
|
|
|
def test_every_index_derived_module_is_registered():
|
|
"""`DERIVATIONS` is a hand-written mirror, so it gets an equality check rather than a promise.
|
|
|
|
Without this, adding a fifth index-derived guard and forgetting to register it leaves that guard
|
|
unproven while this file reads as covering them all — a completeness claim standing behind a
|
|
hand-maintained list, which is the defect one altitude up (#773 Family C, and the shape that put
|
|
`MARKED_JOBS` in the record as a residual gap).
|
|
|
|
THE SCOPE THIS CANNOT SEE, stated because a check described as complete stops being re-examined:
|
|
it detects modules that IMPORT the shared helper. A module deriving a file population some other
|
|
way — shelling out to `git ls-files` itself, or going back to `Path.rglob` — is invisible to it,
|
|
and no mechanical check can close that. `test_guard_inventory.py`'s own header argues the same
|
|
point about flagging filter-shaped guards by token, and #774 concluded there that the honest
|
|
answer is no. What is mechanised here is the case that actually recurs: someone adopts the
|
|
helper and forgets this file.
|
|
|
|
Registration is MODULE-level, not derivation-level, so a second population added inside an
|
|
already-registered module is covered only if it is registered too.
|
|
|
|
`POPULATION_EXEMPT` is the opt-out, and it lives HERE rather than as a marker comment in the
|
|
exempt file because the two directions are not symmetric: a false import-match only reddens,
|
|
while a false EXEMPTION is silent. A marker a file grants itself by containing a token is
|
|
trippable from that file's prose — this file's own error message names the token — so it would
|
|
be a one-line silent kill switch, the shape `test_ci_release_path_scan_job.py` argues against
|
|
for its recursion fence. Listing exemptions beside the registrations makes adding one a visible
|
|
edit here.
|
|
|
|
WHAT NO ASSERTION CAN DECIDE, dated so it is re-examined rather than assumed: whether an
|
|
exemption is still WARRANTED. A stale key and an unexplained one are both caught below, but an
|
|
exempt module that later grows a real derived population stays uncovered and silent. Reviewed
|
|
2026-08-22 — the single entry uses the helper only to assemble a tmp fixture copy and asserts
|
|
nothing about membership.
|
|
"""
|
|
exempt = {Path(__file__).name} | set(POPULATION_EXEMPT)
|
|
importers = _modules_importing_the_helper()
|
|
registered = {label.split(".", 1)[0] + ".py" for label, _, _ in DERIVATIONS}
|
|
|
|
# ANTI-VACUITY, and only that. A broken parse is caught loudly by `phantom` below — every
|
|
# registered module would go missing at once — so this is the cheaper, more specific signal, not
|
|
# the thing standing between a broken parse and a green run.
|
|
assert len(importers) >= len(DERIVATIONS), (
|
|
f"the import parse found only {sorted(importers)}, fewer modules than DERIVATIONS registers "
|
|
f"({sorted(registered)}) — the parse has broken."
|
|
)
|
|
|
|
stale = sorted(name for name in POPULATION_EXEMPT if name not in importers)
|
|
assert not stale, (
|
|
f"POPULATION_EXEMPT lists {stale}, which the parser no longer sees importing the shared "
|
|
"helper (renamed, "
|
|
"deleted, or the import removed). A stale exemption is worse than none: if the filename is "
|
|
"ever reused, the new module is exempt from birth without anyone deciding that."
|
|
)
|
|
thin = sorted(name for name, why in POPULATION_EXEMPT.items() if not str(why).strip())
|
|
assert not thin, f"POPULATION_EXEMPT entries with no stated reason: {thin}"
|
|
|
|
unregistered = sorted(importers - registered - exempt)
|
|
assert not unregistered, (
|
|
f"{unregistered} import the shared index derivation but are not in DERIVATIONS, so neither "
|
|
"proof in this file covers them. Add a named derivation function and register it, or add "
|
|
"the module to POPULATION_EXEMPT in this file if it imports the helper without deriving a "
|
|
"population."
|
|
)
|
|
phantom = sorted(registered - importers)
|
|
assert not phantom, (
|
|
f"DERIVATIONS registers {phantom}, which no longer import the shared helper. A row for "
|
|
"a derivation that is not there reads as coverage and is not."
|
|
)
|