sema-test

0.1.0

Test framework for Sema — define, run, and report test results

$ sema pkg install sema-test
Readme Versions 1 Dependencies 0

sema-test

Test framework for Sema — define, run, and report test results.

Install

sema pkg add sema-test

Quick start

Tests are plain named functions containing assertions. Pass them to (test/run! ...), which prints a report and exits with code 1 if any fail — so sema tests.sema works directly in CI.

(import "sema-test")

(define (addition)
  (test/assert-equal 4 (+ 2 2))
  (test/assert-equal 0 (+ -1 1)))

(define (strings)
  (test/assert-contains "hello world" "lo wo")
  (test/assert-true (string/starts-with? "hello" "hel")))

(test/run! addition strings)
;
;   ✓ addition
;   ✓ strings
;
; All 2 tests passed ✓

API

Every assertion returns #t on success and raises a descriptive error on failure; the runner catches those errors and reports them per test.

Function Description
(test/assert-equal expected actual) Assert deep equality
(test/assert-true value) Assert value is truthy
(test/assert-false value) Assert value is falsy
(test/assert-throws thunk) Assert (thunk) raises an error
(test/assert-near expected actual tolerance) Assert numbers differ by at most tolerance
(test/assert-contains haystack needle) Assert a list contains an element or a string a substring
(test/run . test-fns) Run tests, print a report, return {:passed :failed :total}
(test/run! . test-fns) Like test/run, but exit with code 1 if any test fails

test/assert-equal

(test/assert-equal 4 (+ 2 2))                      ; => #t
(test/assert-equal (list 1 2) (list 1 2))          ; => #t
(test/assert-equal {:a 1} {:a 1})                  ; => #t
(test/assert-equal 1 2)                            ; error: expected 1, got 2

Deep structural equality (equal?) — works on numbers, strings, lists, and maps. expected comes first and is named in the failure message.

test/assert-true

(test/assert-true (string? "x"))   ; => #t
(test/assert-true nil)             ; error: expected truthy value, got falsy

Passes for any truthy value (everything except #f and nil — including 0 and "").

test/assert-false

(test/assert-false (string? 42))   ; => #t
(test/assert-false 1)              ; error: expected falsy value, got 1

Passes only for #f and nil.

test/assert-throws

(test/assert-throws (fn () (error "boom")))   ; => #t
(test/assert-throws (fn () (+ 1 1)))          ; error: expected an error to be
                                              ; thrown, but none was

Takes a zero-argument function (thunk) and calls it. Use it to test that your code rejects bad input.

test/assert-near

(test/assert-near 1.0 1.1 0.2)     ; => #t
(test/assert-near 1.0 1.25 0.25)   ; => #t  (diff equal to tolerance passes)
(test/assert-near 1.0 1.5 0.25)    ; error: expected 1.0 ≈ 1.5 (tolerance
                                   ; 0.25), diff was 0.5

Asserts |expected - actual| <= tolerance; the order of expected and actual doesn't matter. Note the usual floating-point caveat: a difference that is exactly the tolerance only passes when both are binary-exact (e.g. 0.25, not 0.05) — give yourself a little headroom otherwise.

test/assert-contains

(test/assert-contains "hello world" "lo wo")     ; => #t
(test/assert-contains (list 1 2 3) 2)            ; => #t
(test/assert-contains (list "a" (list 1 2)) (list 1 2))   ; => #t (deep equality)
(test/assert-contains (list 1 2 3) 4)            ; error: expected (1 2 3) to contain 4

Strings check for a substring; lists check for an element using deep equality. Any other haystack type raises an error.

test/run

(define (passing) (test/assert-true #t))
(define (failing) (test/assert-equal 1 2))

(test/run passing failing)
;
;   ✓ passing
;   ✗ failing — expected 1, got 2
;
; 1/2 tests failed ✗
; => {:failed 1 :passed 1 :total 2}

Runs each test function, printing / per test with the failure message, and returns a summary map with :passed, :failed, and :total counts. It never exits the process — use it when you want the counts.

test/run!

(test/run! passing)
;
;   ✓ passing
;
; All 1 tests passed ✓
; => {:failed 0 :passed 1 :total 1}

Same report and return value as test/run, but exits the process with code 1 if any test failed. Put it at the bottom of your tests.sema and run sema tests.sema in CI.

Testing

The framework tests itself:

sema tests.sema

(The output includes a few nested reports — the suite runs test/run on deliberately failing tests to check the summary counts.)

License

MIT

VersionSizePublished
0.1.0 4 KB 2026-07-07 21:28:18
No dependencies