From 7ae25f4311b322da5e5bfc728a0ddeebbbad4c8f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:09:38 -0500 Subject: [PATCH 1/7] wip --- src/malli/generator.cljc | 262 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 6 deletions(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index ce3cfd3e6..7bc26a187 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -2,6 +2,7 @@ (ns malli.generator (:require [clojure.spec.gen.alpha :as ga] [clojure.string :as str] + [clojure.set :as set] [clojure.test.check :as check] [clojure.test.check.generators :as gen] [clojure.test.check.properties :as prop] @@ -9,10 +10,14 @@ [clojure.test.check.rose-tree :as rose] [malli.core :as m] [malli.registry :as mr] + [malli.util :as u] [malli.impl.util :refer [-last -merge]] #?(:clj [borkdude.dynaload :as dynaload]))) -(declare generator generate -create) +(declare generator generate -create sampling-eduction) + +; {'x {:last-nth 2 :examples [1 2 3 4]}} +(def ^:private +type-variable-examples+ (atom {})) (defprotocol Generator (-generator [this options] "returns generator for schema")) @@ -68,6 +73,41 @@ (defn- -random [seed] (if seed (random/make-random seed) (random/make-random))) +(defn- seeded + "Creates a generator that depends on the seed parameter. + `sized-gen` is a function that takes an integer and returns + a generator. + + Examples: + + ;; generates an :int with the same seed as the outer sample. + (gen/sample (seeded (fn [seed] + (gen/tuple (gen/return seed) + (generator :int {:seed seed}))))) + => ([-9189345824394269271 0] + [2069340105756572361 -1] + [-382523443817476848 -1] + [-727106358269903677 0] + [3041036363633372983 -1] + [-3816606844531533988 1] + [-5643022030666591503 -1] + [7456223948749621027 -1] + [5327329620473603684 34] + [8284970028005224634 12])" + [seeded-gen] + (#'gen/make-gen + (fn [^clojure.test.check.random.JavaUtilSplittableRandom rnd size] + (let [seeded-gen (seeded-gen (or (.-state rnd) + (throw (m/-exception ::failed-to-recover-seed))))] + (gen/call-gen seeded-gen rnd size))))) + +(comment + (gen/sample (seeded (fn [seed] (gen/return seed)))) + ((requiring-resolve 'clojure.pprint/pprint) (gen/sample (seeded (fn [seed] + (gen/tuple (gen/return seed) + (generator :int {:seed seed})))))) + ) + (defn ^:deprecated -recur [_schema options] (println (str `-recur " is deprecated, please update your generators. See instructions in malli.generator.")) [true options]) @@ -322,12 +362,168 @@ (realized? scalar-ref-gen) (gen/recursive-gen #(generator dschema (assoc-in options [::rec-gen ref-id] %)))))))) +;; # Function generators +;; +;; A naive implementation of a function generator might be to mg/generate the output schema every time +;; the function is called and return the result. Without a seed, this is not reproducible and is not pure. +;; With a seed, mg/generate will return the same value every time---a very boring pure function. +;; +;; + +(defn- non-zero [n] + (cond-> n (zero? n) unchecked-inc)) + +(defn- summarize-string [x] + (non-zero (reduce #(unchecked-add %1 (int %2)) 0 x))) + +(defn- generate-pure-=> [?schema {:keys [seed size] :as options}] + (assert seed) + (assert size) + (let [schema (m/schema ?schema) + options (m/options schema) + _ (assert (= :=> (m/type schema))) + [input output guard] (m/children schema) + _ (assert (not guard) "NYI guards") + ;;TODO only generate a validator if it's 100% accurate + valid-out? (m/validator output)] + (fn [& args] + (let [;;TODO this kind of thing would help generate polymorphic functions + ;; e.g., (all [x] [:-> x x]) would instantiate to something like: + ;; [:-> [:any {:tv x'}] [:any {:tv x'}]] + ;; Then we can remember seeing the input when generating the output + ;; and match them up. + ;; might be more challenges with higher-order polymorphic functions. + output-candidates (atom {}) ;; TODO how to best use this? maybe small size == reuse more input? + n (letfn [(record + ([x] (record nil x)) + ([schema x] + (let [poly (when schema (::poly-poc (m/properties schema)))] + (when poly + (swap! output-candidates update-in [::poly poly] (fnil conj []) x)) + #_(when (valid-out? x) + (swap! output-candidates conj x))))) + (summarize-ident [x] + (non-zero (unchecked-add (unknown (namespace x)) + (unknown (name x))))) + (unknown [x] + (record nil x) + (cond + (boolean? x) (if x 1 0) + (int? x) x + (string? x) (summarize-string x) + (ident? x) (summarize-ident x) + (coll? x) (reduce #(unchecked-add %1 (unknown %2)) 0 + (eduction + (if (and (seq? x) (not (counted? x))) + (take 32) + identity) + x)) + (fn? x) 64 + (ifn? x) -64 + (instance? java.math.BigInteger x) (unknown (.toPlainString ^java.math.BigInteger x)) + (instance? clojure.lang.BigInt x) (unknown (str x)) + (instance? java.math.BigDecimal x) (unknown (.toPlainString ^java.math.BigDecimal x)) + (instance? Float x) (Float/floatToIntBits x) + (instance? Double x) (Double/doubleToLongBits x) + (instance? java.util.concurrent.atomic.AtomicInteger x) (.longValue ^java.util.concurrent.atomic.AtomicInteger x) + (instance? java.util.concurrent.atomic.AtomicLong x) (.longValue ^java.util.concurrent.atomic.AtomicLong x) + (instance? clojure.lang.IAtom2 x) (unchecked-add (unknown @x) 1024) + :else 0)) + (known [schema x] + (record schema x) + (unchecked-multiply + (let [] + (case (m/type schema) + :cat (let [cs (m/children schema) + vs (m/parse schema x options)] + (if (= vs ::m/invalid) + (throw (m/-exception ::invalid-cat {:schema schema :x x})) + (reduce (fn [n i] + (let [c (nth cs i) + v (nth vs i)] + (unchecked-add n (known c v)))) + 0 (range (count cs))))) + :=> (let [[input output guard] (m/children schema) + _ (assert (not guard) (str `generate-pure-=> " TODO :=> guard")) + ;;TODO use output-candidates + args (generate input {:size size :seed seed})] + (prn ":=>" {:f x :args args :schema schema :input input :output output}) + (known output (apply x args))) + (do (or (m/validate schema x) + (throw (m/-exception ::invalid-cat {:schema schema :x x}))) + (unchecked-add + (unknown (m/type schema)) + (unknown x))))) + (unchecked-inc size)))] + (known input args)) + seed (cond-> n seed (unchecked-add seed))] + (generate output (update options ::poly-examples + (fn [poly-examples] + (merge-with (fn [l r] + ) + poly-examples + (update-vals (::poly @output-candidates)))))))))) + +(comment + + ((generate (all [x] [:=> [:cat x] x])) + 1) + (m/parse [:cat :int :int] [1 2]) + (m/parse [:cat] []) + (m/parse [:cat :int :int] [1 2 3]) + (m/parse [:cat [:* :int] :int] [1 2 3]) + (generate-pure-=> 0 10 [:=> [:cat :int] :int]) + (clojure.test/is (= -106 ((generate-pure-=> [:=> [:cat :int] :int] {:seed 0 :size 10}) 2))) + (clojure.test/is (= -7 ((generate-pure-=> [:=> [:cat :int] :int] {:seed 2 :size 5}) 8))) + (clojure.test/is (= -1134619 ((generate-pure-=> [:=> [:cat :boolean] :int] {:seed 0}) true))) + (clojure.test/is (= -6 ((generate-pure-=> [:=> [:cat :boolean] :int] {:seed 1}) true))) + (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :boolean] :boolean] {:size 2 :seed 1}) false))) + (clojure.test/is (= false ((generate-pure-=> [:=> [:cat :boolean] :boolean] {:size 2 :seed 2}) false))) + (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :string] :boolean] {:size 4 :seed 5}) "abc"))) + (clojure.test/is (= false ((generate-pure-=> [:=> [:cat :string] :boolean] {:size 4 :seed 5}) "abcd"))) + (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :any] :boolean] {:size 4 :seed 5}) nil))) + (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :any] :boolean] {:size 4 :seed 5}) nil))) + (clojure.test/is (= nil ((generate-pure-=> [:=> [:cat :any] :any] {:size 0 :seed 5}) nil))) + (clojure.test/is (= 0 ((generate-pure-=> [:=> [:cat [:=> [:cat :int] :int]] :int] {:size 0 :seed 0}) identity))) + (clojure.test/is (= -1 ((generate-pure-=> [:=> [:cat [:=> [:cat :int] :int]] :int] {:size 1 :seed 0}) identity))) + (clojure.test/is (= -585680477447 + ((generate-pure-=> [:=> + [:cat + [:=> + [:cat [:=> [:cat :int] :int]] + :int]] + :int] {:size 50 :seed 1}) (fn me [f] + (prn "arg" f) + (f 13))))) + (generate [:cat :int]) + (let [f (generate [:=> {:gen/impure true} [:cat :int] :int] + {:seed 0})] + (clojure.test/is (= '(0 -1 0 -3 0 1 16 0 7 3) + (repeatedly 10 #(f 1))))) + ) + +(defn generate-impure-=> [schema options] + (let [a (atom (sampling-eduction (:output (m/-function-info schema)) options))] + (m/-instrument {:schema schema} (fn [& _] (ffirst (swap-vals! a rest)))))) + (defn -=>-gen [schema options] - (let [output-generator (generator (:output (m/-function-info schema)) options)] - (gen/return (m/-instrument {:schema schema} (fn [& _] (generate output-generator options)))))) + (let [generate-=> (if (:gen/impure (m/properties schema)) generate-impure-=> generate-pure-=>)] + (gen/sized + (fn [size] + (seeded + (fn [seed] + (gen/return + (generate-=> schema (assoc options :seed seed :size size))))))))) (defn -function-gen [schema options] - (gen/return (m/-instrument {:schema schema, :gen #(generate % options)} options))) + (gen/sized + (fn [size] + (seeded + (fn [seed] + (let [options (-> options + (assoc :size size) + (assoc :seed seed))] + (gen/return (m/-instrument {:schema schema, :gen #(generate % options)} nil options)))))))) (defn -regex-generator [schema options] (if (m/-regex-op? schema) @@ -496,7 +692,11 @@ (extend-protocol Generator #?(:clj Object, :cljs default) (-generator [schema options] - (-schema-generator schema (assoc options ::original-generator-schema schema)))) + (if-some [poly (::poly-poc (m/properties schema))] + (let [] + (gen/sized (fn [size] + (rand-nth )))) + (-schema-generator schema (assoc options ::original-generator-schema schema))))) (defn- -create-from-gen [props schema options] @@ -531,7 +731,17 @@ ;; public api ;; +(defn- current-time [] + #?(:clj (System/currentTimeMillis) + :cljs (.valueOf (js/Date.)))) + +(defn init-generator-options [options] + (-> options + (update ::state #(or % (atom {}))))) + (defn generator + ":seed - set seed + :size - set size" ([?schema] (generator ?schema nil)) ([?schema options] @@ -540,14 +750,22 @@ (-create (m/schema ?schema options) options) (m/-cached (m/schema ?schema options) :generator #(-create % options))))) +(defn- gen-root [options gen rnd size] + (rose/root (gen/call-gen gen rnd size))) + (defn generate ([?gen-or-schema] (generate ?gen-or-schema nil)) ([?gen-or-schema {:keys [seed size] :or {size 30} :as options}] (let [gen (if (gen/generator? ?gen-or-schema) ?gen-or-schema (generator ?gen-or-schema options))] - (rose/root (gen/call-gen gen (-random seed) size))))) + (gen-root options gen (-random seed) size)))) (defn sample + "An infinite eduction of generator samples, or length :samples. + + :seed - set seed + :size - set size + :samples - set number of samples, or :size is used" ([?gen-or-schema] (sample ?gen-or-schema nil)) ([?gen-or-schema {:keys [seed size] :or {size 10} :as options}] @@ -557,6 +775,37 @@ (gen/lazy-random-states (-random seed))) (take size))))) +(defn sampling-eduction + "An infinite eduction of generator samples. + + :seed - set seed + :size - set size + + Second argument can be a transducer that is applied at the end of the eduction. + For 2-arity, transducer must be fn?, otherwise is treated as options. + + (sampling-eduction :int (take 15)) + ;=> (-1 -1 1 -1 -2 -11 0 -7 -46 122 -1 0 -1 0 0) + (sequence (take 15) (sampling-eduction :int {:seed 10})) + ;=> (-1 0 -1 3 1 3 -2 -2 5 0 -1 -1 -2 3 -5) + (sampling-eduction :int (take 15) {:seed 10}) + ;=> (-1 0 -1 3 1 3 -2 -2 5 0 -1 -1 -2 3 -5)." + ([?gen-or-schema] + (sampling-eduction ?gen-or-schema identity nil)) + ([?gen-or-schema ?options-or-xform-fn] + (let [xform? (fn? ?options-or-xform-fn)] + (sampling-eduction ?gen-or-schema + (if xform? ?options-or-xform-fn identity) + (when-not xform? ?options-or-xform-fn)))) + ([?gen-or-schema xform {:keys [seed size] :or {size 10} :as options}] + (let [gen (if (gen/generator? ?gen-or-schema) ?gen-or-schema (generator ?gen-or-schema options))] + (eduction + (map-indexed (fn [iter rnd] + (let [size (mod iter size)] + (gen-root options gen rnd size)))) + (or xform identity) + (gen/lazy-random-states (-random seed)))))) + ;; ;; functions ;; @@ -574,6 +823,7 @@ validate (fn [f args] (as-> (apply f args) $ (and (valid-output? $) (valid-guard? [args $]))))] (fn [f] (let [{:keys [result shrunk]} (->> (prop/for-all* [input-generator] #(validate f %)) + ;;TODO propagate seed/size (check/quick-check =>iterations)) smallest (-> shrunk :smallest first)] (when-not (true? result) From 7b95a20af96cf991c7cb4e54493bf82b66411db0 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:29:21 -0500 Subject: [PATCH 2/7] wip --- src/malli/generator.cljc | 72 ++-------------------------------- test/malli/generator_test.cljc | 32 +++++++++++++++ 2 files changed, 36 insertions(+), 68 deletions(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index 7bc26a187..8078c01d1 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -383,30 +383,12 @@ options (m/options schema) _ (assert (= :=> (m/type schema))) [input output guard] (m/children schema) - _ (assert (not guard) "NYI guards") - ;;TODO only generate a validator if it's 100% accurate - valid-out? (m/validator output)] + _ (assert (not guard) "NYI guards")] (fn [& args] - (let [;;TODO this kind of thing would help generate polymorphic functions - ;; e.g., (all [x] [:-> x x]) would instantiate to something like: - ;; [:-> [:any {:tv x'}] [:any {:tv x'}]] - ;; Then we can remember seeing the input when generating the output - ;; and match them up. - ;; might be more challenges with higher-order polymorphic functions. - output-candidates (atom {}) ;; TODO how to best use this? maybe small size == reuse more input? - n (letfn [(record - ([x] (record nil x)) - ([schema x] - (let [poly (when schema (::poly-poc (m/properties schema)))] - (when poly - (swap! output-candidates update-in [::poly poly] (fnil conj []) x)) - #_(when (valid-out? x) - (swap! output-candidates conj x))))) - (summarize-ident [x] + (let [n (letfn [(summarize-ident [x] (non-zero (unchecked-add (unknown (namespace x)) (unknown (name x))))) (unknown [x] - (record nil x) (cond (boolean? x) (if x 1 0) (int? x) x @@ -430,12 +412,11 @@ (instance? clojure.lang.IAtom2 x) (unchecked-add (unknown @x) 1024) :else 0)) (known [schema x] - (record schema x) (unchecked-multiply (let [] (case (m/type schema) :cat (let [cs (m/children schema) - vs (m/parse schema x options)] + vs (m/parse schema (or x ()) options)] (if (= vs ::m/invalid) (throw (m/-exception ::invalid-cat {:schema schema :x x})) (reduce (fn [n i] @@ -445,9 +426,7 @@ 0 (range (count cs))))) :=> (let [[input output guard] (m/children schema) _ (assert (not guard) (str `generate-pure-=> " TODO :=> guard")) - ;;TODO use output-candidates args (generate input {:size size :seed seed})] - (prn ":=>" {:f x :args args :schema schema :input input :output output}) (known output (apply x args))) (do (or (m/validate schema x) (throw (m/-exception ::invalid-cat {:schema schema :x x}))) @@ -457,50 +436,7 @@ (unchecked-inc size)))] (known input args)) seed (cond-> n seed (unchecked-add seed))] - (generate output (update options ::poly-examples - (fn [poly-examples] - (merge-with (fn [l r] - ) - poly-examples - (update-vals (::poly @output-candidates)))))))))) - -(comment - - ((generate (all [x] [:=> [:cat x] x])) - 1) - (m/parse [:cat :int :int] [1 2]) - (m/parse [:cat] []) - (m/parse [:cat :int :int] [1 2 3]) - (m/parse [:cat [:* :int] :int] [1 2 3]) - (generate-pure-=> 0 10 [:=> [:cat :int] :int]) - (clojure.test/is (= -106 ((generate-pure-=> [:=> [:cat :int] :int] {:seed 0 :size 10}) 2))) - (clojure.test/is (= -7 ((generate-pure-=> [:=> [:cat :int] :int] {:seed 2 :size 5}) 8))) - (clojure.test/is (= -1134619 ((generate-pure-=> [:=> [:cat :boolean] :int] {:seed 0}) true))) - (clojure.test/is (= -6 ((generate-pure-=> [:=> [:cat :boolean] :int] {:seed 1}) true))) - (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :boolean] :boolean] {:size 2 :seed 1}) false))) - (clojure.test/is (= false ((generate-pure-=> [:=> [:cat :boolean] :boolean] {:size 2 :seed 2}) false))) - (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :string] :boolean] {:size 4 :seed 5}) "abc"))) - (clojure.test/is (= false ((generate-pure-=> [:=> [:cat :string] :boolean] {:size 4 :seed 5}) "abcd"))) - (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :any] :boolean] {:size 4 :seed 5}) nil))) - (clojure.test/is (= true ((generate-pure-=> [:=> [:cat :any] :boolean] {:size 4 :seed 5}) nil))) - (clojure.test/is (= nil ((generate-pure-=> [:=> [:cat :any] :any] {:size 0 :seed 5}) nil))) - (clojure.test/is (= 0 ((generate-pure-=> [:=> [:cat [:=> [:cat :int] :int]] :int] {:size 0 :seed 0}) identity))) - (clojure.test/is (= -1 ((generate-pure-=> [:=> [:cat [:=> [:cat :int] :int]] :int] {:size 1 :seed 0}) identity))) - (clojure.test/is (= -585680477447 - ((generate-pure-=> [:=> - [:cat - [:=> - [:cat [:=> [:cat :int] :int]] - :int]] - :int] {:size 50 :seed 1}) (fn me [f] - (prn "arg" f) - (f 13))))) - (generate [:cat :int]) - (let [f (generate [:=> {:gen/impure true} [:cat :int] :int] - {:seed 0})] - (clojure.test/is (= '(0 -1 0 -3 0 1 16 0 7 3) - (repeatedly 10 #(f 1))))) - ) + (generate output (assoc options :seed seed)))))) (defn generate-impure-=> [schema options] (let [a (atom (sampling-eduction (:output (m/-function-info schema)) options))] diff --git a/test/malli/generator_test.cljc b/test/malli/generator_test.cljc index 7e200711d..a137d5198 100644 --- a/test/malli/generator_test.cljc +++ b/test/malli/generator_test.cljc @@ -1034,3 +1034,35 @@ #?(:clj Exception, :cljs js/Error) #":malli\.generator/and-generator-failure" (mg/generate [:and pos? neg?])))) + +(deftest deterministic-pure-fn-gen-test + (is (every? #{-106} (repeatedly 10 (partial (mg/generate [:=> [:cat :int] :int] {:seed 0 :size 10}) 2)))) + (is (= '(-11901892 -5288 -85 -1 -1380 479 -28741703 57202930 0 -46414) + (map (mg/generate [:=> [:cat :int] :int] {:seed 1 :size 30}) (range 10)))) + (is (every? #{2483} (repeatedly 10 (partial (mg/generate [:=> [:cat [:=> [:cat :any] :any]] :int] {:seed 1 :size 30}) + identity)))) + (is (every? #{117} (repeatedly 10 (partial (mg/generate [:=> [:cat [:=> [:cat :any] :any]] :int] {:seed 1 :size 30}) + str)))) + (is (every? #{-7458} (repeatedly 10 (partial (mg/generate [:=> [:cat [:=> [:cat :any] :any]] :int] {:seed 1 :size 30}) + boolean)))) + (is (= '(-11901892 -5288 -85 -1 -1380 479 -28741703 57202930 0 -46414) + (map #(%1 %2) + (repeatedly (mg/generate [:=> :cat [:=> [:cat :int] :int]] {:seed 1 :size 30})) + (range 10))))) + +(deftest deterministic-impure-fn-gen-test + (is (= '(0 -1 0 -3 0 1 16 0 7 3) + (repeatedly 10 (partial (mg/generate [:=> {:gen/impure true} [:cat :int] :int] {:seed 0 :size 10}) 2)))) + ;;TODO make more interesting results + (is (= '(-1 0 1 -2 1 -1 -2 -4 0 -113) + (map (mg/generate [:=> {:gen/impure true} [:cat :int] :int] {:seed 1 :size 30}) (range 10)) + (repeatedly 10 (partial (mg/generate [:=> {:gen/impure true} [:cat [:=> [:cat :any] :any]] :int] {:seed 1 :size 30}) + identity)) + (repeatedly 10 (partial (mg/generate [:=> {:gen/impure true} [:cat [:=> [:cat :any] :any]] :int] {:seed 1 :size 30}) + str)) + (repeatedly 10 (partial (mg/generate [:=> {:gen/impure true} [:cat [:=> [:cat :any] :any]] :int] {:seed 1 :size 30}) + boolean)))) + (is (= '(-11901892 -5288 -85 -1 -1380 479 -28741703 57202930 0 -46414) + (map #(%1 %2) + (repeatedly (mg/generate [:=> :cat [:=> [:cat :int] :int]] {:seed 1 :size 30})) + (range 10))))) From 3a8f549115ae74b077ed69662bdf87f9527095fc Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:29:46 -0500 Subject: [PATCH 3/7] wip --- src/malli/generator.cljc | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index 8078c01d1..b96b59aa1 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -16,9 +16,6 @@ (declare generator generate -create sampling-eduction) -; {'x {:last-nth 2 :examples [1 2 3 4]}} -(def ^:private +type-variable-examples+ (atom {})) - (defprotocol Generator (-generator [this options] "returns generator for schema")) From 22df326de2cd019a49a45df511f46a4fc9884c52 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:30:03 -0500 Subject: [PATCH 4/7] wip --- src/malli/generator.cljc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index b96b59aa1..cad8c9668 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -98,13 +98,6 @@ (throw (m/-exception ::failed-to-recover-seed))))] (gen/call-gen seeded-gen rnd size))))) -(comment - (gen/sample (seeded (fn [seed] (gen/return seed)))) - ((requiring-resolve 'clojure.pprint/pprint) (gen/sample (seeded (fn [seed] - (gen/tuple (gen/return seed) - (generator :int {:seed seed})))))) - ) - (defn ^:deprecated -recur [_schema options] (println (str `-recur " is deprecated, please update your generators. See instructions in malli.generator.")) [true options]) From 002228b24e3b35bda8ea6d89dc047d602a893f81 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:34:07 -0500 Subject: [PATCH 5/7] stub --- test/malli/generator_debug.cljc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/malli/generator_debug.cljc b/test/malli/generator_debug.cljc index 7614442d5..55f17df24 100644 --- a/test/malli/generator_debug.cljc +++ b/test/malli/generator_debug.cljc @@ -46,5 +46,6 @@ (defn not-empty [gen] {:op :not-empty :gen gen}) (defn generator? [& args] (assert nil "no stub for generator?")) (defn call-gen [& args] (assert nil "no stub for call-gen")) +(defn make-gen [& args] (assert nil "no stub for make-gen")) (defn make-size-range-seq [& args] (assert nil "no stub for make-size-range-seq")) (defn lazy-random-states [& args] (assert nil "no stub for lazy-random-states")) From ff4b6409c49719353df5aa336e1ec185d75bf612 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:35:54 -0500 Subject: [PATCH 6/7] wip --- src/malli/generator.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/malli/generator.cljc b/src/malli/generator.cljc index cad8c9668..f780d1543 100644 --- a/src/malli/generator.cljc +++ b/src/malli/generator.cljc @@ -92,7 +92,7 @@ [5327329620473603684 34] [8284970028005224634 12])" [seeded-gen] - (#'gen/make-gen + (#'gen/make-gen ;;FIXME bb (fn [^clojure.test.check.random.JavaUtilSplittableRandom rnd size] (let [seeded-gen (seeded-gen (or (.-state rnd) (throw (m/-exception ::failed-to-recover-seed))))] From 75bf9d7f1b059f05b6cbe780bc82c7b94c86a556 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Wed, 17 Apr 2024 20:42:59 -0500 Subject: [PATCH 7/7] wip --- test/malli/generator_test.cljc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/malli/generator_test.cljc b/test/malli/generator_test.cljc index a137d5198..33aea70bd 100644 --- a/test/malli/generator_test.cljc +++ b/test/malli/generator_test.cljc @@ -1066,3 +1066,25 @@ (map #(%1 %2) (repeatedly (mg/generate [:=> :cat [:=> [:cat :int] :int]] {:seed 1 :size 30})) (range 10))))) +(comment +;; This sequence of results should be reproducible. +(def pure (mg/generate [:=> [:cat :int] :int] {:seed 0 :size 10})) +(def impure (mg/generate [:=> {:gen/impure true} [:cat :int] :int] {:seed 0 :size 10})) +(repeatedly 10 #(pure 2)) +;=> (-106 -106 -106 -106 -106 -106 -106 -106 -106 -106) +(repeatedly 10 #(pure 2)) +;=> (-106 -106 -106 -106 -106 -106 -106 -106 -106 -106) +(repeatedly 10 #(impure 2)) +;=> (0 -1 0 -3 0 1 16 0 7 3) +(repeatedly 10 #(impure 2)) +;=> (0 -1 -1 1 2 3 -2 38 -5 -12) + +(mapv pure (range 10)) +;=> [5 511 -106 20 -51 -322 1 0 434 -1] +(mapv pure (range 10)) +;=> [5 511 -106 20 -51 -322 1 0 434 -1] +(mapv impure (range 10)) +;=> [-1 0 -2 -4 -3 -4 -15 -15 -16 -2] +(mapv impure (range 10)) +;=> [-1 -1 -1 0 1 3 -12 30 -2 1] +)