spec: rows for every untested var (131 -> 0); the probes found five real bugs

test/spec/untested-vars-spec.janet adds 143 rows asserting jolt's documented
behavior for the whole implemented-untested category — primed arithmetic,
the array/aset/coercion stubs, unchecked-*, the chunk family, JVM-shape
stubs (class/bean/proxy/memfn as resolve-only or :throws), ns/REPL
machinery, and the misc seqs. tools/spec_coverage.py now checks each var as
a whole TOKEN in the test sources (call-position-only matching missed *1,
+', ., .., /, and bare transducer refs like cat).

Writing rows from probed truth surfaced five real bugs, all fixed:
- comp with a jolt-IFn stage silently returned nil ((comp seq :content)) —
  raw Janet keyword application is not jolt invoke. comp is the canonical
  overlay defn now (fixed-arity composed fn, so the hot 1-arg path is two
  direct calls); the seed keeps a private td-comp only for the transducer
  machinery. hof bench +9% vs native, the price of correct IFn dispatch.
- extend (the fn) was a nil-expanding stub MACRO shadowing any definition;
  it's a real fn over register-method now, and extends? (a constant-false
  stub) is real over extenders
- (.. x f g) hit the 'ClassName.' constructor branch (a name ending in a
  dot) and died; .. is the canonical threading macro now
- aclone errored on pvecs; ns-interns/ns-imports returned live host tables
  that count/seq reject (now structs)

Thread/sleep + Thread/yield land as Thread statics beside Math/: sleep parks
the WORKER's own event loop (each future thread has one), which makes timed
deref provably fire — futures-spec gains the timeout-fires, sleep-in-body,
and timed-out-future-still-completes rows. The futures impl itself already
ran on real OS threads (ev/spawn-thread + marshalled results); jolt-ejx was
stale.

Dashboard: implemented+tested 433 -> 564 of 694; implemented-untested and
missing-portable are both EMPTY. Gate: jpm exit 0, all tests passed.
This commit is contained in:
Yogthos 2026-06-10 17:52:30 -04:00
parent daab2ab4cc
commit d06b3fe636
8 changed files with 414 additions and 151 deletions

View file

@ -16,7 +16,13 @@
["future returns a map" "{:a 1}" "(deref (future {:a 1}))"]
["deref is cached/idempotent" "[2 2]" "(let [f (future (+ 1 1))] [(deref f) (deref f)])"]
["timed deref of ready future" "42" "(let [f (future 42)] (deref f) (deref f 1000 :nope))"]
["body error re-raised on deref" :throws "(deref (future (throw \"boom\")))"])
["body error re-raised on deref" :throws "(deref (future (throw \"boom\")))"]
# Thread/sleep parks the WORKER's own event loop (each future thread has one),
# so a sleeping future doesn't block the parent — and timed deref can fire.
["timed deref times out" ":timed-out" "(deref (future (do (Thread/sleep 300) :late)) 10 :timed-out)"]
["Thread/sleep in body" ":slept" "(deref (future (do (Thread/sleep 5) :slept)))"]
["timed-out future still completes" ":late"
"(let [f (future (do (Thread/sleep 30) :late))] (deref f 5 :early) (deref f))"])
(defspec "clojure.core / futures — predicates"
["future? true" "true" "(future? (future 1))"]

View file

@ -0,0 +1,177 @@
# Specification: spec rows for every previously-untested implemented var
# (jolt-brh follow-up — promotes the dashboard's implemented-untested category).
# Rows assert jolt's ACTUAL documented behavior, including the stub families:
# arrays are vectors/host buffers, proxies/JVM reflection are resolve-only,
# unchecked-* are plain double ops, chunks are eager equivalents.
(use ../support/harness)
(defspec "untested / primed + division + bit ops"
["+'" "3" "(+' 1 2)"]
["-'" "3" "(-' 5 2)"]
["*'" "12" "(*' 3 4)"]
["inc'" "2.5" "(inc' 1.5)"]
["dec'" "1.5" "(dec' 2.5)"]
["/" "2" "(/ 6 3)"]
["/ ratio-as-double" "0.5" "(/ 1 2)"]
["bit-not" "-6" "(bit-not 5)"]
["bit-and-not" "4" "(bit-and-not 12 10)"]
["bit-flip" "3" "(bit-flip 2 0)"]
["unsigned-bit-shift-right" "2" "(unsigned-bit-shift-right 8 2)"])
(defspec "untested / hash family"
["hash stable" "true" "(= (hash :a) (hash :a))"]
["hash int" "true" "(int? (hash [1 2]))"]
["hash-combine" "true" "(int? (hash-combine 1 2))"]
["hash-ordered-coll" "true" "(int? (hash-ordered-coll [1 2]))"]
["hash-unordered-coll" "true" "(int? (hash-unordered-coll #{1}))"])
(defspec "untested / array stubs (vectors + host buffers)"
["make-array" "(quote (nil nil nil))" "(make-array 3)"]
["into-array" "(quote (1 2))" "(into-array [1 2])"]
["to-array" "(quote (1 2))" "(to-array [1 2])"]
["aclone vec" "(quote (1 2))" "(aclone [1 2])"]
["aclone independent" "(quote (9 2))" "(let [a (aclone (to-array [1 2]))] (aset a 0 9) (seq a))"]
["aset/aget" "9" "(let [a (to-array [1 2 3])] (aset a 0 9) (aget a 0))"]
["aset-int" "7" "(let [a (to-array [1 2])] (aset-int a 0 7) (aget a 0))"]
["aset-boolean" "true" "(let [a (to-array [1])] (aset-boolean a 0 true) (aget a 0))"]
["aset-byte" "9" "(let [a (to-array [0])] (aset-byte a 0 9) (aget a 0))"]
["aset-char" "\\a" "(let [a (to-array [0])] (aset-char a 0 \\a) (aget a 0))"]
["aset-double" "1.5" "(let [a (to-array [0])] (aset-double a 0 1.5) (aget a 0))"]
["aset-float" "2.5" "(let [a (to-array [0])] (aset-float a 0 2.5) (aget a 0))"]
["aset-long" "3" "(let [a (to-array [0])] (aset-long a 0 3) (aget a 0))"]
["aset-short" "4" "(let [a (to-array [0])] (aset-short a 0 4) (aget a 0))"]
["boolean-array" "(quote (false false))" "(boolean-array 2)"]
["int-array" "(quote (1 2))" "(int-array [1 2])"]
["long-array" "(quote (0 0))" "(long-array 2)"]
["double-array" "(quote (0 0))" "(double-array 2)"]
["float-array" "(quote (0 0))" "(float-array 2)"]
["short-array" "(quote (0 0))" "(short-array 2)"]
["char-array count" "2" "(count (char-array 2))"]
["byte-array bytes?" "true" "(bytes? (byte-array 2))"]
["bytes? not vec" "false" "(bytes? [1])"])
(defspec "untested / typed coercion views"
["booleans" "(quote (true))" "(booleans [true])"]
["doubles" "(quote (1))" "(doubles [1.0])"]
["floats" "(quote (1))" "(floats [1.0])"]
["ints" "(quote (1))" "(ints [1])"]
["longs" "(quote (1))" "(longs [1])"]
["shorts" "(quote (1))" "(shorts [1])"]
["chars first" "\\a" "(first (chars [\\a]))"]
["bytes view" "true" "(bytes? (bytes [65]))"]
["byte" "65" "(byte 65)"]
["short" "1" "(short 1)"]
["long truncates" "1" "(long 1.7)"]
["double" "3" "(double 3)"]
["float" "3" "(float 3)"])
(defspec "untested / unchecked-* are plain ops"
["unchecked-add" "3" "(unchecked-add 1 2)"]
["unchecked-add-int" "3" "(unchecked-add-int 1 2)"]
["unchecked-subtract" "3" "(unchecked-subtract 5 2)"]
["unchecked-subtract-int" "3" "(unchecked-subtract-int 5 2)"]
["unchecked-multiply" "6" "(unchecked-multiply 2 3)"]
["unchecked-multiply-int" "6" "(unchecked-multiply-int 2 3)"]
["unchecked-inc" "2" "(unchecked-inc 1)"]
["unchecked-inc-int" "2" "(unchecked-inc-int 1)"]
["unchecked-dec" "2" "(unchecked-dec 3)"]
["unchecked-dec-int" "2" "(unchecked-dec-int 3)"]
["unchecked-negate" "-4" "(unchecked-negate 4)"]
["unchecked-negate-int" "-4" "(unchecked-negate-int 4)"]
["unchecked-divide-int" "3" "(unchecked-divide-int 7 2)"]
["unchecked-remainder-int" "1" "(unchecked-remainder-int 7 2)"]
["unchecked-int" "3" "(unchecked-int 3.7)"]
["unchecked-long" "3" "(unchecked-long 3.7)"]
["unchecked-double" "3" "(unchecked-double 3)"]
["unchecked-float" "3" "(unchecked-float 3)"]
["unchecked-byte" "65" "(unchecked-byte 65)"]
["unchecked-char" "97" "(unchecked-char 97)"]
["unchecked-short" "5" "(unchecked-short 5)"])
(defspec "untested / chunk family (eager equivalents) + cat"
["chunk round-trip" "1"
"(let [cb (chunk-buffer 4)] (chunk-append cb 1) (chunk-first (chunk-cons (chunk cb) nil)))"]
["cat transducer" "[1 2 3]" "(into [] cat [[1] [2 3]])"]
["ensure-reduced wraps" "true" "(reduced? (ensure-reduced 5))"]
["ensure-reduced keeps reduced" "true" "(reduced? (ensure-reduced (reduced 5)))"]
["halt-when" "4" "(transduce (halt-when even?) conj [] [1 3 4 5])"]
["chunk-next exhausted" "nil"
"(let [cb (chunk-buffer 2)] (chunk-append cb 1) (chunk-next (chunk-cons (chunk cb) nil)))"]
["chunk-rest seqable" "()"
"(let [cb (chunk-buffer 2)] (chunk-append cb 1) (vec (chunk-rest (chunk-cons (chunk cb) nil))))"])
(defspec "untested / JVM-shape stubs (documented jolt behavior)"
["class number" "\"java.lang.Number\"" "(class 1)"]
["class string" "\"java.lang.String\"" "(class \"s\")"]
["class keyword" "\"clojure.lang.Keyword\"" "(class :k)"]
["class nil" "nil" "(class nil)"]
["bean is the map" "{:a 1}" "(bean {:a 1})"]
["biginteger" "\"5\"" "(str (biginteger 5))"]
["proxy resolves nil" "nil" "(proxy [Object] [] (toString [] \"x\"))"]
["construct-proxy throws" :throws "(construct-proxy nil)"]
["get-proxy-class throws" :throws "(get-proxy-class)"]
["init-proxy" "nil" "(init-proxy nil {})"]
["update-proxy" "nil" "(update-proxy nil {})"]
["proxy-mappings" "{}" "(proxy-mappings nil)"]
["proxy-call-with-super calls" "1" "(proxy-call-with-super (fn [] 1) nil \"m\")"]
["memfn throws" :throws "((memfn count) \"abc\")"]
["proxy-super throws" :throws "(proxy-super count [1])"]
["re-groups throws" :throws "(re-groups (re-matcher #\"a\" \"b\"))"]
["re-matcher builds" "false" "(nil? (re-matcher #\"a\" \"abc\"))"]
["print-dup" "nil" "(print-dup 1 nil)"]
["print-method" "nil" "(print-method 1 nil)"]
["uri? string" "false" "(uri? \"http://x\")"]
["uri? nil" "false" "(uri? nil)"]
["definterface defines" "true" "(var? (definterface IFoo (foo [x])))"]
["enumeration-seq" "(quote (1 2))" "(enumeration-seq [1 2])"]
["iterator-seq" "(quote (1 2))" "(iterator-seq [1 2])"]
["seque passthrough" "(quote (1 2))" "(seque [1 2])"]
["delay? true" "true" "(delay? (delay 1))"]
["delay? false" "false" "(delay? 1)"]
["future-call" "42" "(deref (future-call (fn [] 42)))"]
[". member resolves nil" "nil" "(. \"abc\" count)"]
[".. threads members" "nil" "(.. \"abc\" count)"])
(defspec "untested / protocols: extend + extends?"
["extend registers" ":str"
"(do (defprotocol Pe (pe [x])) (extend (quote String) Pe {:pe (fn [x] :str)}) (pe \"s\"))"]
["extend two methods" "[1 2]"
"(do (defprotocol P3 (pa [x]) (pb [x])) (extend (quote Long) P3 {:pa (fn [x] 1) :pb (fn [x] 2)}) [(pa 0) (pb 0)])"]
["extends? after extend" "true"
"(do (defprotocol P4 (pc [x])) (extend (quote Long) P4 {:pc (fn [x] 1)}) (extends? P4 (quote Long)))"]
["extends? without" "false" "(do (defprotocol P5 (pd [x])) (extends? P5 (quote Long)))"])
(defspec "untested / ns + REPL machinery"
["all-ns non-empty" "true" "(pos? (count (all-ns)))"]
["ns-interns sees def" "true" "(do (def zz 1) (pos? (count (ns-interns (quote user)))))"]
["ns-interns countable" "true" "(map? (ns-interns (quote user)))"]
["ns-imports empty user" "0" "(count (ns-imports (quote user)))"]
["reset-meta!" "{:doc \"d\"}" "(do (def vv 1) (reset-meta! (var vv) {:doc \"d\"}))"]
["prefers empty" "{}" "(do (defmulti mm identity) (prefers mm))"]
["refer-clojure" "nil" "(refer-clojure)"]
["special-symbol? if" "true" "(special-symbol? (quote if))"]
["special-symbol? fn name" "false" "(special-symbol? (quote foo))"]
["destructure expands" "true" "(pos? (count (destructure (quote [[a b] x]))))"]
["seq-to-map-for-destructuring" "{:a 1}" "(seq-to-map-for-destructuring (quote (:a 1)) nil)"]
["*clojure-version* major" "1" "(:major *clojure-version*)"]
["*ns* user" "\"user\"" "(str *ns*)"]
["*1 nil outside repl" "nil" "*1"]
["*2 nil" "nil" "*2"]
["*3 nil" "nil" "*3"]
["*e nil" "nil" "*e"]
["*unchecked-math*" "false" "*unchecked-math*"]
["*in* bound" "true" "(map? *in*)"])
(defspec "untested / misc seqs + binding machinery"
["nfirst" "(quote (2))" "(nfirst [[1 2] [3]])"]
["xml-seq root" "1" "(count (xml-seq {:tag :a :content []}))"]
["xml-seq walks" "2" "(count (xml-seq {:tag :a :content [{:tag :b :content []}]}))"]
# regression: comp with a keyword stage must use jolt IFn dispatch
["comp keyword stage" "(quote (1 2))" "((comp seq :content) {:content [1 2]})"]
["comp three stages" "4" "((comp inc inc :n) {:n 2})"]
["random-sample all" "(quote (1 2))" "(random-sample 1.0 [1 2])"]
["random-sample none" "()" "(random-sample 0.0 [1 2])"]
["reader-conditional builds" "true" "(reader-conditional? (reader-conditional (quote (:clj 1)) false))"]
["->Eduction" "[2 3]" "(vec (->Eduction (map inc) [1 2]))"]
["bound-fn calls" "42" "((bound-fn [] 42))"]
["push/pop-thread-bindings" ":ok" "(do (push-thread-bindings {}) (pop-thread-bindings) :ok)"])