Completes ticket 0001 started in27f468c. 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 (supplements27f468c): - 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.
310 lines
14 KiB
Bash
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
|