test+docs: spec coverage for migratus-enablement fixes; list migratus
Adds spec/conformance coverage for everything landed enabling migratus:
- conformance corpus (runs interpret/compile/self-host): def 3-arg docstring,
def/defn ^{:map} name meta, defmacro arity-clause + docstring, defmulti
docstring, assoc-nil/assoc-in real maps, try multi-body + finally-on-success,
current-ns restore after a caught throw, cross-ns methods visibility.
- spec suites: host-interop (exception ctors, Character/Thread/Long, Timestamp/
SimpleDateFormat, java.io.File model + File-aware file-seq, clojure.tools.logging),
regex (Pattern statics + MULTILINE + quote, String .matches/.replaceAll/
.replaceFirst), maps (assoc on nil), multimethods (defmulti docstring +
value-based methods/get-method), macros (defmacro arity-clause + name meta).
Rewrote clojure.tools.logging/spy as a single variadic arity (jolt defmacro
takes only the first clause of a multi-arity macro — jolt-q8l).
docs/libraries.md: add migratus and the next.jdbc compatibility layer, with the
janet-lang/sqlite3 int64 caveat for 14-digit timestamp ids.
Full gate green; conformance 350/350 x3 modes.
This commit is contained in:
parent
4f59958d92
commit
72e36f46de
8 changed files with 145 additions and 6 deletions
|
|
@ -223,3 +223,50 @@
|
|||
"(do (def prev (__reader-features)) (__reader-features-set! [\"clj\" \"jolt\" \"default\"]) (def r (contains? (set (__reader-features)) \"clj\")) (__reader-features-set! prev) r)"]
|
||||
["restore returns to default" "false"
|
||||
"(do (def prev (__reader-features)) (__reader-features-set! [\"clj\"]) (__reader-features-set! prev) (contains? (set (__reader-features)) \"clj\"))"])
|
||||
|
||||
# JVM class shims migratus relies on. Exception constructors resolve as bare
|
||||
# class symbols (jolt-6xk) and carry a message; Character/Thread/Long statics;
|
||||
# java.sql.Timestamp is the millis number; SimpleDateFormat formats UTC.
|
||||
(defspec "host-interop / migratus class shims"
|
||||
["Exception. message" "\"boom\""
|
||||
"(try (throw (Exception. \"boom\")) (catch Throwable e (.getMessage e)))"]
|
||||
["IllegalArgumentException." "\"bad\""
|
||||
"(try (throw (IllegalArgumentException. \"bad\")) (catch Exception e (.getMessage e)))"]
|
||||
["InterruptedException." "\"stop\""
|
||||
"(try (throw (InterruptedException. \"stop\")) (catch Throwable e (.getMessage e)))"]
|
||||
["Character/isUpperCase" "true" "(Character/isUpperCase \\A)"]
|
||||
["Character/isLowerCase" "true" "(Character/isLowerCase \\a)"]
|
||||
["Character/isUpperCase neg" "false" "(Character/isUpperCase \\a)"]
|
||||
["Thread/interrupted" "false" "(Thread/interrupted)"]
|
||||
["Long/valueOf" "42" "(Long/valueOf \"42\")"]
|
||||
["Timestamp is millis" "1000" "(.getTime (java.util.Date. (java.sql.Timestamp. 1000)))"]
|
||||
["SimpleDateFormat UTC" "\"19700101000000\""
|
||||
"(let [f (doto (java.text.SimpleDateFormat. \"yyyyMMddHHmmss\") (.setTimeZone (java.util.TimeZone/getTimeZone \"UTC\")))] (.format f (java.util.Date. 0)))"])
|
||||
|
||||
# java.io.File model (jolt-hjw): io/file builds a value that answers
|
||||
# (instance? File _) so migratus's File-vs-jar branch takes the filesystem path;
|
||||
# the method surface and a File-aware file-seq back it; str/slurp coerce to path.
|
||||
(defspec "host-interop / java.io.File"
|
||||
["instance? File" "true" "(do (require '[clojure.java.io :as io]) (instance? java.io.File (io/file \"/a/b\")))"]
|
||||
["str is the path" "\"/a/b\"" "(do (require '[clojure.java.io :as io]) (str (io/file \"/a/b\")))"]
|
||||
["getName" "\"c.txt\"" "(do (require '[clojure.java.io :as io]) (.getName (io/file \"/a/b/c.txt\")))"]
|
||||
["getPath joins" "\"/a/b\"" "(do (require '[clojure.java.io :as io]) (.getPath (io/file \"/a\" \"b\")))"]
|
||||
["isDirectory of repo dir" "true" "(do (require '[clojure.java.io :as io]) (.isDirectory (io/file \"docs\")))"]
|
||||
["isFile of repo file" "true" "(do (require '[clojure.java.io :as io]) (.isFile (io/file \"project.janet\")))"]
|
||||
["exists is false off-disk" "false" "(do (require '[clojure.java.io :as io]) (.exists (io/file \"/no/such/path/xyz\")))"]
|
||||
["file-seq yields File values" "true"
|
||||
"(do (require '[clojure.java.io :as io]) (every? (fn [f] (instance? java.io.File f)) (file-seq (io/file \"docs\"))))"]
|
||||
["file-seq finds files" "true"
|
||||
"(do (require '[clojure.java.io :as io]) (pos? (count (filter (fn [f] (.isFile f)) (file-seq (io/file \"docs\"))))))"])
|
||||
|
||||
# clojure.tools.logging shim (jolt-nzg): the macro surface works, debug/trace
|
||||
# are suppressed (no-op nil) by default, and the impl protocol layer is present.
|
||||
(defspec "host-interop / clojure.tools.logging"
|
||||
["info returns nil" "nil" "(do (require '[clojure.tools.logging :as log]) (log/info \"hi\" 42))"]
|
||||
["debug suppressed" "nil" "(do (require '[clojure.tools.logging :as log]) (log/debug \"x\"))"]
|
||||
["debug skips args" "0" "(do (require '[clojure.tools.logging :as log]) (let [a (atom 0)] (log/debug (reset! a 9)) @a))"]
|
||||
["enabled? info" "true" "(do (require '[clojure.tools.logging :as log]) (log/enabled? :info))"]
|
||||
["enabled? debug" "false" "(do (require '[clojure.tools.logging :as log]) (log/enabled? :debug))"]
|
||||
["spy returns value" "3" "(do (require '[clojure.tools.logging :as log]) (log/spy :info (+ 1 2)))"]
|
||||
["impl factory name" "\"jolt/stderr\""
|
||||
"(do (require '[clojure.tools.logging.impl :as impl]) (impl/name (impl/find-factory)))"])
|
||||
|
|
|
|||
|
|
@ -85,3 +85,11 @@
|
|||
["with-redefs-fn" "42" "(do (defn wr-i [] 1) (with-redefs-fn {(var wr-i) (fn [] 42)} (fn [] (wr-i))))"]
|
||||
["macroexpand full" "true" "(let [e (macroexpand (quote (when-not false 1)))] (= (quote if) (first e)))"]
|
||||
["macroexpand non-macro" "[1 2]" "(macroexpand (quote [1 2]))"])
|
||||
|
||||
# defmacro accepts the arity-clause form (defmacro name ([params] body...)), a
|
||||
# leading docstring, and ^{:map} metadata on the name (jolt-whp, jolt-8w2).
|
||||
(defspec "macros / defmacro arity-clause & name metadata"
|
||||
["arity-clause form" "10" "(do (defmacro tw ([x] (list (quote *) x 2))) (tw 5))"]
|
||||
["docstring + arity" "15" "(do (defmacro th \"triple\" ([x] (list (quote *) x 3))) (th 5))"]
|
||||
["^{:map} name meta" "7" "(do (defmacro ^{:private true} pm [] 7) (pm))"]
|
||||
["multi-form body" "6" "(do (defmacro mb ([a b] (list (quote +) a b))) (mb 2 4))"])
|
||||
|
|
|
|||
|
|
@ -177,3 +177,13 @@
|
|||
["empty? lazy nil elem" "false" "(empty? (cons nil nil))"]
|
||||
["empty? sorted" "[true false]" "[(empty? (sorted-map)) (empty? (sorted-set 1))]"]
|
||||
["empty? number throws" :throws "(empty? 5)"])
|
||||
|
||||
# (assoc nil k v) yields a real immutable map, not a raw host table, so
|
||||
# assoc-in into absent keys nests countable/seqable maps (jolt-w4s).
|
||||
(defspec "map / assoc on nil"
|
||||
["assoc nil is a map" "{:a 1}" "(assoc nil :a 1)"]
|
||||
["count of assoc nil" "1" "(count (assoc nil :a 1))"]
|
||||
["assoc-in nested countable" "1" "(count (:a (assoc-in {} [:a :b] 1)))"]
|
||||
["assoc-in deep get" "9" "(get-in (assoc-in {} [:a :b :c] 9) [:a :b :c])"]
|
||||
["seq over assoc-nil map" ":a" "(ffirst (seq (assoc nil :a 1)))"]
|
||||
["keys of assoc-nil map" "[:a]" "(vec (keys (assoc nil :a 1)))"])
|
||||
|
|
|
|||
|
|
@ -54,3 +54,17 @@
|
|||
"(do (defmulti pm5 identity) (defmethod pm5 :a [x] 1) (defmethod pm5 :b [x] 2) (prefer-method pm5 :a :b) (contains? (get (prefers pm5) :a) :b))"]
|
||||
["exact match needs no preference" ":exact"
|
||||
"(do (derive :t/sq :t/rect) (defmulti pm6 identity) (defmethod pm6 :t/sq [x] :exact) (defmethod pm6 :t/rect [x] :parent) (pm6 :t/sq))"])
|
||||
|
||||
# defmulti drops a leading docstring/attr-map (jolt-es4 — it used to be taken as
|
||||
# the dispatch fn). methods/get-method take the multimethod VALUE and recover
|
||||
# the table, so a bare multifn ref works even when defmethods live elsewhere
|
||||
# (jolt-9pu).
|
||||
(defspec "multimethods / docstring & value-based table ops"
|
||||
["defmulti docstring" "\"A\""
|
||||
"(do (defmulti gd \"the dispatcher\" identity) (defmethod gd :a [_] \"A\") (gd :a))"]
|
||||
["defmulti doc+default" "\"d\""
|
||||
"(do (defmulti gx \"doc\" identity) (defmethod gx :default [_] \"d\") (gx :anything))"]
|
||||
["methods on value" "2"
|
||||
"(do (defmulti gm identity) (defmethod gm 1 [_] :one) (defmethod gm 2 [_] :two) (count (methods gm)))"]
|
||||
["get-method on value" "true"
|
||||
"(do (defmulti gg identity) (defmethod gg :a [_] :x) (fn? (get-method gg :a)))"])
|
||||
|
|
|
|||
|
|
@ -47,3 +47,18 @@
|
|||
["p{Ps}/p{Pe}" "\"(x)\"" `(re-matches #"^\p{Ps}x\p{Pe}$" "(x)")`]
|
||||
["(?u) accepted" "\"hi\"" `(re-matches #"(?u)^hi$" "hi")`]
|
||||
["unknown class throws" :throws `(re-pattern "\p{Greek}")`])
|
||||
|
||||
# java.util.regex.Pattern statics (jolt-47b): compile returns jolt's native
|
||||
# regex value (so .split / str-ops accept it), MULTILINE maps to (?m), quote
|
||||
# escapes metachars. Plus the regex String methods migratus uses.
|
||||
(defspec "regex / Pattern statics & String regex methods"
|
||||
["Pattern/compile is a regex" "true" "(regex? (Pattern/compile \"a.c\"))"]
|
||||
["compiled .split" "[\"a\" \"b\" \"c\"]" "(.split (Pattern/compile \",\") \"a,b,c\")"]
|
||||
["str/replace w/ Pattern" "\"ab\"" "(do (require '[clojure.string :as s]) (s/replace \"a1b2\" (Pattern/compile \"[0-9]\") \"\"))"]
|
||||
["Pattern/MULTILINE ^" "true" "(boolean (re-find (Pattern/compile \"^x\" Pattern/MULTILINE) \"y\\nx\"))"]
|
||||
["Pattern/quote literal" "true" "(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"za.cy\"))"]
|
||||
["Pattern/quote not meta" "false" "(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"zabcy\"))"]
|
||||
[".matches whole string" "true" "(.matches \"abc\" \"a.c\")"]
|
||||
[".matches partial -> false" "false" "(.matches \"abcd\" \"a.c\")"]
|
||||
[".replaceAll regex" "\"a-b-c\"" "(.replaceAll \"a_b_c\" \"_\" \"-\")"]
|
||||
[".replaceFirst regex" "\"a-b_c\"" "(.replaceFirst \"a_b_c\" \"_\" \"-\")"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue