#!/usr/bin/env bash # Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. # See LICENSE.md for license information. # SPDX-License-Identifier: MIT # # verify-all.sh - Run JoeyLib's gates, one kind or all of them, and summarise. # # Driven by `make verify` (all), `make verify-audio` and `make verify-serial`. # # Kinds, in the order `all` runs them: # host the shared layer on the host, no emulator: the new-port # template (blank), file roots, the dirty-band marker, the line # and circle fallbacks at the far ends of their coordinates, the # indexed paste and snap against pixel-by-pixel ones, input, # millis, the module player the software-mixing ports share, # save, serial, sfxmix, tilebank, tile layers against their maps, # the Tiled map importer, and the VGM converter (with Furnace # installed, on its demo songs too) # golden UBER on each port, its per-op surface hashes against # tests/goldens/uber (x68000 against the IIgs's, its own gate; # dvx, sdl3, web, win32, win16, psp, haiku, haiku32, and beos # against the DOS port's, whose chunky stage they share; psp under # PPSSPP with every memory access checked as a PSP checks it, so # an unaligned one fails; haiku, haiku32 and beos in their VMs # under QEMU, scripts/lib/beGate.sh) # amiga: also on an A1200 (68EC020, AGA) and an A4000 (68040, # AGA), each on its own Kickstart (scripts/lib/amigaGate.sh; # optional components kickstartA1200/A4000: SKIP without them) # atarist: also the dirty-band marker the three 68000 ports share # (src/m68k/stageBands.s), held to the per-row widen under Hatari # ste: the STe build (BLiTTER, DMA sound, 4-bit palette) on an STe # and a Mega STe (16 MHz), held to the plain ST's hashes; and # which build runs where -- the STe build refuses a plain ST and # a Mega ST through jlLastError, the ST build runs on an STe # visual iigs: every launchable example on joey.2mg boots and draws # iigs: also the display -- UBER's probe build's runs of draws, # each presented, the $E1 pixels then equal to the stage, every # dirty band clean again, and the SCB and palette blocks the # stage's (make iigs-verify-shrtail) # x68000 and amiga: the display -- UBER's probe build's battery # of draws, each presented, the page or planes shown checked # against the stage's # dvx: full screen -- UBER's hashes through a suspend and a # resume, and KEYS's row read off the 320x200 VGA screen, then # in its window, then on the whole screen again # dvx: also the display -- UBER's probe build on nine desktop # depths and scales and the full screen, every displayed pixel # checked against the stage # win16, win32, haiku, haiku32, and beos: the display -- UBER's # probe build at four window sizes, the window as the shows left # it (each drawing only what changed) checked against it fully # repainted, and the frame against the stage # x68000: the SCB -- every text-palette reload of a 147-change # screen lands in the blanking before its band, timed on the beam # atarist and ste: the palette -- every shifter colour the ST # build writes is the 3-bit truncation, the STe build's (on an # STe and a Mega STe) the exact 4-bit $0RGB # amiga: the palette -- palette and SCB changes present one a # frame (no copper rebuild), and right, after a palette and an # SCB change; and a surface in Fast RAM (chip RAM full) draws and # copies as a chip one # tilebank each port: examples/tilebank loads the port's own .tbk bake # of the host fixture both ways, pixel-exact and with the host # gate's surface hash, and refuses every other target's bake (the # host half is check-tilebank.sh under `host`) # input each port: a typed string reaches jlInputGetChar byte-exact # (sdl3: through SDL's X11 driver on a private Xvfb display; web: # browser key events in headless Chromium; win32: X key events # Wine turns into WM_CHAR; win16: X key events DOSBox turns into # the PC keyboard's, which Windows 3.11 turns into WM_CHAR; # haiku, haiku32, beos: QMP key events into the PS/2 keyboard of # the port's VM) # (dvx: and the present shows it, decoded from the desktop, also # at 8/15/32 bpp and 2x/3x; Ctrl+Esc must not end the program) # (iigs: and its asm key predicates -- examples/keys lights A's # cell only while A is held, jlKeyDown, and Escape quits it, # jlKeyPressed) # (dos: and the mouse through the driver's INT 33h event # callback -- moves both ways, all four edges, every column, both # buttons) # joy (every port but psp, whose input gate covers it, and # haiku, haiku32 and beos, as QEMU's PC has no joystick to give # them; win16 through KPJOY.DRV, JoeyLib's own joystick driver for # Windows 3.1): the sticks -- # every direction, the buttons and every release, read back from # examples/joy's display (scripts/lib/joyGate.sh), each driven # the way its machine allows: # iigs MAME's joystick, from Lua (one stick) # x68000 MAME's pads, from Lua # atarist, ste Hatari's keyboard joysticks (port 1; port 0 is # the mouse's in TOS's IKBD mode, kept); on an STe also # both Jaguar pads in the enhanced ports, Hatari's # joypads on two virtual uinput gamepads (needs # /dev/uinput) # amiga FS-UAE's ports from host keys (lowlevel.library on # the boot drive; unplugged until moved) # dos, dvx, win16 DOSBox's 4-axis game port from host keys (and no # game port: no stick) # sdl3, win32 two virtual uinput gamepads (scripts/lib/ # virtualPad; needs /dev/uinput), then unplugged; # sdl3 again with two joysticks SDL cannot map # web the page's Gamepad API, stood in for by the driver # psp: no keyboard; its d-pad, nub, cross and circle as joystick # 0 (read back from examples/joy's display, resting while the # keyboard on the screen is open), Start as Return (held, and # typed: examples/keys's received row), Select as Escape, and the # keyboard on the screen (F5 and Shift lit in examples/keys, a # string typed on it), pressed through PPSSPP's input script # audio twelve gates, two kinds (the IIgs, X68000 and PSP on AUDIOPRB, # AUDIO pressing its own keys; the rest drive AUDIO's keys): # iigs DOC wavetable + sound IRQ vector (register tap) # x68000 YM2151 noise + PPI port C + ADPCM rate (register tap) # atarist YM2149 write trace, with the pc (register tap) # ste DMA chip samples + YM channel A (register tap) # amiga recorded sound, four loudness phases (Paula has no tap) # dos recorded sound, four loudness phases (SB has no tap) # dvx recorded sound, four loudness phases (DVX's mixer, SB) # sdl3 the mix itself (SDL's disk driver), six phases: also # the noise voice and the MOD # web what the page plays (headless Chromium), the same six # win32 recorded sound under Wine (PulseAudio), the same six # win16 the same on Windows 3.11 in DOSBox (Windows' Sound # Blaster driver; 22.05 kHz mono, 8-bit) # psp what reaches the PSP's audio output (PPSSPP's own # recording): the song, three streamed takes each at its # pitch, and the end on Select (Escape) # conform the audio contract, the same on every port: AUDPAR plays one # timeline of tones, voice and noise steps, effects, pans, a # one-shot module and ticks; its sound is recorded and graded # against what it logged (scripts/verify-audio-conformance.sh, # tests/gate/audioGate.c): pitch, level per attenuation # step, noise rate, mix balance, centring and pan where the port # is stereo, the module's end, the tick rate. Every port that # records sound: all but beos, whose VM has no sound card # serial the host check of the shared layer, then each port's loopback # (banner out, probe echoed back): # host scripts/check-serial.sh (no emulator) # iigs SCC modem port -> MAME null_modem # x68000 SCC channel A -> MAME null_modem # atarist Modem port -> Hatari pty; JL_SERIAL_SLOT unit 2 -> a # Mega STe's SCC channel B (Bconmap), sending only # amiga serial.device -> FS-UAE TCP # dos COM1 -> DOSBox nullmodem # dvx COM1 through DVX's rs232 library -> DOSBox nullmodem # win32 COM1 through the Win32 comms calls -> Wine -> a pty # win16 COM1 through Windows 3.11's OpenComm -> KPCOMM.DRV # -> DOSBox nullmodem # sdl3 termios -> a pty; the close puts back the line's # speed and leaves HUPCL off # haiku, haiku32 termios -> COM2 in the VM -> QEMU TCP (BeOS # R5's VM has no serial ports) # mouse x68000: deltas, clamps and buttons through the IOCS mouse # (and with serial streaming both ways on the same SCC) # save iigs: a SAVES/ blob written and read back through GS/OS # sdl3: SAVE's launch count beside the program after two runs # web: SAVE's launch count back from IndexedDB after a reload # win32 (Wine) and win16 (Windows 3.11 in DOSBox): SAVE's launch # count beside the program after two runs # psp: SAVE's launch count beside the program on the memory stick # after two runs # loss iigs: 4096 bytes through full-screen presents, lossless on the # modem port at 9600 and a Super Serial Card at 2400 # x68000: both serial streams byte-exact and the mouse right while # the SCB changes palette on every line of a run (9600, 19200, 38400) # disk programs packaged to ship: # iigs a one-program boot volume (DRAW's) boots, launches the # program and draws: as a 2048KB volume off a CFFA2 # card, then as an 800KB floppy on ROM 3 and on ROM 01 # macos scripts/check-macos-apps.sh: every app is one macOS # opens -- universal (Apple silicon and Intel), the Apple # silicon half signed, for macOS 13 on, its Info.plist, # program and icon in place (no Mac runs it here) # icon a program's icon (DRAW's), where its system shows it: # host scripts/check-icons.sh: every built port's DRAW carries # its baked icon where the system looks (no emulator) # iigs the Finder's JOEYLIB window, the icon file in Icons/ # amiga icon.library reads Draw.info's kind (a tool, its # stack) and draws it, and a Workbench start runs the # program in its own folder # win16 the program minimised on Windows 3.11's desktop # win32 the window's icon as Wine hands it to X # sdl3 the window's icon as SDL hands it to X # dvx Program Manager # haiku, haiku32, beos Tracker, once the program has run # (it copies its icons from its resources to its # attributes) # # `all` first BUILDS every selected port (make , and iigs-disk plus the # loss probes and game disks for the IIgs), stopping on a build failure, then # runs every kind. A gate for a port that was not selected is left out. # # SEQUENTIAL, deliberately. The recording audio gates drive the emulator on # wall-clock sleeps and grade fixed time windows of the capture, so a # second emulator competing for CPU can slide a phase boundary and fail a # gate that is actually fine. Running several emulators at once also makes # the timing-sensitive register gates noisier. # # Exit codes, distinguishing "it works" from "it did not run": # 0 every selected gate PASSED # 1 at least one gate FAILED (a real defect), or an `all` build failed # 3 no failures, but at least one gate SKIPPED for a missing # prerequisite -- an unbuilt port or absent emulator. Non-zero on # purpose: a run that quietly tested nothing must not look green. # A gate reached through make cannot tell a skip from a failure (make # exits 2 for both), so those count as failures; `all` builds their # prerequisites first, so a missing one is a real problem anyway. # # Each gate already reports its own evidence line; this only adds the # roll-up. Gate stdout/stderr is echoed as it happens, so a failure shows # its diagnosis here and not just a verdict. # # Usage: scripts/verify-all.sh KIND|all [port ...] # with no ports, every port the kind has. Ports: host iigs # x68000 atarist ste amiga dos dvx sdl3 web macos win32 win16 # psp haiku haiku32 beos # Env: VERIFY_ALL_LOG also append the full transcript to this file # Requires: whatever each selected gate requires; missing ones SKIP. set -uo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) kind=${1:-} shift || true label="verify-all $kind" KINDS="host golden visual tilebank input audio conform serial mouse save loss disk icon" ALL_PORTS="host iigs x68000 atarist ste amiga dos dvx sdl3 web macos win32 win16 psp haiku haiku32 beos" # UBER on the Amiga needs longer than bench-amiga.sh's interactive default. AMIGA_UBER_SECONDS=400 # UBER's per-op hashes against a golden: make/tools.mk's diffUberHashes, built # first when it is not current. UBER_HASHES="make -s -f make/tools.mk diffUberHashes > /dev/null && \"${JOEY_BUILD:-build}/tools/diffUberHashes\"" # kind port name command...: every gate, in run order within its kind. GATES=$(cat <&2; exit 2 ;; esac if [ "$kind" = all ]; then kinds=$KINDS kindPorts=$ALL_PORTS else kinds=$kind kindPorts=$(echo "$GATES" | awk -v k="$kind" '$1 == k { print $2 }' | awk '!seen[$0]++' | tr '\n' ' ') fi ports=${*:-$kindPorts} for p in $ports; do case " $kindPorts " in *" $p "*) ;; *) echo "$label: unknown port '$p' (choose from: $kindPorts)" >&2; exit 2 ;; esac done # Echo a command's output as it happens, and into VERIFY_ALL_LOG if set; # returns the command's exit code. stdin is /dev/null so no gate can read # the rest of the gate list. runLogged() { if [ -n "${VERIFY_ALL_LOG:-}" ]; then ( cd "$repo" && bash -c "$1" ) < /dev/null 2>&1 | tee -a "$VERIFY_ALL_LOG" return "${PIPESTATUS[0]}" fi ( cd "$repo" && bash -c "$1" ) < /dev/null } # The build step of `all`: each selected port, then the IIgs extras its # gates need (make iigs builds no examples; iigs-disk does). buildPorts() { local port local cmd for port in $ports; do case $port in host) continue ;; iigs) cmd="make -f make/iigs.mk iigs-disk $repo/build/iigs/bin/SCCPROBE $repo/build/iigs/bin/SERLOSS $repo/build/iigs/bin/SERLOSSS" ;; *) cmd="make $port" ;; esac echo echo "=== $label: build $port ($cmd) ===" if ! runLogged "$cmd"; then echo "$label: FAIL - building $port failed" >&2 exit 1 fi done } if [ "$kind" = all ]; then buildPorts fi gateNames=() gateVerdicts=() gateSeconds=() passed=0 failed=0 skipped=0 for k in $kinds; do while read -r gKind gPort gName gCmd; do [ "$gKind" = "$k" ] || continue case " $ports " in *" $gPort "*) ;; *) continue ;; esac echo echo "=== $label: $gKind $gPort $gName ($gCmd) ===" start=$SECONDS runLogged "$gCmd" rc=$? case $rc in 0) verdict=PASS; passed=$((passed + 1)) ;; 2) verdict="SKIP (prerequisite missing)"; skipped=$((skipped + 1)) ;; *) verdict=FAIL; failed=$((failed + 1)) ;; esac gateNames+=("$gKind $gPort $gName") gateVerdicts+=("$verdict") gateSeconds+=("$((SECONDS - start))") done <<< "$GATES" done echo echo "================ $label summary ================" for i in "${!gateNames[@]}"; do printf " %-26s %-28s %4ds\n" "${gateNames[$i]}" "${gateVerdicts[$i]}" "${gateSeconds[$i]}" done echo " ----" printf " %d passed, %d failed, %d skipped\n" "$passed" "$failed" "$skipped" if [ "$failed" -gt 0 ]; then echo "$label: FAIL" >&2 exit 1 fi if [ "$skipped" -gt 0 ]; then echo "$label: INCOMPLETE - some gates skipped (build those ports, then re-run)" >&2 exit 3 fi if [ "$passed" -eq 0 ]; then echo "$label: nothing selected" >&2 exit 2 fi echo "$label: PASS (all $passed gates)" exit 0