Skip to content

Commit f45a0b8

Browse files
author
dnolen
committed
copy over spec testing support
1 parent b2e3a46 commit f45a0b8

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
; Copyright (c) Rich Hickey. All rights reserved.
2+
; The use and distribution terms for this software are covered by the
3+
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
; which can be found in the file epl-v10.html at the root of this distribution.
5+
; By using this software in any fashion, you are agreeing to be bound by
6+
; the terms of this license.
7+
; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.spec.test
10+
(:require
11+
[cljs.spec :as spec]
12+
[cljs.spec.impl.gen :as gen]))
13+
14+
(defn run-tests
15+
"Like run-all-tests, but scoped to specific namespaces, or to
16+
*ns* if no ns-sym are specified."
17+
[& ns-syms]
18+
(if (seq ns-syms)
19+
(run-var-tests (->> (apply spec/speced-vars ns-syms)
20+
(filter (fn [v] (:args (spec/fn-specs v))))))
21+
(run-tests (.name ^clojure.lang.Namespace *ns*))))
22+
23+
(defn run-all-tests
24+
"Like clojure.test/run-all-tests, but runs test.check tests
25+
for all speced vars. Prints per-test results to *out*, and
26+
returns a map with :test,:pass,:fail, and :error counts."
27+
[]
28+
(run-var-tests (spec/speced-vars)))

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

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
; Copyright (c) Rich Hickey. All rights reserved.
2+
; The use and distribution terms for this software are covered by the
3+
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
; which can be found in the file epl-v10.html at the root of this distribution.
5+
; By using this software in any fashion, you are agreeing to be bound by
6+
; the terms of this license.
7+
; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.spec.test
10+
(:require-macros [cljs.spec.test :as st])
11+
(:require
12+
[cljs.spec :as spec]
13+
[cljs.spec.impl.gen :as gen]))
14+
15+
;; wrap spec/explain-data until specs always return nil for ok data
16+
(defn- explain-data*
17+
[spec v]
18+
(when-not (spec/valid? spec v nil)
19+
(spec/explain-data spec v)))
20+
21+
;; wrap and unwrap spec failure data in an exception so that
22+
;; quick-check will treat it as a failure.
23+
(defn- wrap-failing
24+
[explain-data step]
25+
(ex-info "Wrapper" {::check-call (assoc explain-data :failed-on step)}))
26+
27+
(defn- unwrap-failing
28+
[ret]
29+
(let [ret (if-let [explain (-> ret :result ex-data ::check-call)]
30+
(assoc ret :result explain)
31+
ret)]
32+
(if-let [shrunk-explain (-> ret :shrunk :result ex-data ::check-call)]
33+
(assoc-in ret [:shrunk :result] shrunk-explain)
34+
ret)))
35+
36+
(defn- check-call
37+
"Returns true if call passes specs, otherwise *returns* an exception
38+
with explain-data plus a :failed-on key under ::check-call."
39+
[f specs args]
40+
(let [cargs (when (:args specs) (spec/conform (:args specs) args))]
41+
(if (= cargs ::spec/invalid)
42+
(wrap-failing (explain-data* (:args specs) args) :args)
43+
(let [ret (apply f args)
44+
cret (when (:ret specs) (spec/conform (:ret specs) ret))]
45+
(if (= cret ::spec/invalid)
46+
(wrap-failing (explain-data* (:ret specs) ret) :ret)
47+
(if (and (:args specs) (:ret specs) (:fn specs))
48+
(if (spec/valid? (:fn specs) {:args cargs :ret cret})
49+
true
50+
(wrap-failing (explain-data* (:fn specs) {:args cargs :ret cret}) :fn))
51+
true))))))
52+
53+
(defn check-fn
54+
"Check a function using provided specs and test.check.
55+
Same options and return as check-var"
56+
[f specs
57+
& {:keys [num-tests seed max-size reporter-fn]
58+
:or {num-tests 100 max-size 200 reporter-fn (constantly nil)}}]
59+
(let [g (spec/gen (:args specs))
60+
prop (gen/for-all* [g] #(check-call f specs %))]
61+
(let [ret (gen/quick-check num-tests prop :seed seed :max-size max-size :reporter-fn reporter-fn)]
62+
(if-let [[smallest] (-> ret :shrunk :smallest)]
63+
(unwrap-failing ret)
64+
ret))))
65+
66+
(defn check-var
67+
"Checks a var's specs using test.check. Optional args are
68+
passed through to test.check/quick-check:
69+
70+
num-tests number of tests to run, default 100
71+
seed random seed
72+
max-size how large an input to generate, max 200
73+
reporter-fn reporting fn
74+
75+
Returns a map as quick-check, with :explain-data added if
76+
:result is false."
77+
[v & opts]
78+
(let [specs (spec/fn-specs v)]
79+
(if (:args specs)
80+
(apply check-fn @v specs opts)
81+
(throw (js/Error. (str "No :args spec for " v))))))
82+
83+
(defn- run-var-tests
84+
"Helper for run-tests, run-all-tests."
85+
[vs]
86+
(let [reporter-fn println]
87+
(reduce
88+
(fn [totals v]
89+
(let [_ (println "Checking" v)
90+
ret (check-var v :reporter-fn reporter-fn)]
91+
(prn ret)
92+
(cond-> totals
93+
true (update :test inc)
94+
(true? (:result ret)) (update :pass inc)
95+
(::spec/problems (:result ret)) (update :fail inc)
96+
(instance? js/Error (:result ret)) (update :error inc))))
97+
{:test 0, :pass 0, :fail 0, :error 0}
98+
vs)))
99+
100+
101+
(comment
102+
(require '[cljs.pprint :as pp]
103+
'[cljs.spec :as s]
104+
'[cljs.spec.impl.gen :as gen]
105+
'[cljs.test :as ctest])
106+
107+
(require :reload '[cjls.spec.test :as test])
108+
109+
;; discover speced vars for your own test runner
110+
(s/speced-vars)
111+
112+
;; check a single var
113+
(test/check-var #'-)
114+
(test/check-var #'+)
115+
(test/check-var #'clojure.spec.broken-specs/throwing-fn)
116+
117+
;; old style example tests
118+
(ctest/run-all-tests)
119+
120+
(s/speced-vars 'clojure.spec.correct-specs)
121+
;; new style spec tests return same kind of map
122+
(test/check-var #'subs)
123+
(cljs.spec.test/run-tests 'clojure.core)
124+
(test/run-all-tests)
125+
126+
)
127+
128+
129+
130+
131+

0 commit comments

Comments
 (0)