fix: repair the acceptance and benchmark shell scripts - #396
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #396 +/- ##
=======================================
Coverage 99.78% 99.78%
=======================================
Files 72 72
Lines 7485 7485
=======================================
Hits 7469 7469
Misses 16 16 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
akiomik
force-pushed
the
fix-acceptance-script-bugs
branch
from
September 4, 2026 08:56
07f4b82 to
50e273e
Compare
akiomik
force-pushed
the
fix-acceptance-script-bugs
branch
4 times, most recently
from
September 4, 2026 09:33
6968039 to
4e4322e
Compare
This was referenced Sep 4, 2026
akiomik
force-pushed
the
fix-acceptance-script-bugs
branch
from
September 4, 2026 09:40
4e4322e to
88ff9f7
Compare
akiomik
force-pushed
the
fix-acceptance-script-bugs
branch
18 times, most recently
from
September 4, 2026 15:09
741cdf1 to
8e05517
Compare
akiomik
force-pushed
the
fix-acceptance-script-bugs
branch
26 times, most recently
from
September 4, 2026 20:28
535ce2e to
76e029f
Compare
Three bugs that stop the development scripts from doing what they say, and what running them for the first time in nine months then required. None is user-visible, so no changelog entry. `scripts/acceptance/setup.sh` sets aside the fixtures that cannot be compared. Upstream pairs some with an `X_style.rb`, and `test/test_rules.rb` loads it to select the rules that fixture is checked against and to set their parameters: `long_lines_100_style.rb` is `rule 'MD013', :line_length => 100` beside a document written to that width, and `test.sh` runs each tool once under one config, so those documents would be read at whatever width it says. The loop that sets them aside has never run, for two reasons that hid each other: - `find data/markdownlint/test/rule_tests` is relative, and the script has already `cd`ed into that clone, so it searched `data/markdownlint/data/markdownlint/...` and found nothing. - Fixing the path exposes `[ $style_file -eq "..." ]`, an integer comparison, which raises `Illegal number` on every iteration. `test.sh` and `comparison.sh` assigned `PROJECT_ROOT=($SCRIPT_DIR/../..)`, a bash array in a `#!/bin/sh` script. `dash -n` rejects both at line 4; on macOS they worked because `/bin/sh` is bash, where expanding an array unsubscripted yields its first element. The rest is what a loop that runs needs to be right about. It keys on the document rather than on the `.bak`, so a `git restore` inside the clone sets the documents aside again instead of skipping them; a repeat with the documents already gone does nothing; a style file this rule derives no document from is named on stderr but is not fatal, there being nothing in the corpus to have left there. Having set nothing aside is fatal, counted rather than inferred from `find` having matched, since that is how this bug looked from outside for nine months and a corpus holding only the fallback style file would look the same. `mv` failing fails the run, rather than reaching the exit status only from whichever iteration happened to be last. `SCRIPT_DIR=$(cd $(dirname $0); pwd)` is quoted in all three, its `;` is now `&&`, and it clears `CDPATH`. `$(dirname "$0")` is a relative operand when the script is invoked by a relative path, so `cd` resolves it through `CDPATH` and echoes where it landed into the substitution: with `CDPATH=.` that yields the path twice, newline-separated, and pointed at a tree holding a same-named directory, the wrong tree. `setup.sh` reaches its clone by absolute path for the same reason. Both its `cd`s are checked now, the `find` above having become relative to that clone; that closes the case where the clone failed outright, not the one where `markdownlint/` exists without being a repository, which is #399, and the same pair in `benchmarks/setup.sh` is left to that issue since its fix replaces those lines rather than guarding them. `test.sh` could not run on a fresh clone at all: `tmp/` is gitignored and nothing created it, so both redirects failed and the `sed`s ran against files that were not there. It now creates it and checks that it did, `cd`s to the repository `cargo run` needs, and builds before running, a failed build being the same empty `mado.txt` that compares as mado having found nothing. It also asks, before the build rather than after it, whether `mdl` and `cargo` are installed, whether the corpus is there, and whether `setup.sh` actually set anything aside, all separable from what either tool reported. `mado check` on a missing path exits 0 saying "All checks passed!" and `mdl` prints nothing, so running this before `setup.sh` compared two files with no findings in them; and a `git restore` inside the clone brings the set-aside documents back, which compares the parameter-sensitive fixtures under one shared config and is a wrong answer rather than no answer. That last one looks for the absence of a document beside any style file, not for the `.bak` files: those are untracked, so a `git restore` leaves every one of them in place. It finds them the way `setup.sh` does, so the two cannot disagree about the corpus if it ever stops being flat, and it refuses an empty one, nothing to lint reading exactly like two tools agreeing. The two config files are checked as well, this script naming them rather than leaving them to discovery: `mado --config` on a file that is not there exits 1 with nothing on stdout, which is what finding violations looks like, and `mdl` exits 3 the same way. The set-aside check reads `find`'s output a line at a time rather than iterating it as a word list. `$DOC_PATH` is absolute, so every result carries the checkout's own path, and a space anywhere in it split each one into pieces that exist nowhere, leaving the guard passing on a corpus it never looked at, silently, where the same bug in `setup.sh` at least fails loudly on the `mv`. The `sed`s write through a temporary file instead of `sed -i ''`. The empty suffix is BSD's spelling and GNU sed reads it as another input file, so the same line exits non-zero having edited the file correctly, which meant the three of them could not be checked, and an unchecked filter that genuinely failed left the trailers in place under a successful exit. Writing to a new name behaves the same everywhere and can be, and needs no closing `exit 0` to keep the in-place form's status off the script's own. The set-aside guard collects `find`'s output rather than piping it into a loop. `$DOC_PATH` is absolute, so a space in the checkout path split the list into pieces that exist nowhere; and an `exit` in the last stage of a pipeline ends the script outright on shells that run that stage in the current one, taking its message with it. Checked under dash, bash and zsh. `test.sh` names its two config paths once, the guard and the runs having held them as separate literals, which is how a check comes to assert a file the run does not use. `setup.sh`'s `find -exec` body carries no prose: one apostrophe added to a comment inside that single-quoted string would close it and hand `find` a different operand list, so the reasoning sits above the substitution instead. `setup.sh` asks for `git` up front, as the other two ask for theirs; without it the clone failed and the `cd` after it pointed at a directory rather than at the missing tool. `setup.sh` reports how many fixtures are set aside. The dots its loop prints are the count and are captured rather than shown, so a re-run's only output was `git clone` saying the directory already exists, which made a run that worked look like one that had not. Phrased as a statement about the corpus rather than about the run, the count including what a previous run set aside. `test.sh` clears its working names from a trap rather than at the end, every exit between the first tool and the final rename having left some of them behind until the next run. Two traps, because an `EXIT` trap runs when a signal is caught and not when the shell dies from an uncaught one: measured, `dash` and `zsh` both leave the debris on a `TERM`, which is a CI cancelling a job. `sparse-checkout set` and `git checkout` are checked, so a git too old for the former reports that rather than the empty corpus it leaves behind. And they no longer run against whatever repository the working directory happens to belong to: `cd` proves the directory is there, not that it is the clone, and git resolves `.git` upward, so a directory that exists without being a repository sent those two into mado's own and emptied its working tree. Asking git for the prefix catches it, that being empty only when the working directory is a repository's own top level, and rejects a non-repository, a bare `.git`, a directory in no repository at all, and an exported `GIT_DIR`, where the three earlier proposals on #399 each fell to one of those. The prefix rather than a comparison against `pwd -P`, which would call a good clone bad wherever git and the shell spell the same directory differently, as Git for Windows does. `benchmarks/setup.sh` still wants the same line; that file is untouched here and stays with the issue. `setup.sh`'s loop is handed to `find -exec` for the same reason, having had the opposite half of the problem: zsh does not split an unquoted expansion at all, so `zsh scripts/acceptance/setup.sh` made the whole list one iteration and set nothing aside. Both scripts now read the corpus the same way, and both behave identically under dash, bash and zsh, as does `test.sh` once `status=$?` became `rc=$?`: `status` is read-only in zsh, and assigning to it killed the script the moment mdl returned, leaving an unfiltered `mdl.txt` and no `mado.txt` beside it. The two linters' statuses are looked at after all, just not gated on: both return 1 for violations, the normal case here, but anything above that is the tool failing, and the redirect has already truncated the file it would have written. mado buffers and writes at the end, so a panic leaves an empty `mado.txt` reading as having found nothing, which the script's own `exit 0` would then have reported as success. Their statuses are not the whole story either: a malformed `mado.toml` makes mado exit 1 with nothing on stdout, which is what violations look like, and mdl says nothing at all when its config names a rule that version does not implement. So each output is required to be non-empty, mado printing "All checks passed!" when it genuinely finds none. mado's own stdin is closed as well: it lints stdin instead of the paths given when stdin is neither a terminal nor empty, so piping so much as a newline into this script made it report on that and call the corpus clean, with a non-empty output and a zero status that every guard here accepted. An empty stdin falls through to the paths, which is why `comparison.sh` needs no such redirect. Everything after the guards writes to working names and moves both into place only once both are ready, the trap asking whether the pair is there rather than whether the run reached a flag, so no signal has a window to catch: after the first rename only one exists and both go, after the second both exist and both stay. The pair is removed up front too, so a run stopping at any guard leaves nothing behind that looks finished. That costs a good pair when a precondition fails, taken deliberately: those two are regenerable by re-running, and being misled by them is not. `comparison.sh` names the remedy when the markdownlint commands are missing, and tests them with `-x` rather than `command -v`: given a path rather than a name, dash reports an existing file as found whether or not it can be run. Its corpus has to exist and then hold documents, in that order: `find` otherwise fails on the missing directory and reports it in its own words before the message meant for it. None of `benchmarks/setup.sh`'s git commands being checked. The README names `hyperfine` and `mdl` as prerequisites, which the new checks require and it did not mention, and gains the acceptance flow, which was not documented at all and which now fails hard without `mdl`. and the README gains the `npm ci` its benchmark steps left out: nothing installs `node_modules`, so the documented two-step flow could not work on a fresh clone. `comparison.sh` gets the `cd` too, and with it the checks its new comment described: `cargo build --release` found its project through the working directory there as well, and `hyperfine --ignore-failure` would then time a stale binary, or a `command not found` at microsecond speed, and print either as a result. Nothing installs `node_modules`, so the two markdownlint commands were absent on a fresh clone as the rule rather than the exception, and `node` is asked for alongside them, both being `#!/usr/bin/env node` wrappers that `-x` finds startable while they cannot run. Each of the four is also started once with `--version` before any of them is timed, since a `node_modules` restored from a stale cache leaves the wrappers runnable and still dying on `MODULE_NOT_FOUND` in milliseconds, which `--ignore-failure` would print as the fastest tool there. The `markdownlint-cli2` probe reads its config on the way, that one validating it where `markdownlint` accepts a broken config without complaint when nothing matches. It asks for its corpus and its three config files as well, the same way `test.sh` does, and for mado's own binary where cargo will have put it: a successful `cargo build` says nothing about that. `CARGO_TARGET_DIR` and `CARGO_BUILD_TARGET` are honoured for both the check and the command hyperfine times, and their cargo-config equivalents, which a script cannot see, are named in the failure. The paths hyperfine's command strings need reach them through the environment rather than pasted in: it hands each string to a shell, so a space has to be quoted somehow, and quoting them in place only moves the problem to apostrophes. Naming the values leaves the quoting to the shell that expands them, and each run is given a name, hyperfine otherwise labelling its results with the variable references. Without any of this a spaced checkout path cleared every guard and then failed to start all four commands, which `--ignore-failure` prints as microsecond timings. `SCRIPT_DIR` is checked too. A failed `cd` there yields the empty string, which makes every path derived from it root-relative (`DOC_PATH` becomes `/data/markdownlint/test/rule_tests`), and on a machine that happens to have one, `setup.sh` would run its git commands in it. Which config mado reads is named rather than left to the working directory, and named as `../benchmarks/mado.toml`. `.mdlrc` here is a symlink to `../benchmarks/.mdlrc`, so that is the matched pair, and `comparison.sh` already uses it. The repository's own config is mado's self-lint settings, and under it `allow-different-nesting = true` left mado silent on the three MD024 findings mdl reports; under the benchmark config both report three. Restoring the loop also makes the comparison visible for the first time, and it does not agree: 132 differing lines across the 52 remaining fixtures, much of it MD007, where `benchmarks/mado.toml` says `indent = 4` while `.mdlrc` sets no parameter at all and mdl falls back to 3. That is #401. How these scripts behave when something else is missing is #400, the case where that turns destructive is #399, and the unquoted `find` expansion this loop iterates is #398, along with the `SC2086` quoting elsewhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWNoFSfDhBzXEX9UL68oqf
akiomik
force-pushed
the
fix-acceptance-script-bugs
branch
from
September 4, 2026 20:41
76e029f to
c5c3fcf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three bugs that stop the development scripts from doing what they say. None is
user-visible, so there is no changelog entry.
The fixture loop has never run
scripts/acceptance/setup.shsets aside the fixtures that cannot be compared.Upstream pairs some with an
X_style.rb, andtest/test_rules.rb(
do_lint,do_fix_lint) loads it to select the rules that fixture is checkedagainst and to set their parameters:
test.shcannot reproduce that. It runs each tool once with a config of itsown — mdl under
scripts/acceptance/.mdlrc, mado under whatevermado.tomlthe working directory yields — and neither of those is the fixture's style, so
the document is read at whatever width they say. The fixtures with a style
file are exactly the parameter-sensitive ones (MD003, MD004, MD013, MD035,
MD046 and so on), which is why they are set aside rather than compared.
(Reviewing this turned up that those two configs do not match each other either
— mado is given
indent = 4for MD007 where mdl uses its default of 3, amongothers — which makes
test.shworking-directory dependent and puts part of#401's divergence down to the harness. Recorded there; not something this pull
request changes.)
The loop that does this did nothing, for two reasons that hid each other:
find data/markdownlint/test/rule_testsis relative, and the script hasalready
cded into that clone, so it searched.../markdownlint/data/markdownlint/test/rule_testsand found nothing.[ $style_file -eq "..." ], an integer comparison,which raises
Illegal numberon every iteration.default_test_style.rbis skipped because it is the shared fallbacktest_rules.rbuses for every fixture without a style of its own — which iswhy it is the only style file with no
.mdbeside it.A bash array in
#!/bin/shtest.shandcomparison.shboth hadPROJECT_ROOT=($SCRIPT_DIR/../..).dash -nrejects them at line 4 withSyntax error: "(" unexpected. They ranon macOS only because
/bin/shthere is bash, where expanding an array withouta subscript yields its first element.
Making the loop run
Most of the diff is what running it for the first time requires:
.bakpresentgit restore,.bakpresentmvfailsKeyed on the document rather than on the
.bak: this loop leaves the clonepermanently dirty, so
git restoreinside it is a natural thing to do, and itbrings the documents back while leaving the untracked
.bakfiles alone.The last row is not incidental hardening — a silent, successful exit having
done nothing is how this bug looked from outside for nine months. It counts
what was set aside rather than checking that
findmatched something, becausematching something is not the claim: a corpus holding only the fallback style
file matches and still sets nothing aside.
It claims nothing about why the corpus is wrong, so both
cds are checked aswell. Switching
findto a clone-relative path makes arriving thereload-bearing in a way it was not before, and until now a failed clone let the
two git commands after it loose on mado's own repository. That closes the
clone-failed case; a
markdownlint/that exists without being a repositorystill passes
cd, and that is #399.The line every path derives from
SCRIPT_DIR=$(cd $(dirname $0); pwd)is unquoted in each of these scripts, andits
cdis unguarded twice over. A checkout path containing a spaceword-splits
$(dirname $0); and$(dirname "$0")is a relative operand whenthe script is invoked by a relative path, so
cdresolves it throughCDPATHand echoes where it landed — into the substitution:
Now
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) || exit 1.setup.shreachesits clone by absolute path for the same reason:
cd markdownlintwithCDPATHset lands in an unrelated directory that happens to hold one, successfully, so
the guard above it never fires.
test.shcould not run on a fresh clonetmp/at the repository root is gitignored and nothing creates it, so bothredirects failed and the
seds ran against files that were not there. It worksin an existing checkout only because the directory happens to be there.
It now checks what it needs before producing anything, each of these being
separable from what the tools reported:
mkdir -p "$TEMP_PATH" || exit 1cd "$PROJECT_ROOT"cargo runexits 101 into an already-created emptymado.txtcargo build || exit 1command -v mdl,command -v cargo(gitinsetup.sh;hyperfine,node,cargoincomparison.sh)mdl.txtreading as mdl finding nothing;cargofails loudly, but a contributor without a toolchain should hear it here[ -d "$DOC_PATH" ]mado checkon a missing path exits 0 saying "All checks passed!"< /dev/nullon the mado runcomparison.shneeds no redirectgit restorein the clone, the set-aside documents are back and get compared under one shared configSCRIPT_DIRnon-emptycdthere makes every derived path root-relative —DOC_PATHbecomes/data/markdownlint/test/rule_testsfindotherwise failing on a missing directory in its own words before the message meant for itmado --configon a missing file exits 1 with empty stdout, which is what violations look like;mdlexits 3 the same way. Named once, so the guard and the runs cannot drift apart--versionon three of them and a no-match glob formarkdownlint-cli2, which has no version flag; that one reads its config on the way, wheremarkdownlintaccepts a broken one without complaint when nothing matches (#400): present and executable is not the same as working, and anode_modulesfrom a stale cache dies onMODULE_NOT_FOUNDin milliseconds, which--ignore-failureprints as the fastest toolThe set-aside check looks for the absence of a document, not for the
.bakfiles: those are untracked, so a
git restoreleaves all of them in placewhile bringing the documents back. Same reasoning as
setup.sh's own loop, andit searches recursively as that one does, so the two cannot disagree about the
corpus if upstream ever stops keeping it flat. It collects
find's output rather thanpiping it into a loop.
$DOC_PATHis absolute, so a space anywhere in thecheckout path split every result into pieces that exist nowhere and the guard
passed on a corpus it never looked at; and an
exitin the last stage of apipeline ends the script outright on shells that run that stage in the current
one, taking the diagnostic with it.
setup.sh's loop is handed tofind -execfor the same reason, having had the opposite half of the problem: zsh does not
split an unquoted expansion at all, so
zsh scripts/acceptance/setup.shmadethe whole list one iteration and set nothing aside. Both scripts read the
corpus the same way now, and all three scripts behave identically under dash,
bash and zsh —
test.shonly afterstatus=$?becamerc=$?,statusbeingread-only in zsh, where assigning to it killed the script the moment mdl
returned and left an unfiltered
mdl.txtwith nomado.txtbeside it:The three cheap ones run before the build, so a contributor without the gem or
without a corpus is told so immediately rather than after it.
test.shis quoted throughout, not only on line 3 — itssedoperands splittoo, and since
sedis unchecked the script exited 0 having leftmado.txtunfiltered, a wrong answer rather than a missing one. (Their
> /dev/nullwentwith them;
sed -iprints nothing.)comparison.shneeded the sametreatment inside its command strings. An earlier revision claimed quoting could
not help there, since hyperfine hands each string to a shell that splits it
again — wrong, and the file disproved it: the
markdownlint-cli2line alreadyprotected its glob with quotes inside the string, which only works because that
shell honours them.
Quoting them in place is not the fix either, though — it moves the problem from
spaces to apostrophes, and
/Users/o'brien/src/madocloses the quote early.The paths go through the environment instead, so the shell that expands them
does the quoting — and each run gets a
-nname, hyperfine otherwise labellingits results table with the variable references rather than with what ran:
It does get the same
cdastest.sh, though, and the checks that go with it:cargo build --releasefound its project through the working directory there too, and--ignore-failuretreats a command that could not start as a result ratherthan an error, so it would have timed a stale binary or a
command not foundat microsecond speed and printed either as this tree's number. It now asks for
hyperfine,mdl,nodeand both markdownlint commands up front — theREADME's prerequisites name all three now — naming
npm ciasthe remedy for the latter two, and testing those two with
-xrather thancommand -v— given a path rather than a name, dash reports an existing fileas found whether or not it can be run. Its corpus has to hold documents rather
than merely exist, none of
benchmarks/setup.sh's git commands being checked,and mado's own binary has to be where cargo put it — a successful
cargo buildsays nothing about that.CARGO_TARGET_DIRandCARGO_BUILD_TARGETare honoured for both the check and the command hyperfinetimes; their cargo-config equivalents, which a script cannot see, are named in
the failure message instead of the binary being reported as "not built".
nodeis on the list because both markdownlintcommands are
#!/usr/bin/env nodewrappers, which-xfinds startable whilethey cannot run.
Nothing installs
node_modules, so those two were absent on a fresh clone as the rule ratherthan the exception — along with its corpus and its three config files, as
test.shdoes.Neither output file is left half-written
The pair is removed before the guards run, so stopping at one of them leaves
nothing behind: a previous run's complete pair would otherwise sit there
looking finished and be read as this one's. That costs a good pair whenever a
precondition fails, which is the trade taken on purpose: the two files are
regenerable by re-running, and being misled by them is not. Past the guards, both outputs are
built under working names and moved into place only once both are ready, the
trap asking whether the pair is there rather than whether the run reached a
flag — so no signal has a window to catch: after the first rename only one
exists and both go, after the second both exist and both stay — which covers
the exits after the tools run — a mado failure used to leave a partial
mado.txtbeside an unfilteredmdl.txt, the filtering happening last.Verified both ways: tripping the corpus guard, and failing the mado
invocation, each leave the directory empty where the earlier arrangement left
last run's files in place. This matters more now that the README documents
diff tmp/mdl.txt tmp/mado.txtas a separate step.The clone is checked for being one
cd "$CLONE_DIR"proves the directory is there, not that it is the clone, andgit resolves
.gitupward — so a directory that exists without being arepository sent
sparse-checkout setandgit checkoutinto mado's ownrepository and emptied its working tree.
Asking git for the prefix — empty only when the working directory is a
repository's own top level — is the first proposal that survives every case the
earlier ones failed:
cdguard.gittestGIT_CEILING_DIRECTORIES--show-prefix.gitGIT_DIRexported to the outer repoThe prefix rather than comparing
--show-toplevelagainstpwd -P: those aredifferent spellings of the same directory under Git for Windows, where the
comparison would reject a perfectly good clone and re-cloning would never help.
Deferred through four reviews on the grounds that #399 should settle it once
for both scripts; landed here because this pull request is what made arriving
in the clone load-bearing, and the check is now settled rather than guessed.
benchmarks/setup.shstill needs it and stays with the issue.What is deliberately left unchecked
What the two linters report: both exit non-zero on violations, the normal
case here, so gating on that would abort the comparison this exists to produce.
Their statuses are still looked at — anything above 1 is the tool failing
rather than finding something — and each output has to be non-empty, since mado
prints "All checks passed!" when it genuinely finds none.
The
seds are no longer among them. They write through a temporary file ratherthan
sed -i '', whose empty suffix GNU sed reads as another input file: thesame line exits non-zero having edited the file correctly, so the three of them
could not be checked, and a filter that genuinely failed left the trailers in
place under a successful exit. The closing
exit 0went with them, havingexisted only to paper over that status on Linux. That closes the
sed -i ''half of #398.
setup.shsays how many fixtures are set aside. The dots its loop prints arethe count, captured rather than shown, so a successful re-run's only output was
git clonereporting the directory already exists — success and failure lookedalike. It is phrased about the corpus rather than about the run, since the
count includes what an earlier run set aside and this one had nothing to do
for. (Sending the dots to stderr instead, as suggested, empties the capture and
makes the guard below fire every time; the count line is the version that
works.)
test.shclears its working names from a trap rather than at the end. Everyexit between the first tool and the final rename left some of them in
tmp/until the next run cleaned up — the published names were always right, the
debris was not. It takes two traps, because an
EXITtrap runs when a signalis caught but not when the shell dies from an uncaught one:
INT,EXITtrap aloneTERM,EXITtrap alonetrap 'exit 1' INT TERM HUPTERMis a CI cancelling a job, so the second trap is the one that matters.Raised in review and not changed
Moving the
rm -fof the published pair below the preconditions, so that amissing
mdldoes not cost a good pair. Raised twice, and it is a real tradeeither way: below them, a precondition failure leaves the previous run's files
for the README's
diffstep to read as current. Kept above, because those twoare regenerable by re-running and being misled by them is not — written down in
the script so it stops being re-decided.
The trailing blank line in
tmp/mado.txt, said to makediff tmp/mdl.txt tmp/mado.txtpermanently dirty. Both files end with exactly one blank line, sothey cancel: 132 differing lines either way, and 132 again with every blank
stripped from both.
Scope
This reached 122 lines across four review rounds before being cut back. What
came out needs decisions rather than patches, and is tracked:
benchmarks/setup.sh, which this does nottouch: its two
cds are unquoted andgit reset --hard <sha>sits belowthem, so that file wants the whole treatment rather than one check. The
acceptance side is fixed here — see below.
anything.
differing lines (
diff | grep -c '^[<>]'; the fulldiffoutput is 182,the rest being its own separators) over the 52 remaining fixtures. 43 of those are MD007, where
benchmarks/mado.tomlsaysindent = 4and mdl defaults to 3 — the oneparameter in that file that does not match mdl, checked against
lib/mdl/rules.rb. Not changed here, because that file is also whatcomparison.shhands mado, so the value is not the acceptance harness'salone to pick.
SC2086quoting. The three instances that issueopened with are fixed here: the word-splitting loops go through
find -exec,sed -i ''is gone, and hyperfine's command strings carry their own quotes.The README gains named prerequisites for the benchmark —
hyperfine,mdl,node,npm,cargo,git, all of which its steps need and none of which itlisted — plus the
npm cithose steps left out, and an Acceptance Testingsection — that flow was documented nowhere and
now fails hard without
mdl:node_modulesisgitignored and nothing installed it, so the documented
setup.shthencomparison.shflow could not work on a fresh clone. Developer docs, so nochangelog entry, same as the rest of this.
All four are the same point as #397:
shellcheckfinds none of them, becausethey are facts about
git,hyperfineand cargo rather than about shellsyntax.
Two things reviewers raised that are deliberately not here:
cdpair inbenchmarks/setup.sh. This pull requestguards the ones in
acceptance/setup.shbecause changingfindto aclone-relative path made arriving there newly load-bearing; nothing in the
benchmark script changed, and setup.sh can empty mado's working tree #399's actual fix replaces those
cds ratherthan guarding them. Noted on that issue so the omission is not read as the
sibling being safe — it is the worse of the two. An earlier revision quoted
its
SCRIPT_DIRon its own; that made things worse, becauseDATA_ROOTthengenuinely carried the space that the still-unquoted
cd $DATA_ROOTbelowsplit on. The file wants quoting, both
cds guarded, and setup.sh can empty mado's working tree #399's treatmenttogether or not at all, so it is back out of this diff.
mdlandcargo runreport is still unchecked, deliberately: bothexit non-zero when they find violations, which is the normal case here, so
gating on their status would abort the comparison this script exists to
produce. Whether they ran at all is checked; what they found is not.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JWNoFSfDhBzXEX9UL68oqf