feat: read N/M/ratio/radix/exponent number literals; clean suite measurement

Reader gaps caused the clojure-test-suite worker to crash whole deftests on
literals it could not parse (0N, 1.5M, 2r1010, 1/2), losing every assertion in
the file. read-number now handles:
- N (bigint) / M (bigdec) suffixes -> plain number (Jolt has no bignum/bigdec)
- ratios a/b -> double quotient
- radix integers NrDDD (2r1010, 16rFF, 36rZ) parsed by base
- exponents (1e3, 1.5e-2) and 0X hex

Also fixed suite measurement: when-var-exists now skips silently (its SKIP
print to stdout was corrupting the worker's count line, dropping whole files),
and the worker emits counts on an @@COUNTS sentinel line (robust against test
bodies that print, e.g. with-out-str). Runner parses the sentinel; deftest
crashes now report the underlying message.

Impact: clojure-test-suite 210->231 files run, pass 1955->3535, clean files
24->39. Baseline raised to 3450/38.

spec: numbers/literal-syntax (13 cases). jpm test green.
This commit is contained in:
Yogthos 2026-06-05 09:54:14 -04:00
parent 20ab88dd0c
commit acfcf2f94b
5 changed files with 106 additions and 39 deletions

View file

@ -18,9 +18,9 @@
# Baseline: assertions Jolt currently passes across the suite. Raise as Jolt
# improves so a regression (previously-passing assertion breaking) is caught.
(def baseline-pass 1900)
(def baseline-pass 3450)
# A file is "clean" when it ran with zero failures AND zero errors.
(def baseline-clean-files 22)
(def baseline-clean-files 38)
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s;
# this only fires on infinite-sequence hangs.
(def per-file-timeout 6)
@ -55,11 +55,15 @@
(if (and ok data) (string data) nil))
(defn- parse-counts [s]
# s is "pass fail error"
(def parts (string/split " " (string/trim s)))
(if (= 3 (length parts))
[(scan-number (parts 0)) (scan-number (parts 1)) (scan-number (parts 2))]
nil))
# Find the "@@COUNTS p f e" sentinel line (a test body may have printed other
# lines to stdout, e.g. with-out-str tests).
(var result nil)
(each line (string/split "\n" s)
(when (string/has-prefix? "@@COUNTS " line)
(let [parts (string/split " " (string/trim line))]
(when (= 4 (length parts))
(set result [(scan-number (parts 1)) (scan-number (parts 2)) (scan-number (parts 3))])))))
result)
(if (not (os/stat suite-dir))
(print "clojure-test-suite: ~/src/clojure-test-suite not present — skipped")

View file

@ -37,4 +37,10 @@
(def p (eval-string ctx "(clojure.test/n-pass)"))
(def f (eval-string ctx "(clojure.test/n-fail)"))
(def e (eval-string ctx "(clojure.test/n-error)"))
(printf "%d %d %d" (if (number? p) p 0) (if (number? f) f 0) (if (number? e) e 0)))
# A "dump" 2nd arg (or SUITE_DUMP env) also prints each failure/error message
# (one DUMP line each) for triage.
(when (or (os/getenv "SUITE_DUMP") (= "dump" (get (dyn :args) 2)))
(eval-string ctx "(doseq [m (clojure.test/failures)] (println (str \"DUMP \" m)))"))
# Counts on a sentinel line so parsers find it even if a test body printed to
# stdout (e.g. with-out-str / println-str tests).
(printf "@@COUNTS %d %d %d" (if (number? p) p 0) (if (number? f) f 0) (if (number? e) e 0)))

View file

@ -72,6 +72,24 @@
["int? ##Inf false" "false" "(int? ##Inf)"]
["pos-int? ##Inf" "false" "(pos-int? ##Inf)"])
# Numeric literal syntaxes. Jolt has no true bignum/ratio/bigdec types, so the
# N (bigint) and M (bigdec) suffixes read as the plain number, ratios as the
# double quotient; radix integers (NrDDD) are parsed by base.
(defspec "numbers / literal syntax"
["bigint suffix N" "42" "42N"]
["bigint zero" "0" "0N"]
["bigdec suffix M" "1.5" "1.5M"]
["bigdec int M" "0" "0.0M"]
["ratio -> double" "0.5" "1/2"]
["ratio 3/4" "0.75" "3/4"]
["neg ratio" "-0.5" "-1/2"]
["radix binary" "10" "2r1010"]
["radix hex-ish" "255" "16rFF"]
["radix base36" "35" "36rZ"]
["hex" "255" "0xFF"]
["exponent" "1000.0" "1e3"]
["exponent neg" "0.015" "1.5e-2"])
(defspec "numbers / bit-ops & math"
["bit-and" "4" "(bit-and 12 6)"]
["bit-or" "14" "(bit-or 12 6)"]

View file

@ -79,7 +79,7 @@
;; Run every deftest registered since the last reset, isolating crashes.
(defn run-registered []
(doseq [t @registry]
(try (t) (catch :default e (clojure.test/err! "deftest crashed"))))
(try (t) (catch :default e (clojure.test/err! (str "deftest crashed: " (clojure.core/ex-message e))))))
nil)
;; clojure.test entry points the suite may call — no-ops; the Janet runner
@ -96,10 +96,12 @@
;; Gate a test on whether its target var exists in this dialect. Jolt only
;; implements a subset of clojure.core, so unimplemented fns get skipped
;; cleanly rather than erroring.
;; Skips silently (no stdout) so the worker's stdout stays just the count line;
;; an unimplemented var simply contributes no assertions.
(defmacro when-var-exists [var-sym & body]
(if (resolve var-sym)
`(do ~@body)
`(println "SKIP -" '~var-sym)))
nil))
;; `(thrown? body)` — true iff evaluating body throws. The suite always uses
;; the single-arg (no exception-class) form via this portability helper.