test: verify-build's failure modes are only ever proven by hand — make the battery a committed target #227
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
script/verify-buildis the build-integrity guard, and nothing inmake checktests it. Every one of its ~17 failure modes is re-proven by hand, once per change, by whoever happens to be touching it.That has already cost real defects. Three separate reviews of this script each found a fresh vacuous pass — the
grepexit-2 conflation, the discardedfindstatus, and the line-delimited walk — and each was caught by a human-equivalent constructing a tree by hand, not by a test. The most recent fix (#223) also turned up that NUL delimiting alone was insufficient, which only surfaced because someone built the case and ran it.Two workers have independently raised this. A working battery already exists in throwaway form at
/srv/work/issue-223-battery/battery.sh; it just is not committed and does not run.The obstacle, which is the reason this needs thought rather than just doing it
The Dockerfile declares no
USER, so CI runs as root, and under rootchmod 000does not stopfind. A permission-based case would be a guaranteed false green in CI while passing locally — worse than no test. That has to be solved, not worked around:USER, orid -uis 0 and say so loudly in its output, so a green run never silently means "the permission cases did not run".The second is cheaper; the first is honest. Either is acceptable if the "did not run" case is impossible to mistake for a pass.
Implementation requirements
script/test-verify-build, invoked fromscript/checksomake checkcovers it.dist/— the current hand battery mutates the real build output.dist/as a symlink, unwalkable subtree, dangling and directory-pointing symlinks, symlink to a listed bundle under an unlisted path, missing/empty/unreadable manifest, missing/empty/unreadable listed bundle, unlisted extension, no marker, both markers, wrong marker, plus the untouched-dist/control.sh, matching the house style of the otherscript/entrypoints.Definition of done
make checkfails if anyverify-buildfailure mode stops failing.dist/and leaves no residue.verify-buildmakes the harness fail — demonstrated, with captured output.TODO.mdupdated in the same commit.make checkpasses.Plan, mainly to record the root/permission resolution before writing it.
Fixture:
script/test-verify-buildbuilds a synthetic tree inmktemp -dcontainingscript/verify-buildas a symlink to the real script plus a fakedist/.verify-buildderives itsROOTfromdirname $0/.., so it operates on the fixture and never touches the repo'sdist/. The symlink (rather than a copy) means the harness exercises the live script, which is what makes a deliberate break in it fail the harness.TMPDIRis pointed inside the work dir soverify-build's ownmktempleaves nothing behind either.Root/permission: neither of the two options as stated, but a strict superset of the second, because a case that is permanently skipped in CI gates nothing — and CI is the only place this runs unattended. The harness picks a runner for the three permission-dependent cases and then proves the runner before trusting it, with two probes: a mode-644 file must be readable through it (else the runner is broken) and a mode-000 file must NOT be (else permissions are ineffective). Non-root: the runner is "run directly" and both probes pass trivially. Root: it tries
setpriv --reuid=65534thenrunuser -u nobody, both present in the pinnednode:22-slimCI image, so the cases really run in CI. Only if no candidate passes both probes are those cases skipped, and then the run prints a banner naming each skipped case and a final line that says SKIPPED rather than passed. A false green is impossible by construction: either the mechanism is demonstrated to work on a mode-000 file in this very run, or the output says the cases did not run.Not adding a non-root
USERto the Dockerfile:RUN script/bootstrapandCOPY . .leave/appandnode_modulesroot-owned, andRUN make buildwritesdist/, so a non-rootUSERneeds achown -R /applayer that duplicates the wholenode_modulestree in the image. That is a CI-container change with a real size cost, outside this issue.Cases: exactly the battery's list, each asserting exit status AND a message substring, so a guard failing for the wrong reason is a harness failure. Wired into
script/checkafterscript/test. OneTODO.mdbullet in the same commit. Will report the deliberate-break output and the addedmake checktime on the PR.Built as planned, no deviations: #249, which carries the full record including the deliberate-break output.
Verified:
make checkgreen on the branch (18 battery cases, 0.7s of a 15.2s check);script/cibuildexit 0 withRUN make checkexecuted rather thanCACHED, and its log showing the permission cases enabled viasetprivand all 18 passing as uid 0 in the container; the same image withsetprivandrunusermoved aside produces the skip banner and a final line readingSKIPPED AND NOT PROVEN; breakinghas_marker's exit-2 branch fails 3 cases and exits 1, one of them caught only by the message assertion because that break keeps the exit status at 1 while blaming the bundle for a permissions fault; nodist/created and nothing left in/tmp.