From 089e2569d489a3c25f5d3301a4edb762c0257ca2 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 25 Apr 2026 01:06:23 +0000 Subject: [PATCH] erlang: conformance.sh + scoreboard (358/358 across 9 suites) --- lib/erlang/conformance.sh | 153 +++++++++++++++++++++++++++++++++++++ lib/erlang/scoreboard.json | 16 ++++ lib/erlang/scoreboard.md | 18 +++++ plans/erlang-on-sx.md | 5 +- 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100755 lib/erlang/conformance.sh create mode 100644 lib/erlang/scoreboard.json create mode 100644 lib/erlang/scoreboard.md diff --git a/lib/erlang/conformance.sh b/lib/erlang/conformance.sh new file mode 100755 index 00000000..7b0d7121 --- /dev/null +++ b/lib/erlang/conformance.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# Erlang-on-SX conformance runner. +# +# Loads every erlang test suite via the epoch protocol, collects +# pass/fail counts, and writes lib/erlang/scoreboard.json + .md. +# +# Usage: +# bash lib/erlang/conformance.sh # run all suites +# bash lib/erlang/conformance.sh -v # verbose per-suite + +set -uo pipefail +cd "$(git rev-parse --show-toplevel)" + +SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}" +if [ ! -x "$SX_SERVER" ]; then + SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe" +fi +if [ ! -x "$SX_SERVER" ]; then + echo "ERROR: sx_server.exe not found." >&2 + exit 1 +fi + +VERBOSE="${1:-}" +TMPFILE=$(mktemp) +OUTFILE=$(mktemp) +trap "rm -f $TMPFILE $OUTFILE" EXIT + +# Each suite: name | counter pass | counter total +SUITES=( + "tokenize|er-test-pass|er-test-count" + "parse|er-parse-test-pass|er-parse-test-count" + "eval|er-eval-test-pass|er-eval-test-count" + "runtime|er-rt-test-pass|er-rt-test-count" + "ring|er-ring-test-pass|er-ring-test-count" + "ping-pong|er-pp-test-pass|er-pp-test-count" + "bank|er-bank-test-pass|er-bank-test-count" + "echo|er-echo-test-pass|er-echo-test-count" + "fib|er-fib-test-pass|er-fib-test-count" +) + +cat > "$TMPFILE" << 'EPOCHS' +(epoch 1) +(load "lib/erlang/tokenizer.sx") +(load "lib/erlang/parser.sx") +(load "lib/erlang/parser-core.sx") +(load "lib/erlang/parser-expr.sx") +(load "lib/erlang/parser-module.sx") +(load "lib/erlang/transpile.sx") +(load "lib/erlang/runtime.sx") +(load "lib/erlang/tests/tokenize.sx") +(load "lib/erlang/tests/parse.sx") +(load "lib/erlang/tests/eval.sx") +(load "lib/erlang/tests/runtime.sx") +(load "lib/erlang/tests/programs/ring.sx") +(load "lib/erlang/tests/programs/ping_pong.sx") +(load "lib/erlang/tests/programs/bank.sx") +(load "lib/erlang/tests/programs/echo.sx") +(load "lib/erlang/tests/programs/fib_server.sx") +(epoch 100) +(eval "(list er-test-pass er-test-count)") +(epoch 101) +(eval "(list er-parse-test-pass er-parse-test-count)") +(epoch 102) +(eval "(list er-eval-test-pass er-eval-test-count)") +(epoch 103) +(eval "(list er-rt-test-pass er-rt-test-count)") +(epoch 104) +(eval "(list er-ring-test-pass er-ring-test-count)") +(epoch 105) +(eval "(list er-pp-test-pass er-pp-test-count)") +(epoch 106) +(eval "(list er-bank-test-pass er-bank-test-count)") +(epoch 107) +(eval "(list er-echo-test-pass er-echo-test-count)") +(epoch 108) +(eval "(list er-fib-test-pass er-fib-test-count)") +EPOCHS + +timeout 120 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1 + +# Parse "(N M)" from the line after each "(ok-len ...)" marker. +parse_pair() { + local epoch="$1" + local line + line=$(grep -A1 "^(ok-len $epoch " "$OUTFILE" | tail -1) + echo "$line" | sed -E 's/[()]//g' +} + +TOTAL_PASS=0 +TOTAL_COUNT=0 +JSON_SUITES="" +MD_ROWS="" + +idx=0 +for entry in "${SUITES[@]}"; do + name="${entry%%|*}" + epoch=$((100 + idx)) + pair=$(parse_pair "$epoch") + pass=$(echo "$pair" | awk '{print $1}') + count=$(echo "$pair" | awk '{print $2}') + if [ -z "$pass" ] || [ -z "$count" ]; then + pass=0 + count=0 + fi + TOTAL_PASS=$((TOTAL_PASS + pass)) + TOTAL_COUNT=$((TOTAL_COUNT + count)) + status="ok" + marker="✅" + if [ "$pass" != "$count" ]; then + status="fail" + marker="❌" + fi + if [ "$VERBOSE" = "-v" ]; then + printf " %-12s %s/%s\n" "$name" "$pass" "$count" + fi + if [ -n "$JSON_SUITES" ]; then JSON_SUITES+=","; fi + JSON_SUITES+=$'\n ' + JSON_SUITES+="{\"name\":\"$name\",\"pass\":$pass,\"total\":$count,\"status\":\"$status\"}" + MD_ROWS+="| $marker | $name | $pass | $count |"$'\n' + idx=$((idx + 1)) +done + +printf '\nErlang-on-SX conformance: %d / %d\n' "$TOTAL_PASS" "$TOTAL_COUNT" + +# scoreboard.json +cat > lib/erlang/scoreboard.json < lib/erlang/scoreboard.md < ...` timeout clause (use SX timer primitive) — **9 new eval tests**; synchronous-scheduler semantics: `after 0` polls once; `after Ms` fires when runnable queue drains; `after infinity` = no timeout - [x] `exit/1`, basic process termination — **9 new eval tests**; `exit/2` (signal another) deferred to Phase 4 with links -- [ ] Classic programs in `lib/erlang/tests/programs/`: +- [x] Classic programs in `lib/erlang/tests/programs/`: - [x] `ring.erl` — N processes in a ring, pass a token around M times — **4 ring tests**; suspension machinery rewritten from `shift`/`reset` to `call/cc` + `raise`/`guard` - [x] `ping_pong.erl` — two processes exchanging messages — **4 ping-pong tests** - [x] `bank.erl` — account server (deposit/withdraw/balance) — **8 bank tests** - [x] `echo.erl` — minimal server — **7 echo tests** - [x] `fib_server.erl` — compute fib on request — **8 fib tests** -- [ ] `lib/erlang/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md` +- [x] `lib/erlang/conformance.sh` + runner, `scoreboard.json` + `scoreboard.md` — **358/358 across 9 suites** - [ ] Target: 5/5 classic programs + 1M-process ring benchmark runs ### Phase 4 — links, monitors, exit signals @@ -99,6 +99,7 @@ Core mapping: _Newest first._ +- **2026-04-25 conformance harness + scoreboard green** — `lib/erlang/conformance.sh` loads every test suite via the epoch protocol, parses pass/total per suite via the `(N M)` lists, sums to a grand total, and writes both `lib/erlang/scoreboard.json` (machine-readable) and `lib/erlang/scoreboard.md` (Markdown table with ✅/❌ markers). 9 suites × full pass = 358/358. Exits non-zero on any failure. `bash lib/erlang/conformance.sh -v` prints per-suite counts. Phase 3's only remaining checkbox is the 1M-process ring benchmark target. - **2026-04-25 fib_server.erl green — all 5 classic programs landed** — `lib/erlang/tests/programs/fib_server.sx` with 8 tests. Server runs `Fib` (recursive `fun (0) -> 0; (1) -> 1; (N) -> Fib(N-1) + Fib(N-2) end`) inside its receive loop. Tests cover base cases, fib(10)=55, fib(15)=610, sequential queries summed, recurrence check (`fib(12) - fib(11) - fib(10) = 0`), two clients sharing one server, io-buffer trace `"0 1 1 2 3 5 8 "`. Total suite 358/358. Phase 3 sub-list: 5/5 classic programs done; only conformance harness + benchmark target remain. - **2026-04-25 echo.erl green** — `lib/erlang/tests/programs/echo.sx` with 7 tests. Server: `receive {From, Msg} -> From ! Msg, Loop(); stop -> ok end`. Tests cover atom/number/tuple/list round-trip, three sequential round-trips with arithmetic over the responses (`A + B + C = 60`), two clients sharing one echo, io-buffer trace `"1 2 3 4 "`. Gotcha: comparing returned atom values with `=` doesn't deep-compare dicts; tests use `(get v :name)` for atom comparison or rely on numeric/string returns. Total suite 350/350. - **2026-04-24 bank.erl green** — `lib/erlang/tests/programs/bank.sx` with 8 tests. Stateful server pattern: `Server = fun (Balance) -> receive ... Server(NewBalance) end end` recursively threads balance through each iteration. Handles `{deposit, Amt, From}`, `{withdraw, Amt, From}` (rejects when amount exceeds balance, preserves state), `{balance, From}`, `stop`. Tests cover deposit accumulation, withdrawal within balance, insufficient funds with state preservation, mixed transactions, clean shutdown, two-client interleave. Total suite 343/343.