;; lib/flow/tests/recovery.sx — Phase 3: crash recovery (store export/import + restart). ;; ;; "restart" is simulated within one program: (set! flow-store (list)) wipes the ;; in-memory store (process death), while flow-registry persists as it would after ;; reloading flow definitions. Recovery = import the exported (plain-data) store and ;; resume; the flow proc is re-resolved by name. (define flow-rec-pass 0) (define flow-rec-fail 0) (define flow-rec-fails (list)) (define flow-rec-test (fn (name actual expected) (if (= actual expected) (set! flow-rec-pass (+ flow-rec-pass 1)) (begin (set! flow-rec-fail (+ flow-rec-fail 1)) (append! flow-rec-fails {:name name :expected expected :actual actual}))))) (define flow-r (fn (src) (flow-run src))) ;; ── export / wipe / import ────────────────────────────────────── (flow-rec-test "export nulls the live procedure" (flow-r "(defflow w (lambda (x) (suspend (quote await)))) (flow/start w 10) (car (cdr (car (cdr (car (flow-store-export))))))") false) (flow-rec-test "a wiped store loses the flow (process death)" (flow-r "(defflow w (lambda (x) (suspend (quote await)))) (define id (car (cdr (flow/start w 10)))) (set! flow-store (list)) (flow/resume id 1)") (list "flow-error" "no-such-flow")) (flow-rec-test "import restores a wiped store and resume completes" (flow-r "(defflow w (sequence (lambda (x) (suspend (quote await))) (lambda (c) (list (quote done) c)))) (define id (car (cdr (flow/start w 10)))) (define saved (flow-store-export)) (set! flow-store (list)) (flow-store-import! saved) (flow/resume id 777)") (list "done" 777)) ;; ── resumable scan ────────────────────────────────────────────── (flow-rec-test "resumable-ids lists the suspended flow after import" (flow-r "(defflow w (lambda (x) (suspend (quote await)))) (define id (car (cdr (flow/start w 10)))) (define saved (flow-store-export)) (set! flow-store (list)) (flow-store-import! saved) (flow-resumable-ids)") (list 1)) (flow-rec-test "resumable-ids excludes completed flows" (flow-r "(defflow w (sequence (lambda (x) (suspend (quote await))) (lambda (c) c))) (define id (car (cdr (flow/start w 10)))) (flow/resume id 5) (flow-resumable-ids)") (list)) (flow-rec-test "resumable-ids excludes cancelled flows after import" (flow-r "(defflow w (lambda (x) (suspend (quote await)))) (define id (car (cdr (flow/start w 10)))) (flow/cancel id) (define saved (flow-store-export)) (set! flow-store (list)) (flow-store-import! saved) (flow-resumable-ids)") (list)) ;; ── restart at every step ─────────────────────────────────────── (flow-rec-test "two suspends survive a restart between each step" (flow-r "(defflow two (sequence (lambda (x) (suspend (quote a))) (lambda (x) (suspend (quote b))) (lambda (x) (list (quote end) x)))) (define id (car (cdr (flow/start two 0)))) (define s1 (flow-store-export)) (set! flow-store (list)) (flow-store-import! s1) (flow/resume id 100) (define s2 (flow-store-export)) (set! flow-store (list)) (flow-store-import! s2) (flow/resume id 200)") (list "end" 200)) (flow-rec-test "import preserves the replay log (earlier value survives restart)" (flow-r "(defflow two (sequence (lambda (x) (suspend (quote a))) (lambda (x) (suspend (quote b))) (lambda (x) (list x)))) (define id (car (cdr (flow/start two 0)))) (flow/resume id 11) (define saved (flow-store-export)) (set! flow-store (list)) (flow-store-import! saved) (flow/resume id 22)") (list 22)) (define flow-rec-tests-run! (fn () {:total (+ flow-rec-pass flow-rec-fail) :passed flow-rec-pass :failed flow-rec-fail :fails flow-rec-fails}))