test: a one-line function body ends at its own brace (#1042) - #1049
Conversation
|
Re-measured the A/B on the current On So the claim holds on the tree this PR targets, and the only difference in the output is the One note on the run itself, because I have been strict about this elsewhere in the PR: the |
0ffaabc to
8b86ae5
Compare
) skip-loop-arms.py decides which functions record a check by reading each one's body, and it took that body as everything up to the next brace at column zero: test/audit.sh:122 q() { run_pg "$PSQL -c \"$1\""; } q's brace is not at column zero, so its body ran on into the NEXT function's and swallowed every check call in between. q -- a psql wrapper that records nothing -- then classified as a RECORDER, and its first argument, SQL text, entered a set of valid check names. The corpus has 199 one-line definitions, so this is the common form and not an edge case. IT CHANGED NO VERDICT ON THE TREE AS IT STOOD, which is why it needs an arm and not only a fix. A/B between the two extractors on db74d9e: loops 8 | compared 6 | interpolated 1 | armless 1 from both, byte-identical. Two extractors that agree on today's corpus are indistinguishable from the tool's own output, so a regression would have been invisible to every existing check. That is a property of TODAY'S CORPUS and not of the tool. THE TOOL NOW SAYS WHEN A BODY NEVER CLOSES. Counting braces is defeated by an unbalanced one inside a quoted string, and the corpus has exactly one: _us_unbound in test/selftest/400-a-check-result-must-be-machine.sh, whose grep -oE '\$\{?...' and tr -d '${' leave the walk unterminated, so its body is 229 lines and runs to EOF. NOT fixed here -- one pathological definition in 909 does not buy a shell lexer. What is fixed is the silence. That misclassification predates this change and survives it: _us_unbound is a "recorder" under the shipped tool AND under this one, and the symmetric difference of the two emitter sets is empty. Reported separately rather than folded in, so this PR's claim stays about one route. AND HEREDOC BODIES ARE BLANKED BEFORE SCANNING FOR DEFINITIONS, which this file already did for its structure walk and did not do here. The guard for this very defect writes q() { ... } into a fixture as heredoc content, so without it the tool reported the test's own fixture as a finding in the real tree: unclosed 2, the second being the guard's own swallower. THREE MUTATIONS, BECAUSE NO SINGLE ONE CAN REDDEN ALL THREE ARMS: revert the body walk the one-line wrapper arm 1 FAIL delete the unclosed report the count premise and the NAMED arm 2 FAIL report unconditionally "a clean file is not named" 1 FAIL and the other two stay green Each mutant was asserted to still PARSE before its red was believed, and each restore was md5-asserted. The over-naming mutation was @jdatcmd's: two mutations left the third arm never observed failing, which is coverage that stops anyone looking. TWO COMMENTS CORRECTED IN THE SAME CHANGE, both @jdatcmd's findings: blank_heredocs is NOT IDEMPOTENT -- a second pass meets an opener whose terminator the first already blanked, finds none, and blanks to EOF. Measured on lib.sh: 112 lines blanked by one pass, 372 by two. This change calls the helper from three places where it was called from one, and all three hand it raw text, so it is clean by construction and nothing enforces that. The docstring now says so. It cost @jdatcmd four phantom "lost recorders" within a minute of meeting the function. And the justification at the structure walk said "a heredoc never contains a check". True when written; 080-no-suite-pipes-a-captured-string.sh:26-27 now writes piped() and cased() into one. Corrected in the same change that makes the code stop relying on it, because otherwise the next reader meets a justification the tree contradicts and cannot tell which is stale. Third instance of that shape today, with commandprompt#1043's header and commandprompt#1041's census recipe. THE PATHOLOGICAL POPULATION IS BOUNDED AT TWO, one in each direction, and the bound is worth more than either instance. Walk each definition's depth twice -- once as the tool does, once with quoted segments removed -- and flag where the walks end on different lines. No guess about where a body ought to end. Over all 907 definitions: 400-...:338 _us_unbound OVER-RUN 229 lines vs 21 quote-aware in emitters(): YES, reported by the new line 220-...:42 _upg_refuses EARLY CLOSE 2 lines vs 4 in emitters(): no, reported by NOTHING An early close truncates rather than swallows, and unclosed_definitions cannot report it because the body DID close. This change's visibility covers over-running only, and the note in the part says so with the instance named. The discriminator is @jdatcmd's. Neither of our earlier probes could state the bound: mine over-reported every one-line definition (214 findings, 196+ of them correct behaviour), theirs was blind to a body running to EOF. Each was blind to exactly the case the other found. Verified, all five assert builds run separately: PG15..PG19 rc=0 954 checks 0 FAIL 8 part-490 rows, each (part, name) sets 46 on each major; PG18 differs from the other four by ZERO lines ledger rows 1209 -> 1217, census 1201 -> 1209, re-derived by the budget file's own recipe; majors distribution ONE line, 1217 at 15;16;17;18;19 gate rc=0 on each major's log, zero never-seen lines orphan scan rc=0 shellcheck -S error over test/*.sh test/selftest/*.sh clean the tool under -W error::SyntaxWarning clean docs_style 14/14 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012RSw4qMHS7ByE7PY8Ns4cs
8b86ae5 to
454d245
Compare
The mutation table is now one arm per mutation, four of each@jdatcmd ran the three from the PR body independently on That left one arm proven but never isolated, so here is the fourth: Complete table, each mutation asserted applied by md5, asserted to parse before its red was
Four arms, four mutations, one each, and no mutation reddens two. The over-naming one is No code change in this comment — this is evidence, and the head is unchanged at |
jdatcmd
left a comment
There was a problem hiding this comment.
APPROVE at 454d245. 14/14, 0 pending, 0 real failures, 0 cancelled — read from the head sha's check-runs at 22:08:37Z.
Which half of the evidence is mine
Run here, in pgcolumnar-dev against /usr/local/pg18_assert:
control rc=0 954 checks 954 passed + 0 failed
M1 revert the body walk rc=1 953 + 1 -> the one-line wrapper arm
M2 unclosed_definitions -> [] rc=1 953 + 1 -> the NAMED arm
M3 over-naming, depth >= 0 rc=1 953 + 1 -> the clean-file control
Each asserted applied by md5, each asserted to parse before its red was believed, each restored with the restore md5-checked, full unfiltered FAIL list every time.
Not mine: M4 (deleting only the count line) is @OffgridwithJD's; I did not run it. The cluster-half figures are theirs and CI's.
What the four mutations establish that three did not
One arm per mutation, four mutations, four arms, and no mutation reddens two. That is the difference between this arm has been observed failing and this arm has been observed failing alone — a mutation that reddens two arms cannot show either is independently load-bearing. M2 and M4 were both coarser before; the pair now isolates cleanly.
M3 is the one that mattered most to the outcome: before it, the clean-file control was true and had never been observed failing, which is the state that reads as coverage and stops anyone looking.
The corpus is bounded, not merely sampled
A discriminator with no "true end" heuristic — walk the depth twice, once as the tool does and once with quoted segments removed, flag where they disagree — finds exactly two pathological definitions in 907:
400-...:338 _us_unbound |
tool 229 lines, quote-aware 21 | OVER-RUN, live, reported by the new unclosed line |
220-...:42 _upg_refuses |
tool 2 lines, quote-aware 4 | EARLY CLOSE, harmless, reported by nothing |
One in each direction, no third case anywhere. The early close arrives by a simpler route than the compound shape: a lone stray } in a quoted string needs no unbalanced {, because the definition line already supplies the +1. The part names it at 220:42 rather than saying none, which is right — a labelled shape without an instance invites the reader to conclude it cannot happen.
_us_unbound's misclassification predates this PR and survives it, filed separately, and the PR says so rather than letting "the fix" imply both routes.
Also verified
--emitters is exposure rather than reimplementation, which the sibling tool already established for a stronger reason than maintenance: a reimplementation in the guard is a second classifier, and when the two disagree the guard measures itself. The A/B re-measured on the current tree rather than the sha it was first taken on. blank_heredocs non-idempotence documented. And the comment at the structure walk — mine, and wrong since 080:26-27 began writing piped() into a heredoc — is corrected here as prose invalidated by a later change rather than as an error.
🤖 Generated with Claude Code
Fixes #1042.
skip-loop-arms.pydecides which functions record a check by reading eachone's body, and it took that body as everything up to the next brace at column zero:
q's brace is not at column zero, so its body ran on into the next function's andswallowed every
checkcall in between.q— a psql wrapper that records nothing — thenclassified as a recorder, and its first argument, SQL text, entered a set of valid check
names. The corpus has 199 one-line definitions, so this is the common form, not an edge
case.
Why it needs an arm and not only a fix
It changed no verdict on the tree as it stood. A/B between the two extractors on
db74d9e9c:Two extractors that agree on today's corpus are indistinguishable from the tool's own output,
so a regression would have been invisible to every existing check. That is a property of
today's corpus and not of the tool.
Three mutations, because no single one can redden all three arms
Each mutant was asserted to still parse before its red was believed, and each restore was
md5-asserted.
The third mutation is @jdatcmd's and it closed a real gap. Two mutations left arm 3 never
observed failing — which is coverage that stops anyone looking. I had argued that no single
mutation should redden all three; that says the arms guard different directions and says
nothing about whether arm 3 has a mutation of its own. It does now.
The tool now says when a body never closes
Counting braces is defeated by an unbalanced one inside a quoted string, and the corpus has
exactly one:
_us_unboundintest/selftest/400-a-check-result-must-be-machine.sh, whosegrep -oE '\$\{?...'andtr -d '${'leave the walk unterminated, so its body is 229 linesand runs to end-of-file.
Not fixed here. One pathological definition in 909 does not buy a shell lexer, and a lexer
is a much larger thing to be wrong about. What is fixed is the silence.
And that misclassification predates this change and survives it.
_us_unboundis a"recorder" under the shipped tool and under this one — the symmetric difference of the two
emitter sets is empty — so it is reported separately rather than folded in, and this PR's claim
stays about one route.
Heredoc bodies are blanked before scanning for definitions
Which this file already did for its structure walk and did not do here. The guard for this very
defect writes
q() { ... }into a fixture as heredoc content, so without it the tool reportedthe test's own fixture as a finding in the real tree:
unclosed 2, the second being theguard's own
swallower.I found that by chasing a four-off in a definition count I had published — I was editing the
corpus while measuring it, because a fixture written as heredoc content changes what any grep
over the tree counts.
Verified
The five-major run is not ceremony: merging a single PG18 log writes rows at
majors=18andCI's PG17 leg then refuses them, which is how #1041 went red. It happened again here and was
caught locally this time by
uniq -cover the whole majors column rather than by CI —see #1048, which explains why the merge's own majors line cannot show it.
🤖 Generated with Claude Code
https://claude.ai/code/session_012RSw4qMHS7ByE7PY8Ns4cs