#!/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 "cadr" "(cadr (list 1 2 3))" "2" check "sort-empty" "(sort (list))" "()" check "sort-one" "(sort (list 42))" "(42)" check "sort" "(sort (list 3 1 4 1 5 9 2 6))" "(1 1 2 3 4 5 6 9)" check "sort-sorted" "(sort (list 1 2 3))" "(1 2 3)" check "let*" "(let* ((x 1) (y (+ x 1))) y)" "2" check "let*-seq" "(let* ((a 1) (b a) (c (+ a b))) c)" "2" check "rest-args" "(define (f x . r) r) (f 1 2 3 4)" "(2 3 4)" check "rest-only" "(define (g . a) a) (g 1 2 3)" "(1 2 3)" 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 "random-os-entropic" "(begin (random-seed-from-os!) (define a (random-state)) (random-seed-from-os!) (not (equal? a (random-state))))" "#t" check "random-os-replay" "(begin (random-seed-from-os!) (define s (random-state)) (define v (random-int 1000000)) (random-state! s) (= v (random-int 1000000)))" "#t" 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" < "$LOAD_TMP/b.lsp" < "$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" < "$LOAD_TMP/outer.lsp" < "$LOAD_TMP/c.lsp" < "$LOAD_TMP/portal.sexp" < "$LOAD_TMP/restore.sexp" <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