test: the document may not name a test the corpus does not have (#908) - #914
Conversation
jdatcmd
left a comment
There was a problem hiding this comment.
Approving at db310de0. The absence arm has teeth, the two stale entries were real, and I took you up on the invitation to attack the rule.
The two stale rows were surplus, not lost documentation
Checked on main before the change, which is the direction that matters — you could equally have deleted documentation for real work:
test_layer_rejects_an_absence_assertion_over_an_empty_plan in TESTS.md: 1 defined anywhere: 0
test_layer_allows_an_absence_assertion_over_a_real_plan in TESTS.md: 1 defined anywhere: 0
test_plan_marker_refuses_an_absence_claim_over_an_empty_plan defined: 1
Both names were in the document and in no file. The real equivalent is still named in the document after your change and still on disk. Two rows claimed coverage under names nobody ever wrote, and every other arm in that file passed over them — which is the failure mode this arm was written for, found on the shipped corpus rather than in a fixture.
The absence arm can fail
The shape I said most often cannot fail. Injected a stale name into the real TESTS.md rather than a fixture:
FAIL every test the document names exists in the corpus: got [[1: test_this_was_deleted_last_week]]
pytest 1 failed, 14 passed
control 434 passed + 0 failed + 0 unrunnable
Reddens by name in both harnesses, restores clean.
The rule, attacked
A backticked test name is a claim that it exists. I think it is right, and the false-positive budget being measured over the real document before the arm was written is what makes it defensible — 127 names, 2 absent, both genuine defects, zero noise. Any looser rule sweeps prose; any stricter one needs a registry nobody will maintain.
One hole, latent rather than live. The pattern requires the closing backtick immediately after the name, so these forms are silently exempt:
`test_groups_read[2000]` parametrized id -> no match
`test_layer.py::test_alpha` node id -> no match
Neither appears in the document today — I checked both this PR's and #919's — so nothing is being missed right now. But #902's twin has parametrized tests, and a natural way to cite one is by its id. A stale parametrized id would pass. Worth either widening the pattern or saying in the rule that ids are cited without backticks; I would not hold the PR for it.
On the consequence you flagged
That a dead name must be written without backticks is a real cost, and it biting your own paragraph describing the defect is the kind of thing that argues for a rule rather than against it — the rule caught its author on the first attempt. It is documented where a writer will hit it.
Approving. Merge this before #919, and not because of ordering hygiene: step 1 is the argument for step 2, and landing them as one change hides that the reverse sweep is what makes removing the totals safe.
…andprompt#908) Step 1 of the sequence @jdatcmd proposed and jd agreed: the reverse-direction arm, before the totals line moves. THE SWEEP WENT ONE WAY ONLY ---------------------------- `undocumented()` computes tests on disk the document fails to name. Nothing computed the reverse, so a test DELETED or RENAMED while its TESTS.md entry survived was caught by the totals line and by nothing else: on disk (1, 1) # test_one.py holds test_alpha document states (2, 1) # "test_one.py: test_alpha and test_beta" the NAMING arm : [] <- says nothing is wrong the TOTALS arm : DISAGREE <- the only arm that reddens That matters because the totals line is what commandprompt#908 is about removing: its correct value is a function of the merge, so it collides on every rebase touching the corpus -- ten times in one day. Removing it while this direction was uncovered would have retired a check silently, which is the move this gate exists to prevent. Hence step 1 before step 2 rather than after. IT FOUND TWO ON THE SHIPPED CORPUS, AND THEY ARE MINE ------------------------------------------------------ Section 3 named test_layer_rejects_an_absence_assertion_over_an_empty_plan and a control beside it. Neither had ever been written. The work is real and lives in `test_guards_pinned.py` as `test_plan_marker_refuses_an_absence_claim_over_an_empty_plan`, documented correctly in section 4 -- so two rows claimed coverage under names nobody had written, added by e62dd8d, and every other arm in that file passed over them. Both rows removed. Nothing else in the corpus changes. A BACKTICKED NAME IS A CLAIM THAT IT EXISTS -------------------------------------------- That is the rule the arm enforces, and the false-positive budget was measured over the real document BEFORE the arm was written rather than after: 127 backticked names, 2 genuinely absent, and both were defects rather than noise. It has a consequence for prose: a name that is gone is written WITHOUT backticks, because backticking it would assert it is still there. The first place that bit was my own paragraph describing this defect -- the arm reddened on it, which is the rule earning its keep on the commit that introduced it. BOTH HARNESSES, and 350 is the copy with teeth: nothing in the gate runs pytest. Proved able to fail. Fixture arms for each shape, and then the one that matters -- the real stale row put back into the real document: pytest : every test the document names exists in the corpus: got '[1: test_layer_rejects_an_absence_assertion_over_an_empty_plan]' shell : same message, via harness_selftest accounting: 433 passed + 1 failed restored byte-exact, both green again A fixture proves the pattern matches something; only the real document proves the arm aimed at it would fire (@jdatcmd's condition on commandprompt#907, and it applies here). harness_selftest 434 passed + 0 failed + 0 unrunnable, rc=0 pytest corpus 126 passed docs_style 9 checks PASSED shellcheck -S error clean Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
db310de to
a924426
Compare
jdatcmd
left a comment
There was a problem hiding this comment.
Re-approving at a924426e. The rebase moved one file and I checked that by number rather than re-reading the diff.
Per-file patch md5, approved head against live:
db310de0 a924426e
test/pytest/TESTS.md 2216f562d165 63e943213bcf <- the resolution
test/pytest/test_docs_cover_the_corpus.py 5e69dddfd549 5e69dddfd549
test/selftest/350-the-pytest-corpus-must-be.sh cc2c16430bbd cc2c16430bbd
Both files carrying the arms are byte-identical, so the absence arm, its removal proof against the real document, and the backticked-name rule transfer by identity. Only the totals line needed re-checking, and it is right:
stated (138, 11) on disk (138, 11)
undocumented none
documented_but_absent []
ToC vs headers MATCH (diffed)
The rule proving itself at the rebased head
test_layer_rejects_an_absence_assertion_over_an_empty_plan still appears once in the document — and the arm is green. That is only consistent if the mention is unbackticked, so I checked rather than assuming the arm was lying:
backticked occurrences : 0
total occurrences : 1 <- prose, describing the defect
It is the paragraph explaining what went wrong, written without backticks because a dead name is not a claim. That is the rule you said bit you first, working exactly as designed, and it survived a rebase — which is worth more than the fixture proof, because a rebase is where a convention like this normally gets quietly violated by whoever resolves the conflict.
Approving. The merge is armed and will re-gate immediately before merging, so it will not go in until the two running suite matrices land green.
Step 2 of the sequence. Depends on commandprompt#914. THE NUMBER WAS WRITTEN RATHER THAN DERIVED ------------------------------------------- TESTS.md carried `**N tests in M files.**` and selftest/350 compared it against the corpus. The check was never the problem. Its correct value is a function of the MERGE rather than of either branch, so it collided on essentially every rebase that touched the corpus -- ten times in one day, BOTH SIDES WRONG EVERY TIME. There was no side to pick. REMOVING IT COSTS NOTHING, AND THE ARGUMENT NEEDS THREE ARMS ------------------------------------------------------------- It was first written with two, and @jdatcmd broke it: test_every_file_and_test_is_named_in_the_document disk subset of document test_a_documented_test_that_does_not_exist_is_named document subset of disk Two subsets in opposite directions is equality of the two NAME SETS. That is NOT equality of DEFINITION COUNTS, and a total counts definitions: two files defining test_shared_shape definitions on disk 2 distinct names 1 both arms GREEN -> the counts differ while nothing reddens Reproduced here rather than accepted: both arms clean, 2 against 1. It cannot happen on the corpus today -- 139 definitions against 139 distinct names -- so the conclusion was true in fact but not by construction, which is the difference between an argument and a guard. test_no_test_name_is_defined_twice_in_the_corpus is the missing premise, and it closes something real beyond the argument: `undocumented()` asks whether a name appears in the document AT ALL, so a test defined TWICE and documented ONCE reads as fully covered while pytest runs both. With uniqueness, a count over the document is a count over the corpus, and the stated total was a derived value maintained by hand. THE PROSE COUNT LEAVES WITH IT ------------------------------- jd's condition. The header carried two numbers a line apart, one gated and one not; removing only the gated half would have been a net loss. Nothing read the prose count -- measured, a grep across test/ returns nothing. Both are gone. THE COUNTS MOVE RATHER THAN VANISH ----------------------------------- Both harnesses report them from the corpus every run, where they cannot go stale. AND AN ARM SO THE LINE CANNOT COME BACK ---------------------------------------- Its absence is now a decision rather than an accident. `stated_totals` stays for it, with a premise driving the reader over a fixture that DOES state totals, so "no totals line" cannot be confused with a reader that stopped working. ONE MORE CONSISTENCY FIX, BECAUSE IT COST SOMETHING IMMEDIATELY ---------------------------------------------------------------- `documented_but_absent` returned a preformatted "[n: a b c]" string, copying the bash twin's shape rather than its Python neighbour `undocumented`, which returns a sorted list. Two return types for one concept, and it bit within the hour: my own check of @jdatcmd's finding tested `x in ("[]", "")`, which is False for an empty LIST, and reported a real defect as unreproducible. It returns a sorted list now. STEP 1 CAUGHT STEP 2, ONE COMMIT AFTER IT LANDED ------------------------------------------------- Deleting test_the_stated_totals_are_the_totals_on_disk left its TESTS.md row behind and commandprompt#914's reverse sweep reddened by name. Proved able to fail. The totals line put back WITH THE CORRECT NUMBERS, because the objection is to the document carrying a count at all; both harnesses redden and restore byte-exact. The uniqueness arm proved on a two-file fixture. An earlier run of the totals proof printed `432 passed + 2 failed`, and neither I nor @jdatcmd could reproduce a second failure. The reproducible figure is 1 failed. No explanation is offered and the sentence stays. harness_selftest 437 passed + 0 failed + 0 unrunnable, rc=0 pytest corpus 147 passed docs_style 9 checks PASSED shellcheck -S error clean Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a
Step 1 of #908, which @jdatcmd proposed and jd has agreed to. The totals line does
not move in this PR — that is step 2, and this is what makes it safe.
The sweep went one way only
undocumented()computes tests on disk the document fails to name. Nothingcomputed the reverse, so a test DELETED or RENAMED while its
TESTS.mdentrysurvived was caught by the totals line and by nothing else:
That is exactly the line #908 is about removing — its correct value is a function
of the merge, so it has collided on every rebase touching the corpus, ten times
in one day. Removing it while this direction was uncovered would retire a check
silently, which is the move this gate exists to prevent.
It found two on the shipped corpus, and they are mine
Section 3 named
test_layer_rejects_an_absence_assertion_over_an_empty_plan* anda control beside it. Neither had ever been written. The work is real and lives
in
test_guards_pinned.pyastest_plan_marker_refuses_an_absence_claim_over_an_empty_plan, documentedcorrectly in section 4 — so two rows claimed coverage under names nobody had
written, added by
e62dd8d, and every other arm in that file passed over them.Both rows removed. No test changes.
* written without backticks in the source, for the reason below.
A backticked name is a claim that it exists
That is the rule, and the false-positive budget was measured before the arm was
written rather than after: 127 backticked names in the real document, 2
genuinely absent, both defects rather than noise.
It has a consequence for prose: a name that is gone is written without
backticks, because backticking it would assert it is still there. The first place
that bit was my own paragraph describing this defect — the arm reddened on it,
on the commit that introduced it.
Proved able to fail, on the real document and not only on fixtures
Fixture arms cover each shape (a missing function, a missing file, a control, and
an unbackticked name in prose that must not be reported). Then the one that
matters — the real stale row put back into the real
TESTS.md:A fixture proves the pattern matches something; only the real document proves the
arm aimed at it would fire. That distinction is @jdatcmd's, from #907, and it
applies here.
Both harnesses
test/selftest/350is the copy with teeth — nothing in the gate runs pytest.🤖 Generated with Claude Code
https://claude.ai/code/session_01Uf6UoeBRZYLQZa4KxNiw8a