Skip to content

Commit 0d4439e

Browse files
author
dnolen
committed
naive copy of latest clojure.spec.test, wip
1 parent b47670b commit 0d4439e

File tree

1 file changed

+174
-63
lines changed

1 file changed

+174
-63
lines changed

src/main/cljs/cljs/spec/test.cljs

Lines changed: 174 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
[cljs.pprint :as pp]
1616
[cljs.spec :as s]
1717
[cljs.spec.impl.gen :as gen]
18-
[clojure.test.check]
18+
[clojure.test.check :as stc]
1919
[clojure.test.check.properties]))
2020

2121
(defn distinct-by
@@ -172,85 +172,196 @@ that can be instrumented."
172172
(:stub opts)
173173
(keys (:replace opts))])))
174174

175-
;; wrap and unwrap spec failure data in an exception so that
176-
;; quick-check will treat it as a failure.
177-
(defn- wrap-failing
178-
[explain-data step]
179-
(ex-info "Wrapper" {::check-call (assoc explain-data :failed-on step)}))
175+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
180176

181-
(defn- unwrap-failing
182-
[ret]
183-
(let [ret (if-let [explain (-> ret :result ex-data ::check-call)]
184-
(assoc ret :result explain)
185-
ret)]
186-
(if-let [shrunk-explain (-> ret :shrunk :result ex-data ::check-call)]
187-
(assoc-in ret [:shrunk :result] shrunk-explain)
188-
ret)))
177+
(defn- explain-check
178+
[args spec v role]
179+
(ex-info
180+
"Specification-based check failed"
181+
(when-not (s/valid? spec v nil)
182+
(assoc (s/explain-data* spec [role] [] [] v)
183+
::args args
184+
::val v
185+
::s/failure :check-failed))))
189186

190187
(defn- check-call
191188
"Returns true if call passes specs, otherwise *returns* an exception
192-
with explain-data plus a :failed-on key under ::check-call."
189+
with explain-data + ::s/failure."
193190
[f specs args]
194191
(let [cargs (when (:args specs) (s/conform (:args specs) args))]
195192
(if (= cargs ::s/invalid)
196-
(wrap-failing (explain-data* (:args specs) args) :args)
193+
(explain-check args (:args specs) args :args)
197194
(let [ret (apply f args)
198195
cret (when (:ret specs) (s/conform (:ret specs) ret))]
199196
(if (= cret ::s/invalid)
200-
(wrap-failing (explain-data* (:ret specs) ret) :ret)
197+
(explain-check args (:ret specs) ret :ret)
201198
(if (and (:args specs) (:ret specs) (:fn specs))
202199
(if (s/valid? (:fn specs) {:args cargs :ret cret})
203200
true
204-
(wrap-failing (explain-data* (:fn specs) {:args cargs :ret cret}) :fn))
201+
(explain-check args (:fn specs) {:args cargs :ret cret} :fn))
205202
true))))))
206203

204+
(defn- quick-check
205+
[f specs {gen :gen opts ::stc/opts}]
206+
(let [{:keys [num-tests] :or {num-tests 1000}} opts
207+
g (try (s/gen (:args specs) gen) (catch js/Error t t))]
208+
(if (instance? js/Error g)
209+
{:result g}
210+
(let [prop (gen/for-all* [g] #(check-call f specs %))]
211+
(apply gen/quick-check num-tests prop (mapcat identity opts))))))
212+
213+
(defn- make-check-result
214+
"Builds spec result map."
215+
[check-sym spec test-check-ret]
216+
(merge {:spec spec
217+
::stc/ret test-check-ret}
218+
(when check-sym
219+
{:sym check-sym})
220+
(when-let [result (-> test-check-ret :result)]
221+
(when-not (true? result) {:failure result}))
222+
(when-let [shrunk (-> test-check-ret :shrunk)]
223+
{:failure (:result shrunk)})))
224+
225+
(defn- check-1
226+
[{:keys [s f v spec]} opts]
227+
(let [re-inst? (and v (seq (unstrument s)) true)
228+
f (or f (when v @v))]
229+
(try
230+
(cond
231+
(nil? f)
232+
{:failure (ex-info "No fn to spec" {::s/failure :no-fn})
233+
:sym s :spec spec}
234+
235+
(:args spec)
236+
(let [tcret (quick-check f spec opts)]
237+
(make-check-result s spec tcret))
238+
239+
:default
240+
{:failure (ex-info "No :args spec" {::s/failure :no-args-spec})
241+
:sym s :spec spec})
242+
(finally
243+
(when re-inst? (instrument s))))))
244+
245+
(defn- sym->check-map
246+
[s]
247+
(let [v (resolve s)]
248+
{:s s
249+
:v v
250+
:spec (when v (s/get-spec v))}))
251+
252+
(defn- validate-check-opts
253+
[opts]
254+
(assert (every? ident? (keys (:gen opts))) "check :gen expects ident keys"))
255+
207256
(defn check-fn
208-
"Check a function using provided specs and test.check.
209-
Same options and return as check-var"
210-
[f specs
211-
& {:keys [num-tests seed max-size reporter-fn]
212-
:or {num-tests 100 max-size 200 reporter-fn (constantly nil)}}]
213-
(let [g (s/gen (:args specs))
214-
prop (gen/for-all* [g] #(check-call f specs %))]
215-
(let [ret (gen/quick-check num-tests prop :seed seed :max-size max-size :reporter-fn reporter-fn)]
216-
(if-let [[smallest] (-> ret :shrunk :smallest)]
217-
(unwrap-failing ret)
218-
ret))))
219-
220-
(defn check-var
221-
"Checks a var's specs using test.check. Optional args are
222-
passed through to test.check/quick-check:
223-
224-
num-tests number of tests to run, default 100
225-
seed random seed
226-
max-size how large an input to generate, max 200
227-
reporter-fn reporting fn
228-
229-
Returns a map as quick-check, with :explain-data added if
230-
:result is false."
231-
[v & opts]
232-
(let [fnspec (s/get-spec v)]
233-
(if (:args fnspec)
234-
(apply check-fn @v fnspec opts)
235-
(throw (js/Error. (str "No :args spec for " v))))))
236-
237-
(defn- run-var-tests
238-
"Helper for run-tests, run-all-tests."
239-
[vs]
240-
(let [reporter-fn println]
241-
(reduce
242-
(fn [totals v]
243-
(let [_ (println "Checking" v)
244-
ret (check-var v :reporter-fn reporter-fn)]
245-
(prn ret)
246-
(cond-> totals
247-
true (update :test inc)
248-
(true? (:result ret)) (update :pass inc)
249-
(::s/problems (:result ret)) (update :fail inc)
250-
(instance? js/Error (:result ret)) (update :error inc))))
251-
{:test 0, :pass 0, :fail 0, :error 0}
252-
vs)))
257+
"Runs generative tests for fn f using spec and opts. See
258+
'check' for options and return."
259+
([f spec] (check-fn f spec nil))
260+
([f spec opts]
261+
(validate-check-opts opts)
262+
(check-1 {:f f :spec spec} opts)))
263+
264+
(defn checkable-syms
265+
"Given an opts map as per check, returns the set of syms that
266+
can be checked."
267+
([] (checkable-syms nil))
268+
([opts]
269+
(validate-check-opts opts)
270+
(reduce into #{} [(filter fn-spec-name? (keys (s/registry)))
271+
(keys (:spec opts))])))
272+
273+
(defn check
274+
"Run generative tests for spec conformance on vars named by
275+
sym-or-syms, a symbol or collection of symbols. If sym-or-syms
276+
is not specified, check all checkable vars.
277+
278+
The opts map includes the following optional keys, where stc
279+
aliases clojure.spec.test.check:
280+
281+
::stc/opts opts to flow through test.check/quick-check
282+
:gen map from spec names to generator overrides
283+
284+
The ::stc/opts include :num-tests in addition to the keys
285+
documented by test.check. Generator overrides are passed to
286+
spec/gen when generating function args.
287+
288+
Returns a lazy sequence of check result maps with the following
289+
keys
290+
291+
:spec the spec tested
292+
:sym optional symbol naming the var tested
293+
:failure optional test failure
294+
::stc/ret optional value returned by test.check/quick-check
295+
296+
The value for :failure can be any exception. Exceptions thrown by
297+
spec itself will have an ::s/failure value in ex-data:
298+
299+
:check-failed at least one checked return did not conform
300+
:no-args-spec no :args spec provided
301+
:no-fn no fn provided
302+
:no-fspec no fspec provided
303+
:no-gen unable to generate :args
304+
:instrument invalid args detected by instrument
305+
"
306+
([] (check (checkable-syms)))
307+
([sym-or-syms] (check sym-or-syms nil))
308+
([sym-or-syms opts]
309+
(->> (collectionize sym-or-syms)
310+
(filter (checkable-syms opts))
311+
(map
312+
#(check-1 (sym->check-map %) opts)))))
313+
314+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; check reporting ;;;;;;;;;;;;;;;;;;;;;;;;
315+
316+
(defn- failure-type
317+
[x]
318+
(::s/failure (ex-data x)))
253319

320+
(defn- unwrap-failure
321+
[x]
322+
(if (failure-type x)
323+
(ex-data x)
324+
x))
325+
326+
(defn- result-type
327+
"Returns the type of the check result. This can be any of the
328+
::s/failure keywords documented in 'check', or:
329+
330+
:check-passed all checked fn returns conformed
331+
:check-threw checked fn threw an exception"
332+
[ret]
333+
(let [failure (:failure ret)]
334+
(cond
335+
(nil? failure) :check-passed
336+
(failure-type failure) (failure-type failure)
337+
:default :check-threw)))
338+
339+
(defn abbrev-result
340+
"Given a check result, returns an abbreviated version
341+
suitable for summary use."
342+
[x]
343+
(if (:failure x)
344+
(-> (dissoc x ::stc/ret)
345+
(update :spec s/describe)
346+
(update :failure unwrap-failure))
347+
(dissoc x :spec ::stc/ret)))
348+
349+
(defn summarize-results
350+
"Given a collection of check-results, e.g. from 'check', pretty
351+
prints the summary-result (default abbrev-result) of each.
352+
353+
Returns a map with :total, the total number of results, plus a
354+
key with a count for each different :type of result."
355+
([check-results] (summarize-results check-results abbrev-result))
356+
([check-results summary-result]
357+
(reduce
358+
(fn [summary result]
359+
(pp/pprint (summary-result result))
360+
(-> summary
361+
(update :total inc)
362+
(update (result-type result) (fnil inc 0))))
363+
{:total 0}
364+
check-results)))
254365

255366
(comment
256367
(require

0 commit comments

Comments
 (0)