corpus.edn :expected is now the value reference JVM Clojure produces, set by the new test/conformance/regen-corpus.clj (one JVM process, per-row thread watchdog). 167 rows moved to the JVM value: ratios (/ 1 2)=>1/2, doubles (double 3)=>3.0, shared-heap concurrency (the future/pmap/agent cases), clojure.math doubles. The JVM is the spec; jolt is measured against it. known-divergences.edn shrinks to the rows whose JVM value is an opaque host object that can't round-trip to source (Java arrays, transients, atoms, beans, proxies, chunks all print as #object[..@addr]) plus (fn* foo) and a few racy concurrency cases (:flaky). The zero-janet gate's allowlist becomes the set of host gaps vs the JVM spec (no Class/array/BigDecimal, :jolt reader, jolt's own printing). Math/clojure.math sqrt/pow/floor/trig now return doubles (Chez returns exact for exact args, e.g. (sqrt 9)=>3); JVM always returns a double. extract-corpus.janet no longer writes corpus.edn unless asked (the test runner imported it and was silently overwriting the JVM corpus with the spec sources' placeholder answers). The prelude parity gate is deleted — the zero-janet spine + certify.clj are the oracles. zero-janet 2678 (0 new divergences), certify 0 new / 0 stale, emit-test 330/330.
130 lines
5.7 KiB
Text
130 lines
5.7 KiB
Text
# Extract the case LIST (suite, label, actual) from the spec sources into corpus.edn.
|
|
#
|
|
# corpus.edn :expected is sourced from reference JVM Clojure by
|
|
# test/conformance/regen-corpus.clj — this extractor's :expected column is a
|
|
# placeholder. Use it to (re)derive the case list when adding spec rows, then run
|
|
# regen-corpus.clj to fill :expected from the JVM. Do not commit its raw output.
|
|
#
|
|
# Parses every test/spec/*.janet as DATA (no eval), pulls each
|
|
# (defspec "suite" [label expected actual] ...) triple. Run from repo root:
|
|
# janet test/chez/extract-corpus.janet
|
|
(use ../../src/jolt/reader) # not needed for parse, but keeps paths obvious
|
|
|
|
(defn parse-all [src]
|
|
(def p (parser/new))
|
|
(parser/consume p src)
|
|
(parser/eof p)
|
|
(def out @[])
|
|
(while (parser/has-more p) (array/push out (parser/produce p)))
|
|
out)
|
|
|
|
(defn edn-str [s]
|
|
# escape a Clojure-source string into an EDN/Janet string literal
|
|
(def b @`"`)
|
|
(each c s
|
|
(cond
|
|
(= c (chr `"`)) (buffer/push b `\"`)
|
|
(= c (chr "\\")) (buffer/push b "\\\\")
|
|
(= c (chr "\n")) (buffer/push b "\\n")
|
|
(= c (chr "\t")) (buffer/push b "\\t")
|
|
(buffer/push-byte b c)))
|
|
(buffer/push b `"`)
|
|
(string b))
|
|
|
|
(defn triples-from [form]
|
|
# form is (defspec "suite" [l e a] ...) as a parsed tuple
|
|
(when (and (indexed? form) (> (length form) 0) (= (first form) 'defspec))
|
|
(def suite (in form 1))
|
|
(def rows @[])
|
|
(each case (slice form 2)
|
|
(when (and (indexed? case) (>= (length case) 3))
|
|
(def label (in case 0))
|
|
(def expected (in case 1))
|
|
(def actual (in case 2))
|
|
# expected is either a Clojure-source string or the :throws keyword;
|
|
# actual is always a Clojure-source string. Skip non-literal rows.
|
|
(when (and (string? label) (string? actual)
|
|
(or (string? expected) (keyword? expected)))
|
|
(array/push rows {:suite suite :label label
|
|
:expected expected :actual actual})))
|
|
)
|
|
rows))
|
|
|
|
(def spec-dir "test/spec")
|
|
(def all @[])
|
|
(each f (sort (os/dir spec-dir))
|
|
(when (string/has-suffix? "-spec.janet" f)
|
|
(def path (string spec-dir "/" f))
|
|
(each form (parse-all (slurp path))
|
|
(when-let [rows (triples-from form)]
|
|
(array/concat all rows)))))
|
|
|
|
# --- fold in the inline conformance cases (jolt-ohtd) -------------------------
|
|
# test/integration/conformance-test.janet carries ~355 hand-written cases as a
|
|
# (def cases [["label" "expected" "actual"] ...]) vector — historically invisible to
|
|
# the corpus because extract only reads test/spec/. Pull them in too, deduped by
|
|
# :actual against the spec rows, so the host-neutral corpus is the union of both.
|
|
# Suites come from the file's `### ---- section ----` headers via a line scan (the
|
|
# parser drops comments, so section structure is recovered from raw text).
|
|
(def conf-path "test/integration/conformance-test.janet")
|
|
|
|
(defn- section-map [src]
|
|
# label-string -> section-name, by scanning raw lines: track the most recent
|
|
# `### ---- NAME ----` / `### ==== NAME ====` header above each `["label" ...]`.
|
|
(def out @{})
|
|
(var section "misc")
|
|
(each line (string/split "\n" src)
|
|
(def tl (string/trim line))
|
|
(cond
|
|
(string/has-prefix? "###" tl)
|
|
(let [body (string/trim (string/trim (string/trim tl "#") " ") "-= ")]
|
|
(when (> (length body) 0) (set section body)))
|
|
(string/has-prefix? "[\"" tl)
|
|
(let [close (string/find "\"" tl 2)]
|
|
(when close (put out (string/slice tl 2 close) section)))))
|
|
out)
|
|
|
|
(def conf-src (slurp conf-path))
|
|
(def sections (section-map conf-src))
|
|
(def seen-actual (tabseq [r :in all] (r :actual) true))
|
|
(var conf-added 0)
|
|
(each form (parse-all conf-src)
|
|
(when (and (indexed? form) (>= (length form) 3)
|
|
(= (first form) 'def) (= (in form 1) 'cases))
|
|
(each c (in form 2)
|
|
(when (and (indexed? c) (= 3 (length c))
|
|
(string? (in c 0)) (string? (in c 1)) (string? (in c 2)))
|
|
(def [label expected actual] [(in c 0) (in c 1) (in c 2)])
|
|
(unless (seen-actual actual)
|
|
(put seen-actual actual true)
|
|
(++ conf-added)
|
|
(array/push all {:suite (string "conformance / " (get sections label "misc"))
|
|
:label label :expected expected :actual actual}))))))
|
|
(printf "folded %d unique conformance cases (deduped by :actual)" conf-added)
|
|
|
|
# emit EDN-and-Janet-valid corpus. [suite label] is the canonical case id, so make
|
|
# it unique: a duplicate label within a suite gets " (N)" appended (jolt-3447 — the
|
|
# conformance fold can repeat a label, e.g. two "str/replace regex" rows). Rows are
|
|
# immutable structs, so disambiguate the label here at emit time.
|
|
(def label-seen @{})
|
|
(def out @"[\n")
|
|
(each row all
|
|
(def k (string (row :suite) "\x00" (row :label)))
|
|
(def n (get label-seen k))
|
|
(put label-seen k (if n (+ n 1) 1))
|
|
(def label (if n (string (row :label) " (" (+ n 1) ")") (row :label)))
|
|
(buffer/push out
|
|
(string " {:suite " (edn-str (row :suite))
|
|
" :label " (edn-str label)
|
|
" :expected " (if (keyword? (row :expected)) ":throws" (edn-str (row :expected)))
|
|
" :actual " (edn-str (row :actual)) "}\n")))
|
|
(buffer/push out "]\n")
|
|
# corpus.edn is JVM-sourced (regen-corpus.clj); writing the Janet-extracted answers
|
|
# here would clobber it. Only write the case list to a separate path when explicitly
|
|
# asked (then re-source :expected with regen-corpus.clj). The test runner imports
|
|
# this file but never sets the flag, so it has no side effect.
|
|
(def out-path (os/getenv "JOLT_EXTRACT_CORPUS_OUT"))
|
|
(if out-path
|
|
(do (spit out-path out)
|
|
(printf "extracted %d cases from %s into %s" (length all) spec-dir out-path))
|
|
(print "extract-corpus: set JOLT_EXTRACT_CORPUS_OUT=<path> to write the case list (corpus.edn is JVM-sourced via regen-corpus.clj)"))
|