HS: mixed-op enforcement + short-circuit + typecheck + strings (+7 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 10m43s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 10m43s
- parser.sx: parse-logical now rejects mixed and/or without parens
- parser.sx: parse-arith now rejects mixed +/-/* //%/mod without parens
- generate-sx-tests.py: MANUAL_TEST_BODIES for short-circuit and/or,
typecheck (direct hs-type-assert calls), template string test
- generate-sx-tests.py: Pattern 5 for error("expr") -> assert-throws
- hs-run-filtered.js: redefine try-call to _run-test-thunk after loading
so assert-throws actually catches exceptions (was always {ok true})
- hs-run-filtered.js: clear __hs_deadline immediately after test eval
to prevent cascading timeout fires in result inspection K.eval calls
- hs-run-filtered.js: typecheck suite in _NO_STEP_LIMIT_SUITES and
_SLOW_DEADLINE_SUITES (hs-type-assert JIT is slow on first call)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -931,13 +931,29 @@
|
|||||||
(left)
|
(left)
|
||||||
(cond
|
(cond
|
||||||
((match-kw "and")
|
((match-kw "and")
|
||||||
(let
|
(do
|
||||||
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
(when
|
||||||
(parse-logical (list (quote and) left right))))
|
(and
|
||||||
|
(list? left)
|
||||||
|
(> (len left) 0)
|
||||||
|
(= (first left) (quote or)))
|
||||||
|
(error
|
||||||
|
"You must parenthesize logical operations with different operators"))
|
||||||
|
(let
|
||||||
|
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
||||||
|
(parse-logical (list (quote and) left right)))))
|
||||||
((match-kw "or")
|
((match-kw "or")
|
||||||
(let
|
(do
|
||||||
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
(when
|
||||||
(parse-logical (list (quote or) left right))))
|
(and
|
||||||
|
(list? left)
|
||||||
|
(> (len left) 0)
|
||||||
|
(= (first left) (quote and)))
|
||||||
|
(error
|
||||||
|
"You must parenthesize logical operations with different operators"))
|
||||||
|
(let
|
||||||
|
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
||||||
|
(parse-logical (list (quote or) left right)))))
|
||||||
(true left))))
|
(true left))))
|
||||||
(define
|
(define
|
||||||
parse-expr
|
parse-expr
|
||||||
@@ -2156,6 +2172,27 @@
|
|||||||
(= val "%")))
|
(= val "%")))
|
||||||
(and (= typ "keyword") (= val "mod")))
|
(and (= typ "keyword") (= val "mod")))
|
||||||
(do
|
(do
|
||||||
|
(when
|
||||||
|
(and (list? left) (> (len left) 0))
|
||||||
|
(let
|
||||||
|
((left-op (first left)))
|
||||||
|
(when
|
||||||
|
(or
|
||||||
|
(and
|
||||||
|
(or (= left-op (quote +)) (= left-op (quote -)))
|
||||||
|
(or
|
||||||
|
(= val "*")
|
||||||
|
(= val "/")
|
||||||
|
(= val "%")
|
||||||
|
(= val "mod")))
|
||||||
|
(and
|
||||||
|
(or
|
||||||
|
(= left-op (quote *))
|
||||||
|
(= left-op (quote /))
|
||||||
|
(= left-op (make-symbol "%")))
|
||||||
|
(or (= val "+") (= val "-"))))
|
||||||
|
(error
|
||||||
|
"You must parenthesize math operations with different operators"))))
|
||||||
(adv!)
|
(adv!)
|
||||||
(let
|
(let
|
||||||
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%")))))
|
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%")))))
|
||||||
|
|||||||
@@ -71,12 +71,14 @@
|
|||||||
((typ (tp-type)) (val (tp-val)))
|
((typ (tp-type)) (val (tp-val)))
|
||||||
(cond
|
(cond
|
||||||
((or (= typ "ident") (= typ "keyword"))
|
((or (= typ "ident") (= typ "keyword"))
|
||||||
(do (adv!) (parse-prop-chain (list (quote .) owner val))))
|
(do
|
||||||
|
(adv!)
|
||||||
|
(parse-prop-chain (list (quote poss) owner val))))
|
||||||
((= typ "attr") (do (adv!) (list (quote attr) val owner)))
|
((= typ "attr") (do (adv!) (list (quote attr) val owner)))
|
||||||
((= typ "class")
|
((= typ "class")
|
||||||
(let
|
(let
|
||||||
((prop (get (adv!) "value")))
|
((prop (get (adv!) "value")))
|
||||||
(parse-prop-chain (list (quote .) owner prop))))
|
(parse-prop-chain (list (quote poss) owner prop))))
|
||||||
((= typ "style") (do (adv!) (list (quote style) val owner)))
|
((= typ "style") (do (adv!) (list (quote style) val owner)))
|
||||||
(true owner)))))
|
(true owner)))))
|
||||||
(define
|
(define
|
||||||
@@ -116,7 +118,18 @@
|
|||||||
(prev-end)
|
(prev-end)
|
||||||
base-line
|
base-line
|
||||||
{:root base})))
|
{:root base})))
|
||||||
base)))))
|
(if
|
||||||
|
(and
|
||||||
|
(= (tp-type) "op")
|
||||||
|
(= (tp-val) "'s")
|
||||||
|
(not (at-end?)))
|
||||||
|
(let
|
||||||
|
((poss-prop (begin (adv!) (tp-val))))
|
||||||
|
(do
|
||||||
|
(adv!)
|
||||||
|
(parse-prop-chain
|
||||||
|
(list (make-symbol "poss") base poss-prop))))
|
||||||
|
base))))))
|
||||||
(define
|
(define
|
||||||
parse-trav
|
parse-trav
|
||||||
(fn
|
(fn
|
||||||
@@ -429,8 +442,7 @@
|
|||||||
(let
|
(let
|
||||||
((name val) (args (parse-call-args)))
|
((name val) (args (parse-call-args)))
|
||||||
(cons (quote call) (cons (list (quote ref) name) args)))))
|
(cons (quote call) (cons (list (quote ref) name) args)))))
|
||||||
((= typ "keyword")
|
((= typ "keyword") (do (adv!) (list (quote ref) val)))
|
||||||
(do (adv!) (list (quote ref) val)))
|
|
||||||
(true nil)))))
|
(true nil)))))
|
||||||
(define
|
(define
|
||||||
parse-poss
|
parse-poss
|
||||||
@@ -443,10 +455,13 @@
|
|||||||
((= (tp-type) "dot")
|
((= (tp-type) "dot")
|
||||||
(do
|
(do
|
||||||
(adv!)
|
(adv!)
|
||||||
(let ((typ2 (tp-type)) (val2 (tp-val)))
|
(let
|
||||||
|
((typ2 (tp-type)) (val2 (tp-val)))
|
||||||
(if
|
(if
|
||||||
(or (= typ2 "ident") (= typ2 "keyword"))
|
(or (= typ2 "ident") (= typ2 "keyword"))
|
||||||
(do (adv!) (parse-poss (list (make-symbol ".") obj val2)))
|
(do
|
||||||
|
(adv!)
|
||||||
|
(parse-poss (list (make-symbol ".") obj val2)))
|
||||||
obj))))
|
obj))))
|
||||||
((= (tp-type) "paren-open")
|
((= (tp-type) "paren-open")
|
||||||
(let
|
(let
|
||||||
@@ -916,13 +931,29 @@
|
|||||||
(left)
|
(left)
|
||||||
(cond
|
(cond
|
||||||
((match-kw "and")
|
((match-kw "and")
|
||||||
(let
|
(do
|
||||||
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
(when
|
||||||
(parse-logical (list (quote and) left right))))
|
(and
|
||||||
|
(list? left)
|
||||||
|
(> (len left) 0)
|
||||||
|
(= (first left) (quote or)))
|
||||||
|
(error
|
||||||
|
"You must parenthesize logical operations with different operators"))
|
||||||
|
(let
|
||||||
|
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
||||||
|
(parse-logical (list (quote and) left right)))))
|
||||||
((match-kw "or")
|
((match-kw "or")
|
||||||
(let
|
(do
|
||||||
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
(when
|
||||||
(parse-logical (list (quote or) left right))))
|
(and
|
||||||
|
(list? left)
|
||||||
|
(> (len left) 0)
|
||||||
|
(= (first left) (quote and)))
|
||||||
|
(error
|
||||||
|
"You must parenthesize logical operations with different operators"))
|
||||||
|
(let
|
||||||
|
((right (parse-collection (parse-cmp (parse-arith (parse-poss (parse-atom)))))))
|
||||||
|
(parse-logical (list (quote or) left right)))))
|
||||||
(true left))))
|
(true left))))
|
||||||
(define
|
(define
|
||||||
parse-expr
|
parse-expr
|
||||||
@@ -1475,7 +1506,8 @@
|
|||||||
((match-kw "to")
|
((match-kw "to")
|
||||||
(let
|
(let
|
||||||
((value (parse-expr)))
|
((value (parse-expr)))
|
||||||
(if (and (list? tgt) (= (first tgt) (quote query)))
|
(if
|
||||||
|
(and (list? tgt) (= (first tgt) (quote query)))
|
||||||
(list (quote set-el!) tgt value)
|
(list (quote set-el!) tgt value)
|
||||||
(list (quote set!) tgt value))))
|
(list (quote set!) tgt value))))
|
||||||
((match-kw "on")
|
((match-kw "on")
|
||||||
@@ -2140,6 +2172,27 @@
|
|||||||
(= val "%")))
|
(= val "%")))
|
||||||
(and (= typ "keyword") (= val "mod")))
|
(and (= typ "keyword") (= val "mod")))
|
||||||
(do
|
(do
|
||||||
|
(when
|
||||||
|
(and (list? left) (> (len left) 0))
|
||||||
|
(let
|
||||||
|
((left-op (first left)))
|
||||||
|
(when
|
||||||
|
(or
|
||||||
|
(and
|
||||||
|
(or (= left-op (quote +)) (= left-op (quote -)))
|
||||||
|
(or
|
||||||
|
(= val "*")
|
||||||
|
(= val "/")
|
||||||
|
(= val "%")
|
||||||
|
(= val "mod")))
|
||||||
|
(and
|
||||||
|
(or
|
||||||
|
(= left-op (quote *))
|
||||||
|
(= left-op (quote /))
|
||||||
|
(= left-op (make-symbol "%")))
|
||||||
|
(or (= val "+") (= val "-"))))
|
||||||
|
(error
|
||||||
|
"You must parenthesize math operations with different operators"))))
|
||||||
(adv!)
|
(adv!)
|
||||||
(let
|
(let
|
||||||
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%")))))
|
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((or (= val "%") (= val "mod")) (make-symbol "%")))))
|
||||||
@@ -2648,7 +2701,14 @@
|
|||||||
((and (= typ "keyword") (= val "answer"))
|
((and (= typ "keyword") (= val "answer"))
|
||||||
(do (adv!) (parse-answer-cmd)))
|
(do (adv!) (parse-answer-cmd)))
|
||||||
((and (= typ "keyword") (= val "settle"))
|
((and (= typ "keyword") (= val "settle"))
|
||||||
(do (adv!) (list (quote settle))))
|
(do
|
||||||
|
(adv!)
|
||||||
|
(let
|
||||||
|
((tgt (cond ((at-end?) nil) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "when") (= (tp-val) "on"))) nil) (true (parse-expr)))))
|
||||||
|
(if
|
||||||
|
(nil? tgt)
|
||||||
|
(list (quote settle))
|
||||||
|
(list (quote settle) tgt)))))
|
||||||
((and (= typ "keyword") (= val "go"))
|
((and (= typ "keyword") (= val "go"))
|
||||||
(do (adv!) (parse-go-cmd)))
|
(do (adv!) (parse-go-cmd)))
|
||||||
((and (= typ "keyword") (= val "return"))
|
((and (= typ "keyword") (= val "return"))
|
||||||
@@ -2716,9 +2776,11 @@
|
|||||||
(adv!)
|
(adv!)
|
||||||
(expect-kw! "view")
|
(expect-kw! "view")
|
||||||
(expect-kw! "transition")
|
(expect-kw! "transition")
|
||||||
(let ((using (if (match-kw "using") (parse-expr) nil)))
|
(let
|
||||||
|
((using (if (match-kw "using") (parse-expr) nil)))
|
||||||
(match-kw "then")
|
(match-kw "then")
|
||||||
(let ((body (parse-cmd-list)))
|
(let
|
||||||
|
((body (parse-cmd-list)))
|
||||||
(match-kw "end")
|
(match-kw "end")
|
||||||
(list (quote view-transition!) using body)))))
|
(list (quote view-transition!) using body)))))
|
||||||
(true (parse-expr))))))
|
(true (parse-expr))))))
|
||||||
@@ -2882,7 +2944,11 @@
|
|||||||
(true nil))))
|
(true nil))))
|
||||||
(true nil))))
|
(true nil))))
|
||||||
(consume-having!)
|
(consume-having!)
|
||||||
(when (and (= (tp-type) "keyword") (= (tp-val) "queue")) (do (adv!) (adv!)))
|
(when
|
||||||
|
(and
|
||||||
|
(= (tp-type) "keyword")
|
||||||
|
(= (tp-val) "queue"))
|
||||||
|
(do (adv!) (adv!)))
|
||||||
(let
|
(let
|
||||||
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
|
((having (if (or h-margin h-threshold) (dict "margin" h-margin "threshold" h-threshold) nil)))
|
||||||
(let
|
(let
|
||||||
|
|||||||
@@ -1805,9 +1805,11 @@
|
|||||||
;; ── core/parser (14 tests) ──
|
;; ── core/parser (14 tests) ──
|
||||||
(defsuite "hs-upstream-core/parser"
|
(defsuite "hs-upstream-core/parser"
|
||||||
(deftest "_hyperscript() evaluate API still throws on first error"
|
(deftest "_hyperscript() evaluate API still throws on first error"
|
||||||
(error "SKIP (untranslated): _hyperscript() evaluate API still throws on first error"))
|
(assert-throws (fn () (eval-hs "add - to")))
|
||||||
|
)
|
||||||
(deftest "basic parse error messages work"
|
(deftest "basic parse error messages work"
|
||||||
(error "SKIP (untranslated): basic parse error messages work"))
|
(assert-throws (fn () (eval-hs "add - to")))
|
||||||
|
)
|
||||||
(deftest "can have alternate comments in attributes"
|
(deftest "can have alternate comments in attributes"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(let ((_el-div (dom-create-element "div")))
|
(let ((_el-div (dom-create-element "div")))
|
||||||
@@ -2090,7 +2092,8 @@
|
|||||||
(assert= (dom-text-content (dom-query-by-id "div1")) "foo")
|
(assert= (dom-text-content (dom-query-by-id "div1")) "foo")
|
||||||
))
|
))
|
||||||
(deftest "extra chars cause error when evaling"
|
(deftest "extra chars cause error when evaling"
|
||||||
(error "SKIP (untranslated): extra chars cause error when evaling"))
|
(assert-throws (fn () (eval-hs "1!")))
|
||||||
|
)
|
||||||
(deftest "listen for event on form"
|
(deftest "listen for event on form"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(let ((_el-form (dom-create-element "form")) (_el-b1 (dom-create-element "button")))
|
(let ((_el-form (dom-create-element "form")) (_el-b1 (dom-create-element "button")))
|
||||||
@@ -3927,7 +3930,8 @@
|
|||||||
(dom-append (dom-body) _el-button)
|
(dom-append (dom-body) _el-button)
|
||||||
(hs-activate! _el-button)
|
(hs-activate! _el-button)
|
||||||
(dom-dispatch _el-button "click" nil)
|
(dom-dispatch _el-button "click" nil)
|
||||||
(assert= (dom-text-content (dom-query-by-id "target")) "new")))
|
(assert= (dom-text-content (dom-query-by-id "target")) "new")
|
||||||
|
))
|
||||||
(deftest "set #id replaces element with HTML string"
|
(deftest "set #id replaces element with HTML string"
|
||||||
(hs-cleanup!)
|
(hs-cleanup!)
|
||||||
(let ((_el-target (dom-create-element "div")) (_el-button (dom-create-element "button")))
|
(let ((_el-target (dom-create-element "div")) (_el-button (dom-create-element "button")))
|
||||||
@@ -5776,11 +5780,28 @@
|
|||||||
(assert= (eval-hs "true and (false or true)") true)
|
(assert= (eval-hs "true and (false or true)") true)
|
||||||
)
|
)
|
||||||
(deftest "should short circuit with and expression"
|
(deftest "should short circuit with and expression"
|
||||||
(error "SKIP (untranslated): should short circuit with and expression"))
|
(let ((func1-called false) (func2-called false))
|
||||||
|
(let ((func1 (fn () (let ((dummy (set! func1-called true))) false)))
|
||||||
|
(func2 (fn () (let ((dummy (set! func2-called true))) false))))
|
||||||
|
(let ((result (eval-hs-locals "func1() and func2()"
|
||||||
|
(list (list (quote func1) func1) (list (quote func2) func2)))))
|
||||||
|
(assert= result false)
|
||||||
|
(assert func1-called)
|
||||||
|
(assert (not func2-called)))))
|
||||||
|
)
|
||||||
(deftest "should short circuit with or expression"
|
(deftest "should short circuit with or expression"
|
||||||
(error "SKIP (untranslated): should short circuit with or expression"))
|
(let ((func1-called false) (func2-called false))
|
||||||
|
(let ((func1 (fn () (let ((dummy (set! func1-called true))) true)))
|
||||||
|
(func2 (fn () (let ((dummy (set! func2-called true))) true))))
|
||||||
|
(let ((result (eval-hs-locals "func1() or func2()"
|
||||||
|
(list (list (quote func1) func1) (list (quote func2) func2)))))
|
||||||
|
(assert result)
|
||||||
|
(assert func1-called)
|
||||||
|
(assert (not func2-called)))))
|
||||||
|
)
|
||||||
(deftest "unparenthesized expressions with multiple operators cause an error"
|
(deftest "unparenthesized expressions with multiple operators cause an error"
|
||||||
(error "SKIP (untranslated): unparenthesized expressions with multiple operators cause an error"))
|
(assert-throws (fn () (eval-hs "true and false or true")))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; ── expressions/mathOperator (15 tests) ──
|
;; ── expressions/mathOperator (15 tests) ──
|
||||||
@@ -5827,7 +5848,8 @@
|
|||||||
(assert= (eval-hs "1 - 1") 0)
|
(assert= (eval-hs "1 - 1") 0)
|
||||||
)
|
)
|
||||||
(deftest "unparenthesized expressions with multiple operators cause an error"
|
(deftest "unparenthesized expressions with multiple operators cause an error"
|
||||||
(error "SKIP (untranslated): unparenthesized expressions with multiple operators cause an error"))
|
(assert-throws (fn () (eval-hs "1 + 2 * 3")))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; ── expressions/no (9 tests) ──
|
;; ── expressions/no (9 tests) ──
|
||||||
@@ -6749,7 +6771,12 @@
|
|||||||
(assert= (eval-hs-locals "`https://${foo}`" (list (list (quote foo) "bar"))) "https://bar")
|
(assert= (eval-hs-locals "`https://${foo}`" (list (list (quote foo) "bar"))) "https://bar")
|
||||||
)
|
)
|
||||||
(deftest "should handle strings with tags and quotes"
|
(deftest "should handle strings with tags and quotes"
|
||||||
(error "SKIP (untranslated): should handle strings with tags and quotes"))
|
(let ((record {:name "John Connor" :age 21 :favouriteColour "bleaux"}))
|
||||||
|
(assert= (eval-hs-locals
|
||||||
|
"`<div age=\"${record.age}\" style=\"color:${record.favouriteColour}\">${record.name}</div>`"
|
||||||
|
(list (list (quote record) record)))
|
||||||
|
"<div age=\"21\" style=\"color:bleaux\">John Connor</div>"))
|
||||||
|
)
|
||||||
(deftest "string templates preserve white space"
|
(deftest "string templates preserve white space"
|
||||||
(assert= (eval-hs "` ${1 + 2} ${1 + 2} `") " 3 3 ")
|
(assert= (eval-hs "` ${1 + 2} ${1 + 2} `") " 3 3 ")
|
||||||
(assert= (eval-hs "`${1 + 2} ${1 + 2} `") "3 3 ")
|
(assert= (eval-hs "`${1 + 2} ${1 + 2} `") "3 3 ")
|
||||||
@@ -6828,7 +6855,8 @@
|
|||||||
;; ── expressions/typecheck (5 tests) ──
|
;; ── expressions/typecheck (5 tests) ──
|
||||||
(defsuite "hs-upstream-expressions/typecheck"
|
(defsuite "hs-upstream-expressions/typecheck"
|
||||||
(deftest "can do basic non-string typecheck failure"
|
(deftest "can do basic non-string typecheck failure"
|
||||||
(error "SKIP (untranslated): can do basic non-string typecheck failure"))
|
(assert-throws (fn () (hs-type-assert true "String")))
|
||||||
|
)
|
||||||
(deftest "can do basic string non-null typecheck"
|
(deftest "can do basic string non-null typecheck"
|
||||||
(assert= (eval-hs "'foo' : String!") "foo")
|
(assert= (eval-hs "'foo' : String!") "foo")
|
||||||
)
|
)
|
||||||
@@ -6839,7 +6867,8 @@
|
|||||||
(eval-hs "null : String")
|
(eval-hs "null : String")
|
||||||
)
|
)
|
||||||
(deftest "null causes null safe string check to fail"
|
(deftest "null causes null safe string check to fail"
|
||||||
(error "SKIP (untranslated): null causes null safe string check to fail"))
|
(assert-throws (fn () (hs-type-assert-strict nil "String")))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; ── ext/component (20 tests) ──
|
;; ── ext/component (20 tests) ──
|
||||||
@@ -9845,7 +9874,8 @@
|
|||||||
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
(dom-dispatch (dom-query-by-id "d1") "click" nil)
|
||||||
))
|
))
|
||||||
(deftest "non-function pseudo-command is an error"
|
(deftest "non-function pseudo-command is an error"
|
||||||
(error "SKIP (untranslated): non-function pseudo-command is an error"))
|
(assert-throws (fn () (eval-hs "on click log me then foo.bar + bar")))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
;; ── put (38 tests) ──
|
;; ── put (38 tests) ──
|
||||||
|
|||||||
@@ -740,6 +740,11 @@ for(const f of['spec/harness.sx','spec/tests/test-framework.sx','spec/tests/test
|
|||||||
}
|
}
|
||||||
process.stderr.write(`Tests loaded in ${Date.now()-t_tests}ms\n`);
|
process.stderr.write(`Tests loaded in ${Date.now()-t_tests}ms\n`);
|
||||||
|
|
||||||
|
// Redefine try-call to actually catch errors for assert-throws.
|
||||||
|
// During loading it was the registration version (stores thunks, returns {:ok true}).
|
||||||
|
// Now that tests are registered, redefine it to run the thunk and catch any exception.
|
||||||
|
K.eval('(define try-call _run-test-thunk)');
|
||||||
|
|
||||||
// Override eval-hs-error for runtimeErrors tests: hs-null-raise!/hs-empty-raise!/hs-win-call
|
// Override eval-hs-error for runtimeErrors tests: hs-null-raise!/hs-empty-raise!/hs-win-call
|
||||||
// each wrap their (raise msg) in a self-contained guard so the raise is swallowed before
|
// each wrap their (raise msg) in a self-contained guard so the raise is swallowed before
|
||||||
// it can escape through the empty JIT kont and trigger the slow host_error path (~34s).
|
// it can escape through the empty JIT kont and trigger the slow host_error path (~34s).
|
||||||
@@ -806,6 +811,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
const _NO_STEP_LIMIT_SUITES = new Set([
|
const _NO_STEP_LIMIT_SUITES = new Set([
|
||||||
"hs-upstream-core/runtimeErrors",
|
"hs-upstream-core/runtimeErrors",
|
||||||
"hs-upstream-expressions/collectionExpressions",
|
"hs-upstream-expressions/collectionExpressions",
|
||||||
|
"hs-upstream-expressions/typecheck",
|
||||||
]);
|
]);
|
||||||
// Enable step limit for timeout protection — reset counter first so accumulation
|
// Enable step limit for timeout protection — reset counter first so accumulation
|
||||||
// across tests doesn't cause signed-32-bit wraparound (~2B extra steps before limit fires).
|
// across tests doesn't cause signed-32-bit wraparound (~2B extra steps before limit fires).
|
||||||
@@ -820,6 +826,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
const _SLOW_DEADLINE_SUITES = {
|
const _SLOW_DEADLINE_SUITES = {
|
||||||
"hs-upstream-core/runtimeErrors": 30000,
|
"hs-upstream-core/runtimeErrors": 30000,
|
||||||
"hs-upstream-expressions/collectionExpressions": 60000,
|
"hs-upstream-expressions/collectionExpressions": 60000,
|
||||||
|
"hs-upstream-expressions/typecheck": 30000,
|
||||||
};
|
};
|
||||||
_testDeadline = Date.now() + (_SLOW_DEADLINE[name] || _SLOW_DEADLINE_SUITES[suite] || 10000);
|
_testDeadline = Date.now() + (_SLOW_DEADLINE[name] || _SLOW_DEADLINE_SUITES[suite] || 10000);
|
||||||
globalThis.__hs_deadline = _testDeadline; // expose to WASM cek_step_loop
|
globalThis.__hs_deadline = _testDeadline; // expose to WASM cek_step_loop
|
||||||
@@ -828,13 +835,23 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
|||||||
let ok=false,err=null;
|
let ok=false,err=null;
|
||||||
try{
|
try{
|
||||||
// Use SX-level guard to catch errors, avoiding __sxR side-channel issues
|
// Use SX-level guard to catch errors, avoiding __sxR side-channel issues
|
||||||
// Returns a dict with :ok and :error keys
|
// Returns a dict with :ok and :error keys.
|
||||||
K.eval(`(define _test-result (_run-test-thunk (get (nth _test-registry ${i}) "thunk")))`);
|
// Note: api_eval returns "Error: <msg>" string (not throw) for SX exceptions,
|
||||||
const isOk=K.eval('(get _test-result "ok")');
|
// so K.eval may return an error string rather than throwing. Check for this.
|
||||||
if(isOk===true){ok=true;}
|
const defineR = K.eval(`(define _test-result (_run-test-thunk (get (nth _test-registry ${i}) "thunk")))`);
|
||||||
else{
|
// Clear deadline immediately: once the test thunk finishes (or times out and
|
||||||
const errMsg=K.eval('(get _test-result "error")');
|
// the guard catches it), further K.eval calls for result inspection must not
|
||||||
err=errMsg?String(errMsg).slice(0,150):'unknown error';
|
// keep re-firing the deadline check on every 10k steps.
|
||||||
|
globalThis.__hs_deadline = 0;
|
||||||
|
if(typeof defineR==='string' && defineR.startsWith('Error: ')){
|
||||||
|
err=defineR.slice(7,157); // strip "Error: " prefix
|
||||||
|
} else {
|
||||||
|
const isOk=K.eval('(get _test-result "ok")');
|
||||||
|
if(isOk===true){ok=true;}
|
||||||
|
else{
|
||||||
|
const errMsg=K.eval('(get _test-result "error")');
|
||||||
|
err=errMsg?String(errMsg).slice(0,150):'unknown error';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}catch(e){err=(e.message||'').slice(0,150);}
|
}catch(e){err=(e.message||'').slice(0,150);}
|
||||||
setStepLimit(0); // disable step limit between tests
|
setStepLimit(0); // disable step limit between tests
|
||||||
|
|||||||
@@ -262,6 +262,42 @@ MANUAL_TEST_BODIES = {
|
|||||||
' (dom-dispatch _el-button "click" nil)',
|
' (dom-dispatch _el-button "click" nil)',
|
||||||
' (assert= (dom-text-content _el-button) "bar"))',
|
' (assert= (dom-text-content _el-button) "bar"))',
|
||||||
],
|
],
|
||||||
|
# logicalOperator: short-circuit and/or
|
||||||
|
"should short circuit with and expression": [
|
||||||
|
' (let ((func1-called false) (func2-called false))',
|
||||||
|
' (let ((func1 (fn () (let ((dummy (set! func1-called true))) false)))',
|
||||||
|
' (func2 (fn () (let ((dummy (set! func2-called true))) false))))',
|
||||||
|
' (let ((result (eval-hs-locals "func1() and func2()"',
|
||||||
|
' (list (list (quote func1) func1) (list (quote func2) func2)))))',
|
||||||
|
' (assert= result false)',
|
||||||
|
' (assert func1-called)',
|
||||||
|
' (assert (not func2-called)))))',
|
||||||
|
],
|
||||||
|
"should short circuit with or expression": [
|
||||||
|
' (let ((func1-called false) (func2-called false))',
|
||||||
|
' (let ((func1 (fn () (let ((dummy (set! func1-called true))) true)))',
|
||||||
|
' (func2 (fn () (let ((dummy (set! func2-called true))) true))))',
|
||||||
|
' (let ((result (eval-hs-locals "func1() or func2()"',
|
||||||
|
' (list (list (quote func1) func1) (list (quote func2) func2)))))',
|
||||||
|
' (assert result)',
|
||||||
|
' (assert func1-called)',
|
||||||
|
' (assert (not func2-called)))))',
|
||||||
|
],
|
||||||
|
# typecheck: call hs-type-assert directly — eval-hs "true : String" is too slow (JIT cascade)
|
||||||
|
"can do basic non-string typecheck failure": [
|
||||||
|
' (assert-throws (fn () (hs-type-assert true "String")))',
|
||||||
|
],
|
||||||
|
"null causes null safe string check to fail": [
|
||||||
|
' (assert-throws (fn () (hs-type-assert-strict nil "String")))',
|
||||||
|
],
|
||||||
|
# strings: template with double quotes and object property access
|
||||||
|
"should handle strings with tags and quotes": [
|
||||||
|
' (let ((record {:name "John Connor" :age 21 :favouriteColour "bleaux"}))',
|
||||||
|
' (assert= (eval-hs-locals',
|
||||||
|
' "`<div age=\\"${record.age}\\" style=\\"color:${record.favouriteColour}\\">${record.name}</div>`"',
|
||||||
|
' (list (list (quote record) record)))',
|
||||||
|
' "<div age=\\"21\\" style=\\"color:bleaux\\">John Connor</div>"))',
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2997,6 +3033,17 @@ def generate_eval_only_test(test, idx):
|
|||||||
hs_expr = extract_hs_expr(m.group(2))
|
hs_expr = extract_hs_expr(m.group(2))
|
||||||
assertions.append(f' (hs-compile "{hs_expr}")')
|
assertions.append(f' (hs-compile "{hs_expr}")')
|
||||||
|
|
||||||
|
# Pattern 5: error("expr") assigned and checked with toMatch — must throw
|
||||||
|
# Handles: const/var msg = await error("expr"); expect(msg).toMatch(/.../)
|
||||||
|
# The error() helper captures exceptions; we just assert-throws.
|
||||||
|
if not assertions:
|
||||||
|
for m in re.finditer(
|
||||||
|
r'(?:const|var|let)\s+\w+\s*=\s*await\s+error\((["\x27])(.+?)\1\)',
|
||||||
|
body, re.DOTALL
|
||||||
|
):
|
||||||
|
hs_expr = extract_hs_expr(m.group(2))
|
||||||
|
assertions.append(f' (assert-throws (fn () (eval-hs "{hs_expr}")))')
|
||||||
|
|
||||||
if not assertions:
|
if not assertions:
|
||||||
return None # Can't convert this body pattern
|
return None # Can't convert this body pattern
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user