-
Notifications
You must be signed in to change notification settings - Fork 0
Amiga port testing
Three paths to test the port, each suited to a different stage of work:
| Method | When |
|---|---|
| vamos (host-side, headless) | Day-to-day iteration; widest automated test coverage |
| Amiberry (full emulation) | Real-AmigaOS behaviour — Workbench launch, icon.library, 68881 FPU, persistent history, anything serial.device-shaped |
| CI (cross-compile only) | Confirming a branch builds in the canonical container; produces release-style artefacts |
For day-to-day work, vamos. For anything that needs Kickstart, an emulator. CI gives you the same binary you'd ship.
vamos is the userspace 68k/AmigaOS emulator from the amitools package.
It boots in milliseconds, has no GUI, and integrates with the standard
tests/run-tests.py harness. Setup here assumes vamos is installed at
~/vamos/ and activated via pipenv.
cd ~/vamos
pipenv run vamos --cpu 68020 \
-V "mp:/path/to/micropython/tests" \
--cwd mp:basics \
-- /path/to/build/micropython string1.py-
--cpu 68020is required — vamos defaults to 68000, which faults onm68020instructions. -
-V name:/host/pathmounts a host directory as an AmigaOS volume. -
--cwdsets cwd; with basename invocation,sys.argv[0]/__file__match host CPython exactly. -
--separates vamos options from the binary's args.
tools/amiga-vamos-run.sh is a MICROPY_MICROPYTHON wrapper. It mounts
tests/ as the internal mp: volume, points vamos's cwd at the test's
directory, replaces the script argument with its basename, uses a private
--vols-base-dir so parallel workers don't collide on the auto RAM:
volume, and -q so vamos logs don't pollute captured stdout.
export MICROPY_MICROPYTHON="$(pwd)/tools/amiga-vamos-run.sh"
cd tests
./run-tests.py -d basics float io micropython miscExclude directories that can't run on Amiga (no socket-server harness under vamos, no asyncio in the port, no inline asm for non-68k targets, etc.):
./run-tests.py -d basics float io micropython misc \
-e "inlineasm|machine_|thread|extmod/ussl|extmod/uasync"AMIGA_VARIANT=standard|68040 picks build + matching --cpu
flag. Default is standard. (68020fpu builds 68881 instructions
that vamos can't emulate, so it's Amiberry-only.)
AMIGA_VARIANT=68040 ./run-tests.py basics/string1.py68020fpu cannot run under vamos — it builds 68881 instructions and
vamos has no 68881/68882 emulation. The wrapper detects this and exits
with an explanation; test that variant under Amiberry or real hardware.
For run-tests.py to work against the Amiga binary, the port must:
- Accept
-X <option>flags as no-ops (run-tests.pyalways emits-X emit=bytecode; macOS adds-X realtime). - Return POSIX-style exit codes
(
MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING (1)). - Free
AllocVec'd argv buffers before returning — vamos's orphan-memory check makes the process exit non-zero otherwise.
All three are already in place.
-
Bebbo argv parser is broken under vamos for multi-arg invocations;
amiga_parse_argsparsespr_Argumentsitself. -
WaitForCharreturns 0 immediately under vamos — the port uses plainFGetCfor console reads. -
SetMode(fh, 1)is a no-op under vamos (it already delivers one char at a time). - No 68881 emulation — see variant selection above.
Vamos doesn't translate SetMode(stdin, 1) to tcsetattr on the host
TTY, so an interactive REPL launched naked sees the host shell's cooked
mode (cursor keys echoed as ^[[D, bytes only delivered on newline).
tools/amiga-vamos-repl.sh flips the host TTY into raw / no-echo /
no-isig before launching vamos and restores the original mode on exit.
tools/amiga-vamos-repl.sh # standard variant
AMIGA_VARIANT=68040 tools/amiga-vamos-repl.shPipe input is unaffected; only interactive sessions need the wrapper.
Run via tools/amiga-vamos-run.sh against the standard variant:
| Directory | Files | Pass | Self-skip | Fail | Notes |
|---|---|---|---|---|---|
basics/ |
574 | 490 | 83 | 1 |
struct1.py (bebbo ABI alignment) |
float/ |
68 | 54 | 11 | 3 | EXACT-mode precision at double-range edges |
io/ |
16 | 15 | 1 | 0 |
argv.py now passes |
import/ |
30 | 29 | 0 | 1 |
import_file.py (vamos host-path rewriting) |
micropython/ |
108 | 70 | 18 | 20 | Remaining native_* / viper_* -- Phase 12 (NLR + viper register / pointer gaps); the bulk of the native_* set started passing after the asm68k MOVEQ fix |
extmod/ |
192 | 76 | 102 | 13 | vamos socket / select / time-quantum / vfs_userfs gaps; one known-flaky (time_time_ns.py) ignored |
misc/ |
14 | 6 | 8 | 0 | Skips: settrace, sys_exc_info, cexample |
cmdline/ |
27 | 9 | 2 | 14 | Unix-port-specific (REPL banner, -v, terminal editing); not re-run this snapshot -- vamos's stdin/stdout fan-out across run-tests.py worker fork hangs on the REPL tests, so the row carries the previous (2026-05-12) numbers verbatim |
stress/ |
13 | 12 | 0 | 1 |
bytecode_limit.py parser memory pressure |
Aggregate: 761 pass / 225 self-skip / 53 fail out of 1042 files.
Excluding Phase-12 native/viper failures, Unix-port-specific cmdline
tests, vamos emulation gaps, and the bytecode_limit.py parser edge
case, 4 individual tests fail -- the same real platform
differences as in the 2026-05-12 snapshot (m68k struct alignment,
long-double EXACT-mode precision, vamos host-path rewriting).
Notable movement vs the 2026-05-12 snapshot:
-
micropython/: +27 pass / -27 fail. Theasm_68k_moveqdestination-register fix (commitfb1aeab92) unblocked everynative_*test that wasn't dependent on the still-missing 68k NLR story. The remaining 20 failures all hit known Phase 12 limits:try/except/within native (needsnlr68k.S), viper's one-register local cap, and viper pointer ops. -
io/argv.py: now passes (was a vamos host-path rewriting failure); the path rewriting intools/amiga-vamos-run.shcaught up. -
extmod/: net file count dropped 205 → 192 (13 tests removed upstream); on the larger active set, pass count rose 67 → 76 as more surfaces became reachable after Phase 39's hashlib / deflate / btree additions.
vamos re-run (standard, dirs basics float io import micropython extmod misc stress, same exclusions): 752 pass / 40 fail / 223 self-skip,
no regressions — every failure is a previously-documented category.
For the first time the failing extmod/ surfaces were also run on real
Amiberry via the new run-tests.py -t serial path (above). This splits
the vamos "re-run under Amiberry" TODO bucket into real passes vs genuine
gaps:
| Test | vamos | Amiberry | Verdict |
|---|---|---|---|
extmod/socket_tcp_basic.py |
FAIL | PASS | vamos bsdsocket stub only |
extmod/socket_badconstructor.py |
FAIL | PASS | vamos bsdsocket stub only |
extmod/socket_fileno.py |
FAIL | PASS | vamos bsdsocket stub only |
extmod/time_ms_us.py |
FAIL | PASS | vamos ~10 ms clock quantum |
extmod/time_time_ns.py |
FAIL | PASS | vamos clock quantum (also flaky-tagged) |
extmod/ssl_sslcontext.py |
FAIL | PASS | needed real AmiSSL |
extmod/ssl_sslcontext_verify_mode.py |
FAIL | PASS | needed real AmiSSL |
extmod/select_poll_basic.py |
FAIL | FAIL | genuine: no select()/poll() on the port |
extmod/time_res.py |
FAIL | FAIL→fixed |
time.time() froze: a port bug left the 68881 FPCR rounding-precision at single, so mathieeedoubbas's FPU path truncated every double. Fixed by resetting FPCR at startup; passes with the FPU enabled. See note below. |
extmod/ssl_basic.py |
FAIL | FAIL | genuine: AmiSSL API gap |
extmod/ssl_ioctl.py |
FAIL | FAIL | genuine: AmiSSL API gap |
extmod/ssl_keycert.py |
FAIL | FAIL | genuine: AmiSSL API gap |
import/import_file.py |
FAIL | FAIL→fixed | absolute __file__; fixed by the sys.path[0] change below (vamos + on-device now pass; -t still ImportError, a harness limit) |
So 7 of the 13 vamos failures are emulator artifacts, not port bugs;
import_file.py and time_res.py were both then fixed outright (see
below). The real-hardware failure set is now: 4 platform-diffs + 20 Phase-12
native/viper + select_poll_basic + 3 AmiSSL
(ssl_basic/ssl_ioctl/ssl_keycert) + vfs_userfs + platform_basic
-
stress/bytecode_limit.
time_res.py was a port bug, fixed 2026-06-20 (FPCR rounding
precision). It failed because time.time() was frozen: with an FPU
present, every double (epoch seconds ~1.78e9, 0.1+0.2, …) was truncated to
single (~24-bit) precision, so consecutive seconds collapsed to the same
value. The loss tracked FPU presence (byte-identical across host-64-bit,
host-80-bit and softfloat-80-bit — the FPU-mode selector made no difference)
and vanished with the FPU disabled, which initially looked like an Amiberry
emulation artifact. It was not.
Root cause (found by bisect): the bebbo C startup leaves the 68881/68882
FPCR rounding-precision field set to single (0x40) on entry to the binary
— a bare C program built with the same toolchain gets double (0x90).
MicroPython emits no FPU control writes (all double math is soft-float
library calls), but LIBS:mathieeedoubbas.library is a single on-disk
binary that auto-selects software vs direct-FPU math by FPU presence, and on
its direct-FPU path it honours the inherited FPCR. With rounding stuck at
single, every result rounded to ~24 bits. The bisect ruled out -lm and
every library MicroPython opens: FPCR read 0x40 before main and before
the earliest constructor, so it was the startup, not any OpenLibrary.
(Diagnostic trick: inline fmove.l %fpcr,%0 reads FPCR even in a
-msoft-float build — the assembler accepts FPU opcodes regardless of
codegen flags.)
Fix: amiga_fpu_fix_rounding() in ports/amiga/main.c, called first thing
in main(), sets the rounding-precision field to double
((fpcr & ~0xC0) | 0x80) via inline fmove.l. Guarded by
SysBase->AttnFlags & (AFF_68881|AFF_68882|AFF_FPU40) because the soft-float
standard build also targets FPU-less 68020s, where fmove would trap.
Verified on an FPU-enabled Amiberry: 0.1+0.2 == 0.30000000000000004,
consecutive large integers stay distinct, time.time() advances, and
time_res passes — no more need for a no-FPU config. (The residual
~1-2 ulp imprecision on transcendentals / repr(pi) digit count is the
separate, pre-existing bebbo soft-float / dtoa tax that also causes
float_format_accuracy / float_parse; present under vamos too.)
import_file.py fix (2026-06-20). Triage showed two layers: (1) over
run-tests.py -t, ImportError because the board harness ships no sibling
modules — inherent, not a port bug; (2) when the sibling is reachable, the
import worked but import1b.__file__ came back absolute
(Python:tests/import/import1b.py) vs the expected relative import1b.py.
Layer (2) was a real port deviation: for a basename script main.c set
sys.path[0] to the absolute cwd (NameFromLock) instead of "". Setting
it to "" (the MicroPython convention, resolved relative to cwd by
VfsAmiga) matches CPython/unix; import_file.py now passes under vamos and
the on-device runner, all 30 import/ tests pass, sibling imports still
work. Committed ports/amiga: Keep sys.path[0] relative for a basename script.
Genuine platform differences — present whether tested under vamos, Amiberry, or real hardware. Both sides are effectively correct; the test's expectations match CPython on x86 (or full-precision IEEE doubles) rather than the AmigaOS m68k ABI and bebbo's soft-float library:
| Test | Cause |
|---|---|
basics/struct1.py |
struct.calcsize("97sI") == 102, test expects 104. bebbo gcc on m68k uses 2-byte int alignment per the AmigaOS m68k ABI; CPython on x86 uses 4. Both platform-correct. |
float/float_parse*.py |
Very long mantissa with very negative exponent; 1e+300 vs 9.999…e+299 differing by 2 ULP; 1e4294967301 not detected as overflow — 1–2 ULP off. Bebbo's 80-bit long-double soft-float loses just enough precision that EXACT-mode parsing can't always nail the closest double. |
float/float_format_accuracy.py |
repr round-trip rate ~72 % vs ≥ 99.7 % expected. Same long-double precision tax. |
float/inf_nan_arith.py |
min/max over mixed nan/±inf operands picks the wrong-signed inf in one row (nan -inf -inf vs expected nan inf -inf). bebbo soft-float comparison quirk when a nan is involved; the ordered arithmetic itself is correct. |
Tests below fail on the current amiga-port head. None are regressions;
each is a documented gap waiting on a specific piece of port work.
Originally captured 2026-06-13 against commit 98a3a6e4c (standard
under vamos); updated 2026-06-20 with real-Amiberry verification of
the socket / select / time / ssl bucket. After verification the genuine
real-hardware gaps are: 6 native, 14 viper, 3 ssl, 1 select,
1 vfs, 1 platform = 24 entries (the 3 socket tests and 2 sslcontext
tests turned out to be emulator artifacts that pass on a correctly-configured
target, and 2 of the time tests were vamos clock-quantum artifacts;
import_file.py and time_res.py were both fixed — see the snapshot
section above).
| Test(s) | Cause | TODO |
|---|---|---|
micropython/native_closure.py, native_const.py, native_gen.py, native_try.py, native_try_deep.py, native_with.py
|
Phase 12 native emitter gap: @micropython.native needs the 68k NLR for try/except/with/closure scope-chain unwind. |
Land ports/amiga/nlr68k.S saving D2–D7/A2–A5 in nlr_buf_t; wire it through MICROPY_NLR_*. Tracked in the design log's "Other known limitations" table. |
micropython/viper_binop_arith.py, viper_binop_bitwise_uint.py, viper_globals.py, viper_misc2.py, viper_ptr16_load.py, viper_ptr16_load_boundary.py, viper_ptr16_store_boundary.py, viper_ptr32_load.py, viper_ptr32_load_boundary.py, viper_ptr32_store.py, viper_ptr32_store_boundary.py, viper_subscr_multi.py, viper_try.py, viper_with.py
|
Phase 12 viper limits: register allocator caps at 1 local (D7) — many tests need ≥2 simultaneously live locals — and 16/32-bit pointer load/store opcodes aren't emitted on m68k. | Two strands. (a) Extend asm68k.h / emitnative.c so viper can spill across D2–D7; (b) add asm_68k_loadN / asm_68k_storeN covering byte/word/long pointer ops. The try/with subset additionally needs the NLR above. |
extmod/ssl_basic.py, ssl_ioctl.py, ssl_keycert.py
|
AmiSSL ships a subset of the OpenSSL API surface. (ssl_sslcontext.py and ssl_sslcontext_verify_mode.py were in this bucket but pass on Amiberry as of 2026-06-20 — they only failed because vamos has no AmiSSL.) Remaining suspects: certificate-format combinations and ioctl flag mappings. |
Triage each .py.out vs .py.exp in tests/results/ to attribute the divergence to either a real AmiSSL API gap (file an issue upstream / wrap with stubs) or a port-side wrapper bug in ports/amiga/modssl.c. |
extmod/socket_tcp_basic.py, socket_badconstructor.py, socket_fileno.py |
vamos's bsdsocket.library stub is incomplete. |
Resolved 2026-06-20: all three PASS on Amiberry against a real bsdsocket.library. vamos-only failures; no port work needed. |
extmod/select_poll_basic.py |
The port doesn't expose select() / poll() over bsdsocket. |
Confirmed 2026-06-20: still FAILS on Amiberry → genuine gap (not just a vamos limitation). Real TODO if select/poll support is wanted. |
extmod/time_res.py |
time.time() was frozen. |
Fixed 2026-06-20 — port bug (FPCR rounding precision). The bebbo C startup left the 68881 FPCR rounding-precision field at single, so mathieeedoubbas's direct-FPU path truncated every double, collapsing time.time(). Fixed by resetting FPCR to double precision at startup (amiga_fpu_fix_rounding() in main.c, guarded by AttnFlags); passes with the FPU enabled. time_ms_us.py/time_time_ns.py also pass on Amiberry (they only failed on vamos's ~10 ms clock quantum). |
import/import_file.py |
FIXED 2026-06-20 (vamos + on-device). The absolute-__file__ half is resolved: main.c now leaves sys.path[0] as "" for a basename script instead of the absolute NameFromLock cwd, so import1b.__file__ is the relative import1b.py matching CPython/unix. All 30 import/ tests pass under vamos. |
Over run-tests.py -t the test still raises ImportError — the board harness ships no sibling modules (test source sent plain in emit=bytecode), so import import1b isn't on the target's sys.path. Generic board limitation, not a port bug; nothing further to do unless -t gains sibling-shipping. |
extmod/vfs_userfs.py |
The test mounts a Python-defined VFS at /, which collides with the port's built-in VfsAmiga already mounted at / by main.c. |
Either skip this test for the Amiga port, or rework vfs_amiga.c to permit a runtime unmount of the default root so userfs can take over. The first is the pragmatic answer. |
extmod/platform_basic.py |
Port reports sys.platform = "amiga"; the test's expected-platforms list doesn't include it. |
Decide whether to upstream "amiga" into extmod/modplatform.c (preferred), or carry a downstream .py.exp override (tests/extmod/platform_basic.py.exp.amiga or similar). |
bebbo gcc 6.5b on -msoft-float ships incorrect floating-point helpers
in libgcc / clib2 / libnix. ports/amiga/floatconv.c overrides each one
(some directly, some via --wrap because clib2 fat-packs them with
__muldf3 and friends). Keep this list in mind when triaging arithmetic
oddities:
| Routine | Bug | Trigger |
|---|---|---|
__floatunsidf, __floatundidf, __floatdidf
|
High-bit-set values convert to garbage |
float("9"*51 + "e-39"), array.array('Q', [...]), any mp_obj_new_int_from_uint(>2³¹) → double |
__eqdf2, __nedf2, __ledf2, __gedf2, __ltdf2, __gtdf2
|
NaN treated as ordered/equal |
== / != / <= / >= with NaN; math.isclose, set/dict NaN keys, x != x
|
pow(-1, NaN) (libnix) |
Returns 1.0, CPython expects NaN |
(-1) ** float('nan') |
tgamma(-inf) (libnix) |
Returns +inf, CPython raises |
math.gamma(-inf) |
__fixdfsi (clib2) |
Calls IEEEDPFix which under vamos aborts the whole emulator on NaN |
hash(float('nan')) (gcc 6.5 emits __fixdfsi for mp_float_hash's bit-level body after opt) |
Use Amiberry when behaviour needs Kickstart — Workbench launch, the icon
/ datatypes / intuition libraries, 68881 FPU, persistent REPL history in
S:, anything where AmigaDOS shell quirks matter, anything where vamos
"close enough" isn't.
One-shot scripted runs use a startup hook: S:user-startup runs
py0:boot if present (where py0: is the repo root mounted as an
Amiberry hard drive), and boot redirects its own output to
py0:boot.log that the host can read directly. Custom tooling, but
handy for a single canned script.
tests/run-tests.py -t does now drive the emulated Amiga like a
bare-metal board, over Amiberry's TCP serial port. (Earlier the RX path
looked broken — SER: delivered host→Amiga zero bytes — but that was
the raw serial handler: it's single-open and doesn't assert the CTS/RTS
handshake the bridge needs. The AUX console works fine.)
Setup, Amiga side — attach a shell to the serial console on boot, with
an explicit baud, in S:user-startup:
newshell AUX:57600/8n1
Use 57600 (or 38400). 115200 is flaky on the Amiga. At 57600/8n1 the console is byte-clean both directions (no spurious CR doubling).
Setup, host side — tools/pyboard.py can't speak TCP, so a tiny bridge
relays the socket to/from stdin/stdout as a raw serial line. It lives at
tests/ports/amiga/amiga_serial.py and runs via pyboard's exec:
transport:
cd tests
./run-tests.py -t "exec:python3 $PWD/ports/amiga/amiga_serial.py --bridge --wait" \
extmod/socket_tcp_basic.py extmod/time_ms_us.pyPlatform auto-detects as amiga. --wait polls for the console so
run-tests is the sole connection.
Three port changes made this work (pyboard soft-resets with Ctrl-D before every test, which the port previously treated as "exit to shell"):
-
main.cfactors the runtime intoamiga_runtime_init/deinitand wraps the REPL in a soft-reset loop: Ctrl-D / SystemExit in the raw REPL rebuilds the VM, printsMPY: soft reboot, and re-enters (like a real board); friendly-REPL Ctrl-D still drops to the AmigaShell. -
mphalport.caddsamiga_stdin_hit_eof()so a client disconnect (FGetC < 0) exits cleanly instead of spinning in the soft-reset loop. -
main.copensbsdsocket.library/ AmiSSL once at startup, not per soft reset — Amiberry'sbsdsocklib_Closepumps SDL/Cocoa events off the main thread and SIGABRTs the emulator on macOS, so a long sweep of per-reset closes used to crash it.
Practical limits:
-
Pacing: leave the bridge at its default 5 ms/byte. The link has no
flow control;
--delay 0.002is fine for the helper's own paste-modeexec()but corrupts pyboard's raw-paste protocol (read_until timeouts, run-tests hangs minutes before the first test). -
Per-test / small batches only. A full-suite sweep over serial is
impractical — run-tests ships every test body + import prologue over the
link (~1 hr for
basics/) — and a single destabilising test (e.g.memoryerror.pyexhausting the heap) desyncs the shell and cascades into false failures. For bulk, use vamos (above) or the on-device runner (below); the serial path is for verifying the handful of tests that need real Kickstart. -
One run-tests session per boot. The AUX console doesn't survive a
host disconnect, so reset the emulator (
reset_emulation, hard) before each session;--waitthen handles boot timing.
The bridge is also a standalone REPL driver: amiga_serial.py -c "code",
-f file.py, --shell "cmd", or no args for the banner.
After the FPCR rounding-precision fix (the time_res.py note above), the
whole float/ suite (68 tests) was run over serial with the FPU enabled
to confirm double precision is restored end-to-end:
cd tests
./run-tests.py -t "exec:python3 $PWD/ports/amiga/amiga_serial.py --bridge --wait" float/*.pyResult: 48 passed, 11 skipped, 9 reported failures. All the
precision-sensitive tests pass (float1, float_format,
float_format_ints, math_fun, math_constants, math_isclose,
int_64_float, string_format, …) — the fix holds. None of the failures
are regressions: the pre-fix binary truncated every double to single
(strictly worse), so the fix can only have improved float results.
-
11 skipped — all
*_intbig/*_endian; features the port doesn't build, expected. -
5 of the 9 "failures" are harness timeouts, not real. Soft-float
arithmetic over the paced (5 ms/byte, no-flow-control) serial link is slow
enough that
run-tests.pyhit its per-test timeout and Ctrl-C'd a still- running test (KeyboardInterrupt: CRASHin the captured.out). Re-run directly against the binary with a generous timeout, all five pass:math_domain,float_divmod,cmath_fun,float_struct_e,string_format_modulo2. These pass under vamos and on-device too — the timeout is a property of the serial transport, not the port. (To get them green in a serial sweep, raise the run-tests timeout or run them via the on-device runner below.) -
4 genuine failures — the pre-existing bebbo soft-float / dtoa tax
(present under vamos too, not caused by the fix):
float_format_accuracy(~72 % exact-repr round-trip,max_err≈4.1e-16≈ 2 ulp),float_parse+float_parse_doubleprec(huge-value overflow yields8.99e+307instead ofinf, plus subnormal edges), andinf_nan_arith(a nan/inf sign quirk inmin/max). See Known failures that are not port bugs.
Running the full basics/ suite (~440 tests) over serial is the textbook
case of why bulk-over-serial is unreliable — three independent failure
modes stacked up, and the raw run-tests.py summary was almost entirely
misleading:
-
Cascade.
basics/memoryerror.pydeliberately exhausts the heap; on the Amiga that desyncs the raw-REPL shell, so the next several tests came backraw REPL failed(b'') and run-tests gave up with "Too many raw REPL failures, aborting" — the run aborted ~⅔ of the way through (326 of ~440 "performed"), leaving the wholememoryview1…with1tail un-run. -
Flakiness. Re-running the tail (with
memoryerrorexcluded) the raw-paste protocol failed different tests each time —bytearray_slice_assign/generator_throwfailed once then passed, whileset_remove/string_format2newly "failed". Timing, not bugs. -
Stray local
.exp. Manybasics/*.py.expfiles left over from the on-device runner (amiga-gen-exp.py, below) are untracked — so run-tests diffed Amiga output against old Amiga captures instead of CPython, turning any nondeterminism into a spurious FAIL.
The honest result only emerged by verifying every test that ever failed
directly against the authoritative reference (host CPython for
CPython-diff tests, the shipped .exp for the two MicroPython-specific
ones). Of the 12 distinct "failures" seen across the serial runs, 10 pass
when run directly — bytearray_slice_assign, class_setname_hazard_rand,
dict_del, generator_throw, memoryerror, memoryview_gc,
memoryview_slice_assign, memoryview_slice_size, set_remove,
string_format2. The remaining two are not real failures either:
-
struct1.py— the documented m68kstruct.calcsizeABI diff (102vs104); see Known failures that are not port bugs. -
memoryview_itemsize.py— legitimately printsSKIP(feature probe), which run-tests only flagged because the stale local.expheld different output.
Bottom line: basics/ is effectively clean on the Amiga — every test
passes (or correctly skips) except the one known struct1 ABI difference.
But do not trust a serial bulk run for this: use vamos (above) or the
on-device runner (below) for authoritative basics/ coverage, and reserve
the serial -t path for the handful of tests that genuinely need real
Kickstart. (When chasing a serial failure, also git status tests/basics
first — an untracked .py.exp means you're diffing against a stale capture,
not CPython.) Those stray .py.exp files were since removed (797 of them
across tests/); only the shipped, git-tracked ones remain, so a serial run
now diffs against CPython again. They will regenerate if you re-run the
on-device runner below.
The full extmod/ suite (206 tests) ran over serial in one clean session
— no cascade (nothing in extmod/ exhausts the heap the way
basics/memoryerror.py does). Raw summary: 91 performed, 74 passed, 115
skipped, 17 failed. The 115 skips are absent subsystems (asyncio,
machine_*, vfs_fat/vfs_lfs, tls_*, cryptolib_*, little-endian
uctypes_*, websocket) — expected for this port.
But the serial flakiness is just as real here as for basics/: verifying
the 17 failures (the surprising ones re-run under vamos, the rest already
triaged on Amiberry) splits them three ways —
-
9 were transport artifacts — they pass under vamos:
deflate_compress,deflate_compress_memory_error,framebuf_ellipse,framebuf_polygon,framebuf8,random_extra,random_extra_float,time_mktime,vfs_basic. (deflate_compresseven desynced the serial shell when re-run directly, yet passes cleanly under vamos — pure serial fragility.) -
1 is environmental, not a port bug:
socket_tcp_basicneeds a routable TCP path from the Amiga to the host test server, which neither the serial bridge nor vamos provides. -
7 are genuine, already-documented gaps:
select_poll_basic&select_ipoll(noselect()/poll()),ssl_basic/ssl_ioctl/ssl_keycert(AmiSSL API gaps),vfs_userfs, andplatform_basic(platform-identity diff).
So over half the reported serial failures (9 of 17) were noise. The real
extmod/ picture is ~83 effective passes with only the seven known gaps
(plus the environmental socket test) — unchanged from the documented
baseline. As with basics/: treat a serial bulk summary as a list of
suspects, not failures, and confirm each under vamos or directly.
The on-device runner walks a single test directory, runs each .py test
via amiga.execute("micropython … >T:…"), and compares the captured
output against the matching .py.exp file. No CPython to diff against
on the Amiga side, so it relies entirely on the .exp files.
Upstream only ships .exp files where MicroPython output is expected
to differ from CPython. Generate the rest first:
tools/amiga-gen-exp.py tests/basics tests/float tests/io \
tests/import tests/micropython tests/misc tests/cmdline tests/stressExisting .exp files are never overwritten — many of them use the
######## wildcard or regex matching that the test framework depends on.
Delete a specific .exp and re-run if you need to regenerate one.
.exp files are not checked in. Add tests/**/*.py.exp to
.git/info/exclude if the chatter in git status bothers you.
The default 4–8 KB stack is too small for the deep compile-time
recursion a handful of tests trigger (e.g. try_except_break.py); a
stack overflow there manifests as Software Failure 8000000B (Line F
trap) rather than a clean Python RuntimeError.
1> Stack 32768
1> cd py0:
1> micropython tools/amiga-runtests.py tests/basics
1> micropython tools/amiga-runtests.py tests/float T:my-results/
The second argument is the result directory (default T:mp-test-results/).
On FAIL, <dir>/<test>.py.out (captured stdout) and <dir>/<test>.py.exp
(expected reference) land there. On pass / skip, stale artefacts from a
previous run are deleted.
T: lives in RAM under AmigaOS — a clean reboot wipes the artefacts.
The runner skips test classes the port can't pass for structural
reasons, matching the equivalent feature-check skips in run-tests.py:
-
int_big*/*_intbig.py— no arbitrary-precision ints (MICROPY_LONGINT_IMPL_LONGLONG). -
*_endian.py— big-endian platform, most tests assume little-endian. -
native_*/viper_*— Phase 12 (@micropython.nativecall support is the next emitter rework). -
cmd_*/repl_*— need# cmdline:directives, regex.expmatching, and PTY interaction; onlytests/run-tests.pycan drive those. - A small frozenset of one-off tests that hit disabled extmod features
(
weakref_*,fun_code,attrtuple2.py, etc.).
The Amiga port emits \r\n on stdout even when redirected to a file —
this is mp_hal_stdout_tx_strn_cooked doing the AmigaShell-compatible
thing. The on-device runner strips trailing \r line by line before
comparing against the LF .exp reference, so the .exp files generated
by amiga-gen-exp.py stay platform-neutral. Any host-side diff against
captured output needs the same normalisation:
diff <(tr -d '\r' < captured.out) reference.exp-
68020fpu— needs 68881; vamos has no 68881/68882 emulation. -
Workbench launch —
WBStartup,icon.library, tooltype handling. Vamos has noicon.library. -
AmiSSL (Phase 28) — needs Kickstart and a real
amisslmaster.libraryinstall (see SSL section below).
TLS support is on in all three shipped variants. Exercising ssl on
target needs:
-
AmiSSL v5 installed.
amisslmaster.librarymust be reachable viaLIBS:. The conventional install puts it atSYS:AmiSSL/Libs/amisslmaster.librarywith the CA bundle in a c_rehash dir atSYS:AmiSSL/certs/. AmiSSL's own installer addsAssign AmiSSL: SYS:AmiSSLandAssign LIBS: AmiSSL:Libs ADDtoS:user-startup. -
AmiSSL assigns in scope at micropython launch. Watch the
ordering in
user-startup: if the boot hook runspy0:bootbefore the AmiSSL assigns, the boot script must add them itself orimport sslwill raise onSSLContext().
Recommended verify path:
import ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.set_default_verify_paths() # preferred over load_verify_locationsset_default_verify_paths() sidesteps two AmigaOS-specific
gotchas with manual cert loading:
-
Trailing-slash capath fails.
load_verify_locations(capath= "AmiSSL:certs/")concatenates internally to a double-slash that AmigaDOS interprets as parent-dir reference, and lookups silently miss. Pass"AmiSSL:certs"(no trailing slash) if you want to use it explicitly. - AmiSSL c_rehash uses the old (pre-OpenSSL-1.0.0) subject hash algorithm. New-hash filenames silently miss in lookups even though byte-identical files exist under their old-hash names in the same directory.
CERT_REQUIRED HTTPS against TLS-1.3-eager fronts (Cloudflare,
GitHub) completes the handshake (cert chain verifies fine) but
the server closes the connection before any application data can
be written: ws.write returns EPIPE / broken pipe with zero
bytes sent. Hosts that don't strict-front like this (e.g.
www.python.org) work cleanly.
Reproducible with AmiSSL's own openssl s_client — same
CONNECTION CLOSED BY SERVER + tls_retry_write_records:Broken pipe trace, with all four certs in the chain reporting
verify return:1. So it is below the MicroPython layer. Workaround
attempts inside s_client (forcing classic X25519 to skip the
post-quantum MLKEM hybrid, disabling session tickets, forcing
TLS 1.2) either reproduce the same close or get rejected outright
at ClientHello. See docs/phase28-ssl-plan.md for the full
diagnostic log.
tools/amiga-runtests.py does not gate these out, so they run
when the on-device runner walks extmod/. A handful require
features we don't yet expose (ssl.wrap_socket module-level
legacy form, cadata= parameter, server-mode handshake against a
canned cert pair) and will fail; the SSLContext-shape tests
(ssl_sslcontext.py, ssl_sslcontext_verify_mode.py etc.) should
pass.
tests/ports/amiga/test_intuition_smoke.py covers the module-registration
and argument-validation surface; it runs under vamos and prints OK
on success. The actual modal dialog can't be tested headlessly —
vamos's intuition.library stub is a no-op print, and the requester
needs a real public screen.
For end-to-end visual confirmation under Amiberry, from the REPL:
>>> from amiga import intuition
>>> intuition.easy_request("Title", "Pick one.\nMulti-line works.",
... ["Apple", "Banana", "Cancel"])
# requester pops; left → 0, middle → 1, right → 2
>>> intuition.auto_request("Replace existing file?")
# returns True for Yes, False for No
>>> intuition.message("Done.")
# single-button requester; clicking OK returns Noneintuition.library opens its own screen if no public screen is
already up, so the requester appears regardless of whether
Workbench is loaded. OSError(EIO) is reserved for the rare case
where EasyRequestArgs returns -1 (genuine intuition-side
failure, not a missing screen).
tests/ports/amiga/test_asl_smoke.py covers the module-registration,
arg-shape validation, and the multi=True + save=True
ValueError guard; it runs under vamos and prints OK on success.
The actual file-pick dialog needs Amiberry or real hardware.
For end-to-end visual confirmation:
>>> from amiga import asl
>>> asl.file_request(title="Pick a script", initial_drawer="Sys:")
'Sys:Prefs/Workbench' # picked, or
None # cancelled
>>> asl.file_request(save=True, initial_file="out.txt")
'Ram Disk:out.txt'
>>> asl.file_request(multi=True, pattern="#?.py")
['Work:scripts/foo.py', 'Work:scripts/bar.py']
>>> asl.file_request(drawers_only=True)
'Work:scripts'file_request runs the underlying AllocAslRequest +
AslRequest calls on a 32 KB scratch stack via StackSwap, so
the dialog works even from a default-stack shell prompt — no
Stack 32768 prelude required.
tests/ports/amiga/test_rexx_polish.py covers the C-primitive +
Python-facade surface area; it runs under vamos and prints OK.
Real driving of an ARexx host needs rexxsyslib.library and a
running peer, so it lives under Amiberry / real hardware.
For end-to-end visual confirmation:
>>> import amiga
>>> amiga.rexx_open() # so we have a port to be queried
'MICROPYTHON.1'
>>> amiga.rexx_exists("MICROPYTHON.1")
True
>>> "MICROPYTHON.1" in amiga.rexx_list()
True
>>> from amiga import RexxClient
>>> with RexxClient("MICROPYTHON.1") as c:
... # ... a `rexx_serve()` loop on another task would handle this
... pass
>>> # Forgetting close() is OK -- amiga_rexx_shutdown picks it up
>>> # on process exit so the MsgPort doesn't leak.Driving a real host (DOpus, IBrowse, YAM) in a tight loop is what
RexxClient was built for: the persistent reply MsgPort saves
the CreateMsgPort / DeleteMsgPort cost per send. The one-shot
amiga.rexx_send / amiga.rexx helpers are still the right
shape for single sends.
tests/ports/amiga/test_platform_smoke.py covers the CPython-shaped
platform surface plus the underlying amiga.* accessors; it
runs under vamos. platform.amiga_info() and amiga.chipset()
need graphics.library (not stubbed by vamos) and are skipped
when that fails.
For end-to-end confirmation under Amiberry:
>>> import platform
>>> platform.amiga_info()
'CPU: 68020 | FPU: 68881 | Chipset: AGA | Kickstart: 45.57 | Chip: 1856KB | Fast: 14336KB'
>>> platform.machine(), platform.release()
('68020', '45.57')The CPU and FPU strings reflect runtime AmigaOS state -- a
standard binary built -m68020 -msoft-float running on a
68040 reports '68040' / '68040', not the compile-time target.
Memory values are currently free bytes (AvailMem(MEMF_*)),
not total installed.
tests/ports/amiga/test_os_smoke.py covers the surface of the
frozen os.py / _ospath.py extensions: module assembly via
from uos import *, the _osamiga re-exports (chmod,
getprotect, FIBF_*), makedirs, walk, and every os.path
helper. Under vamos the test also round-trips makedirs / walk
against RAM: (vamos's RAM disk maps to a host temp directory).
chmod round-tripping only fully exercises on Amiberry because
vamos's SetProtection is a partial stub.
For interactive REPL confirmation under Amiberry:
>>> import os
>>> os.makedirs("RAM:t/a/b", exist_ok=True)
>>> os.path.isdir("RAM:t/a/b")
True
>>> os.chmod("RAM:t/a/b", os.FIBF_DELETE)
>>> hex(os.getprotect("RAM:t/a/b"))
'0x1'
>>> os.chmod("RAM:t/a/b", 0)
>>> hex(os.getprotect("RAM:t/a/b"))
'0x0'
>>> list(os.walk("RAM:t"))
[('RAM:t', ['a'], []), ('RAM:t/a', ['b'], []), ('RAM:t/a/b', [], [])]getprotect returns the raw fib_Protection ULONG;
a set bit denies the operation for RWED, so mask == 0
means "everything allowed". The APSH bits (FIBF_ARCHIVE,
FIBF_PURE, FIBF_SCRIPT, FIBF_HOLD) follow the conventional
"set means yes". This matches the AmigaShell Protect
command's encoding.
tests/ports/amiga/test_icon_smoke.py covers the _icon module
registration, all WB* constants, the error paths (missing path,
non-DiskObject to write, unknown type code to new, non-dict
tooltypes kwarg), and -- when the host can satisfy
GetDefDiskObject (i.e. ENV:sys/def_project.info is reachable) --
a full new() → mutate → write() → read() round trip against
RAM:. Vamos soft-passes the round trip when ENV:sys defaults
aren't available; Amiberry exercises it end-to-end.
For interactive REPL confirmation under Amiberry:
>>> from amiga import icon
>>> d = icon.read("SYS:Prefs")
>>> d.type, d.current_x, d.current_y
('drawer', 80, 24)
>>> list(d.tooltypes)
['SHOW=ICONS']
>>> d.close()
>>>
>>> new = icon.new(
... icon.WBPROJECT,
... default_tool="C:Ed",
... stack_size=8192,
... tooltypes={"WINDOW": "CON:0/0/640/256/Test", "FLAG": None},
... )
>>> icon.write("RAM:test", new)
>>> new.close()
>>> back = icon.read("RAM:test")
>>> back.default_tool, back.stack_size
('C:Ed', 8192)
>>> back.tooltypes["FLAG"]
b''
>>> back.close()Tooltype values come back as bytes (the AmigaOS on-disk
representation), assignments accept str / bytes / None
(None ⇒ flag-style entry, no = separator).
After the Phase 39 extmod opt-ins shipped, the port had targeted
smoke tests for the phase-numbered surfaces (intuition / asl / icon /
catalog / rexx / platform / os-extensions / hashlib / deflate /
btree / wallclock time) but most of the older Amiga-specific surface
had no port-local test. A sweep filled the gap, adding 17 smoke
tests in tests/ports/amiga/ that cover the foundational ports of
the port:
| Test file | Surface |
|---|---|
test_library_proxy_smoke.py |
amiga.Library + .fd-driven trampoline (_amiga.lib_open / lib_call / lib_close) |
test_mem_smoke.py |
_amiga.alloc_vec / free_vec, peek_b/w/l/bytes, poke_b/w/l/bytes, big-endian byte order, MEMF_* constants |
test_amiga_introspection_smoke.py |
_amiga.os_version / heap_info / exists / volumes / assigns / disk_info / match / imatch
|
test_signal_smoke.py |
_amiga.signal / wait_signal + SIGBREAKF_CTRL_* constants |
test_amiga_execute_smoke.py |
_amiga.execute (dos.library SystemTagList() wrap) |
test_workbench_smoke.py |
_amiga.launched_from_workbench / wb_selected_files / tooltype
|
test_history_smoke.py |
REPL history ring buffer (_amiga.readline_history / readline_push_history) |
test_time_monotonic_smoke.py |
time.ticks_ms / ticks_us / ticks_cpu / ticks_add / ticks_diff / sleep_ms / sleep_us
|
test_os_envvar_smoke.py |
os.getenv / putenv / unsetenv ↔ AmigaDOS ENV: + os.chmod round-trip via getprotect
|
test_vfs_smoke.py |
VfsAmiga file I/O: open r/w/a / b+text, write / read / readline / readlines, seek / tell, ilistdir, stat, rename, mkdir / rmdir / remove, chdir / getcwd, error paths |
test_stdio_smoke.py |
sys.stdin / stdout / stderr -- POSIX fd numbers, write direction guards, TextIOWrapper method set |
test_json_smoke.py |
Port-local modjson -- primitives, composites, whitespace tolerance, string escapes, error paths, dumps/loads round-trip |
test_socket_smoke.py |
socket module surface (constants pinned to AmigaOS bsdsocket numbering, OSError when bsdsocket not present) |
test_ssl_smoke.py |
ssl.SSLContext surface (constants pinned to CPython values, OSError when AmiSSL not present) |
test_urequests_smoke.py |
Frozen urequests -- _parse_url, _quote, _urlencode, verb surface, Response class shape |
test_floatconv_smoke.py |
ports/amiga/floatconv.c libgcc / libm wraps (__floatunsidf / __floatdidf / __wrap_pow / __wrap_tgamma / __fixdfsi / NaN comparison) |
test_native_emitter_smoke.py |
Regression test for the MOVEQ encoding fix (see below) |
All 17 pass under vamos on the standard variant. The
test_workbench_smoke.py / test_socket_smoke.py /
test_ssl_smoke.py / test_amiga_execute_smoke.py files document
which assertions only fully exercise on Amiberry / real hardware
(Workbench launch state, live bsdsocket I/O, live AmiSSL handshakes,
live SystemTagList command execution) and the test structure accepts
either the vamos-stub or live-success path so the same file is
portable to the Amiberry rig.
Real bug found during the sweep. While drafting
test_native_emitter_smoke.py, every @micropython.native binary
op against a small-int literal in the range -64..63 was raising
TypeError: unsupported types for __X__: 'int', '' or wandering
into a vamos invalid-memory crash. Root cause was a one-character
typo in py/asm68k.h asm_68k_moveq: the destination data register
was being placed at bit position 8 instead of 9, so
MOVEQ #1, D2 (wanted opcode 0x7401) was generating 0x7201 =
MOVEQ #1, D1. That clobbered the operand register the lhs was
about to be loaded into, and the rhs register the binary op
expected stayed stale. Fixed in commit fb1aeab92; the regression
test pins both sides of the MOVEQ-range boundary (literal 63 vs 64)
plus <, >, ==, !=, +, -, *, branch-on-literal,
while-loop with literal-bounded condition, and a viper compare for
completeness. Bug had been present since the original 68k native
emitter commit (42beaaeca, Phase 12).
One Amiga-specific surface remains without a targeted smoke
test: Ctrl+C interrupt polling (amiga_check_ctrl_c invoked by
the VM hook every 1024 bytecodes). vamos's exec.library
Signal / Wait emulation doesn't deliver signals to the same
task, so the round trip can only be verified on Amiberry / real
hardware. The hook itself is exercised implicitly by every test
that loops more than 1024 bytecodes without crashing.
tests/ports/amiga/test_catalog_smoke.py covers the _catalog
module registration, the language() accessor, error paths
(missing catalog, missing name, non-str language kwarg), the
context-manager surface, and -- when the host can satisfy a
catalog open -- a lookup round trip. Vamos has no real
locale.library, so it soft-passes the round-trip block;
Amiberry covers it end-to-end against Sys/monitors.catalog.
For interactive REPL confirmation under Amiberry:
>>> from amiga import catalog
>>> catalog.language()
'english'
>>> # Stock Workbench is English -- declaring the binary's built-in
>>> # language as "german" forces a translation lookup that actually
>>> # loads the file rather than treating it as already in place.
>>> with catalog.open("Sys/monitors.catalog",
... language="english",
... built_in_language="german") as cat:
... print(cat.lookup(99999, "FALLBACK"))
...
FALLBACKClosed catalogs are safe to call lookup on — GetCatalogStr
treats NULL as "no catalog" and returns the caller's default. The
context manager closes on block exit so the underlying
locale.library allocation is released promptly.
.github/workflows/ports_amiga.yml runs on workflow_dispatch with a
ref input (branch / tag / commit, default amiga-port). One job
inside stefanreinauer/amiga-gcc:latest builds mpy-cross once, then
all four variants sequentially. Each binary uploads as a separate
artifact named micropython-amiga-<variant>-<ref>-<sha>.
CI does not run the test suite — it's a cross-compile gate plus an artefact producer. Functional testing happens locally against vamos / Amiberry.
Runs the same image with the same commands, so local and CI binaries are bit-identical:
tools/amiga-build.sh # all four variants
tools/amiga-build.sh standard # one
tools/amiga-build.sh standard 68040 # several
tools/amiga-build.sh clean # clean all build dirsFiles are written as the host user (via --user), not root.
# Full vamos sweep against the standard variant
cd ports/amiga && make
export MICROPY_MICROPYTHON="$(pwd)/../../tools/amiga-vamos-run.sh"
cd ../../tests
./run-tests.py -d basics float io import micropython misc \
-e "inlineasm|machine_|thread|extmod/ussl|extmod/uasync"
# Interactive REPL under vamos
tools/amiga-vamos-repl.sh
# Generate .exp files for on-device runner
tools/amiga-gen-exp.py tests/basics tests/float tests/io tests/import \
tests/micropython tests/misc tests/cmdline tests/stress
# Then boot Amiberry, `Stack 32768`, `cd py0:`, and
# `micropython tools/amiga-runtests.py tests/basics`
# CI-identical build all variants
tools/amiga-build.sh