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
predname
and functionf
,defpred
will attempt to create a random sample generator as outlined by inspect-fn. - An explicitly supplied
generator
will be preferred. - If
generator
is explicitly provided, a canonical value may also be supplied. If the optional canonical value is not supplied, a keyword is automatically generated using the symbolpredname
appended 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
and
oror
must contain aclojure.core
predicate for a scalar, such asint?
. - Subsequent clauses of
and
will be injected into aclojure.test.check.generators/such-that
filter. - Direct descendants of a top-level
or
will producen
separate random sample generators, each with1/n
probability 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]
betweenm
andn
(integers), inclusivei
exactlyi
(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])