test: derive the suite totals and the Valgrind list instead of restating them - #95
Conversation
…rged broken master (22d8f8b) does not compile. Two independent double-merges, both the same shape: two PRs fixing adjacent things landed on stale bases, each was green on its own branch, and the result was never rebuilt. 1. include/eos_image.h — embeddedos-org#93 replaced reserved[30] with tlv_len (2) + tlv_hash[28], preserving every offset. embeddedos-org#87 merged afterwards carrying asserts written against the older struct: error: no member named 'reserved' in 'eos_image_header_t' (x2) embeddedos-org#93 already asserts tlv_len at 62 and tlv_hash at 64, so the offset assert was a duplicate; the width assert had no replacement and is restored as two asserts covering both halves of the same 30-byte span. No offset moves and the wire format is unchanged. 2. core/ed25519_verify.c — embeddedos-org#86 and embeddedos-org#57 both landed a subgroup guard, so the file carried two byte-identical point_is_identity() definitions: error: redefinition of 'point_is_identity' Only embeddedos-org#57's public_key_is_valid_subgroup() is wired to the call site, so embeddedos-org#86's key_has_prime_order() was dead. Kept the live function, folded embeddedos-org#86's fuller rationale onto it, deleted the duplicate. 3. tests/unit/test_ed25519.c — collateral from the same merge. Two copies of test_ed25519_identity_key_forgery_rejected, main() calling it twice and two tests not at all, and test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery referencing k_low_order[] and messages[] that the merge had dropped. While restoring the corpus, corrected it (review finding on embeddedos-org#86): the array claimed to hold "the eight low-order point encodings" and held five. Every order here was computed rather than copied — decode y, recover x, add the point to itself until it reaches the identity — giving 1, 2, 4, 4, 8, 8, 8, 8. Missing before: y=0 with the sign bit set, and both sign-flipped order-8 encodings. D9FF..FF was in the array and is not a low-order point at all — no x satisfies the curve equation for that y — so it moves to a separate k_non_canonical[], with EDFF..FF7F (y=p) and EEFF..FF7F (y=p+1). tests_run was assigned a literal (11) in main() and never incremented, which is how the duplicate call and the two unregistered tests went unnoticed. The TEST macro now increments it, so the total cannot drift. Verified: cmake -DEBLDR_BUILD_TESTS=ON on master FAILS to build, 3 errors same with this commit builds clean ctest 21/21 PASS ctest -DEBLDR_SANITIZE=ON (ASan+UBSan) 21/21 PASS pytest tests/ 24 passed, 1 skipped test_ed25519 14/14 PASS (was 11 claimed, 12 run) discrimination, with `public_key_is_valid_subgroup` disabled: test_ed25519_low_order_keys_rejected FAILS, as it must test_ed25519_non_canonical_... still PASSES — those are refused by unpackneg() on canonicality, a different mechanism, which is the reason they are held in a separate array rather than counted among the eight.
…ing them
Two findings arrived one after another in review, on different PRs, with the
same shape underneath: a number or a list that describes what the tests do,
written out separately from the thing it describes, with nothing checking the
two agree. Fixing them one instance at a time was going to keep producing the
same finding, so this fixes the class and adds the guard.
**1. Suites stated a total instead of counting one.** The `TEST()` macro
incremented `tests_passed`; `main()` assigned `tests_run = <literal>`. A test
defined but never wired into `main()` was skipped with a zero exit, and the
suite still reported "N/N passed" for an N that was a claim.
18 of 21 suites carried it. It was not theoretical:
- `test_ed25519.c` had a hardcoded 11 that masked one test called twice and
two never called at all (repaired in embeddedos-org#94, which is where this started).
- `test_tlv_auth.c` assigns `tests_run` twice in one function -- 8, then 7.
The stale 8 survives only because the later assignment wins. Its suite
reports 7/7 today by luck.
- `test_secure_boot.c` counted nothing and ended `return 0`, so a suite that
ran none of its cases still reported success. The ASSERT macro exits on
failure, so that return could only ever have signalled the one case it
ignored.
`TEST()` now increments `tests_run` where it invokes the test, every literal
assignment and every `%d/<literal>` in a summary is gone, and
`test_secure_boot.c` compares and returns accordingly.
**2. The Valgrind list named its suites again by hand**, and had drifted to 17
of 21 -- `test_ecc`, `test_rollback`, `test_secure_boot` and `test_storage`
got no memory-safety run, and nothing failed when a name was forgotten.
Each `add_test()` now appends to `EBLDR_UNIT_TESTS` and the `foreach` iterates
that, so a suite added without touching the block still gets a Valgrind
target. Confirmed with a stub `valgrind` on PATH: 21 `valgrind_*` tests are
generated, up from 17.
**3. tests/unit/test_suite_bookkeeping.py** keeps both from returning. Four
checks, no C toolchain needed: no suite hardcodes its own total; every
`TEST()` macro counts the test it runs; every defined test is actually called;
and the Valgrind list is derived rather than repeated. Three suites with no
`TEST()` macro are listed in `NO_TEST_MACRO` with a reason each.
Verified:
ctest 21/21 PASS
ctest -DEBLDR_SANITIZE=ON (ASan+UBSan) 21/21 PASS
pytest tests/ 42 passed
valgrind targets, with a stub on PATH 21 (was 17)
every suite's reported total now equals what it ran -- e.g. test_bootctl
12/12, test_tlv_auth 7/7, test_secure_boot 4/4
Each of the four guards was probed against the defect it is written for:
reintroduce `tests_run = 12` -> test_bootctl.c: tests_run = 12
remove `tests_run++` from the macro -> FAIL
stop calling a defined TEST() -> FAIL
drop a suite from EBLDR_UNIT_TESTS -> FAIL
and all four pass again on restore.
Refs embeddedos-org#94, embeddedos-org#80, embeddedos-org#82
| RUN(test_append_does_not_advance_head_when_write_fails); | ||
| RUN(test_entry_layout_is_stable); | ||
| printf("\n%d/11 tests passed\n", tests_passed); | ||
| printf("\n%d/%d tests passed\n", tests_passed); |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
srpatcha
left a comment
There was a problem hiding this comment.
Review — eBoot#95 "test: derive the suite totals and the Valgrind list instead of restating them"
head: 93632f3 author: Kartikey1306 ci: pass
Verdict: Right fix, right shape — both hand-maintained lists are replaced by derived ones and a guard is added so they cannot come back. Every verification claim in the body that I could run reproduced exactly. The one substantive gap is that the guard's own escape hatch, NO_TEST_MACRO, reintroduces the pattern the PR exists to remove: I mutated two suites past it and all four checks still passed.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | Medium | tests/unit/test_suite_bookkeeping.py:36-40,74 |
NO_TEST_MACRO is an unvalidated allowlist, and test_every_test_macro_counts_the_test_it_runs continues on it before looking at the file. Two mutations against this head, both silently green: (a) appended #define TEST(name) static void name(void) — a macro that counts nothing — to test_ecc.c, which is on the list; (b) added "test_deleted_long_ago.c" to the dict, naming a file that does not exist. The body's "What this does not do" calls the list hand-maintained but treats it as acceptable; the difference from the two defects this PR fixes is only that this list is three entries long today. |
Two assertions, no restructuring: for each key, assert the file exists under tests/unit/, and assert it still contains no #define TEST(. The exemption then expires by itself the moment the suite grows a harness, and a rename fails loudly instead of widening the hole. |
| 2 | Low | tests/unit/test_suite_bookkeeping.py:44 |
_suites() globs tests/unit/test_*.c only. tests/test_performance_benchmarks.c and tests/test_emulation_simulation.c sit outside it. Harmless today — I checked both: neither has a TEST() macro or a tests_run assignment, and neither is referenced by any CMakeLists.txt, .cmake or workflow, so nothing compiles them. Worth recording because "no suite hardcodes its total" reads as a statement about the repo and is a statement about one directory. |
Either widen the glob to tests/**/test_*.c, or say "unit suites" in the docstring and the assertion message. The two orphaned files are a separate matter and not this PR's to fix. |
| 3 | Low | tests/unit/test_suite_bookkeeping.py:60 |
r"tests_run\s*=\s*([1-9]\d*)\s*;" catches only a non-zero decimal literal. tests_run = N_TESTS;, tests_run = 0x0C; and tests_run += 4; all pass. The %d/<literal> companion check has the same shape. |
Low priority; if it is worth closing, match `tests_run\s*(?:= |
| 4 | Low | tests/CMakeLists.txt:7-10 |
set(EBLDR_UNIT_TESTS "") lands between target_link_libraries(eboot_test_bootctl ...) and add_test(NAME test_bootctl ...), i.e. inside the first suite's block and under its # --- test_bootctl comment. It works, and I confirmed the leading empty string produces no empty list element, but it reads as if it belongs to test_bootctl. |
Move it above the # --- test_bootctl comment with the explanatory comment that is already attached to it. |
| 5 | Low | — | The duplicate-call defect named in the body ("one test called twice") is not covered by any guard: test_every_defined_test_is_actually_called checks defined - called only. I verified this by duplicating run_test_init_defaults(); in test_bootctl.c — all four checks passed. This is now harmless, because a derived count reports 13/13 honestly instead of 12/12 falsely, so it is a note rather than a defect. |
Nothing required. If it is ever worth catching, it is collections.Counter(called) and one assertion. |
Cross-PR note, not a finding
eBoot#82 adds test_fdt_loader test_fw_decrypt by hand to the exact foreach this PR deletes — and those two names have no matching targets, so on any host with valgrind installed #82 fails at CMake generate time (I reported that on #82 with a reproduction). If #95 lands first, #82's hunk conflicts and the broken lines disappear in the rebase; if #82 lands first, #95 removes them. Either order is safe, but #95-then-#82 is the one that does not leave a red weekly.yml Valgrind Memcheck job in between. #94 has to precede both, as the body says.
Architecture conformance
Conforms. Test-harness bookkeeping only; no runtime code, no headers, no dependency edges. Master design §5.1 is untouched — nothing here is linked into an image, and tests/ is the layer .ai/architect.md places it in for both reference layouts. Tier placement is correct: eBoot is Tier 1 Foundation (§21) and these are its own suites.
It also serves §28's evidence policy directly. A suite reporting N/N for an N it asserted rather than counted is a "Validated" claim resting on nothing, which is exactly what §28 forbids; deriving the count makes the number evidence again.
Proposed changes
tests/unit/test_suite_bookkeeping.py— close finding 1:Probe it the way the body probed the other four: it must fail on a bogus key and on adef test_the_no_test_macro_exemptions_are_still_true(): """An exemption that outlives its reason is the same drifting list one level up.""" stale, outgrown = [], [] for name in sorted(NO_TEST_MACRO): path = UNIT / name if not path.exists(): stale.append(name) elif "#define TEST(" in path.read_text(encoding="utf-8"): outgrown.append(name) assert not stale, f"NO_TEST_MACRO names files that do not exist: {stale}" assert not outgrown, ( f"these suites now have a TEST() macro and must be checked like the " f"rest -- drop them from NO_TEST_MACRO: {outgrown}" )
#define TEST(appended totest_ecc.c, and pass again on restore.tests/CMakeLists.txt— move theset(EBLDR_UNIT_TESTS "")two lines up, above# --- test_bootctl.- Findings 2 and 3 are judgement calls; a one-word docstring change covers 2 and 3 can be left.
Nothing here blocks merge. Finding 1 is worth doing in this PR rather than later, because the PR's argument is that fixing instances one at a time keeps reproducing the finding — and the allowlist is the next instance.
Not checked
- Not verified:
pytest tests/→ "42 passed".pytestis not installed on this host, so I ran the four guards by importing the module and calling them directly. They pass; the other Python suites intests/were not run by me. CI's "Host Build & Tests" is green, which is evidence for the claim but not the same evidence. - Not verified: the four "probed against the defect it exists for" restorations in the body. I ran my own three mutations (findings 1 and 5) and they behaved as I describe; I did not reproduce the author's four.
- Not verified: the claim that the Valgrind list "had drifted to 17 of 21". I confirmed
origin/masterlists 17 names and this head produces 21 targets, which is consistent, but I did not check each of the four named suites individually. - Not checked: whether
test_boot_log.c,test_ecc.candtest_image_abi.care correctly exempt on the merits — only that all three genuinely have noTEST()macro today, which is what the list claims. - Not run: valgrind itself. It is not installed here; I configured with
-DVALGRIND=/usr/bin/trueto exercise the block, so the targets are proven to exist and not to pass.
Evidence
Against this head, from a read-only git archive extraction — no repository was modified:
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DEBLDR_BUILD_TESTS=ON -> OK
cmake --build build -j4 -> OK
ctest --no-tests=error -> 21/21 passed
cmake -B build-san ... -DEBLDR_SANITIZE=ON && ctest --no-tests=error -> 21/21 passed (29.1s)
cmake -B build-vg ... -DVALGRIND=/usr/bin/true -> OK
ctest -N | grep -c valgrind_ -> 21 (master: 17)
first entries: valgrind_test_bootctl, valgrind_test_crypto, valgrind_test_image_verify
no empty-named target -- set(EBLDR_UNIT_TESTS "") does not leave a leading element
test_suite_bookkeeping.py, all four checks called directly -> 4/4 PASS
So ctest 21/21, sanitizer 21/21 and "21 valgrind targets, was 17" all reproduce exactly as claimed. Mutations for findings 1 and 5:
append a non-counting `#define TEST(name)` to test_ecc.c (in NO_TEST_MACRO) -> 4/4 PASS (should fail)
add "test_deleted_long_ago.c" to NO_TEST_MACRO -> 4/4 PASS (should fail)
duplicate `run_test_init_defaults();` in test_bootctl.c -> 4/4 PASS (harmless now)
Automated architecture review of 93632f33b919 — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
Two review findings arrived one after another, on different PRs, with the same shape underneath: a number or a list that describes what the tests do, written out separately from the thing it describes, with nothing checking the two agree. Fixing them one instance at a time was going to keep producing the same finding, so this fixes the class and adds the guard.
Stacked on #94, which repairs
master— without it nothing here builds.1. Suites stated a total instead of counting one
TEST()incrementedtests_passed;main()assignedtests_run = <literal>. A test defined but never wired intomain()was skipped with a zero exit, and the suite still reportedN/N passedfor an N that was a claim rather than a count.18 of 21 suites carried it, and it was not theoretical:
test_ed25519.c11masking one test called twice and two never called at all — repaired in #94, which is where this startedtest_tlv_auth.ctests_runtwice in one function —8, then7. The stale8survives only because the later assignment wins; it reports 7/7 today by lucktest_secure_boot.creturn 0, so a suite that ran none of its cases still reported success. TheASSERTmacro exits on failure, so that return could only ever have signalled the one case it ignoredTEST()now incrementstests_runwhere it invokes the test; every literal assignment and every%d/<literal>in a summary is gone.2. The Valgrind list named its suites again by hand
It had drifted to 17 of 21 —
test_ecc,test_rollback,test_secure_bootandtest_storagegot no memory-safety run, and nothing failed when a name was forgotten, because a missing entry is simply a test that never gets one.Each
add_test()now appends toEBLDR_UNIT_TESTSand theforeachiterates that, so a suite added without touching the block still gets a Valgrind target.3. A guard so neither returns
tests/unit/test_suite_bookkeeping.py— four checks, no C toolchain needed:TEST()macro counts the test it runsThree suites with no
TEST()macro sit inNO_TEST_MACROwith a reason each, following theNOT_BUILTconvention already used in this repo.Verification
ctestctestunder-DEBLDR_SANITIZE=ON(ASan+UBSan)pytest tests/valgrindon PATHEvery suite's reported total now equals what it ran —
test_bootctl12/12,test_tlv_auth7/7,test_secure_boot4/4.Each guard was probed against the defect it exists for, and all four pass again on restore:
What this does not do
The
NO_TEST_MACROlist is itself hand-maintained — the same shape one level up. It is three entries with reasons rather than a silent omission, which is the best available without imposing one harness on suites that legitimately differ. Worth revisiting if it grows.Refs #94, #80, #82