Commit graph

14 commits

Author SHA1 Message Date
jedarden
cc24a1cf23 test(runner): document C11 7.13.2.1 setjmp clobber audit at call site (bf-31rd)
Relocate the per-variable setjmp/longjmp clobber analysis to the setjmp call
site and expand it into a full boundary audit per C11 7.13.2.1: enumerate every
automatic in scope (the loop index i; confirm there are no local tallies and
that g_failure_count/g_test_jmp are file-scope static, to which 7.13.2.1 does
not apply), and show why each is determinate — i is written only by the for-init
and for-increment, the latter running after control resumes, so no write lands
between setjmp and a longjmp fired in the body.

volatile on i is retained: the standard does not strictly require it (i is not
changed in the window), but gcc's -Wclobbered is heuristic and ignores that
distinction — empirically verified that the loop warns at -O1..-Os once the
incidental preceding qsort() stops biasing the register heuristic. volatile is
the sanctioned suppression. Compile gate confirmed clean at -O0..-Os with
-Wall -Wextra and explicit -Wclobbered; full suite still 29 RUN / 0 FAIL.

Analysis + comment + build verification only; no behavior change vs bf-22vg.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 04:29:59 -04:00
jedarden
f254c8583a test(runner): record per-test RUN-line isolation confirmation (bf-50yh)
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Document the dynamic confirmation that the recovery loop prints exactly one
RUN: line per registered test and drives the whole suite. Captured stdout of a
successful `make -C firmware/test test` run: 29 registered TEST() definitions,
29 RUN lines emitted, bidirectional set diff identical (none skipped, none
duplicated). Emitted order is byte-for-byte strcmp-sorted (qsort-by-name), as
expected for the all-passing suite where every body returns normally — no
FAIL/stderr interleaving. RUN-line set and order unchanged vs the naive
direct-call baseline (bf-1fd4). Satisfies umbrella bf-tof1 / parent bf-22vg.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 02:29:01 -04:00
jedarden
7b6a3d89b2 test(runner): volatile-qualify setjmp loop index for -Wclobbered-clean build (bf-2k3o)
The per-test loop in main() spans the setjmp(g_test_jmp)/longjmp recovery
target: i is read (loop test + increment) on the longjmp-return path. gcc's
-Wclobbered (enabled under -Wall) flags exactly this loop-index-across-setjmp
shape. The build was clean today only coincidentally — the preceding qsort()
call biases gcc's register heuristic; an identical loop without it warns at
-O1/-O2 (verified). Qualify i as volatile per C11 7.13.2.1, the sanctioned
remedy (no pragma, no flag downgrade), with no behavior change. This is the
compile-cleanliness gate for the guard (parent bf-22vg).

Verified: gcc -std=c11 -Wall -Wextra -Wclobbered -Werror is clean at
-O0/-O1/-O2/-O3/-Os/-Og; make -C firmware/test test builds warning-free and
runs all 30 tests (exit 0). Only firmware/test/test_runner.c touched.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 02:29:01 -04:00
jedarden
b62fa3ed60 test(runner): gate test body on setjmp(g_test_jmp) recovery target (bf-27ud)
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Wrap the per-test g_tests[i].fn() call in if (setjmp(g_test_jmp) == 0),
establishing the longjmp recovery target declared in bf-3id before each
test runs. The RUN: printf stays before the setjmp so the per-test marker
still prints regardless of how the body ends; the non-zero (longjmp) path
falls through with no else body, so the loop's i++ still advances and a
failure in test N never blocks N+1..end.

PASS/FAIL labels, tallies, summary, and non-zero exit remain sibling scope
(child 3, bf-1na) — main() still returns 0 unconditionally here.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 20:49:56 -04:00
jedarden
6a2d1406c9 test(runner): strip main() to naive direct-call baseline (bf-1fd4) 2026-07-03 20:25:54 -04:00
jedarden
d299306cb0 test: drop stale PASS/FAIL reference in host-harness loop comment
The per-test setjmp loop's doc comment still said it lands back in the
loop "to print PASS/FAIL and advance" — but child 1 (bf-52k2) and
child 2 (bf-344n) already replaced both branches' outcome labels with
the neutral RUN marker family ("RUN: <name>" / "RUN: <name>
(assertion failed)"). The comment now contradicts the code, so align
it with the "neutral marker line" phrasing already used lower in the
same block. No code change; gcc -std=c11 -Wall -Wextra still clean.

Co-Authored-By: Claude <noreply@anthropic.com>
Bead-Id: bf-344n
2026-07-03 14:55:47 -04:00
jedarden
b86cb670a8 test: print neutral RUN marker on direct setjmp==0 path
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
The direct branch of main()'s per-test setjmp loop — taken when a test
runs normally without longjmping back — now prints "RUN: <name>" instead
of "PASS: <name>". This is the same neutral marker family child 1
(bf-52k2) chose for the else branch ("RUN: <name> (assertion failed)"),
so a passing test prints "RUN: <name>" and a failing test prints
"RUN: <name> (assertion failed)".

After this child, NEITHER branch emits PASS/FAIL; each test emits exactly
one neutral line. The internal passed++ counter is unchanged — it feeds
the run summary ("N passed, M failed of T"), not the per-test output, so
it is not an "observable label". g_failure_count accounting is likewise
untouched.

Child 2 of 4 for bf-38e9 (split-child 2 of bf-53ut).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 14:01:29 -04:00
jedarden
6e454803fb test: print neutral RUN marker on longjmp failure-return branch
The else branch of main()'s per-test setjmp loop — taken when a failed
assertion longjmps back — now prints "RUN: <name> (assertion failed)"
instead of "FAIL: <name>". The marker still names the failing test and
its own per-test `failed` counter, but contains no PASS/FAIL outcome
token (outcome labels are deferred to a later sibling of bf-53ut).

g_failure_count accounting is unchanged: test_record_failure() already
bumped it before the longjmp, so this branch only prints.

Child 1 of 4 for bf-38e9 (split-child 2 of bf-53ut).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 13:37:19 -04:00
jedarden
8fbc2e82c3 test: document setjmp volatile-analysis in firmware host harness loop
Add a comment next to the per-test setjmp(g_test_jmp) guard in main()'s
loop recording the C11 7.13.2.1 volatile analysis: the loop index i is
read in the post-longjmp path (g_tests[i].name) but is not modified
between the setjmp() call and a possible longjmp() — the body only reads
i, and the only write is the for-loop increment, which runs after control
returns. So no volatile qualifier is needed. (bf-2ftl)

Co-Authored-By: Claude <noreply@anthropic.com>
Bead-Id: bf-2ftl
2026-07-03 12:44:25 -04:00
jedarden
549dc1f179 test: add firmware host harness main driver — name-sort, setjmp loop, summary, exit code
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Completes the runner (bf-bq9, child of bf-2i4). main() qsort()s the
registered tests by name for deterministic order regardless of constructor
or link order, drives each through the per-test setjmp/longjmp recovery
loop (print PASS on normal return, FAIL on longjmp return so one test's
failure never blocks the rest), prints a passed/failed/total summary, and
returns 1 iff g_failure_count > 0 — the non-zero-on-failure exit code CI
relies on via `make -C firmware/test test`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 10:12:46 -04:00
jedarden
83250700ef test: add firmware host harness failure recovery — jmp_buf + test_record_failure longjmp
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
bf-3id (child of bf-2i4). Adds the per-test failure-recovery machinery to
firmware/test/test_runner.c:

- static jmp_buf g_test_jmp at file scope — the live setjmp() target the
  header ASSERT_* macros longjmp into; declared at file scope so the main()
  landing in bf-bq9 can setjmp() it directly before each test.
- static int g_failure_count — a run-wide counter test_record_failure()
  bumps on each failure, so main() can return non-zero on any failure.
- test_record_failure(file, line, fmt, ...) — prints file:line plus the
  vfprintf-formatted detail to stderr, bumps the counter, and longjmp()s
  into g_test_jmp so the current test aborts but the runner continues.

main() (the setjmp caller + PASS/FAIL reporting + non-zero exit) is
intentionally absent — it lands in the sibling bead bf-bq9. Until then this
TU compiles to an object (gcc -std=c11 -Wall -Wextra -c) but has no live
setjmp target for the longjmp, and does not link into a runnable harness.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 09:51:43 -04:00
jedarden
ef1360ee76 test: add firmware host harness test_register — append + capacity guard
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Implement test_register() in firmware/test/test_runner.c, child 3 of the
bf-lfz sub-split. Appends each test entry at index g_test_count in GCC
constructor order and bumps the count, so the registry is fully populated
before main() runs. On a full registry (g_test_count >= MAX_TESTS) it logs
to stderr naming the skipped test and the cap and returns without writing
past the end — never overflows the array.

The static registry storage (g_tests/g_test_count) is now referenced, so the
__attribute__((unused)) it carried through child 2 (bf-uvv) is dropped.
test_record_failure() and main() remain intentionally absent (siblings
bf-3id and bf-bq9). Compiles cleanly to an object with zero warnings:
gcc -std=c11 -Wall -Wextra -Werror -c (no link, no main).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 09:13:52 -04:00
jedarden
18bce75f9e test: add firmware host harness registry storage — MAX_TESTS array + count
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Child 2 of the bf-lfz sub-split (registry storage). Adds to
firmware/test/test_runner.c: #define MAX_TESTS 128, the static
test_entry_t g_tests[] array, and static g_test_count, with comments on why a
fixed static array is appropriate (GCC constructors from TEST() populate it
before main(); keeps the harness dependency-free; 128 is far more than the
handful of pure-logic host tests this harness targets).

The two statics are marked __attribute__((unused)): nothing reads or writes
the registry until child 3's test_register() lands, and gcc 14 (unlike the
older gcc the original "static file-scope symbols do not warn when unused"
assumption rested on) DOES warn on unused file-scope statics under
-Wall -Wextra. Without the attribute the object emits two -Wunused-variable
warnings and fails the bead's zero-warning build gate.

Compiles cleanly to an object with zero warnings:
  gcc -std=c11 -Wall -Wextra -c firmware/test/test_runner.c
No changes to firmware/main or firmware/CMakeLists.txt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 09:04:13 -04:00
jedarden
191adbe9ad test: add firmware host harness runner skeleton — comment block + libc includes
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Child 1 of 3 of the bf-lfz sub-split (bf-lfz is itself a child of bf-2i4;
grandparent bf-56v; great-grandparent bf-4ne; header API in bf-1xs). bf-lfz
failed three times as a single big-bang change, so its registry work is
decomposed into three atomic, independently-compilable pieces. This is the
skeleton (child 1):

- A top-of-file comment block documenting the incremental build-out: child 2
  adds the registry storage (the array + count), child 3 adds test_register(),
  and the failure handler + main() arrive in the sibling beads bf-3id and
  bf-bq9.
- Exactly the five includes the full runner will need: "test_runner.h" then
  <setjmp.h>, <stdio.h>, <stdlib.h>, <string.h> — libc only, no includes from
  firmware/main, by design (see test_runner.h's header comment and the bf-21t
  decision record).

With only the skeleton present this TU compiles cleanly to an object
(gcc -std=c11 -Wall -Wextra -c, zero warnings) but is not yet linkable into a
runnable harness: test_register() and test_record_failure() are still undefined
and there is no main(). Those arrive with the later children and siblings. No
registry array, count, or test_register() yet.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-03 08:37:30 -04:00