SxRational type in OCaml (Rational of int * int, stored reduced, denom>0) and JS (SxRational class with _rational marker). n/d reader syntax in spec/parser.sx. Arithmetic contagion: int op rational → rational, rational op float → float. JS keeps int/int → float for CSS backward compatibility. OCaml as_number + safe_eq extended for cross-type rational equality so (= 2.5 5/2) → true. 62 tests in test-rationals.sx, all pass. JS: 2232 passed. OCaml: 4532 passed (+11 vs pre-fix baseline). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
577 lines
14 KiB
Plaintext
577 lines
14 KiB
Plaintext
;; ==========================================================================
|
|
;; test.sx — Self-hosting SX test suite (backward-compatible entry point)
|
|
;; ==========================================================================
|
|
|
|
(defmacro
|
|
deftest
|
|
(name &rest body)
|
|
(quasiquote
|
|
(let
|
|
((result (try-call (fn () (splice-unquote body)))))
|
|
(if
|
|
(get result "ok")
|
|
(report-pass (unquote name))
|
|
(report-fail (unquote name) (get result "error"))))))
|
|
|
|
(defmacro
|
|
defsuite
|
|
(name &rest items)
|
|
(quasiquote
|
|
(do (push-suite (unquote name)) (splice-unquote items) (pop-suite))))
|
|
|
|
(define
|
|
assert-equal
|
|
(fn
|
|
(expected actual)
|
|
(assert
|
|
(equal? expected actual)
|
|
(str "Expected " (str expected) " but got " (str actual)))))
|
|
|
|
(define
|
|
assert-not-equal
|
|
(fn
|
|
(a b)
|
|
(assert
|
|
(not (equal? a b))
|
|
(str "Expected values to differ but both are " (str a)))))
|
|
|
|
(define
|
|
assert-true
|
|
(fn (val) (assert val (str "Expected truthy but got " (str val)))))
|
|
|
|
(define
|
|
assert-false
|
|
(fn (val) (assert (not val) (str "Expected falsy but got " (str val)))))
|
|
|
|
(define
|
|
assert-nil
|
|
(fn (val) (assert (nil? val) (str "Expected nil but got " (str val)))))
|
|
|
|
(define
|
|
assert-type
|
|
(fn
|
|
(expected-type val)
|
|
(let
|
|
((actual-type (if (nil? val) "nil" (if (boolean? val) "boolean" (if (number? val) "number" (if (string? val) "string" (if (list? val) "list" (if (dict? val) "dict" "unknown"))))))))
|
|
(assert
|
|
(= expected-type actual-type)
|
|
(str "Expected type " expected-type " but got " actual-type)))))
|
|
|
|
(define
|
|
assert-length
|
|
(fn
|
|
(expected-len col)
|
|
(assert
|
|
(= (len col) expected-len)
|
|
(str "Expected length " expected-len " but got " (len col)))))
|
|
|
|
(define
|
|
assert-contains
|
|
(fn
|
|
(item col)
|
|
(assert
|
|
(some (fn (x) (equal? x item)) col)
|
|
(str "Expected collection to contain " (str item)))))
|
|
|
|
(define
|
|
assert-throws
|
|
(fn
|
|
(thunk)
|
|
(let
|
|
((result (try-call thunk)))
|
|
(assert
|
|
(not (get result "ok"))
|
|
"Expected an error to be thrown but none was"))))
|
|
|
|
(defsuite
|
|
"literals"
|
|
(deftest
|
|
"numbers are numbers"
|
|
(assert-type "number" 42)
|
|
(assert-type "number" 3.14)
|
|
(assert-type "number" -1))
|
|
(deftest
|
|
"strings are strings"
|
|
(assert-type "string" "hello")
|
|
(assert-type "string" ""))
|
|
(deftest
|
|
"booleans are booleans"
|
|
(assert-type "boolean" true)
|
|
(assert-type "boolean" false))
|
|
(deftest "nil is nil" (assert-type "nil" nil) (assert-nil nil))
|
|
(deftest
|
|
"lists are lists"
|
|
(assert-type "list" (list 1 2 3))
|
|
(assert-type "list" (list)))
|
|
(deftest "dicts are dicts" (assert-type "dict" {:b 2 :a 1})))
|
|
|
|
(defsuite
|
|
"arithmetic"
|
|
(deftest
|
|
"addition"
|
|
(assert-equal 3 (+ 1 2))
|
|
(assert-equal 0 (+ 0 0))
|
|
(assert-equal -1 (+ 1 -2))
|
|
(assert-equal 10 (+ 1 2 3 4)))
|
|
(deftest
|
|
"subtraction"
|
|
(assert-equal 1 (- 3 2))
|
|
(assert-equal -1 (- 2 3)))
|
|
(deftest
|
|
"multiplication"
|
|
(assert-equal 6 (* 2 3))
|
|
(assert-equal 0 (* 0 100))
|
|
(assert-equal 24 (* 1 2 3 4)))
|
|
(deftest
|
|
"division"
|
|
(assert-equal 2 (/ 6 3))
|
|
(assert-equal 2.5 (/ 5 2)))
|
|
(deftest
|
|
"modulo"
|
|
(assert-equal 1 (mod 7 3))
|
|
(assert-equal 0 (mod 6 3))))
|
|
|
|
(defsuite
|
|
"comparison"
|
|
(deftest
|
|
"equality"
|
|
(assert-true (= 1 1))
|
|
(assert-false (= 1 2))
|
|
(assert-true (= "a" "a"))
|
|
(assert-false (= "a" "b")))
|
|
(deftest
|
|
"deep equality"
|
|
(assert-true
|
|
(equal?
|
|
(list 1 2 3)
|
|
(list 1 2 3)))
|
|
(assert-false
|
|
(equal? (list 1 2) (list 1 3)))
|
|
(assert-true (equal? {:a 1} {:a 1}))
|
|
(assert-false (equal? {:a 1} {:a 2})))
|
|
(deftest
|
|
"ordering"
|
|
(assert-true (< 1 2))
|
|
(assert-false (< 2 1))
|
|
(assert-true (> 2 1))
|
|
(assert-true (<= 1 1))
|
|
(assert-true (<= 1 2))
|
|
(assert-true (>= 2 2))
|
|
(assert-true (>= 3 2))))
|
|
|
|
(defsuite
|
|
"strings"
|
|
(deftest
|
|
"str concatenation"
|
|
(assert-equal "abc" (str "a" "b" "c"))
|
|
(assert-equal "hello world" (str "hello" " " "world"))
|
|
(assert-equal "42" (str 42))
|
|
(assert-equal "" (str)))
|
|
(deftest
|
|
"string-length"
|
|
(assert-equal 5 (string-length "hello"))
|
|
(assert-equal 0 (string-length "")))
|
|
(deftest
|
|
"substring"
|
|
(assert-equal "ell" (substring "hello" 1 4))
|
|
(assert-equal "hello" (substring "hello" 0 5)))
|
|
(deftest
|
|
"string-contains?"
|
|
(assert-true (string-contains? "hello world" "world"))
|
|
(assert-false (string-contains? "hello" "xyz")))
|
|
(deftest
|
|
"upcase and downcase"
|
|
(assert-equal "HELLO" (upcase "hello"))
|
|
(assert-equal "hello" (downcase "HELLO")))
|
|
(deftest
|
|
"trim"
|
|
(assert-equal "hello" (trim " hello "))
|
|
(assert-equal "hello" (trim "hello")))
|
|
(deftest
|
|
"split and join"
|
|
(assert-equal (list "a" "b" "c") (split "a,b,c" ","))
|
|
(assert-equal "a-b-c" (join "-" (list "a" "b" "c")))))
|
|
|
|
(defsuite
|
|
"lists"
|
|
(deftest
|
|
"constructors"
|
|
(assert-equal
|
|
(list 1 2 3)
|
|
(list 1 2 3))
|
|
(assert-equal (list) (list))
|
|
(assert-length 3 (list 1 2 3)))
|
|
(deftest
|
|
"first and rest"
|
|
(assert-equal 1 (first (list 1 2 3)))
|
|
(assert-equal
|
|
(list 2 3)
|
|
(rest (list 1 2 3)))
|
|
(assert-nil (first (list)))
|
|
(assert-equal (list) (rest (list))))
|
|
(deftest
|
|
"nth"
|
|
(assert-equal
|
|
1
|
|
(nth (list 1 2 3) 0))
|
|
(assert-equal
|
|
2
|
|
(nth (list 1 2 3) 1))
|
|
(assert-equal
|
|
3
|
|
(nth (list 1 2 3) 2)))
|
|
(deftest
|
|
"last"
|
|
(assert-equal 3 (last (list 1 2 3)))
|
|
(assert-nil (last (list))))
|
|
(deftest
|
|
"cons and append"
|
|
(assert-equal
|
|
(list 0 1 2)
|
|
(cons 0 (list 1 2)))
|
|
(assert-equal
|
|
(list 1 2 3 4)
|
|
(append (list 1 2) (list 3 4))))
|
|
(deftest
|
|
"reverse"
|
|
(assert-equal
|
|
(list 3 2 1)
|
|
(reverse (list 1 2 3)))
|
|
(assert-equal (list) (reverse (list))))
|
|
(deftest
|
|
"empty?"
|
|
(assert-true (empty? (list)))
|
|
(assert-false (empty? (list 1))))
|
|
(deftest
|
|
"len"
|
|
(assert-equal 0 (len (list)))
|
|
(assert-equal 3 (len (list 1 2 3))))
|
|
(deftest
|
|
"contains?"
|
|
(assert-true
|
|
(contains? (list 1 2 3) 2))
|
|
(assert-false
|
|
(contains? (list 1 2 3) 4)))
|
|
(deftest
|
|
"flatten"
|
|
(assert-equal
|
|
(list 1 2 3 4)
|
|
(flatten
|
|
(list (list 1 2) (list 3 4))))))
|
|
|
|
(defsuite
|
|
"dicts"
|
|
(deftest
|
|
"dict literal"
|
|
(assert-type "dict" {:b 2 :a 1})
|
|
(assert-equal 1 (get {:a 1} "a"))
|
|
(assert-equal 2 (get {:b 2 :a 1} "b")))
|
|
(deftest
|
|
"assoc"
|
|
(assert-equal {:b 2 :a 1} (assoc {:a 1} "b" 2))
|
|
(assert-equal {:a 99} (assoc {:a 1} "a" 99)))
|
|
(deftest "dissoc" (assert-equal {:b 2} (dissoc {:b 2 :a 1} "a")))
|
|
(deftest
|
|
"keys and vals"
|
|
(let
|
|
((d {:b 2 :a 1}))
|
|
(assert-length 2 (keys d))
|
|
(assert-length 2 (vals d))
|
|
(assert-contains "a" (keys d))
|
|
(assert-contains "b" (keys d))))
|
|
(deftest
|
|
"has-key?"
|
|
(assert-true (has-key? {:a 1} "a"))
|
|
(assert-false (has-key? {:a 1} "b")))
|
|
(deftest
|
|
"merge"
|
|
(assert-equal {:c 3 :b 2 :a 1} (merge {:b 2 :a 1} {:c 3}))
|
|
(assert-equal {:b 2 :a 99} (merge {:b 2 :a 1} {:a 99}))))
|
|
|
|
(defsuite
|
|
"predicates"
|
|
(deftest
|
|
"nil?"
|
|
(assert-true (nil? nil))
|
|
(assert-false (nil? 0))
|
|
(assert-false (nil? false))
|
|
(assert-false (nil? "")))
|
|
(deftest
|
|
"number?"
|
|
(assert-true (number? 42))
|
|
(assert-true (number? 3.14))
|
|
(assert-false (number? "42")))
|
|
(deftest
|
|
"string?"
|
|
(assert-true (string? "hello"))
|
|
(assert-false (string? 42)))
|
|
(deftest
|
|
"list?"
|
|
(assert-true (list? (list 1 2)))
|
|
(assert-false (list? "not a list")))
|
|
(deftest
|
|
"dict?"
|
|
(assert-true (dict? {:a 1}))
|
|
(assert-false (dict? (list 1))))
|
|
(deftest
|
|
"boolean?"
|
|
(assert-true (boolean? true))
|
|
(assert-true (boolean? false))
|
|
(assert-false (boolean? nil))
|
|
(assert-false (boolean? 0)))
|
|
(deftest
|
|
"not"
|
|
(assert-true (not false))
|
|
(assert-true (not nil))
|
|
(assert-false (not true))
|
|
(assert-false (not 1))
|
|
(assert-false (not "x"))))
|
|
|
|
(defsuite
|
|
"special-forms"
|
|
(deftest
|
|
"if"
|
|
(assert-equal "yes" (if true "yes" "no"))
|
|
(assert-equal "no" (if false "yes" "no"))
|
|
(assert-equal "no" (if nil "yes" "no"))
|
|
(assert-nil (if false "yes")))
|
|
(deftest
|
|
"when"
|
|
(assert-equal "yes" (when true "yes"))
|
|
(assert-nil (when false "yes")))
|
|
(deftest
|
|
"cond"
|
|
(assert-equal "a" (cond true "a" :else "b"))
|
|
(assert-equal "b" (cond false "a" :else "b"))
|
|
(assert-equal "c" (cond false "a" false "b" :else "c")))
|
|
(deftest
|
|
"and"
|
|
(assert-true (and true true))
|
|
(assert-false (and true false))
|
|
(assert-false (and false true))
|
|
(assert-equal 3 (and 1 2 3)))
|
|
(deftest
|
|
"or"
|
|
(assert-equal 1 (or 1 2))
|
|
(assert-equal 2 (or false 2))
|
|
(assert-equal "fallback" (or nil false "fallback"))
|
|
(assert-false (or false false)))
|
|
(deftest
|
|
"let"
|
|
(assert-equal
|
|
3
|
|
(let ((x 1) (y 2)) (+ x y)))
|
|
(assert-equal
|
|
"hello world"
|
|
(let ((a "hello") (b " world")) (str a b))))
|
|
(deftest
|
|
"let clojure-style"
|
|
(assert-equal 3 (let (x 1 y 2) (+ x y))))
|
|
(deftest
|
|
"do / begin"
|
|
(assert-equal 3 (do 1 2 3))
|
|
(assert-equal "last" (begin "first" "middle" "last")))
|
|
(deftest "define" (define x 42) (assert-equal 42 x))
|
|
(deftest
|
|
"set!"
|
|
(define x 1)
|
|
(set! x 2)
|
|
(assert-equal 2 x)))
|
|
|
|
(defsuite
|
|
"lambdas"
|
|
(deftest
|
|
"basic lambda"
|
|
(let
|
|
((add (fn (a b) (+ a b))))
|
|
(assert-equal 3 (add 1 2))))
|
|
(deftest
|
|
"closure captures env"
|
|
(let
|
|
((x 10))
|
|
(let
|
|
((add-x (fn (y) (+ x y))))
|
|
(assert-equal 15 (add-x 5)))))
|
|
(deftest
|
|
"lambda as argument"
|
|
(assert-equal
|
|
(list 2 4 6)
|
|
(map
|
|
(fn (x) (* x 2))
|
|
(list 1 2 3))))
|
|
(deftest
|
|
"recursive lambda via define"
|
|
(define
|
|
factorial
|
|
(fn
|
|
(n)
|
|
(if
|
|
(<= n 1)
|
|
1
|
|
(* n (factorial (- n 1))))))
|
|
(assert-equal 120 (factorial 5)))
|
|
(deftest
|
|
"higher-order returns lambda"
|
|
(let
|
|
((make-adder (fn (n) (fn (x) (+ n x)))))
|
|
(let
|
|
((add5 (make-adder 5)))
|
|
(assert-equal 8 (add5 3))))))
|
|
|
|
(defsuite
|
|
"higher-order"
|
|
(deftest
|
|
"map"
|
|
(assert-equal
|
|
(list 2 4 6)
|
|
(map
|
|
(fn (x) (* x 2))
|
|
(list 1 2 3)))
|
|
(assert-equal (list) (map (fn (x) x) (list))))
|
|
(deftest
|
|
"filter"
|
|
(assert-equal
|
|
(list 2 4)
|
|
(filter
|
|
(fn (x) (= (mod x 2) 0))
|
|
(list 1 2 3 4)))
|
|
(assert-equal
|
|
(list)
|
|
(filter (fn (x) false) (list 1 2 3))))
|
|
(deftest
|
|
"reduce"
|
|
(assert-equal
|
|
10
|
|
(reduce
|
|
(fn (acc x) (+ acc x))
|
|
0
|
|
(list 1 2 3 4)))
|
|
(assert-equal
|
|
0
|
|
(reduce (fn (acc x) (+ acc x)) 0 (list))))
|
|
(deftest
|
|
"some"
|
|
(assert-true
|
|
(some
|
|
(fn (x) (> x 3))
|
|
(list 1 2 3 4 5)))
|
|
(assert-false
|
|
(some
|
|
(fn (x) (> x 10))
|
|
(list 1 2 3))))
|
|
(deftest
|
|
"every?"
|
|
(assert-true
|
|
(every?
|
|
(fn (x) (> x 0))
|
|
(list 1 2 3)))
|
|
(assert-false
|
|
(every?
|
|
(fn (x) (> x 2))
|
|
(list 1 2 3))))
|
|
(deftest
|
|
"map-indexed"
|
|
(assert-equal
|
|
(list "0:a" "1:b" "2:c")
|
|
(map-indexed (fn (i x) (str i ":" x)) (list "a" "b" "c")))))
|
|
|
|
(defsuite
|
|
"components"
|
|
(deftest
|
|
"defcomp creates component"
|
|
(defcomp ~test-comp (&key title) (div title))
|
|
(assert-true (not (nil? ~test-comp))))
|
|
(deftest
|
|
"component renders with keyword args"
|
|
(defcomp ~greeting (&key name) (span (str "Hello, " name "!")))
|
|
(assert-true (not (nil? ~greeting))))
|
|
(deftest
|
|
"component with children"
|
|
(defcomp ~box (&key &rest children) (div :class "box" children))
|
|
(assert-true (not (nil? ~box))))
|
|
(deftest
|
|
"component with default via or"
|
|
(defcomp ~label (&key text) (span (or text "default")))
|
|
(assert-true (not (nil? ~label)))))
|
|
|
|
(defsuite
|
|
"macros"
|
|
(deftest
|
|
"defmacro creates macro"
|
|
(defmacro
|
|
unless
|
|
(cond &rest body)
|
|
(quasiquote (if (not (unquote cond)) (do (splice-unquote body)))))
|
|
(assert-equal "yes" (unless false "yes"))
|
|
(assert-nil (unless true "no")))
|
|
(deftest
|
|
"quasiquote and unquote"
|
|
(let
|
|
((x 42))
|
|
(assert-equal
|
|
(list 1 42 3)
|
|
(quasiquote (1 (unquote x) 3)))))
|
|
(deftest
|
|
"splice-unquote"
|
|
(let
|
|
((xs (list 2 3 4)))
|
|
(assert-equal
|
|
(list 1 2 3 4 5)
|
|
(quasiquote (1 (splice-unquote xs) 5))))))
|
|
|
|
(defsuite
|
|
"threading"
|
|
(deftest
|
|
"thread-first"
|
|
(assert-equal 8 (-> 5 (+ 1) (+ 2)))
|
|
(assert-equal "HELLO" (-> "hello" upcase))
|
|
(assert-equal "HELLO WORLD" (-> "hello" (str " world") upcase))))
|
|
|
|
(defsuite
|
|
"truthiness"
|
|
(deftest
|
|
"truthy values"
|
|
(assert-true (if 1 true false))
|
|
(assert-true (if "x" true false))
|
|
(assert-true (if (list 1) true false))
|
|
(assert-true (if true true false)))
|
|
(deftest
|
|
"falsy values"
|
|
(assert-false (if false true false))
|
|
(assert-false (if nil true false))))
|
|
|
|
(defsuite
|
|
"edge-cases"
|
|
(deftest
|
|
"nested let scoping"
|
|
(let
|
|
((x 1))
|
|
(let ((x 2)) (assert-equal 2 x))))
|
|
(deftest
|
|
"recursive map"
|
|
(assert-equal
|
|
(list (list 2 4) (list 6 8))
|
|
(map
|
|
(fn (sub) (map (fn (x) (* x 2)) sub))
|
|
(list (list 1 2) (list 3 4)))))
|
|
(deftest
|
|
"keyword as value"
|
|
(assert-equal "class" :class)
|
|
(assert-equal "id" :id))
|
|
(deftest
|
|
"dict with evaluated values"
|
|
(let ((x 42)) (assert-equal 42 (get {:val x} "val"))))
|
|
(deftest
|
|
"nil propagation"
|
|
(assert-nil (get {:a 1} "missing"))
|
|
(assert-equal "default" (or (get {:a 1} "missing") "default")))
|
|
(deftest
|
|
"empty operations"
|
|
(assert-equal (list) (map (fn (x) x) (list)))
|
|
(assert-equal (list) (filter (fn (x) true) (list)))
|
|
(assert-equal
|
|
0
|
|
(reduce (fn (acc x) (+ acc x)) 0 (list)))
|
|
(assert-equal 0 (len (list)))
|
|
(assert-equal "" (str))))
|