fix(make): derive the selftest list instead of restating it (backend#1966) - #253
Conversation
…1966) `make check` ran four of the selftests while advertising "all four". CI runs seven. The count was wrong twice over: six when backend#1966 was filed, seven by the time it was verified, because kanban-deploy-state landed in between. Restating the list with three more entries would reproduce the same defect one release later, so nothing here hand-maintains it: SELFTEST_FILES comes from scripts/tests/ via $(wildcard) selftests-cover fails until every matched file is run by a recipe every message prints $(words $(SELFTEST_FILES)) The three that were missing are now wired: bricked-prs, kanban-columns and kanban-deploy-state. PyYAML dependence was MEASURED per selftest with the yaml module blocked, not inferred from the CI step: bricked-prs needs it (exit 2 without, via caller-drift.py's reader), kanban-deploy-state needs it, and kanban-columns does not -- so it does not depend on guard-pyyaml. The per-selftest targets stay explicit rather than generated from the glob, because the invocations are not uniform: selftest-blocked-marker runs a SECOND command, version-bump-gate is bash, and only some need PyYAML. A glob-driven recipe would have silently dropped that second command. What is derived is the coverage assertion, not the invocation. selftests-cover searches RECIPE LINES ONLY, and that is the correctness of it. The first revision grepped the whole file and passed on an unwired selftest because a comment in its own doc block named it -- a guard a comment can satisfy. Caught by mutation-testing the guard rather than by review. Mutation-proved, each with its anchor asserted: a new *-selftest.py with no target, an off-convention file in scripts/tests/, unwiring a recipe line, and a comment-only mention. All four redden; all four restore. MEASURED: make check 24.4s (was ~18s) against a 60s budget -- the three added selftests cost 0.18s together, the rest is noise. Note for the ticket: neither bricked-prs nor kanban-columns is a required status check on develop or main, so these two guards were advice at BOTH tiers. Arming them is a protection change and needs admin -- not in this PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e text Bugbot on #253: selftests-cover treated a substring on ANY tab-prefixed line as proof a selftest is executed, so a recipe comment, an echo of the path, or a target that exists but is not listed under `selftests` all satisfied it. The guard could go green while `make check` never ran the file - a success path that did not verify the contract the target claims. It now asks make for the commands it would actually run: make --dry-run --no-print-directory $(SELFTEST_TARGETS) SELFTEST_TARGETS names the runnable targets once; `selftests` depends on it and the guard expands it. Adding a selftest is one edit that both wires it into `make check` and brings it under the guard. The dry run is taken over SELFTEST_TARGETS, NOT over `selftests` - `make -n selftests` prints selftests-cover own recipe, which contains the expanded SELFTEST_FILES list, so the guard would have matched itself. Empty output is a hard failure rather than a pass: not being able to ask make is not evidence of coverage. Mutation-proved, both of the holes named: target defined but omitted from SELFTEST_TARGETS -> exit 1 recipe comment naming an unwired file -> exit 1 restored -> exit 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1374e95. Configure here.
shujaatTracebloc
left a comment
There was a problem hiding this comment.
Approve (backend#1966). Replaces the hand-maintained "all four selftests" (stale twice — said four while CI ran six, then seven) with SELFTEST_FILES derived from the directory and a selftests-cover guard that fails until every matched file is wired to a target. The guard searches RECIPE lines only (tab-prefixed) — the earlier whole-file grep passed on an unwired selftest because a comment in the block named it, exactly the inert-verification shape this fixes. Per-selftest targets stay explicit (invocations arent uniform: a second command, bash, per-target pyyaml), so what is derived is the coverage assertion, not the recipe. Mutation-proved (3 reddening cases). CI green, Bugbot clean.
Closes tracebloc/backend#1966
Summary
make checkran four selftests while the Makefile advertised "all four". CI runs seven. The count was stale twice over — six when #1966 was filed, seven by the time I verified it, becausekanban-deploy-state-selftestlanded in between. That is the argument for deriving rather than restating, made by the file itself while the ticket sat open.Nothing here hand-maintains the count:
SELFTEST_FILES$(wildcard scripts/tests/*-selftest.{py,sh})selftests-cover$(words $(SELFTEST_FILES))Three selftests are now wired up:
bricked-prs,kanban-columns,kanban-deploy-state.Why the targets are still explicit
The ticket suggested enumerating the selftests from disk and generating the invocation. That would have silently dropped coverage:
selftest-blocked-markerruns a second command (blocked-marker.py --title …, which the Makefile itself calls "the only coverage for a 'blocked'-in-a-path false positive"),version-bump-gateis bash, and only some need PyYAML. So what is derived is the coverage assertion, not the invocation.PyYAML dependence was measured per selftest with the
yamlmodule blocked, not copied from the CI step:guard-pyyamlbricked-prscaller-drift.py's reader)kanban-deploy-statekanban-columnsThe guard caught itself being inert
selftests-coversearches recipe lines only, and that is the whole correctness of it. The first revision grepped the entire Makefile and passed on an unwired selftest — because a comment in its own doc block named the file. A guard a comment can satisfy is inert verification of itself. Found by mutation-testing the guard, not by reading it.Verification
Mutation-proved, each with the anchor asserted (an inert mutation and good coverage look identical in a log):
make checkgreen, 24.4s against the documented 60s budget (was ~18s; the three added selftests cost 0.18s together).Not in this PR
Neither
bricked-prsnorkanban-columnsis a required status check ondevelopormain— so both guards were advice at both tiers, which is the part of #1966 that matters more than the wording. Arming them is a branch-protection change and needs admin; flagged on the ticket rather than silently left undone.Test plan
make check— green, 24.4smake selftests— 7/7 runmake selftests-cover— 4 mutations redden, all restore cleanmake help— reports "all 7", derived🤖 Generated with Claude Code
Note
Low Risk
Makefile and local CI-parity tooling only; no runtime services, auth, or data paths change.
Overview
make checkno longer understates how many gate selftests it runs. The Makefile used to say “all four” while CI had seven; counts now come fromSELFTEST_FILES(wildcardoverscripts/tests/*-selftest.{py,sh}) andhelp/ messages use$(words $(SELFTEST_FILES)).A new
selftests-coverstep runs before the selftests: every file underscripts/tests/must match the naming convention, and every discovered selftest must appear on a recipe line in amake --dry-runofSELFTEST_TARGETS(recipe-only grep so comments cannot fake coverage). Per-selftest targets stay hand-written because invocations differ (extrablocked-marker.pycommand, bash vs python, selectiveguard-pyyaml).Three workflows are now mirrored locally:
selftest-bricked-prs,selftest-kanban-columns, andselftest-kanban-deploy-state, with PyYAML guards only where measured imports require them.Reviewed by Cursor Bugbot for commit 1374e95. Bugbot is set up for automated code reviews on this repo. Configure here.