speculoos.utility
Utility functions to make using and interacting with Speculoos nicer.
- 
Constructive functions create conforming data or specifications, e.g., synthesizing valid data when given a specification. 
- 
“Destructive” functions return new versions of data and/or specification with non-conforming datums or un-satisfied predicates altered or removed so that validation fully succeeds, e.g., replacing all invalid pairs with nil/nil?.All data structures remain immutable. These functions are only “destructive” in the sense that if you forward the new version of data and/or specification, the recipient will have lost some information. 
- 
Reporting functions highlight some aspect of a data set or specification, e.g., returning non-predicates within a specification. 
- 
Helper functions decrease keypresses, e.g., get fourth element of a sequence. 
*ordinal-offset*
dynamic
=1st
(=1st s) 
 (=2nd s) 
 (=3rd s) 
 (=4th s) 
 (=5th s) 
 (=6th s) 
 (=7th s) 
 (=8th s) 
 (=9th s) 
 (=10th s) 
 (=11th s) 
 (=12th s)
Convenience functions to access elements of a sequence s. Uses one-based indexing by default.
Examples:
(=1st [:one :two :three]) ;; => :one
(=2nd [:one :two :three]) ;; => :two
(=3rd [:one :two :three]) ;; => :three
(=2nd (list 'foo 'bar 'baz)) ;; => bar
Re-bind *ordinal-offset* for zero-based indexing.
Example:
(binding [*ordinal-offset* 0]
    (=1st [:one :two :three])) ;; => :two
all-specs-okay
(all-specs-okay spec)Returns true if all entries in specification spec are a function. Otherwise, returns all-paths entries {:path _ :element _} to non-functions. See non-predicates for limitations.
Examples:
(all-specs-okay [int? keyword? ratio?]) ;; => true
(all-specs-okay [int? keyword? 9.87]) ;; => ({:path [2], :value 9.87})
apathetic
(apathetic data spec)Return a scalar specification spec for which any specification predicate which returns invalid, is transmorgrified to any?.
Examples:
(apathetic [42 :foo 22/7] [int? symbol? ratio?]) ;; => [int? any? ratio?]
(apathetic {:a 42 :b 'foo} {:a int? :b keyword?}) ;; => {:a int?, :b any?}
basic-collection-spec-from-data
(basic-collection-spec-from-data data)Given data, a heterogeneous, arbitrarily-nested collection, returns a similar structure that can serve as basic collection specification. Non-terminating sequences are clamped to length 3 and converted to vectors.
Note: Speculoos validates maps with predicates at keys which do not exist in data, so a pseudo-qualified key :speculoos.utility/collection-predicate is created that is applied by validate-collections.
Examples:
(basic-collection-spec-from-data [42 [:foo [22/7]]])
;; => [[[vector?] vector] vector?]
(basic-collection-spec-from-data {:a 42 :b {:c ['foo true]}})
;; => {:b {:c [vector?], :speculoos.utility/collection-predicate map?},
;;     :speculoos.utility/collection-predicate map?}
(basic-collection-spec-from-data [42 #{:foo} (list 22/7) (range)])
;; => [#{set?} (list?) [vector] vector]
clamp-every
(clamp-every coll x)Given a heterogeneous, arbitrarily-nested collection coll, ‘clamp’ every (possibly) non-terminating sequence at length x. Sequences are converted to vectors. See clamp-in* for particulars on behavior.
Examples:
(clamp-every [42 (range) 99 (repeat :foo) 33] 3)
;; => [42 [0 1 2] 99 [:foo :foo :foo] 33]
(clamp-every {:a (cycle ['foo 'bar 'baz]) :b (iterate inc 100)} 5)
;; => {:a [foo bar baz foo bar], :b [100 101 102 103 104]}
clamp-in*
(clamp-in* coll path x)Given a heterogeneous, arbitrarily-nested collection coll, ‘clamp’ (possibly) non-terminating sequence at path to length x. Sequences are converted to vectors.
Beware: Behaves like take, so will also possibly shorten vectors, lists, sets, and maps if located at path.
Examples:
(clamp-in* [:foo (range) 'bar] [1] 3)
;; => [:foo [0 1 2] bar]
(clamp-in* {:a [42 'foo {:b (cycle [:x :y :z])}]} [:a 2 :b] 5)
;; => {:a [42 foo {:b [:x :y :z :x :y]}]}
;; does not discriminate; operates on terminating sequences, too
(clamp-in* [:foo 42 [:x :y :z :a :b :c]] [2] 4)
;; => [:foo 42 [:x :y :z :a]]
;; sequences that are shorter than `x` are unchanged
(clamp-in* {:a (take 3 (range))} [:a] 6)
;; => {:a [0 1 2]}
collections-without-predicates
(collections-without-predicates data spec)Given data a heterogeneous, arbitrarily-nested data structure, returns a set of data’s all-paths elements for collections which lack at least one corresponding predicate in collection specification spec. See also scalars-without-predicates and scalars-with-predicates.
Examples:
;; all collections have a corresponding collection predicate; returns an empty set
(collections-without-predicates [11 [22] 33] [vector? [vector?]])
;; => #{}
;; missing predicate(s)
(collections-without-predicates [11 [22] 33] [vector? []])
;; => #{{:path [1], :value [22]}}
(collections-without-predicates {:a [11 {:b (list 22 33)}]} {:a [vector? {:is-a-map? map?}]})
;; => #{{:path [:a 1 :b], :value (22 33)}
;;      {:path [], :value {:a [11 {:b (22 33)}]}}}
(collections-without-predicates [11 [22] 33 (cycle [44 55 66])] [vector? [vector?]])
;; => #{{:path [3], :value []}}
data-from-spec
(data-from-spec spec)(data-from-spec spec opt)Given heterogeneous, arbitrarily-nested scalar specification spec, create a sample data structure whose values validate to that specification. Works for singular, clojure.core predicates, e.g., int?, string?, or for #(and (int? %) (< 5 %)) with generators at metadata key :speculoos/predicate->generator. Unknown predicates yield nil. Defaults to canonical values, i.e., 'abc' for string?.
Optional trailing arg :random uses clojure.test.check generators when available. Trailing arg :canonical (default) uses lookup table to supply aesthetic values, i.e., 42 for int?.
Sets are assumed to be a predicate. :canonical uses whatever clojure.core/first returns (consider a sorted set with the first element an exemplar); :random yields a random selection from the set.
Bare regular expressions yield random strings that match.
Examples:
;; default canonical datums
(data-from-spec [int? keyword? ratio?]) ;; => [42 :kw 22/7]
(data-from-spec {:a symbol? :b decimal?}) ;; => {:a speculoos/canonical-symbol, :b 1M}
;; optional random values
(data-from-spec [int? [keyword? [ratio?]]] :random) ;; => [-715 [:! [15/2]]]
(data-from-spec {:a symbol? :b {:c boolean? :d pos-int?}} :random) ;; => {:a x2_qV/x, :b {:c true, :d 26}}
;; set as a predicate, defaults to (first #{...})
(data-from-spec [int? #{:red :green :blue} ratio?]) ;; => [42 :green 22/7]
;; set as a predicate, optional random selection
(data-from-spec [int? #{:red :green :blue} ratio?] :random) ;; => [-208 :red 7/2]
;; regular expression as a predicate
(data-from-spec [#"Q\d{3}Y\d{1,2}" #"foo[2468]"])
;; => ["Q690Y61" "foo6"]
If the metadata of a non-set, non-regex predicate contains either key :speculoos/predicate->generator or key :speculoos/canonical-sample, the associated value is presumed to be a competent generator or canonical sample, respectively, and will be preferred. See validate-predicate->generator.
Note: Presence of either metadata key implies that a default generator will not be found in the lookup table, and therefore requires a customized generator for invoking with the :random option. But it also implies that a canonical value will also not be located, and therefore requires a custom canonical value, which is referenced when explicitly invoked with :canonical, but also the default, no-option invocation.
;; as is, Speculoos cannot generate a sample datum from this predicate
(def opaque-predicate (fn [x] (and (int? x) (pos? x) (< x 99))))
;; Speculoos can however retrieve a generator at metadata key :speculoos/predicate->generator
(data-from-spec {:a string? :b (with-meta opaque-predicate {:speculoos/predicate->generator #(rand-int 100)})} :random)
;; => {:a "Q56BPs2ownU08ExMOy3yVD37M6Q", :b 15}
;; predicates not found in the lookup table must explicitly provide a canonical value when invoking without `opt`
(data-from-spec [ratio? (with-meta int? {:speculoos/canonical-sample 987654321})])
;; => [22/7 987654321]
See defpred for a helper to set up a predicate with a generator.
(defpred f4 int? #(rand-int 99)) ;; optional canonical value not supplied
(defpred f5 number? #(rand 999) 1.234)
(data-from-spec {:a f4 :b [f5]} :random)
;; => {:a 31, :b [147.28249222486664]}
(data-from-spec {:a f4 :b [f5]} :canonical)
;; => {:a :f4-canonical-sample, :b [1.234]}
defpred
macro
(defpred predname f)(defpred predname f generator)(defpred predname f generator canonical)define a predicate by binding symbol predname to function f while associating a random sample generator.
- With only a name prednameand functionf,defpredwill attempt to create a random sample generator as outlined by inspect-fn.
- An explicitly supplied generatorwill be preferred.
- If generatoris explicitly provided, a canonical value may also be supplied. If the optional canonical value is not supplied, a keyword is automatically generated using the symbolprednameappended with-canonical-sample.
Useful for defining predicates consumed by data-from-spec, exercise, and friends.
*such-that-max-tries* is bound to an integer (default 50) that governs the number of attempts the random sample generator will make to create a valid sample.
Note: Can not use bare predicates such as int?; they must be ‘wrapped’, like #(int? %). See examples.
Examples:
(macroexpand-1 '(defpred foo (fn [i] (int? i)) (fn [] (rand-int 99)) 42))
;; => (def foo (clojure.core/with-meta (fn [i] (int? i)) #:speculoos{:canonical-sample 42, :predicate->generator (fn [] (rand-int 99))}))
(defpred f1 #(<= % 99) #(rand-int 99) 42)
(defpred f2 #(<= % 5)  #(- (rand-int 99))) ;; no canonical value supplied
(defpred f3 #(> % 999) #(rand 999) 1.23)
(data-from-spec [f1 f2 f3] :random)
;; => [86 -77 971.3462532541377]
(data-from-spec [f1 f2 f3] :canonical)
;; => [42 :f2-canonical-sample 1.23]
;; using (and...) to modify a core predicate
(defpred even-int? #(and (int? %) (even? %) (< 3 %)))
(data-from-spec [even-int? even-int? even-int? even-int?] :random)
;; => [14 24 16 30]
;; using (or...) to generate alternatives
(defpred int-or-kw? #(or (int? %) (keyword? %)))
(data-from-spec [int-or-kw? int-or-kw? int-or-kw? int-or-kw?] :random)
;; => [:J0+ -25 22 :o_6W.l:?]
;; using both (and...) and (or...)
(defpred odd-int-or-short-kw? (fn [x] (or (and (int? x)
                                               (odd? x))
                                          (and (keyword? x)
                                               (>= 3 (count (str x)))))))
(data-from-spec [odd-int-or-short-kw?
                 odd-int-or-short-kw?
                 odd-int-or-short-kw?
                 odd-int-or-short-kw?] :random)
;; => [:Y9.c :i. -3 :xxk]
exercise
(exercise spec)(exercise spec n)Generates a number n (default 10) of values compatible with scalar specification spec and maps valid-scalars? over them, returning a sequence of [val valid?] tuples.
If n is :canonical, only one data set is produced, consisting of the predicates’ canonical values.
See data-from-spec for details on predicates.
Examples, passing optional count n 3 for brevity:
(exercise [int? [keyword? ratio?] char?] 3)
;; => ([[-282 [:_1z9L -17/21] \G] true]
;;     [[ 469 [:c*2y   -7/11] \9 true]
;;     [[-293 [:*C     -3/7 ] \i] true])
(exercise {:a symbol? :b [pos-int? decimal? char?]} 3)
;; => ([{:a U8D_/gs+H,   :b [17 1M \Z]} true]
;;     [{:a b-187+/Xv,   :b [27 1M \3]} true]
;;     [{:a ?l4zv/Y.Ro!, :b [22 1M \G]} true])
Example with canonical values:
(exercise [int? char? ratio? string? double?] :canonical)
;; => ([[42 \c 22/7 "abc" 1.0E32] true])
in?
(in? coll item)Returns true if item is found somewhere in collection coll. This utility function is a replacement for what the name clojure.core/contains? suggests it might do. Works on all collection types, but map elements are checked as MapEntry key-value pairs. Pass (vals m) if you want to only check values. Properly handles nil and false membership.
Examples:
;; `contains?` tests for existence of a sequence's index, not its values
(contains? [:a :b :c] 1) ;; => true
;; `in?` tests for existence of a sequence's values
(in? [:a :b :c] 1) ;; => false
(in? [:a :b :c] :b) ;; true
(in? (list 'foo 'bar 'baz) 'foo) ;; => true
(in? #{:red :green :blue} :green) ;; => true
(in? (range) 3) ;; => true
;; elements of a map are tested as MapEntries
(in? {:a 1 :b 2 :c 3} 3) ;; => false
;; passing just the vals of a map
(in? (vals {:a 1 :b 2 :c 3}) 3) ;; => true
;; testing existence of a MapEntry
(in? {:a 1 :b 2 :c 3} [:c 3]) ;; => true
;; nil presence
(in? [42 nil 22/7] nil) ;; => true
;; false presence
(in? (vals {:a 42 :b false}) false) ;; => true
inspect-fn
(inspect-fn f)Inspect expression f to attempt to create a random sample generator. If unable to do so, returns nil. f must be of the form (fn [...] ...) or #(...). Returns a vector whose first element is the symbolic representation of the generator and whose second element is an invocable generator object.
This implementation is a proof-of-concept that only descends to a maximum depth of two levels. It does not generically handle arbitrary nesting depths nor all possible Clojure data types
The predicate body must fulfill these properties:
- The first symbol must be and,or, or a basic predicate for a Clojure built-in scalar, such asint?that is registered atspeculoos.utility/predicate->generator.
- The first clause after andorormust contain aclojure.corepredicate for a scalar, such asint?.
- Subsequent clauses of andwill be injected into aclojure.test.check.generators/such-thatfilter.
- Direct descendants of a top-level orwill producenseparate random sample generators, each with1/nprobability for any one invocation.
Examples:
(inspect-fn '(fn [i] (int? i)))
(inspect-fn '(fn [i] (and (int? i) (even? i))))
(inspect-fn '#(int? %))
(inspect-fn '#(and (int? %) (even? %)))
(inspect-fn '(fn [x] (or (int? x) (ratio? x))))
(inspect-fn '#(or (and (int? %) (odd? %))
                  (and (string? %) (<= 3 (count %)))))
non-predicates
(non-predicates spec)Returns all-paths entries {:path _ :value _} for all elements in specification spec that are not functions.
This function name is possibly mis-leading: It tests only fn?, not if the function is a competent predicate. Sets are flagged as ‘non-predicates’.
Examples:
(non-predicates [int? 42 decimal?]) ;; => ({:path [1], :value 42})
(non-predicates {:a int? :b 'foo}) ;; => ({:path [:b], :value foo})
Sets may serve as a membership predicate for a scalar specification, but are flagged as ‘non-predicates’.
Demonstration:
;; `validate-scalars` considers #{:red} as a predicate-like thing, but ...
;; ... `non-predicates` is unable to distinguish without data to accompany spec
(non-predicates [int? #{:red} ratio?]) ;; => ({:path [1 :red], :value :red})
predicates-without-collections
(predicates-without-collections data spec)Given data at heterogeneous, arbitrarily-nested data structure and a collection specification spec, returns a set of all-paths elements for predicates in spec which cannnot be paired with a collection element in data. See also collections-without-predicates, scalars-without-predicates, and predicates-without-scalars.
Examples:
;; all predicates are paired
(predicates-without-collections [42] [vector?])
;; => #{}
;; one un-paired predicate
(predicates-without-collections [42] [vector? [map?]])
;; => #{{:path [1 0], :value map?}}
;; one un-paired predicate
(predicates-without-collections {:a 42} {:is-map? map? :b [vector?]})
;; => #{{:path [:b 0], :value vector?}}
;; one un-paired predicate <-- collection validation only applies predicates to non-scalars
(predicates-without-collections {:a 42 :b 99} {:is-map? map? :b [vector?]})
;; => #{{:path [:b 0], :value vector?}}
predicates-without-scalars
(predicates-without-scalars data spec)Returns only scalar specification spec elements which lack a corresponding element in data.
Examples:
(predicates-without-scalars [42 :foo] [int? keyword? ratio?]) ;; => ({:path [2], :value ratio?})
(predicates-without-scalars {:a 42} {:a int? :b symbol?}) ;; => ({:path [:b], :value symbol?})
scalars-with-predicates
(scalars-with-predicates data spec)Returns data all-paths scalar elements which have a corresponding predicate in scalar specification spec. See also scalars-without-predicates and collections-without-predicates.
Examples:
(scalars-with-predicates [42 :foo 22/7] [int?]) ;; => ({:path [0], :value 42})
(scalars-with-predicates {:a 42 :b 'foo} {:a int?}) ;; => ({:path [:a], :value 42})
scalars-without-predicates
(scalars-without-predicates data spec)Returns a set of data all-paths scalar elements which lack corresponding predicates in scalar specification spec. See also scalars-with-predicates and collections-without-predicates.
Examples:
(scalars-without-predicates [42 :foo 22/7] [int? keyword?]) ;; => #{{:path [2], :value 22/7}}
(scalars-without-predicates {:a 42 :b 'foo} {:a int?}) ;; => #{{:path [:b], :value foo}}
seq-regex
(seq-regex s & pred-regexes)Returns true if sequence s fully satisfies pred-regex, pairs of predicates and regex-like operators. Predicates partition the sequence, regex operators check the quantity, according to the following.
- :?zero-or-one
- :.exactly-one
- :+one-or-more
- :*zero-or-more
- [m n]between- mand- n(integers), inclusive
- iexactly- i(integer)
Examples:
(seq-regex [1 2 3 :a :b 'foo] int? :* keyword? :+ symbol? :.) ;; => true
Tests zero-or-more integers, followed by one-or-more keywords, followed by exactly-one symbol.
You must supply predicate and regexes to exhaust the sequence, otherwise returns false. If you don’t care about entities beyond a particular index, use the duplet any? :*.
(seq-regex [1 2 :a :b true false] int? 2 keyword? [1 3] any? :*) ;; => true
Tests exactly two integers, followed by one to three keywords, followed by any number of anything.
Failing example:
(seq-regex [1 2 :a :b] int? 2) ;; => false
Tests exactly two integers; example fails because trailing keywords are not matched.
Any unused pred-regex pairs are ignored.
(seq-regex [1 2 3] int? 3 keyword? 3) ;; => true
If your first regex is zero-or-more :*, then it will match an empty sequence, regardless of any trailing pred-regex pairs.
(seq-regex [] int? :* keyword :+) ;; => true
Do not stack the one-or-more :. regex operator in an attempt to get an integer >1.
(seq-regex [1 2 3] int? :. int? :. int? :.) ;; => false
Instead, use the exactly-integer or the range ops.
(seq-regex [1 2 3] int? 3) ;; => true
(seq-regex [1 2 3] int? [1 4]) ;; => true
Predicates must be specific enough so that they aren’t consumed further than you intend. This is treacherous when, e.g., converting to string. Here’s possibly surprising failing example:
(seq-regex [:a :b :c 'fo 'br 'bz] #(= 2 (count (str %))) 3 symbol? 3)
;; => false
'fo, 'br, and 'bz all satisfy length=2 when converted to string, leaving symbol? no values to test. Instead, insert a guarding predicate.
(seq-regex [:a :b :c 'fo 'br 'bz] #(and (keyword? %)
                                        (= 2 (count (str %)))) 3 symbol? 3)
;; => true
sore-thumb
(sore-thumb data spec)(sore-thumb data spec replacement)Prints *out* a version of data and spec that highlights where the datums+predicates invalidate. Non-invalids (i.e., datums that satisfy their own predicates) are de-emphasized with replacement, defaulting to '_.
'… is also kinda nice.
Examples:
;; default replacement '_
(with-out-str (sore-thumb [42 :foo 22/7] [int? symbol? ratio?]))
;; printed to *out*: data: [_ :foo _] spec: [_ symbol? _]
;; optional replacement '…
(with-out-str (sore-thumb {:a 42 :b 'foo :c 22/7} {:a int? :b keyword? :c ratio?} '…))
;; printed to *out*: data: {:a …, :b foo, :c …} spec: {:a …, :b keyword?, :c …}
spec-from-data
(spec-from-data data)(spec-from-data data clamp-at)Given heterogeneous, arbitrarily-nested collection data, create a scalar specification whose predicates validate. Non-terminating sequences are clamped at clamp-at, defaults to 7.
Examples:
(spec-from-data [42 [:foo] 22/7]) ;; => [int? [keyword?] ratio?]
(spec-from-data {:a 42 :b {:c 'foo :d 22/7}}) ;; => {:a int?, :b {:c symbol?, :d ratio?}}
;; non-terminating data sequence, optional clamp-at supplied
(spec-from-data [:foo (cycle [42 22/7 'baz])] 5) ;; => [keyword? [int? ratio? symbol? int? ratio?]]
swap-non-predicates
(swap-non-predicates spec)(swap-non-predicates spec pred)Returns a new scalar specification spec with all non-predicate elements swapped for any? (default) or optional supplied predicate pred.
Examples:
;; default predicate replacement
(swap-non-predicates [int? 99 ratio?]) ;; => [int? any? ratio?]
;; non-default predicate replacement
(swap-non-predicates {:a int? :b 99 :c 'foo} number?) ;; => {:a int?, :b number?, :c number?}
thoroughly-valid-collections?
(thoroughly-valid-collections? data spec)Given a heterogeneous, arbitrarily-nested data structure data and collection specification spec, returns true if every collection contained in data has a corresponding predicate in spec, and every collection satisfies its predicate.
See valid-collections?, collections-without-predicates, and thoroughly-valid?.
Examples:
;; all collections have satisfied predicates
(thoroughly-valid-collections? [11 {:x 22/7 :y 'foo}] [vector? {:is-map? map?}])
;; => true
;; all collections have predicates, but not all satisfied
(thoroughly-valid-collections? [11 {:x 22/7 :y 'foo}] [list? {:is-map? map?}])
;; => false
;; all predicates satisfied, but one collection is missing a predicate
(thoroughly-valid-collections? [11 {:x 22/7 :y 'foo}] [{:is-map? map?}])
;; => false
thoroughly-valid-scalars?
(thoroughly-valid-scalars? data spec)Given a heterogeneous, arbitrarily-nested data structure data and scalar specification spec, returns true if every scalar contained in data has a corresponding predicate in spec, and every scalar satisfies its predicate.
See valid-scalars?, scalars-without-predicates, and thoroughly-valid?.
Examples:
;; all scalars have satisfied predicates
(thoroughly-valid-scalars? [11 {:x 22/7 :y 'foo}] [int? {:x ratio? :y symbol?}])
;; => true
;; all scalars have predicates, but not all satisfied
(thoroughly-valid-scalars? [11 {:x 22/7 :y 'foo}] [char? {:x ratio? :y symbol?}])
;; => false
;; all predicates satisfied, but one scalar is missing a predicate
(thoroughly-valid-scalars? [11 {:x 22/7 :y 'foo}] [int? {:x ratio?}])
;; => false
thoroughly-valid?
(thoroughly-valid? data scalar-spec collection-spec)Given a heterogeneous, arbitrarily-nested data structure data, returns true if every scalar in data has a predicate in scalar-spec and every collection has at least one predicate in collection-spec, and all predicates are satisfied.
See valid?, scalars-without-predicates, and collections-without-predicates.
Examples:
;; all scalars and colls have satisfied predicates
(thoroughly-valid? [11 {:x 22/7 :y 'foo}] [int? {:x ratio? :y symbol?}] [vector? {:is-map? map?}])
;; => true
;; all scalars and colls have predicates, but not all satisfied
(thoroughly-valid? [11 {:x 22/7 :y 'foo}] [char? {:x ratio? :y symbol?}] [vector? {:is-map? map?}])
;; => false
;; all predicates satisfied, but one scalar is missing a predicate
(thoroughly-valid? [11 {:x 22/7 :y 'foo}] [int? {:x ratio?}] [vector? {:is-map? map?}])
;; => false
;; all predicates satisfied, but one coll is missing a predicate
(thoroughly-valid? [11 {:x 22/7 :y 'foo}] [int? {:x ratio? :y symbol?}] [{:is-map? map?}])
;; => false
unfindable-generators
(unfindable-generators spec)Given scalar specification spec, check that a random sample generator can be located for each predicate-like thing within spec. Returns a sequence of any thingy that does not, and a path to its location within spec.
- Many basic predicates such as int?,string?,ratio?, etc., are provided byclojure.test.check.generators.
- Sets and regular expressions are competent generators.
- Compound predicates such as #(and (number? %) (< % 99))may be explicitly supplied with a custom generator located within its metadata map at key:speculoos/predicate->generator.
Note: Being able to locate a random sample generator does not imply that it works properly.
;; all good generators
(unfindable-generators [int? #{:red :green :blue} #"foo" (with-meta #(int? %) {:speculoos/predicate->generator #(rand-int 99)})])
;; => []
;; all bad generators
(unfindable-generators {:no-meta-data #(int? %)
                        :no-generator +
                        :improper-key (with-meta #(int? %) {:speculoos/oops #(rand-int 99)})})
;; => [{:path [:no-meta-data], :value #fn--34610]}
;;     {:path [:no-generator], :value clojure.core/+}
;;     {:path [:improper-key], :value #function[clojure.lang.AFunction/1]]}]
validate-predicate->generator
(validate-predicate->generator f)(validate-predicate->generator f n)Repeatedly invoke predicate function f, with sample arguments produced by generator located at key :speculoos/predicate->generator in the function metadata. n invocations (default 7). Returns a sequence of vectors containing the generated value and the result of the validation.
Useful for checking manually-injected generators consulted by data-from-spec.
Example:
;; basic integer predicate and generator
(validate-predicate->generator (with-meta #(int? %) {:speculoos/predicate->generator #(rand-int 99)}))
;; => ([64 true] [84 true] [21 true] [74 true] [88 true] [13 true] [33 true])
;; compound integer predicate with compound integer generator
(require '[clojure.test.check.generators :as gen])
;; positive, even integers, less than one hundred; warm up generator by 25 calls and peeling off last
(def gen-1 #(last (gen/sample (gen/such-that even? (gen/large-integer* {:min 0 :max 99})) 25)))
(validate-predicate->generator (with-meta #(and (int? %) (pos? %) (even? %) (< % 100)) {:speculoos/predicate->generator gen-1}))
;; => ([72 true] [52 true] [2 true] [82 true] [64 true] [56 true] [10 true])
;; same thing, but intentionally wrong generator that produces odd integers
(def incorrect-gen-1 #(last (gen/sample (gen/such-that odd? (gen/large-integer* {:min 0 :max 99})) 25)))
(validate-predicate->generator (with-meta #(and (int? %) (pos? %) (even? %) (< % 100)) {:speculoos/predicate->generator incorrect-gen-1}))
;; => ([61 false] [57 false] [3 false] [97 false] [53 false] [77 false] [63 false])
;; validating regular expression generator: capital Z, followed by one to three digits, followed by a, b, or c
(def re-spec #"Z\d{1,3}[abc]")
;; re-rand generates random strings that satisfy a given regex [https://github.com/weavejester/re-rand]
(require '[re-rand :refer [re-rand]])
(validate-predicate->generator (with-meta #(boolean (re-matches re-spec %)) {:speculoos/predicate->generator #(re-rand re-spec)}))
;; (["Z919b" true] ["Z99b" true] ["Z5a" true] ["Z210a" true] ["Z711c" true] ["Z319a" true] ["Z81c" true])