spec: coroutine tests — expand to 27 (was 17)
10 new tests: state field transitions (ready/suspended/dead), yield from nested helper function, initial resume arg ignored by ready coroutine, mutable closure state via dict-set!, complex yield values (list/dict), round-robin scheduling, factory creates independent coroutines, resuming non-coroutine raises error. 27/27 pass on both OCaml and JS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -137,8 +137,11 @@ using call/cc+perform/resume.
|
||||
All CEK primitives already in sx-browser.js. Fix: pre-load spec/coroutines.sx +
|
||||
spec/signals.sx in run_tests.js so (import (sx coroutines)) resolves without suspension.
|
||||
17/17 pass in JS. 1965/2500 (+25 vs 1940 baseline). Zero new failures.
|
||||
- [ ] Tests: 25+ tests — multi-yield, final return, arg passthrough, alive? predicate,
|
||||
- [x] Tests: 25+ tests — multi-yield, final return, arg passthrough, alive? predicate,
|
||||
nested coroutines, "final return vs yield" distinction (the Lua gotcha).
|
||||
27 tests: added 10 new — state field inspection (ready/suspended/dead), yield from
|
||||
nested helper, initial resume arg ignored, mutable closure state, complex yield values,
|
||||
round-robin scheduling, factory-shared-no-state, non-coroutine error. 27/27 OCaml+JS.
|
||||
- [ ] Commit: `spec: coroutine primitive (make-coroutine/resume/yield)`
|
||||
|
||||
---
|
||||
@@ -671,6 +674,7 @@ Brief each language's loop agent (or do inline) after rebasing their branch onto
|
||||
|
||||
_Newest first._
|
||||
|
||||
- 2026-04-26: Phase 4 Tests step done — 27 tests total (10 new: state field inspection, yield-from-helper, initial-arg-ignored, mutable-closure, complex-values, round-robin, factory-no-state, non-coroutine-error). 27/27 OCaml+JS.
|
||||
- 2026-04-26: Phase 4 JS step done — all CEK primitives already in sx-browser.js; fix was pre-loading spec/coroutines.sx+spec/signals.sx in run_tests.js so (import (sx coroutines)) resolves synchronously. 17/17 coroutine tests pass JS. 1965/2500 total (+25), zero new failures.
|
||||
- 2026-04-26: Phase 4 OCaml step done — no native SxCoroutine type needed; existing cek-step-loop/cek-resume/perform/make-cek-state primitives in run_tests.ml fully support the spec/coroutines.sx library. 284/284 pass (coroutines+vectors+numeric-tower+dynamic-wind), zero regressions.
|
||||
- 2026-04-26: Phase 4 Spec step done — spec/coroutines.sx define-library with make-coroutine/coroutine-resume/coroutine-yield/coroutine?/coroutine-alive?; make-coroutine stub in evaluator.sx; 17/17 coroutine tests pass (OCaml). Key insight: coroutine body must use (define loop (fn...)) + (loop 0) not named let — named let uses cek_call→cek_run which errors on IO suspension.
|
||||
|
||||
@@ -199,4 +199,107 @@
|
||||
(assert= true (get c "done"))
|
||||
(assert= 5 (get c "value"))
|
||||
(assert= true (get d "done"))
|
||||
(assert= 6 (get d "value")))))))))
|
||||
(assert= 6 (get d "value"))))))))
|
||||
(deftest
|
||||
"coroutine state field is ready before first resume"
|
||||
(let
|
||||
((co (make-coroutine (fn () (coroutine-yield 1)))))
|
||||
(assert= "ready" (get co "state"))))
|
||||
(deftest
|
||||
"coroutine state field is suspended between yields"
|
||||
(let
|
||||
((co (make-coroutine (fn () (coroutine-yield 1) 2))))
|
||||
(coroutine-resume co nil)
|
||||
(assert= "suspended" (get co "state"))))
|
||||
(deftest
|
||||
"coroutine state field is dead after completion"
|
||||
(let
|
||||
((co (make-coroutine (fn () nil))))
|
||||
(coroutine-resume co nil)
|
||||
(assert= "dead" (get co "state"))))
|
||||
(deftest
|
||||
"yield works when called from nested helper function"
|
||||
(let
|
||||
((co (make-coroutine (fn () (define helper (fn (x) (coroutine-yield x))) (helper 10) (helper 20)))))
|
||||
(let
|
||||
((r1 (coroutine-resume co nil)))
|
||||
(let
|
||||
((r2 (coroutine-resume co nil)))
|
||||
(let
|
||||
((r3 (coroutine-resume co nil)))
|
||||
(assert= false (get r1 "done"))
|
||||
(assert= 10 (get r1 "value"))
|
||||
(assert= false (get r2 "done"))
|
||||
(assert= 20 (get r2 "value"))
|
||||
(assert= true (get r3 "done")))))))
|
||||
(deftest
|
||||
"initial resume argument is ignored by ready coroutine"
|
||||
(let
|
||||
((co (make-coroutine (fn () (coroutine-yield 42)))))
|
||||
(let
|
||||
((r (coroutine-resume co "ignored")))
|
||||
(assert= false (get r "done"))
|
||||
(assert= 42 (get r "value")))))
|
||||
(deftest
|
||||
"coroutine with mutable closure state"
|
||||
(let
|
||||
((counter {:value 0}))
|
||||
(let
|
||||
((co (make-coroutine (fn () (dict-set! counter "value" 1) (coroutine-yield "a") (dict-set! counter "value" 2) (coroutine-yield "b")))))
|
||||
(assert= 0 (get counter "value"))
|
||||
(coroutine-resume co nil)
|
||||
(assert= 1 (get counter "value"))
|
||||
(coroutine-resume co nil)
|
||||
(assert= 2 (get counter "value")))))
|
||||
(deftest
|
||||
"coroutine can yield complex values"
|
||||
(let
|
||||
((co (make-coroutine (fn () (coroutine-yield (list 1 2 3)) (coroutine-yield {:key "val"})))))
|
||||
(let
|
||||
((r1 (coroutine-resume co nil)))
|
||||
(let
|
||||
((r2 (coroutine-resume co nil)))
|
||||
(assert= false (get r1 "done"))
|
||||
(assert= 3 (len (get r1 "value")))
|
||||
(assert= false (get r2 "done"))
|
||||
(assert= "val" (get (get r2 "value") "key"))))))
|
||||
(deftest
|
||||
"round-robin scheduling of multiple coroutines"
|
||||
(let
|
||||
((results (list))
|
||||
(co1
|
||||
(make-coroutine
|
||||
(fn () (coroutine-yield "a") (coroutine-yield "b"))))
|
||||
(co2
|
||||
(make-coroutine
|
||||
(fn () (coroutine-yield "c") (coroutine-yield "d")))))
|
||||
(append! results (get (coroutine-resume co1 nil) "value"))
|
||||
(append! results (get (coroutine-resume co2 nil) "value"))
|
||||
(append! results (get (coroutine-resume co1 nil) "value"))
|
||||
(append! results (get (coroutine-resume co2 nil) "value"))
|
||||
(assert= 4 (len results))
|
||||
(assert= "a" (nth results 0))
|
||||
(assert= "c" (nth results 1))
|
||||
(assert= "b" (nth results 2))
|
||||
(assert= "d" (nth results 3))))
|
||||
(deftest
|
||||
"coroutines created from same factory share no state"
|
||||
(let
|
||||
((make-counter (fn (start) (make-coroutine (fn () (define loop (fn (n) (coroutine-yield n) (loop (+ n 1)))) (loop start))))))
|
||||
(let
|
||||
((c1 (make-counter 0)) (c2 (make-counter 100)))
|
||||
(let
|
||||
((a (get (coroutine-resume c1 nil) "value")))
|
||||
(let
|
||||
((b (get (coroutine-resume c2 nil) "value")))
|
||||
(let
|
||||
((c (get (coroutine-resume c1 nil) "value")))
|
||||
(let
|
||||
((d (get (coroutine-resume c2 nil) "value")))
|
||||
(assert= 0 a)
|
||||
(assert= 100 b)
|
||||
(assert= 1 c)
|
||||
(assert= 101 d))))))))
|
||||
(deftest
|
||||
"resuming non-coroutine raises error"
|
||||
(assert-throws (fn () (coroutine-resume "not-a-coroutine" nil)))))
|
||||
|
||||
Reference in New Issue
Block a user