;; ========================================================================== ;; test-framework.sx — Reusable test macros and assertion helpers ;; ;; Loaded first by all test runners. Provides deftest, defsuite, and ;; assertion helpers. Requires 5 platform functions from the host: ;; ;; try-call (thunk) -> {:ok true} | {:ok false :error "msg"} ;; report-pass (name) -> platform-specific pass output ;; report-fail (name error) -> platform-specific fail output ;; push-suite (name) -> push suite name onto context stack ;; pop-suite () -> pop suite name from context stack ;; ;; Any host that provides these 5 functions can run any test spec. ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; 1. Test framework macros ;; -------------------------------------------------------------------------- (defmacro deftest (name &rest body) `(let ((result (try-call (fn () ,@body)))) (if (get result "ok") (report-pass ,name) (report-fail ,name (get result "error"))))) (defmacro defsuite (name &rest items) `(do (push-suite ,name) ,@items (pop-suite))) ;; -------------------------------------------------------------------------- ;; 2. Assertion helpers — defined in SX, available in test bodies ;; -------------------------------------------------------------------------- (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 :as string) 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 :as number) (col :as list)) (assert (= (len col) expected-len) (str "Expected length " expected-len " but got " (len col))))) (define assert-contains (fn (item (col :as list)) (assert (some (fn (x) (equal? x item)) col) (str "Expected collection to contain " (str item))))) (define assert-throws (fn ((thunk :as lambda)) (let ((result (try-call thunk))) (assert (not (get result "ok")) "Expected an error to be thrown but none was"))))