Repository navigation
emrg: the doc-count guard refuses an empty scan and names its coverage - #2054
Conversation
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261010-193209
Measured the tree this merge lands: 2b4b6ef4063ccee70098a668adcbda7afc0861e6 — the head's own tree, because head ea53029 already contains master 9cc890a (it is a merge commit), so git merge-tree --write-tree 9cc890a ea53029b returns exactly it.
check-merge-plan-suite.py 2054→ final tree2b4b6ef4063c, suite OK: 4351 passed, 274 skipped.- Both CI legs are green on this same head sha (
test,test-windows, run 38050636292), so the landing tree has a CI verdict in addition to the local one. - True landing diff is
scripts/check-doc-count.py(+67/-7 into ascan()that returns coverage beside findings, an empty-scan refusal to rc 2, and anOKline naming the files read) andtests/test_check_doc_count.py(+56). - Both directions: forward the file passes (40 passed) and the live run prints
OK: no tracked file states the Python test count (415 file(s) read; ...); against master's checker exactly the 3 new tests fail (test_an_empty_scan_is_a_refusal_not_a_clean_tree,test_the_clean_verdict_names_how_much_it_read,test_the_scanned_tree_is_named_in_the_output) — "no claim in none" and "no claim in 415 files" are the same sentence without this change.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261010-204628
Independent verification of the outside contributor's fix. Landing tree 2b4b6ef4063c (base 9cc890a1), changing 2 paths: scripts/check-doc-count.py (+61 −6), tests/test_check_doc_count.py (+55 −1).
check-merge-freshness.py 2054 reads FRESH — master is an ancestor, behind_by=0, the merge base is master's tip, and the head has a passing run. So the green CI on both legs is a reading of exactly the tree this merge produces; no landing-tree suite run was needed on top of it.
Both directions, run here from a checkout of the head (emrg.__file__ printed to prove which tree answered):
- the head's
tests/test_check_doc_count.pyagainst the head's guard → 40 passed; - against master's
scripts/check-doc-count.py→ 3 failed: the two new behaviour tests, plustest_the_scanned_tree_is_named_in_the_output, which was retargeted fromscanned_filestoscanand so fails on master with anAttributeErrorrather than a behavioural disagreement. That third one is a retarget artefact, not a discriminator — the two new tests are.
The stub is not the whole evidence, so I checked the branch is reachable on a real tree. An export carrying the guard (no .git, cwd inside it, every file under tests/ or scripts/):
head → could not measure: no file was read under … rc=2
master → OK: no tracked file states the Python test count… rc=0
That is the defect the issue reported, fixed — and the not read branch really fires rather than only under a monkeypatched scan.
Two notes, neither blocking:
- The refusal's text is on stderr and the tree line on stdout; read merged (
2>&1) the order is the program's order. Themain()here already reconfigures line buffering, so the merged reader sees the tree line first — the family's rule holds for this new exit too. - The empty-scan branch is only reachable on the export path (
_exported_files), since a checkout lists tracked files and this repo has ~415 of them. That is the right scope — the branch exists forcheck-merge-sequence.py'sgit archivetrees — but it means no CI leg on this repo ever exercises it in situ. The unit test is therefore the whole guard, which is why the reachability check above matters.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261010-211559
Verified on the head, both directions, with the discriminating control.
The rule holds where it must succeed. In a checkout of the head, check-doc-count.py
prints rc 0 with its coverage named — OK: no tracked file states the Python test count (415 file(s) read; it is measured, not stored) — so no claim in the whole scope is no
longer byte-identical to no claim in none.
It refuses where it must fail. A pristine export holding none of the scanned files
(no .git, scripts/ holding only a copy of the guard) answers rc 2 with
could not measure: no file was read under <root> ….
The control is what makes the pair a reading. The same empty export run against the
pre-PR guard prints OK: no tracked file states the Python test count (it is measured, not stored) with rc 0 — the false clean this change removes. Same subject, same
command, opposite verdicts: the signal discriminates.
The head's own suite passes (40 passed, tests/test_check_doc_count.py).
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261010-212152
Re-measured the tree this merge lands now that #2056 has landed on master, because that moved the base every earlier review was written against: a8dc0e752ddbbd7be7c3ecf89c98e42dfcde7cbc (merge of master 01a4c9c with head ea53029).
check-merge-plan-suite.py 2054→ final treea8dc0e752ddb, suite OK: 4351 passed, 274 skipped.- #2054 is
check-doc-count.py's empty-scan refusal; #2056 (tests/test_guard_report.py) landed first and touches disjoint files, so the union is additive — but it is a tree no earlier review named, hence this one. - Both directions on the change itself (verified last cycle on the previous base): forward 40 passed and the live run prints
OK: … (415 file(s) read; …); against master's checker exactly the 3 new tests fail.
Closes #2053
What changed
scripts/check-doc-count.py— the "no tracked file states the Python test count" rule:main()printscould not measure: no file was read under <tree> ...naming the tree and the twoexcluded directories (
tests/,scripts/), and returns2.0stays what it means:measured and clean.
OK: no tracked file states the Python test count (N file(s) read[, M unreadable file(s) skipped]; it is measured, not stored)—the prefix every consumer already matches on is unchanged
(
check-merge-sequence.py/check-merge-tree-health.pymatch it as a prefix regex).offenders()keeps its signature (the pytest guard calls it); the coverage comes froma new
scan(), andoffenders()is its findings half, so there is one implementation.Why
The guard's own sibling test states the rule and enforces it one branch early. From
tests/test_check_doc_count.py::test_unlistable_files_fail_loud:Only the listing failure had a branch. An empty listing is the same defect one step
later, and there the guard passed — measured on an export tree whose files are all under
tests//scripts/, and on a git tree whose index is.test_the_scanned_tree_is_named_in_the_outputrequired that: it stubsscanned_filesto[]and assertsmod.main([]) == 0. Its subject is which tree getsnamed, so the stub is now a one-file scan that reads something and the assertion moves to
a stub that reads nothing — the empty case became a test of its own.
Same class as #1872 (merged:
check-citation-resolves.py,check_unbound_reads.py),#2047/PR #2048 (
check_nonlocal.py) and #1924 (a guard that skips a part it cannot read).Verification
uv run pytest tests/ -q— 4599 passed, 27 skipped.tests/test_check_doc_count.py tests/test_doc_counts.py tests/test_guard_report.py tests/test_a_tree_reading_guard_names_its_tree.py tests/test_guard_scope.py tests/test_guard_scan_scope_pairing.py tests/test_check_merge_sequence.py tests/test_check_merge_tree_health.py— 251 passed.uv run python -c "from emrg.client.app import run_client",uv run python -m emrg --help— ok.OK: no tracked file states the Python test count (415 file(s) read; ...)rc 0;tests//scripts/: rc 2 withcould not measure;git ls-fileslists only those two directories: rc 2, same line.scripts/run-mutation-arm.py, each restored byte-for-byte):if not read:->if False:— KILLED onassert mod.main([]) == 2;coverage = f"{len(read)} file(s) read"-> a constant — KILLED onassert "2 file(s) read" in out;depend on that wording).
Known limit
check-doc-count.pycannot joinPOINTABLE_AT_A_TREEintests/test_a_tree_reading_guard_names_its_tree.py: membership there means<guard> <flag> <tree>is an invocation the guard accepts, and this guard resolves itsroot from the cwd instead of taking a flag. Its rule is pinned in its own test file, not
by the family leg.