Skip to content

Migrate on-target testing from Amiberry to Copperline - #24

Merged
sidick merged 10 commits into
mainfrom
copperline-migration
Aug 16, 2026
Merged

Migrate on-target testing from Amiberry to Copperline#24
sidick merged 10 commits into
mainfrom
copperline-migration

Conversation

@sidick

@sidick sidick commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Migrates on-target testing from Amiberry to Copperline: networking now uses Copperline's [hostsocket] board in net = "host" mode (the direct equivalent of Amiberry's bsdsocket_emu=true), which let boot-net/iscopperline be collapsed away entirely -- boot now boots identically under either emulator.
  • Adds host-side golden-file regression tests (tests/run_golden.sh + tests/mock_wyoming_server.py, a deterministic canned Wyoming server) wired into CI's host-build job -- this repo previously had zero automated coverage of the Wyoming protocol/audio-sink code beyond "does it compile." Found and fixed a real bug along the way: a local config/narrator.wyoming silently overrode a positional host argument on the host build.
  • Adds the first actual on-target CI coverage this repo has ever had: copperline-smoke cross-compiles and boots wyomingtest under Copperline (bundled AROS ROM, no licensed Kickstart) inside ghcr.io/sidick/amiga-dev:1, byte-comparing its output against the same golden fixture.
  • Adds nondist/, a gitignored local directory for licensed content (Kickstart ROMs, a real AHI driver, a licensed Workbench install) that can never be accidentally committed.
  • Extends devtest/failtest with grep-able PASS:/FAIL: markers and adds tests/check_audio_wav.py (non-silence/duration check against Copperline's --audio-wav capture).
  • Adds tests/run_nondist.sh: a local-only regression script (needs nondist/'s licensed content, so never runs in CI) that boots devtest + failtest + the Say acceptance test under Copperline in one run and checks all of it automatically -- closing the biggest remaining gap, since narrator.device/translator.library/AHI/Say previously had zero automated coverage, only manual "eyeball a screenshot" checks. Building this surfaced and fixed a real mock-server bug (it only answered one request per connection; narrator.device holds one persistent connection across writes, same as real Piper).
  • Rewrites CLAUDE.md's on-target testing section for the new workflow.

Test plan

All of the following were run locally against real Copperline + the real cross-compiler image, not just written:

  • tests/run_golden.sh -- host-side golden tests pass
  • tests/run_smoke.sh -- on-target wyomingtest smoke test passes (cross-compiled via ghcr.io/sidick/amiga-dev:1, run under Copperline's bundled AROS ROM)
  • tests/run_nondist.sh -- full local regression (devtest + failtest + Say + AHI audio-content check) passes against nondist/'s real Kickstart + AHI-installed Workbench
  • devtest/failtest rebuild clean under -Wall -Wextra -Werror via the cross-compiler
  • CI itself (this PR's checks, once they run)

Filed #23 for a follow-up (real rhasspy/wyoming-piper integration check, occasional/manual, not per-PR).

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U

sidick and others added 10 commits August 16, 2026 20:47
Adds the pieces for driving on-target tests through Copperline instead
of Amiberry: a copperline-narrator.toml.example config (A2065 + real
Roadshow stack, matching the existing HardDrives/narrator boot volume
and NR0:/Narrator: repo mount), iscopperline (probes Copperline's
Zorro identification board so a shared S:User-Startup can pick this
boot script over the Amiberry one), and boot-net, the Copperline boot
script counterpart to `boot` (Roadshow bring-up still TODO before the
Say acceptance test can run against a real network stack).

Ignore the local Copperline test rig directory (staged boot volume +
run artifacts) the same way Amiberry's HardDrives/ is kept out of the
repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
Replaces the A2065 + Roadshow plan with Copperline's [hostsocket]
board in net = "host" mode -- bsdsocket.library autoboots from the
board's ROM and every socket call delegates straight to a host OS
socket, the same shape as Amiberry's bsdsocket_emu=true. This drops
the need to install/configure a guest TCP/IP stack at all, so
boot-net no longer needs the Roadshow bring-up step and is now
otherwise identical to `boot`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
With hostsocket "host" mode, Copperline's networking setup is
identical to Amiberry's bsdsocket_emu=true -- no guest stack, no
interface to bring up -- so boot-net and boot had converged to the
same script bar comments. Drop boot-net and the iscopperline
identification-board probe that only existed to pick between them;
`boot` now boots unmodified under either emulator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
Replaces the Amiberry/MCP-tool workflow description with the
Copperline CLI equivalent (--benchmark-until for log-based runs,
--screenshot-after for the Say acceptance test's Guru check), and
notes the still-unverified claim that boots/results validated under
Amiberry hold under Copperline too. Carries over the amiauth project's
own Copperline gotchas (AUX: redirection, CRLF serial output, the
0.12.0/0.13.0 filesys-mount hang) as forward-looking debugging notes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
The host build had no automated coverage of the actual Wyoming
protocol/audio code beyond "does it compile and not segfault on bad
args" -- CI never exercised wyoming.c's framing (chunk assembly, the
data_length block Piper uses, buffer-boundary handling) or
audio_host.c's WAV writing at all.

tests/mock_wyoming_server.py is a small stdlib-only Python TCP server
that serves a fixed PCM fixture (tests/golden/fixture.pcm, a
synthetic sine wave -- not real Piper output, so this tests our
client-side code, not TTS quality) split into three unevenly-sized
chunks alternating between the inline-fields and separate
data_length-block header shapes, to exercise both paths in
wyo_read_event(). tests/run_golden.sh runs wyomingtest/saytest against
it and byte-compares their output against tests/golden/*, wired into
ci.yml's host-build job.

While generating the golden fixtures, found and fixed a real bug in
both main.c and saytest.c: the host-argv parsing loop's positional
host/port branch gated on `!g_host[0]`, but g_host is already
non-empty by then because read_config() runs unconditionally first --
so a local config/narrator.wyoming's host silently overrode a
positional argv host instead of the reverse, contrary to the code's
own comment ("On the host, argv is reliable and overrides the config
file"). Track whether argv has supplied the host yet with a separate
flag instead. run_golden.sh works around the same hazard by running
each binary from a directory with no config/narrator.wyoming to find.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
…tes)

The first actual on-target CI coverage this repo has had -- until now,
CI only cross-compiled the Amiga binaries, never ran them. Runs in
ghcr.io/sidick/amiga-dev:1 (the same Bebbo m68k-amigaos-gcc 6.5.0b
toolchain this project already targets, plus Copperline and its
lavapipe Vulkan dependency preinstalled), so one job both
cross-compiles wyomingtest and boots it under Copperline against the
mock Wyoming server from the golden-file tests, reached through
[hostsocket] net = "host" -> 127.0.0.1 on the runner. No licensed
Kickstart needed (bundled AROS ROM).

Scoped to wyomingtest deliberately: it only needs dos.library +
bsdsocket.library, so this stays a minimal single-purpose boot volume
rather than a full installed Workbench with AHI -- proving the real
cross-compiled binary's network+Wyoming-parsing path works on target,
without the bigger lift of a licensed-content-free AHI/Say environment
(narrator.device/AHI/Say coverage stays manual under the developer's
own Copperline or Amiberry rig for now).

Reuses tests/golden/wyomingtest.pcm from the host-build golden tests
as the expected output, so the same fixture now gets checked on both
the host protocol code and the actual m68k binary under emulation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
Kickstart ROMs, a real AHI driver, a licensed Workbench install, etc.
have nowhere safe to live locally without risking an accidental
`git add -A`/`git add .` picking them up. nondist/* is gitignored
(README.md and .gitkeep excepted, so the directory itself survives a
fresh checkout) -- drop licensed content in here for local Copperline/
Amiberry testing and it can't be committed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
Prep work for scripted regression testing (currently these are
"eyeball the log" tools): every devtest phase and failtest scenario
now prints a grep-able "PASS: <name>"/"FAIL: <name>" line based on
its own success criteria (io_Error/io_Actual for CMD_WRITE phases,
IOERR_ABORTED for the AbortIO/CMD_FLUSH phase, a 10KB AvailMem-delta
threshold for the leak audits), plus an overall PASS/FAIL summary and
matching process exit code. Verified both still build clean under
-Wall -Wextra -Werror via the amiga-dev cross-compiler.

tests/check_audio_wav.py checks a Copperline --audio-wav capture has
real (non-silent) content of at least a minimum duration -- moves Say
acceptance verification beyond "no Guru" towards "audio actually
played". Copperline writes WAVE_FORMAT_EXTENSIBLE with an IEEE-float
SubFormat GUID rather than plain WAVE_FORMAT_IEEE_FLOAT (found by
running it against a real capture -- Python's wave module doesn't
support either, hence the manual RIFF parsing); verified against both
synthetic silent/tone fixtures and a real 45s Copperline capture
against a live Piper server (peak 0.48, correctly PASS).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
Prep for a devtest/failtest regression script: devtest alone opens
15+ separate connections across its phases (multiple CMD_WRITEs,
100+10 open/close cycles), which the previous one-shot-then-exit
server couldn't support. New optional trailing COUNT arg (default 1,
unchanged behavior) -- 0 serves until killed, for callers with no
fixed connection count to wait for. Re-verified run_golden.sh and
run_smoke.sh still pass unmodified (both rely on the COUNT=1 default).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
The biggest coverage gap flagged in review: narrator.device/
translator.library/AHI/Say -- the actual product -- had zero
automated regression testing; only "eyeball a screenshot for no
Guru" manual checks. tests/run_nondist.sh closes that locally (never
in CI -- needs nondist/'s licensed ROM + AHI-installed Workbench):
boots devtest + failtest + the Say acceptance test under Copperline
in one run, greps their PASS/FAIL markers, and checks the captured
--audio-wav actually contains non-silent audio via
tests/check_audio_wav.py.

Never modifies the tracked `boot` script. Instead it temporarily
swaps nondist/HardDrives/narrator's own S/User-Startup (itself never
committed, so freely editable) for one that installs the device/
library/prefs, runs devtest+failtest, then the same Say line boot
uses -- restored unconditionally on exit via trap. Points everything
at tests/mock_wyoming_server.py via a scratch ENV:narrator.wyoming
(also cleaned up on exit) rather than the developer's own local
config/narrator.wyoming, so the run is deterministic and doesn't
depend on a reachable real Piper server.

Building this surfaced a real mock-server bug, not a device bug:
narrator.device holds one persistent connection across multiple
CMD_WRITEs by design (see "Persistent connection + own task" in
CLAUDE.md), same as real Piper -- but mock_wyoming_server.py only
answered one request per accepted connection and then closed it, so
devtest's second write on the same session came back io_Error=-105
(NWERR_PROTO, "malformed/short Wyoming response") against a socket
the server had already hung up on. Fixed to loop reading/answering
requests per connection until the client closes it, matching real
Piper's persistent-connection behavior; re-verified run_golden.sh and
run_smoke.sh still pass against the fixed server.

Also documents CLAUDE.md's --audio-wav capability (previously
believed uncaptureable under --benchmark-until; now verified working
end to end against a real boot) and the host-side golden tests, which
had never been written up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNgYjaNTaAhz9Xf32zEU2U
@sidick
sidick enabled auto-merge (squash) August 16, 2026 20:41
@sidick
sidick merged commit 033c928 into main Aug 16, 2026
3 of 4 checks passed
@sidick
sidick deleted the copperline-migration branch August 16, 2026 20:43
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