Playwright sandbox: offline browser test environment for WASM kernel
New sx_playwright mode="sandbox" — injects the WASM kernel into about:blank with full FFI, IO suspension tracing, and real DOM. No server needed. Predefined stacks: core (kernel only), web (full web stack), hs (+ hyperscript), test (+ test framework). Custom files and setup expressions supported. Reproduces the host-callback IO suspension bug: direct callFn chains 6/6 suspensions correctly, but host-callback → addEventListener → _driveAsync only completes 1/6. Bug is in the _driveAsync resume chain context. Also: debug.sx mock DOM harness, test_hs_repeat.js Node.js reproduction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -333,7 +333,7 @@
|
|||||||
(hs-to-sx (nth ast 1))
|
(hs-to-sx (nth ast 1))
|
||||||
(hs-to-sx (nth ast 2))))
|
(hs-to-sx (nth ast 2))))
|
||||||
((= head (quote empty?))
|
((= head (quote empty?))
|
||||||
(list (quote empty?) (hs-to-sx (nth ast 1))))
|
(list (quote hs-empty?) (hs-to-sx (nth ast 1))))
|
||||||
((= head (quote exists?))
|
((= head (quote exists?))
|
||||||
(list
|
(list
|
||||||
(quote not)
|
(quote not)
|
||||||
@@ -356,10 +356,13 @@
|
|||||||
(hs-to-sx (nth ast 2))
|
(hs-to-sx (nth ast 2))
|
||||||
(hs-to-sx (nth ast 1))))
|
(hs-to-sx (nth ast 1))))
|
||||||
((= head (quote of))
|
((= head (quote of))
|
||||||
(list
|
(let
|
||||||
(quote get)
|
((prop (hs-to-sx (nth ast 1)))
|
||||||
(hs-to-sx (nth ast 2))
|
(target (hs-to-sx (nth ast 2))))
|
||||||
(hs-to-sx (nth ast 1))))
|
(cond
|
||||||
|
((= prop (quote first)) (list (quote first) target))
|
||||||
|
((= prop (quote last)) (list (quote last) target))
|
||||||
|
(true (list (quote get) target prop)))))
|
||||||
((= head "!=")
|
((= head "!=")
|
||||||
(list
|
(list
|
||||||
(quote not)
|
(quote not)
|
||||||
|
|||||||
@@ -187,20 +187,26 @@
|
|||||||
((and (= typ "keyword") (= val "some"))
|
((and (= typ "keyword") (= val "some"))
|
||||||
(do
|
(do
|
||||||
(adv!)
|
(adv!)
|
||||||
(let
|
(if
|
||||||
((var-name (tp-val)))
|
(and
|
||||||
(do
|
(= (tp-type) "ident")
|
||||||
(adv!)
|
(> (len tokens) (+ p 1))
|
||||||
(match-kw "in")
|
(= (get (nth tokens (+ p 1)) "value") "in"))
|
||||||
(let
|
(let
|
||||||
((collection (parse-expr)))
|
((var-name (tp-val)))
|
||||||
(do
|
(do
|
||||||
(match-kw "with")
|
(adv!)
|
||||||
(list
|
(match-kw "in")
|
||||||
(quote some)
|
(let
|
||||||
var-name
|
((collection (parse-expr)))
|
||||||
collection
|
(do
|
||||||
(parse-expr))))))))
|
(match-kw "with")
|
||||||
|
(list
|
||||||
|
(quote some)
|
||||||
|
var-name
|
||||||
|
collection
|
||||||
|
(parse-expr))))))
|
||||||
|
(list (quote not) (list (quote no) (parse-expr))))))
|
||||||
((and (= typ "keyword") (= val "every"))
|
((and (= typ "keyword") (= val "every"))
|
||||||
(do
|
(do
|
||||||
(adv!)
|
(adv!)
|
||||||
@@ -277,7 +283,7 @@
|
|||||||
(do
|
(do
|
||||||
(adv!)
|
(adv!)
|
||||||
(let
|
(let
|
||||||
((strict (if (string-ends-with? type-name "!") (string-slice type-name 0 (- (len type-name) 1)) nil)))
|
((strict (if (= (nth type-name (- (len type-name) 1)) "!") (string-slice type-name 0 (- (len type-name) 1)) nil)))
|
||||||
(if
|
(if
|
||||||
strict
|
strict
|
||||||
(list
|
(list
|
||||||
@@ -327,7 +333,7 @@
|
|||||||
(do
|
(do
|
||||||
(adv!)
|
(adv!)
|
||||||
(let
|
(let
|
||||||
((strict (if (string-ends-with? type-name "!") (string-slice type-name 0 (- (len type-name) 1)) nil)))
|
((strict (if (= (nth type-name (- (len type-name) 1)) "!") (string-slice type-name 0 (- (len type-name) 1)) nil)))
|
||||||
(if
|
(if
|
||||||
strict
|
strict
|
||||||
(list (quote type-check!) left strict)
|
(list (quote type-check!) left strict)
|
||||||
|
|||||||
@@ -295,7 +295,15 @@
|
|||||||
|
|
||||||
(define
|
(define
|
||||||
hs-falsy?
|
hs-falsy?
|
||||||
(fn (v) (or (nil? v) (= v false) (and (string? v) (= v "")))))
|
(fn
|
||||||
|
(v)
|
||||||
|
(cond
|
||||||
|
((nil? v) true)
|
||||||
|
((= v false) true)
|
||||||
|
((and (string? v) (= v "")) true)
|
||||||
|
((and (list? v) (= (len v) 0)) true)
|
||||||
|
((= v 0) true)
|
||||||
|
(true false))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-matches?
|
hs-matches?
|
||||||
@@ -313,4 +321,15 @@
|
|||||||
(cond
|
(cond
|
||||||
((list? collection) (some (fn (x) (= x item)) collection))
|
((list? collection) (some (fn (x) (= x item)) collection))
|
||||||
((string? collection) (string-contains? collection item))
|
((string? collection) (string-contains? collection item))
|
||||||
|
(true false))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-empty?
|
||||||
|
(fn
|
||||||
|
(v)
|
||||||
|
(cond
|
||||||
|
((nil? v) true)
|
||||||
|
((string? v) (= (len v) 0))
|
||||||
|
((list? v) (= (len v) 0))
|
||||||
|
((dict? v) (= (len (keys v)) 0))
|
||||||
(true false))))
|
(true false))))
|
||||||
@@ -152,7 +152,8 @@
|
|||||||
"includes"
|
"includes"
|
||||||
"contain"
|
"contain"
|
||||||
"undefined"
|
"undefined"
|
||||||
"exist"))
|
"exist"
|
||||||
|
"match"))
|
||||||
|
|
||||||
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
(list (quote hs-type-check!) hs-type-check!)
|
(list (quote hs-type-check!) hs-type-check!)
|
||||||
(list (quote hs-matches?) hs-matches?)
|
(list (quote hs-matches?) hs-matches?)
|
||||||
(list (quote hs-coerce) hs-coerce)
|
(list (quote hs-coerce) hs-coerce)
|
||||||
(list (quote hs-contains?) hs-contains?)))
|
(list (quote hs-contains?) hs-contains?)
|
||||||
|
(list (quote hs-empty?) hs-empty?)))
|
||||||
(overrides (list)))
|
(overrides (list)))
|
||||||
(do
|
(do
|
||||||
(when
|
(when
|
||||||
@@ -51,17 +52,19 @@
|
|||||||
(list (quote let) defaults (list (quote let) overrides sx)))))))))
|
(list (quote let) defaults (list (quote let) overrides sx)))))))))
|
||||||
|
|
||||||
;; ── run-hs-fixture: evaluate one test case ────────────────────────────
|
;; ── run-hs-fixture: evaluate one test case ────────────────────────────
|
||||||
(define
|
(begin
|
||||||
run-hs-fixture
|
(define _hs-error-sentinel "_HS_EVAL_ERROR_")
|
||||||
(fn
|
(define
|
||||||
(f)
|
run-hs-fixture
|
||||||
(let
|
(fn
|
||||||
((src (get f "src"))
|
(f)
|
||||||
(expected (get f "expected"))
|
|
||||||
(ctx (if (or (get f "locals") (get f "me")) {:me (get f "me") :locals (get f "locals")} nil)))
|
|
||||||
(let
|
(let
|
||||||
((result (if ctx (eval-hs src ctx) (eval-hs src))))
|
((src (get f "src"))
|
||||||
(assert= result expected src)))))
|
(expected (get f "expected"))
|
||||||
|
(ctx (if (or (get f "locals") (get f "me")) {:me (get f "me") :locals (get f "locals")} nil)))
|
||||||
|
(let
|
||||||
|
((result (if ctx (eval-hs src ctx) (eval-hs src))))
|
||||||
|
(assert= result expected src))))))
|
||||||
|
|
||||||
;; ── arrayIndex (1 fixtures) ──────────────────────────────
|
;; ── arrayIndex (1 fixtures) ──────────────────────────────
|
||||||
(defsuite
|
(defsuite
|
||||||
|
|||||||
Reference in New Issue
Block a user