Repository navigation
Host tests in CI, and one copy of the window layout on the ST side - #6
Merged
Merged
Conversation
tests/host/ builds the firmware's pure-C units with the host compiler, no Pico SDK, and runs them: make -C tests/host test. Each test_*.c is one program that names the firmware sources it tests on its first line and exits non-zero on a failed check (test.h: CHECK, CHECK_EQ, TEST_END); shim/ stands in for the SDK headers those units include. Tests are always rebuilt, so none passes on a binary older than the source it checks, and run under the address and undefined-behaviour sanitizers, so a write past a buffer fails. test_*.py files run from the same command, as unittest modules. The build workflow runs it before the firmware build: a failing check fails the pull request.
fb_fill_rect, fb_blit, fb_blit_key and the band variants against a pixel-by-pixel reference, at 121 positions per size (inside, across each edge and corner, just and far outside), for sizes from one pixel to larger than the screen, and for five bands; empty and negative sizes and missing bitmaps draw nothing. The keyed row routine is Thumb assembly on the RP; here it is the C it stands for. 3,030 checks. A right-edge clip off by one pixel fails it (a write past the framebuffer).
The layout lived in main.s, and userfw.s repeated its addresses as literals ($00FA400C, $00FA4040, $00FA4100, $00FA8300, $FB8200...), each with a comment asking it to agree with main.s. It now lives in inc/sidecart_layout.s, as in md-microfirmware-template, which both include: the window's blocks, the command values, the ROM3 signalling windows and FB_COPY_LINES, under main.s's names (userfw's FB_FRAME_COUNTER, CMD_MAGIC_SENTINEL and UFW_FB_SRC become FB_FRAME_COUNTER_ADDR, CMD_MAGIC_SENTINEL_ADDR and FRAMEBUFFER_ADDR). BOOT.BIN is byte-identical, built with the same RELEASE_DATE before and after.
test_layout.py reads the equs of inc/sidecart_layout.s and userfw.s and the defines of cart_shared.h, ikbd.h, fb.c and audio.c, evaluating the simple expressions both use, and compares them row by row: the code budget (also against target/atarist/build.sh's check), the sentinel and its values, the frame counter, the shared variables, the palette, the audio buffer, the boot block, APP_FREE, the framebuffer, the blit's lines, bytes, MOVEM chunk, chunk count and tail, and the ROM3 windows. The Timer-B rate must round to the RP's audio rate, and the bytes the RP refills per frame must be what the ST plays in one, within a sample. The window's blocks must not overlap and the framebuffer must end the window. Green on this tree; each of eleven one-sided edits (FB_COPY_LINES, the audio buffer size, the boot message size, CMD_BOOT_GEM, the blit-done window, the palette entries, the hello window, the IKBD window, TIMERB_COUNT, the bytes per frame, the build script's budget) fails it. cart_shared.h's comments now point to inc/sidecart_layout.s.
test_chunk_layout.c draws patterns into the chunky buffer, runs the RP's fb_chunky_to_planar() (the transpose on both cores, then the publish with the 48-byte chunks reversed), models the m68k's MOVEM blit from cart_shared.h's constants (each chunk read forward and stored below the last, the tail in place), decodes the ST's planes and compares every pixel with what was drawn: a gradient, the four corners and the tail's line, and bytes whose high nibble is set. The two Thumb workers stand in as the C they mean: the ST's planar format and a chunk-reversed copy. shim/pico/multicore.h runs core 1 as a thread with the FIFOs as blocking queues; the RP passes pointers through the 32-bit FIFO, so on a 64-bit host the shim restores their high half from an address in the program. A reversed copy one chunk short, or a tail copied from the wrong place, fails it.
test_wav_to_ym4.py checks tools/wav_to_ym4.py: the 64-entry Ghostbusters table holds 4-bit volume pairs, every PCM byte plays entry byte >> 2, silence the middle entry and clipping the ends; the resampler's length follows the rate. End to end, a half-second tone converted for the RP's rate becomes a .YMS file that audio.c's reader accepts: the magic, the rate and mode tag it checks (read from audio.c), the header size, the data length, and two bytes of 4-bit volumes per sample. A mode tag the reader refuses fails it.
CLAUDE.md's Tests section says what the host tests cover, how to add one and what still needs hardware; the shared-region and ROM3 sections name inc/sidecart_layout.s as the ST's copy and test_layout.py as the check. The README gets a "Testing and debugging" section, and the changelog's v1.1.0 "for developers" entry the host tests and the shared include. The firmware is unchanged: release builds of this branch and of its base, with one RELEASE_DATE, differ only in the build ID.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Host tests for the firmware's pure logic, run on every pull request, and one copy of the cartridge window's layout on the ST side. The firmware binary is unchanged: release builds of this branch and of its base, with one
RELEASE_DATE, differ only in the 7 bytes of the build ID, andBOOT.BINis byte-identical.Host tests
make -C tests/host testruns in about a second. It builds firmware units unchanged with the host compiler, under the address and undefined-behaviour sanitizers;shim/stands in for the few SDK headers they include. A new build step runs it before the firmware build, so a failing check fails the PR.test_fb_blit.c:fb_fill_rect,fb_blit,fb_blit_keyand the band variants, against a pixel-by-pixel reference. It covers every edge and corner, just outside and far outside, one pixel to larger than the screen, and five bands: 3,030 checks.test_chunk_layout.c: what the app draws is what the ST shows. It runs the realfb_chunky_to_planar(), with core 1 as a thread, and a model of the m68k's MOVEM blit built fromcart_shared.h. It then decodes the ST's planes and compares every pixel.test_layout.py: the ST's and the RP's copies of 31 shared constants agree. They cover:target/atarist/build.sh;APP_FREEand the framebuffer;It also checks the Timer-B rate against the RP's sample rate and bytes per frame, and that the window's blocks don't overlap.
test_wav_to_ym4.py: the converter's 64-entry table and its resampled length, and a.YMSfile thataudio.c's reader accepts.Every check was proven against a deliberate bug: an off-by-one clip, a publish one chunk short, a misplaced tail, a wrong mode tag, and eleven one-sided constant edits. The suite passes with Apple clang and with GCC 13 on Ubuntu.
One copy of the layout on the ST side
The layout lived in
main.s, anduserfw.srepeated its addresses as literals ($00FA400C,$00FA4040,$00FA4100,$00FA8300,$FB8200...). It now lives intarget/atarist/src/inc/sidecart_layout.s, as in md-microfirmware-template, which both include.test_layout.pycompares it withcart_shared.h.CLAUDE.md, the README and the changelog describe the tests and how to add one.