Skip to content

fix(make): derive the selftest list instead of restating it (backend#1966) - #253

Merged
LukasWodka merged 2 commits into
developfrom
fix/1966-selftests-complete
Aug 14, 2026
Merged

fix(make): derive the selftest list instead of restating it (backend#1966)#253
LukasWodka merged 2 commits into
developfrom
fix/1966-selftests-complete

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Closes tracebloc/backend#1966

Summary

make check ran 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, because kanban-deploy-state-selftest landed 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:

what derived from
SELFTEST_FILES $(wildcard scripts/tests/*-selftest.{py,sh})
selftests-cover fails until every matched file is run by a recipe
every "all N" message $(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-marker runs 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-gate is 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 yaml module blocked, not copied from the CI step:

selftest with yaml blocked guard-pyyaml
bricked-prs exit 2 yes (via caller-drift.py's reader)
kanban-deploy-state exit 1 yes
kanban-columns exit 0 no — not added

The guard caught itself being inert

selftests-cover searches 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):

M1  new *-selftest.py with no target        exit=2  REDDENS ✓  (names the file)
M2  off-convention file in scripts/tests/   exit=2  REDDENS ✓
M3  unwire an existing recipe line          exit=2  REDDENS ✓  (names the file)
M4  comment-only mention of a selftest      exit=2  REDDENS ✓
baseline / after each restore               exit=0  green

make check green, 24.4s against the documented 60s budget (was ~18s; the three added selftests cost 0.18s together).

Not in this PR

Neither bricked-prs nor kanban-columns is a required status check on develop or main — 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.4s
  • make selftests — 7/7 run
  • make selftests-cover — 4 mutations redden, all restore clean
  • make 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 check no longer understates how many gate selftests it runs. The Makefile used to say “all four” while CI had seven; counts now come from SELFTEST_FILES (wildcard over scripts/tests/*-selftest.{py,sh}) and help / messages use $(words $(SELFTEST_FILES)).

A new selftests-cover step runs before the selftests: every file under scripts/tests/ must match the naming convention, and every discovered selftest must appear on a recipe line in a make --dry-run of SELFTEST_TARGETS (recipe-only grep so comments cannot fake coverage). Per-selftest targets stay hand-written because invocations differ (extra blocked-marker.py command, bash vs python, selective guard-pyyaml).

Three workflows are now mirrored locally: selftest-bricked-prs, selftest-kanban-columns, and selftest-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.

…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>
@LukasWodka LukasWodka self-assigned this Aug 14, 2026
Comment thread Makefile
…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>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 shujaatTracebloc left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@LukasWodka
LukasWodka merged commit 0a17aff into develop Aug 14, 2026
9 checks passed
@LukasWodka
LukasWodka deleted the fix/1966-selftests-complete branch August 14, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants