Skip to content

Generate the test images instead of committing - #23

Merged
jserv merged 1 commit into
mainfrom
cleanup
Aug 6, 2026
Merged

Generate the test images instead of committing#23
jserv merged 1 commit into
mainfrom
cleanup

Conversation

@jserv

@jserv jserv commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

tests/mux-smoke.dec was 28 bare integers including 2147483654 and 2147483675, and the loader accepts bare integers with no comment syntax, so nothing in the file could say which part of the encoding each cell covered. scripts/gen-mux-smoke.fth emits the same image from named cells: the MOVE, the SUBLEQ that adds 32 to reach 'a', the masked MUX that builds 'C', and the branch to a negative pc that halts. The emitted cells are identical to the deleted file apart from spelling its two negative values unsigned, which load_muxleq() accepts either way.

Building the MOVE encoding from a sign bit and mask address 6 is for readability only. Unlike MUX_MOVE_C, which the interpreter and its emitters expand from one macro in one translation unit, a Forth copy of those constants can drift exactly as far as a literal could, and the comment says so rather than claiming a guarantee.

The generator asserts its own layout: each datum is emitted through a word that checks it lands on the address its constant claims, so reordering the data block fails the build instead of silently rewiring the code above it. A leading "decimal" pins BASE, which every literal and the .r output depend on.

The two loader fixtures held one malformed token each, so they become two printfs in GEN_LOADER_REJECTS, shared by check-loader-rejects and check-sanitize so the malformed data keeps one spelling. Both images are generated inline in the recipes, matching how gen-mux-high-image.py is already invoked in check-mux, so none of this needs a rule or a variable holding a path.

check-loader-rejects now runs the VM under $(RUN). It was the one target loading an image without the bound the comment above RUN says every test run gets, and that only became reachable once the fixtures were generated rather than committed: a generation bug can produce a valid looping image, which hangs the gate instead of failing it. GEN_LOADER_REJECTS joins its printfs with && for the same reason, since with ";" a failed first write was swallowed.


Summary by cubic

Generate test images at build time instead of committing fixtures to improve readability and catch layout mistakes early. Align local checks with CI by pinning tools and making generators resilient.

  • Refactors
    • Replace committed tests/mux-smoke.dec with a generated image from scripts/gen-mux-smoke.fth; skip when gforth is missing; uses named addresses; asserts layout; pins decimal.
    • Inline loader-reject fixtures via GEN_LOADER_REJECTS for both check-loader-rejects and check-sanitize; run the VM under $(RUN); join writes with && to fail fast.
    • CI deep job: install pinned shellcheck v0.11.0 by hash; run make CPPCHECK= check-analyze; rework scripts/rtos-check.sh no_mext to an if-form to drop SC2015.
    • Enumerate C/Python/Forth/Shell sources via git ls-files (wrapped in $(wildcard ...)) so deleted paths don’t linger; refuse empty lists outside a git checkout.
    • Expand indent: keep clang-format v20 gate; run black for Python and shfmt for shell when present; lint Forth as before; check-analyze uses git-enumerated C sources and respects CPPCHECK to allow explicit CI disable.

Written for commit 6ebfedb. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

tests/mux-smoke.dec was 28 bare integers including 2147483654 and
2147483675, and the loader accepts bare integers with no comment syntax,
so nothing in the file could say which part of the encoding each cell
covered. scripts/gen-mux-smoke.fth emits the same image from named
cells: the MOVE, the SUBLEQ that adds 32 to reach 'a', the masked MUX
that builds 'C', and the branch to a negative pc that halts. The emitted
cells are identical to the deleted file apart from spelling its two
negative values unsigned, which load_muxleq() accepts either way.

Building the MOVE encoding from a sign bit and mask address 6 is for
readability only. Unlike MUX_MOVE_C, which the interpreter and its
emitters expand from one macro in one translation unit, a Forth copy of
those constants can drift exactly as far as a literal could, and the
comment says so rather than claiming a guarantee.

The generator asserts its own layout: each datum is emitted through a
word that checks it lands on the address its constant claims, so
reordering the data block fails the build instead of silently rewiring
the code above it. A leading "decimal" pins BASE, which every literal
and the .r output depend on.

The two loader fixtures held one malformed token each, so they become
two printfs in GEN_LOADER_REJECTS, shared by check-loader-rejects and
check-sanitize so the malformed data keeps one spelling. Both images are
generated inline in the recipes, matching how gen-mux-high-image.py is
already invoked in check-mux, so none of this needs a rule or a variable
holding a path.

check-loader-rejects now runs the VM under $(RUN). It was the one target
loading an image without the bound the comment above RUN says every test
run gets, and that only became reachable once the fixtures were
generated rather than committed: a generation bug can produce a valid
looping image, which hangs the gate instead of failing it.
GEN_LOADER_REJECTS joins its printfs with && for the same reason, since
with ";" a failed first write was swallowed.

The deep job's check-analyze failed on findings no contributor could
reproduce: the runner image ships an older shellcheck that reports
SC2015 for no_mext in scripts/rtos-check.sh, which 0.11.0 does not.
shellcheck findings drift between releases the way clang-format output
does, so it is now pinned in that job by version and content hash like
the format job pins shfmt. That is what makes a local check-analyze
predict CI rather than merely resemble it.

no_mext is spelled as an if regardless. The old "A && { ...; } || :"
was correct, since the braces exit and the ":" was unreachable once the
grep hit, but it is the exact shape SC2015 names and the trailing no-op
only existed to keep the no-match case returning 0 under set -e. An if
gets that for free. Both branches verified: no match reaches the end with
status 0, a match still fails loudly with status 1.
@jserv
jserv merged commit dbd50f2 into main Aug 6, 2026
13 of 18 checks passed
@jserv
jserv deleted the cleanup branch August 6, 2026 05:16
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.

1 participant