;; lib/flow/tests/control.sx — Phase 2: control flow + error handling. (define flow-ctl-pass 0) (define flow-ctl-fail 0) (define flow-ctl-fails (list)) (define flow-ctl-test (fn (name actual expected) (if (= actual expected) (set! flow-ctl-pass (+ flow-ctl-pass 1)) (begin (set! flow-ctl-fail (+ flow-ctl-fail 1)) (append! flow-ctl-fails {:name name :expected expected :actual actual}))))) (define flow-c (fn (src) (flow-run src))) (define flow-cs (fn (src) (get (flow-run src) :scm-string))) ;; ── branch ────────────────────────────────────────────────────── (flow-ctl-test "branch: true selects then" (flow-c "(flow/start (branch (lambda (x) (> x 0)) (lambda (x) (* x 100)) (lambda (x) (- 0 x))) 5)") 500) (flow-ctl-test "branch: false selects else" (flow-c "(flow/start (branch (lambda (x) (> x 0)) (lambda (x) (* x 100)) (lambda (x) (- 0 x))) -3)") 3) (flow-ctl-test "branch: predicate sees the threaded input" (flow-c "(flow/start (sequence (lambda (x) (+ x 1)) (branch (lambda (x) (> x 3)) (flow-const 100) (flow-const 0))) 3)") 100) (flow-ctl-test "branch: branches are full nodes (sequence inside)" (flow-c "(flow/start (branch (lambda (x) (< x 10)) (sequence (lambda (x) (+ x 1)) (lambda (x) (* x 2))) (flow-const 0)) 4)") 10) (flow-ctl-test "branch: nested branch (3-way sign)" (flow-c "(defflow sign (branch (lambda (x) (> x 0)) (flow-const 1) (branch (lambda (x) (< x 0)) (flow-const -1) (flow-const 0)))) (list (flow/start sign 7) (flow/start sign -7) (flow/start sign 0))") (list 1 -1 0)) (flow-ctl-test "branch: publish-shaped approval gate" (flow-cs "(defflow publish (branch (lambda (post) (>= (string-length post) 3)) (lambda (post) (string-append post \" [published]\")) (lambda (post) (string-append post \" [rejected]\")))) (flow/start publish \"ok\")") "ok [rejected]") ;; ── error model — explicit (fail reason) values ───────────────── (flow-ctl-test "fail: failed? is true for a failure value" (flow-c "(failed? (fail 404))") true) (flow-ctl-test "fail: fail-reason extracts the reason" (flow-c "(fail-reason (fail 404))") 404) (flow-ctl-test "fail: failed? is false for a plain value" (flow-c "(failed? 7)") false) (flow-ctl-test "fail: failed? is false for an ordinary list" (flow-c "(failed? (list 1 2 3))") false) (flow-ctl-test "fail: a node may emit a failure as data" (flow-c "(defflow validate (lambda (s) (if (>= (string-length s) 3) s (fail (quote too-short))))) (failed? (flow/start validate \"hi\"))") true) (flow-ctl-test "fail: failure flows downstream, branch recovers" (flow-c "(defflow guarded (sequence (lambda (s) (if (>= (string-length s) 3) (string-length s) (fail (quote too-short)))) (branch failed? (lambda (f) (list (quote recovered) (fail-reason f))) (lambda (n) (list (quote ok) n))))) (flow/start guarded \"hi\")") (list "recovered" "too-short")) ;; ── try-catch — reify raised exceptions ───────────────────────── (flow-ctl-test "try-catch: no exception returns node result" (flow-c "(flow/start (try-catch (lambda (x) (* x 2)) (lambda (e) -1)) 5)") 10) (flow-ctl-test "try-catch: handler runs on raise" (flow-c "(flow/start (try-catch (lambda (x) (raise (quote boom))) (flow-const 99)) 1)") 99) (flow-ctl-test "try-catch: handler receives the reified error" (flow-c "(flow/start (try-catch (lambda (x) (raise 42)) (lambda (e) e)) 0)") 42) (flow-ctl-test "try-catch: catches exception from deep inside a sequence" (flow-c "(flow/start (try-catch (sequence (lambda (x) (+ x 1)) (lambda (x) (raise (quote deep)))) (flow-const -99)) 5)") -99) (flow-ctl-test "try-catch: handler may convert to a failure value" (flow-c "(failed? (flow/start (try-catch (lambda (x) (raise (quote bad))) (lambda (e) (fail e))) 0))") true) (flow-ctl-test "try-catch: composes — recover then continue" (flow-c "(flow/start (sequence (try-catch (lambda (x) (raise (quote x))) (flow-const 10)) (lambda (n) (* n 5))) 0)") 50) (define flow-ctl-tests-run! (fn () {:total (+ flow-ctl-pass flow-ctl-fail) :passed flow-ctl-pass :failed flow-ctl-fail :fails flow-ctl-fails}))