lumbda/asm/test.sh
russell@unturf.com 54c4c651bb portal-rng: asm xoshiro256** + cross-impl tests + default seed=0
Completes ticket 0001 started in 27f468c. All three impls now carry
bit-identical xoshiro256**; portal state round-trips across process
boundaries in every producer x consumer cell (Python <-> C <-> asm).

asm impl:
- 4 new builtins: random-seed!, random-int, random-state, random-state!
- g_rng_state in BSS (4 x u64); rng_splitmix64_step, rng_seed, rng_next
- Binary portal header bumped LUMBDAB1/48 -> LUMBDAB2/80; carries
  32 bytes of rng state at offsets 40..64, reserved moved to 72
- No float support in asm, so (random) intentionally omitted there
- _start seeds with 0 so the stream is deterministic from startup

Python + C (supplements 27f468c):
- rng_seed(0) auto-invoked at module load / register_portal_builtins
  so (random) without explicit (random-seed!) returns a real value
  instead of the all-zero xoshiro fixed point

Tests:
- tests/functional.lsp: 7 new shared assertions (Python + C)
- asm/test.sh: 5 new asm-local assertions (142 -> 147)
- tests/portal-rng-save.lsp / portal-rng-load.lsp: portable S-expression
  portal that captures both state AND next-5 baseline so loader self-
  verifies without a separate harness
- tests/portal-cross-test.sh: 9 new producer x consumer RNG cells; all
  18 cells pass end-to-end

Verified: seed=42, (random-int 1000000) draws 1..10 =
558742 543102 559009 124193 317476 750584 200754 814407 344958 929085
identical in Python, C, and asm.

unmoad scan: zero new findings in added code.
2026-04-20 11:11:47 -04:00

310 lines
14 KiB
Bash

#!/bin/bash
# test.sh — Unit, integration, and functional tests for the asm Scheme interpreter
#
# Usage: bash test.sh
#
# Tests verify expected output. Any mismatch = FAIL.
set -e
cd "$(dirname "$0")"
PASS=0
FAIL=0
UL="${LUMBDA_BIN:-./lumbda}"
check() {
local name="$1" input="$2" expected="$3"
local got
got=$(echo "$input" | $UL 2>/dev/null | tr -d '\n')
if [ "$got" = "$expected" ]; then
PASS=$((PASS + 1))
else
FAIL=$((FAIL + 1))
echo "FAIL: $name"
echo " input: $input"
echo " expected: $expected"
echo " got: $got"
fi
}
echo "═══════════════════════════════════════════════════════"
echo "asm/lumbda test suite"
echo "═══════════════════════════════════════════════════════"
# ─── UNIT TESTS: individual primitives ────────────────────
echo "[unit] arithmetic"
check "add" "(+ 1 2)" "3"
check "sub" "(- 10 3)" "7"
check "mul" "(* 6 7)" "42"
check "nested" "(+ (* 3 4) (- 10 5))" "17"
check "negate" "(- 5)" "-5"
check "zero-add" "(+)" "0"
check "one-mul" "(*)" "1"
check "multi-add" "(+ 1 2 3 4)" "10"
check "multi-mul" "(* 2 3 4)" "24"
check "modulo" "(modulo 10 3)" "1"
check "remainder" "(remainder 10 3)" "1"
check "quotient" "(quotient 10 3)" "3"
check "abs" "(abs -7)" "7"
check "min" "(min 3 1 4)" "1"
check "max" "(max 3 1 4)" "4"
check "expt" "(* 2 2 2 2 2 2 2 2 2 2)" "1024"
check "div" "(/ 10 2)" "5"
check "expt-bi" "(expt 2 10)" "1024"
check "isqrt-0" "(isqrt 0)" "0"
check "isqrt-1" "(isqrt 1)" "1"
check "isqrt-perfect" "(isqrt 144)" "12"
check "isqrt-floor" "(isqrt 10)" "3"
check "isqrt-big" "(isqrt 1000000000000)" "1000000"
check "random-int-1" "(begin (random-seed! 42) (random-int 1000000))" "558742"
check "random-int-2" "(begin (random-seed! 42) (random-int 1000000) (random-int 1000000))" "543102"
check "random-reseed" "(begin (random-seed! 42) (random-int 1000000) (random-seed! 42) (random-int 1000000))" "558742"
check "random-state-len" "(begin (random-seed! 42) (length (random-state)))" "8"
check "random-state-restore" "(begin (random-seed! 42) (define s (random-state)) (random-int 1000000) (random-state! s) (random-int 1000000))" "558742"
check "odd?" "(odd? 3)" "#t"
check "odd?-even" "(odd? 4)" "#f"
check "even?" "(even? 4)" "#t"
check "even?-odd" "(even? 3)" "#f"
echo "[unit] comparison"
check "eq" "(= 5 5)" "#t"
check "neq" "(= 5 6)" "#f"
check "lt" "(< 3 5)" "#t"
check "gt" "(> 5 3)" "#t"
check "le" "(<= 3 3)" "#t"
check "ge" "(>= 5 5)" "#t"
check "lt-false" "(< 5 3)" "#f"
echo "[unit] booleans"
check "not-false" "(not #f)" "#t"
check "not-true" "(not #t)" "#f"
check "not-num" "(not 42)" "#f"
echo "[unit] predicates"
check "zero?" "(zero? 0)" "#t"
check "zero?-1" "(zero? 1)" "#f"
check "null?-nil" "(null? '())" "#t"
check "null?-pair" "(null? (cons 1 2))" "#f"
check "pair?" "(pair? (cons 1 2))" "#t"
check "pair?-num" "(pair? 42)" "#f"
check "number?" "(number? 42)" "#t"
check "boolean?" "(boolean? #t)" "#t"
check "symbol?" "(symbol? 'x)" "#t"
check "positive?" "(positive? 5)" "#t"
check "negative?" "(negative? -3)" "#t"
check "integer?" "(integer? 42)" "#t"
check "integer?-f" "(integer? #t)" "#f"
check "list?" "(list? (list 1 2))" "#t"
check "list?-f" "(list? 42)" "#f"
echo "[unit] pairs & lists"
check "cons" "(cons 1 2)" "(1 . 2)"
check "car" "(car (cons 1 2))" "1"
check "cdr" "(cdr (cons 1 2))" "2"
check "list" "(list 1 2 3)" "(1 2 3)"
check "length" "(length (list 1 2 3 4 5))" "5"
check "car-list" "(car (list 10 20 30))" "10"
check "cdr-list" "(car (cdr (list 10 20 30)))" "20"
check "append" "(append (list 1 2) (list 3 4))" "(1 2 3 4)"
check "append-nil" "(append '() (list 1))" "(1)"
check "reverse" "(reverse (list 1 2 3))" "(3 2 1)"
check "member" "(car (member 3 (list 1 2 3 4)))" "3"
check "member-f" "(member 9 (list 1 2 3))" "#f"
check "assoc" "(cdr (assoc 'b (list (cons 'a 1) (cons 'b 2))))" "2"
echo "[unit] and/or"
check "and-all" "(and 1 2 3)" "3"
check "and-false" "(and 1 #f 3)" "#f"
check "and-empty" "(and)" "#t"
check "or-first" "(or 1 2)" "1"
check "or-false" "(or #f #f 42)" "42"
check "or-empty" "(or)" "#f"
# ─── INTEGRATION TESTS: combined features ────────────────
echo "[integration] special forms"
check "if-true" "(if #t 1 2)" "1"
check "if-false" "(if #f 1 2)" "2"
check "if-pred" "(if (< 1 2) 42 99)" "42"
check "quote" "(quote (1 2 3))" "(1 2 3)"
check "begin" "(begin 1 2 3)" "3"
check "cond" "(cond ((= 1 2) 10) ((= 1 1) 20) (else 30))" "20"
check "cond-else" "(cond (#f 1) (else 99))" "99"
echo "[integration] bindings"
check "define-val" "(define x 10) (+ x 5)" "15"
check "define-fn" "(define (f x) (+ x 1)) (f 41)" "42"
check "set!" "(define x 1) (set! x 42) x" "42"
check "let" "(let ((x 10) (y 20)) (+ x y))" "30"
check "lambda" "((lambda (x y) (+ x y)) 3 4)" "7"
check "closure" "(define (make-adder n) (lambda (x) (+ x n))) ((make-adder 10) 5)" "15"
echo "[integration] named-let"
check "named-let-sum" "(let loop ((i 5) (acc 0)) (if (= i 0) acc (loop (- i 1) (+ acc i))))" "15"
echo "[integration] recursion"
check "factorial" "(define (fact n) (if (<= n 1) 1 (* n (fact (- n 1))))) (fact 10)" "3628800"
check "fib-rec" "(define (fib n) (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2))))) (fib 10)" "55"
echo "[integration] higher-order"
check "apply-lambda" "((lambda (f x) (f x)) (lambda (n) (* n n)) 7)" "49"
check "map" "(map (lambda (x) (* x x)) (list 1 2 3))" "(1 4 9)"
check "map-add" "(map (lambda (x) (+ x 10)) (list 1 2 3))" "(11 12 13)"
check "filter" "(filter odd? (list 1 2 3 4 5))" "(1 3 5)"
check "filter-all" "(filter even? (list 1 3 5))" "()"
check "fold-left" "(fold-left + 0 (list 1 2 3 4))" "10"
check "fold-sub" "(fold-left - 100 (list 10 20 30))" "40"
echo "[integration] strings"
check "str-length" "(string-length \"hello\")" "5"
check "str-eq" "(string=? \"abc\" \"abc\")" "#t"
check "str-neq" "(string=? \"abc\" \"def\")" "#f"
check "str-to-num" "(string->number \"42\")" "42"
check "char-to-int" "(char->integer (string-ref \"A\" 0))" "65"
echo "[integration] vectors"
check "vec-ref" "(vector-ref (vector 10 20 30) 1)" "20"
check "vec-ref0" "(vector-ref (vector 10 20 30) 0)" "10"
check "vec-len" "(vector-length (vector 1 2 3))" "3"
check "vec?" "(vector? (vector 1))" "#t"
check "vec?-f" "(vector? 42)" "#f"
check "vec-set" "(let ((v (vector 1 2 3))) (vector-set! v 1 99) (vector-ref v 1))" "99"
check "makevec" "(vector-ref (make-vector 3 0) 1)" "0"
check "makevec-len" "(vector-length (make-vector 5 42))" "5"
check "makevec-fill" "(vector-ref (make-vector 4 7) 2)" "7"
check "makevec-mut" "(let ((v (make-vector 3 0))) (vector-set! v 1 99) (vector-ref v 1))" "99"
check "makevec-big" "(vector-length (make-vector 1021 0))" "1021"
# ─── FUNCTIONAL TESTS: real programs ─────────────────────
echo "[functional] fibonacci"
check "fib-35" "(define (fib n) (let loop ((a 0) (b 1) (i 0)) (if (= i n) a (loop b (+ a b) (+ i 1))))) (fib 35)" "9227465"
echo "[functional] ackermann"
check "ack-3-4" "(define (ack m n) (cond ((= m 0) (+ n 1)) ((= n 0) (ack (- m 1) 1)) (else (ack (- m 1) (ack m (- n 1)))))) (ack 3 4)" "125"
echo "[functional] tail-call optimization"
check "tco-deep" "(define (count n) (if (= n 0) 0 (count (- n 1)))) (count 100000)" "0"
echo "[functional] list processing"
check "list-ops" "(define (sum lst) (if (null? lst) 0 (+ (car lst) (sum (cdr lst))))) (sum (list 1 2 3 4 5))" "15"
check "list-rev" "(define (rev lst acc) (if (null? lst) acc (rev (cdr lst) (cons (car lst) acc)))) (rev (list 1 2 3) '())" "(3 2 1)"
echo "[functional] mutual recursion"
check "even-odd" "(define (my-even n) (if (= n 0) #t (my-odd (- n 1)))) (define (my-odd n) (if (= n 0) #f (my-even (- n 1)))) (my-even 10)" "#t"
echo "[functional] closure state"
check "counter" "(define c (let ((n 0)) (lambda () (set! n (+ n 1)) n))) (c) (c) (c)" "123"
# ─── LOAD BUILTIN: unit + integration + functional ───────
LOAD_TMP=$(mktemp -d)
trap "rm -rf $LOAD_TMP" EXIT
echo "[unit] load — basic file"
cat > "$LOAD_TMP/a.lsp" <<EOF
(define loaded-x 42)
EOF
check "load-basic" "(load \"$LOAD_TMP/a.lsp\") loaded-x" "42"
echo "[unit] load — define procedure"
cat > "$LOAD_TMP/b.lsp" <<EOF
(define (square n) (* n n))
EOF
check "load-proc" "(load \"$LOAD_TMP/b.lsp\") (square 9)" "81"
echo "[unit] load — missing file returns #f"
check "load-missing" "(load \"/tmp/__nope_lumbda_$$\")" "#f"
echo "[unit] load — empty file is void"
: > "$LOAD_TMP/empty.lsp"
check "load-empty" "(load \"$LOAD_TMP/empty.lsp\") 99" "99"
echo "[integration] load — nested (outer loads inner)"
cat > "$LOAD_TMP/inner.lsp" <<EOF
(define inner-val 7)
EOF
cat > "$LOAD_TMP/outer.lsp" <<EOF
(define outer-val 99)
(load "$LOAD_TMP/inner.lsp")
EOF
check "load-nested" "(load \"$LOAD_TMP/outer.lsp\") (+ outer-val inner-val)" "106"
echo "[integration] load — continues after loaded file"
cat > "$LOAD_TMP/c.lsp" <<EOF
(define c-val 10)
EOF
check "load-after" "(load \"$LOAD_TMP/c.lsp\") (define after-val 20) (+ c-val after-val)" "30"
echo "[integration] load — S-expression portal resume"
cat > "$LOAD_TMP/portal.sexp" <<EOF
;; portable state
(define p-int 777)
(define p-list '(1 2 3 4 5))
(define p-str "hello")
EOF
check "load-portal-int" "(load \"$LOAD_TMP/portal.sexp\") p-int" "777"
check "load-portal-list" "(load \"$LOAD_TMP/portal.sexp\") (length p-list)" "5"
check "load-portal-str" "(load \"$LOAD_TMP/portal.sexp\") (display p-str)" "hello"
echo "[functional] load — set! then restore from file"
cat > "$LOAD_TMP/restore.sexp" <<EOF
(define restored 12345)
EOF
check "load-restore" "(define restored 0) (load \"$LOAD_TMP/restore.sexp\") restored" "12345"
# ─── WRITE-FILE + FILE->STRING ───────────────────────────
echo "[unit] write-file / file->string"
# `check` joins all REPL outputs; suffix the "answer" so we can match.
check "wf-basic" "(begin (write-file \"$LOAD_TMP/wf.txt\" \"abc\") (file->string \"$LOAD_TMP/wf.txt\"))" '"abc"'
check "wf-truth" "(write-file \"$LOAD_TMP/wf.txt\" \"x\")" "#t"
check "fs-missing" "(file->string \"/tmp/__no_such_$$__\")" "#f"
check "wf-overwrite" "(begin (write-file \"$LOAD_TMP/wf.txt\" \"one\") (write-file \"$LOAD_TMP/wf.txt\" \"two\") (file->string \"$LOAD_TMP/wf.txt\"))" '"two"'
check "fs-length" "(begin (write-file \"$LOAD_TMP/wf.txt\" \"hello\") (string-length (file->string \"$LOAD_TMP/wf.txt\")))" "5"
echo "[integration] write-file + load round-trip"
check "wf-load-rt" "(begin (write-file \"$LOAD_TMP/rt.sexp\" \"(define rt-val 456)\") (define rt-val 0) (load \"$LOAD_TMP/rt.sexp\") rt-val)" "456"
# ─── PORTS: open-output-file, close-port, port?, display/write/newline to port ──
echo "[unit] ports"
check "port?-true" "(port? (open-output-file \"$LOAD_TMP/p.txt\"))" "#t"
check "port?-false" "(port? 42)" "#f"
check "port-close" "(begin (let ((p (open-output-file \"$LOAD_TMP/p.txt\"))) (close-port p)) 1)" "1"
check "port-display" "(begin (let ((p (open-output-file \"$LOAD_TMP/p.txt\"))) (display \"hi\" p) (close-port p)) (file->string \"$LOAD_TMP/p.txt\"))" '"hi"'
# newline in file gets eaten by tr -d '\n' in check helper, so count chars: a + \n + b = 3
check "port-newline" "(begin (let ((p (open-output-file \"$LOAD_TMP/p.txt\"))) (display \"a\" p) (newline p) (display \"b\" p) (close-port p)) (string-length (file->string \"$LOAD_TMP/p.txt\")))" "3"
check "port-write-num" "(begin (let ((p (open-output-file \"$LOAD_TMP/p.txt\"))) (write 42 p) (close-port p)) (file->string \"$LOAD_TMP/p.txt\"))" '"42"'
echo "[unit] tcp sockets (create/close only)"
check "tcp-listen-close" "(let ((s (tcp-listen 0))) (port? s))" "#t"
# Note: tcp-listen 0 binds to ephemeral port. Can't accept (would block).
# Full connection round-trip tests live in the web-benchmark integration script.
echo "[integration] ports — S-expression portal written via ports"
PORTAL_TMP="$LOAD_TMP/port-portal.sexp"
check "port-portal" "(begin (define p (open-output-file \"$PORTAL_TMP\")) (display \"(define pp-x \" p) (write 42 p) (display \")\" p) (newline p) (display \"(define pp-y \" p) (write 777 p) (display \")\" p) (newline p) (close-port p) (define pp-x 0) (define pp-y 0) (load \"$PORTAL_TMP\") (+ pp-x pp-y))" "819"
echo "[functional] display output"
# display writes to stdout without newline, result is void
got=$(echo '(display 42) (newline)' | $UL 2>/dev/null)
if echo "$got" | grep -q "42"; then
PASS=$((PASS + 1))
else
FAIL=$((FAIL + 1))
echo "FAIL: display-42"
fi
# ─── SUMMARY ─────────────────────────────────────────────
echo
echo "═══════════════════════════════════════════════════════"
echo "Results: $PASS passed, $FAIL failed"
if [ $FAIL -eq 0 ]; then
echo "ALL TESTS PASSED"
else
echo "SOME TESTS FAILED"
exit 1
fi