Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 55s
(attempt n1 n2 ...) threads like sequence but stops at the first node returning a (fail ...) value, returning that failure. Makes the fail/recover error model compose into validation/ETL pipelines (railway-oriented). 132/132 across 8 suites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.9 KiB
Plaintext
74 lines
2.9 KiB
Plaintext
;; lib/flow/tests/railway.sx — Phase 6: railway-oriented composition (attempt).
|
|
|
|
(define flow-rail-pass 0)
|
|
(define flow-rail-fail 0)
|
|
(define flow-rail-fails (list))
|
|
|
|
(define
|
|
flow-rail-test
|
|
(fn
|
|
(name actual expected)
|
|
(if
|
|
(= actual expected)
|
|
(set! flow-rail-pass (+ flow-rail-pass 1))
|
|
(begin
|
|
(set! flow-rail-fail (+ flow-rail-fail 1))
|
|
(append! flow-rail-fails {:name name :expected expected :actual actual})))))
|
|
|
|
(define flow-r (fn (src) (flow-run src)))
|
|
|
|
;; ── attempt — short-circuit on the first (fail ...) ─────────────
|
|
(flow-rail-test
|
|
"attempt: threads like sequence when nothing fails"
|
|
(flow-r
|
|
"(flow/start (attempt (lambda (x) (+ x 1)) (lambda (x) (* x 10))) 4)")
|
|
50)
|
|
(flow-rail-test
|
|
"attempt: empty is identity"
|
|
(flow-r "(flow/start (attempt) 7)")
|
|
7)
|
|
(flow-rail-test
|
|
"attempt: returns the first failure"
|
|
(flow-r
|
|
"(failed? (flow/start (attempt (lambda (x) (fail (quote bad))) (lambda (x) (* x 10))) 4))")
|
|
true)
|
|
(flow-rail-test
|
|
"attempt: the failure carries its reason"
|
|
(flow-r
|
|
"(fail-reason (flow/start (attempt (lambda (x) x) (lambda (x) (fail (quote rejected)))) 4))")
|
|
"rejected")
|
|
(flow-rail-test
|
|
"attempt: nodes after a failure do not run"
|
|
(flow-r
|
|
"(define ran 0) (flow/start (attempt (lambda (x) (fail (quote stop))) (lambda (x) (begin (set! ran (+ ran 1)) x))) 0) ran")
|
|
0)
|
|
(flow-rail-test
|
|
"attempt: a failed input short-circuits immediately"
|
|
(flow-r
|
|
"(define ran 0) (fail-reason (flow/start (attempt (lambda (x) (begin (set! ran (+ ran 1)) x))) (fail (quote pre))))")
|
|
"pre")
|
|
(flow-rail-test
|
|
"attempt: middle failure halts the chain"
|
|
(flow-r
|
|
"(define ran 0) (flow/start (attempt (lambda (x) (+ x 1)) (lambda (x) (fail (quote mid))) (lambda (x) (begin (set! ran (+ ran 1)) x))) 5) ran")
|
|
0)
|
|
|
|
;; ── attempt + recover (rejoin the happy track) ──────────────────
|
|
(flow-rail-test
|
|
"attempt + recover: recover turns a failure into a value"
|
|
(flow-r
|
|
"(flow/start (recover (attempt (lambda (x) (if (> x 0) x (fail (quote non-positive)))) (lambda (x) (* x 2))) (flow-const 0)) -5)")
|
|
0)
|
|
(flow-rail-test
|
|
"attempt + recover: happy path passes recover through"
|
|
(flow-r
|
|
"(flow/start (recover (attempt (lambda (x) (if (> x 0) x (fail (quote non-positive)))) (lambda (x) (* x 2))) (flow-const 0)) 5)")
|
|
10)
|
|
(flow-rail-test
|
|
"attempt: validation pipeline reports the failing stage"
|
|
(flow-r
|
|
"(defflow validate (attempt (lambda (s) (if (>= (string-length s) 3) s (fail (quote too-short)))) (lambda (s) (if (<= (string-length s) 8) s (fail (quote too-long)))) (lambda (s) (list (quote ok) (string-length s))))) (list (fail-reason (flow/start validate \"hi\")) (flow/start validate \"hello\"))")
|
|
(list "too-short" (list "ok" 5)))
|
|
|
|
(define flow-rail-tests-run! (fn () {:total (+ flow-rail-pass flow-rail-fail) :passed flow-rail-pass :failed flow-rail-fail :fails flow-rail-fails}))
|