Skip to content

fix(make): propagate test failures instead of masking via POSIX ; chaining (#550)#587

Merged
DorianZheng merged 1 commit into
boxlite-ai:mainfrom
G4614:fix/issue-550-makefile-chain-status
May 28, 2026
Merged

fix(make): propagate test failures instead of masking via POSIX ; chaining (#550)#587
DorianZheng merged 1 commit into
boxlite-ai:mainfrom
G4614:fix/issue-550-makefile-chain-status

Conversation

@G4614

@G4614 G4614 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Three Makefile recipes chained commands with ; instead of && / status-accumulation. POSIX evaluates if A; then B; C; fi to the rc of C, so an earlier failure was silently swallowed — CI sees green even when tests fail. Meta-bug: this masks every other bug, so it's the highest-leverage fix to land first.

What was broken

Fix

  • test:unit:rust: status accumulation (|| rc=\$?) so both crates always run and final rc reflects any failure. Added --no-tests=warn to keep make test:unit:rust FILTER=<test-only-in-one-crate> working (nextest's default --no-tests=fail would otherwise exit 4 from the crate with zero matches).
  • dist:python / dist:c: switched intra-branch chains to && so downstream commands can't run against a failed upstream.
  • .pre-commit-config.yaml: widened lint-fix hook's files filter from make/quality\\.mk to make/.*\\.mk so future make/test.mk / make/dist.mk edits go through the same autofix gate (the original narrow filter is exactly what let this class of issue land without local validation).

Test plan

  • Happy path: make test:unit:rust FILTER=container_layout (matches 1 in boxlite-shared, 0 in boxlite) → exit 0 — no false-fail from the empty crate.
  • Failure path: make test:unit:rust FILTER=can_create_process_in_new_user_ns on an AppArmor-restricted host (the Question: should test_can_create_process_in_new_user_ns treat AppArmor’s EACCES as an expected errno? #544 EACCES panic in -p boxlite) → recipe exit 100, make exit 2 — the failure correctly propagates instead of being swallowed by -p boxlite-shared finishing clean.
  • Shell logic sanity-checked with bash -c 'rc=0; false || rc=\$?; true || rc=\$?; exit \$rc' → exit 1.

Closes #550.

🤖 Generated with Claude Code

@G4614

G4614 commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Why this slipped — and why it matters more now

Worth calling out the practical detection asymmetry, since it explains both how this lived so long and why the fix matters more in 2026 than it would have in 2020:

  • A human running make test:unit:rust in a terminal reads the test runner's text output and notices test result: FAILED. 674 passed; 29 failed even when the recipe exits 0 — they form their pass/fail judgment from the screen, not from the rc. So the masked rc was effectively invisible to the human-driven workflow that originally wrote and ran these recipes.
  • Every non-human consumer — make's if evaluation, GitHub Actions, any agent-driven test loop, any wrapping script — has no eyes. They consume the rc and only the rc. The "29 failed" line is just stdout to them.

As more test runs get kicked off by CI and by AI/agent-driven dev loops, the false-green signal lands on exactly the consumer that can't see the "failed" line, which is when it does the most damage. The fix's biggest payoff isn't for the developer who runs make test:unit:rust once and reads the output — it's for everything downstream of that recipe in any pipeline.

…ining

Fixes boxlite-ai#550. Three recipes chained multiple commands inside one shell
with `;` instead of `&&` / status-accumulation. POSIX evaluates
`if A; then B; C; fi` to the rc of C, so:

  - test:unit:rust: a `-p boxlite` lib-test failure was hidden by a
    passing `-p boxlite-shared` run — CI saw exit 0 with 29 failed tests
    in the log. Now uses status-accumulation (both crates always run,
    final rc reflects any failure), plus `--no-tests=warn` to avoid a
    false-fail when FILTER scopes to a test that only lives in one crate
    (nextest's default --no-tests=fail exits 4 with zero matches).
  - dist:python Linux branch: build-guest.sh failure was masked by
    cibuildwheel's rc. Switched to && so cibuildwheel can't proceed
    against a stale guest binary.
  - dist:c per-platform branches: a failing dynamic-lib cp left the
    static-lib cp to "succeed", producing a half-staged sdks/c/dist with
    exit 0. Switched to && so any cp failure aborts the platform branch.

Verified locally:
  - happy path: make test:unit:rust FILTER=container_layout → exit 0
    (matches 1 in boxlite-shared, 0 in boxlite — no false-fail)
  - failure path: make test:unit:rust FILTER=can_create_process_in_new_user_ns
    → recipe exit 100, make exit 2 (the boxlite-ai#544 EACCES panic in -p boxlite
    correctly propagates instead of being swallowed by -p boxlite-shared
    finishing clean)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@G4614 G4614 force-pushed the fix/issue-550-makefile-chain-status branch from b0b5c29 to 3cf3636 Compare May 25, 2026 04:22
@DorianZheng DorianZheng merged commit 7e37c9e into boxlite-ai:main May 28, 2026
9 checks passed
@G4614 G4614 deleted the fix/issue-550-makefile-chain-status branch May 28, 2026 08:39
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.

Should test:unit:rust / dist:python / dist:c chain commands with && (or accumulate $status) instead of ;?

2 participants