Skip to content

Commit 9db921b

Browse files
extension point for non-defspec generative tests
1 parent 03d113f commit 9db921b

3 files changed

Lines changed: 109 additions & 59 deletions

File tree

src/main/clojure/clojure/test/generative.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@
9797
seq)]
9898
(throw (IllegalArgumentException. (str "Missing tags for " (seq (map first missing-tags)) " in " name))))
9999
`(defn ~(with-meta name (assoc (meta name)
100-
::inputs (into [] (map #(-> % meta :tag tag->gen eval) args))))
100+
::type :defspec
101+
::arg-fns (into [] (map #(-> % meta :tag tag->gen eval) args))))
101102
~(into [] (map (fn [a#] (with-meta a# (dissoc (meta a#) :tag))) args))
102103
(let [~'% (apply ~fn-to-test ~args)]
103104
~@validator-body

src/main/clojure/clojure/test/generative/runner.clj

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -100,40 +100,17 @@
100100
(when-let [e (ctevent->event m)]
101101
(event/report-fn e)))
102102

103-
(defprotocol Test
104-
(test-name [_])
105-
(test-fn [_])
106-
(test-input [_]))
107-
108-
(extend-protocol Test
109-
clojure.lang.Var
110-
(test-name
111-
[v]
112-
(-> (when-let [ns (.ns v)]
113-
(str ns "/" (.sym v))
114-
(.sym v))
115-
symbol))
116-
(test-fn
117-
[this]
118-
@this)
119-
(test-input
120-
[v]
121-
(map #(%) (:clojure.test.generative/inputs (meta v)))))
122-
123103
(defn run-iter
124104
"Run a single test iteration"
125-
[test]
126-
(let [name (test-name test)
127-
f (test-fn test)
128-
input (test-input test)]
129-
(event/report :test/iter :level :debug :name name :args input :tags #{:begin})
130-
(try
131-
(let [result (apply f input)]
132-
(when-not (realized? *failed*)
133-
(event/report :test/iter :level :debug :name name :return result :tags #{:end})))
134-
(catch Throwable t
135-
(deliver *failed* :error)
136-
(event/report :error :name name :exception t)))))
105+
[name f input]
106+
(event/report :test/iter :level :debug :name name :args input :tags #{:begin})
107+
(try
108+
(let [result (apply f input)]
109+
(when-not (realized? *failed*)
110+
(event/report :test/iter :level :debug :name name :return result :tags #{:end})))
111+
(catch Throwable t
112+
(deliver *failed* :error)
113+
(event/report :error :name name :exception t))))
137114

138115
(defn run-for
139116
"Run f (presumably for side effects) repeatedly on n threads,
@@ -144,27 +121,38 @@
144121
(map
145122
#(future
146123
(try
147-
(let [seed (+ % 42)]
124+
(let [seed (+ % 42)
125+
name (:name test)
126+
f (:fn test)]
148127
(binding [gen/*seed* seed
149128
gen/*rnd* (java.util.Random. seed)
150129
*failed* (promise)]
151-
(event/report :test/test :tags #{:begin} :test/seed gen/*seed* :name (test-name test))
152-
(loop [iter 0]
153-
(let [result (run-iter test)
154-
now (System/currentTimeMillis)
130+
(event/report :test/test :tags #{:begin} :test/seed gen/*seed* :name name)
131+
(loop [iter 0
132+
[input & more] ((:inputs test))]
133+
(let [now (System/currentTimeMillis)
155134
failed? (realized? *failed*)]
156-
(if (and (< now (+ start msec))
157-
(not failed?))
158-
(recur (inc iter))
135+
(if input
136+
(let [result (run-iter name f input)]
137+
(if (and (< now (+ start msec))
138+
(not failed?))
139+
(recur (inc iter) more)
140+
(event/report :test/test
141+
:msec (- now start)
142+
:count (inc iter)
143+
:tags #{:end}
144+
:test/result (if failed? :test/fail :test/pass)
145+
:level (if failed? :warn :info)
146+
:name name)))
159147
(event/report :test/test
160148
:msec (- now start)
161149
:count (inc iter)
162-
:tags #{:end}
150+
:tags #{:end :test/inputs-exhausted}
163151
:test/result (if failed? :test/fail :test/pass)
164152
:level (if failed? :warn :info)
165-
:name (test-name test)))))))
153+
:name name))))))
166154
(catch Throwable t
167-
(event/report :error :level :error :exception t :name (test-name test)))))
155+
(event/report :error :level :error :exception t :name name))))
168156
(range nthreads)))]
169157
(doseq [f futs] @f)))
170158

@@ -176,13 +164,39 @@
176164
(doseq [test tests]
177165
(run-for test nthreads test-msec))))
178166

179-
#_(defn set-seed
180-
[n]
181-
(set! gen/*rnd* (java.util.Random. n)))
182-
183-
(defn gentest?
184-
[v]
185-
(boolean (:clojure.test.generative/inputs (meta v))))
167+
(defmulti var-tests
168+
"TestContainer.tests support for vars. To create custom test
169+
types, define vars that have :c.t.g/type metadata, and then add
170+
a matching var-tests method that returns a collection of tests."
171+
(fn [v] (:clojure.test.generative/type (meta v))))
172+
173+
(defmethod var-tests :defspec [^clojure.lang.Var v]
174+
[{:name (-> (when-let [ns (.ns v)]
175+
(str ns "/" (.sym v))
176+
(.sym v))
177+
symbol)
178+
:fn @v
179+
:inputs (fn []
180+
(repeatedly
181+
(fn []
182+
(mapv #(%) (:clojure.test.generative/arg-fns (meta v))))))}])
183+
184+
(defmethod var-tests nil [v] nil)
185+
186+
(defprotocol TestContainer
187+
(tests
188+
[_]
189+
"Returns a collection of generative tests, where a test is a map with
190+
:name ns-qualified symbol
191+
:fn fn to test
192+
:inputs fn returning a (possibly infinite!) sequence of inputs
193+
194+
All input generation should use gen/*seed* and gen/*rnd*
195+
if a source of pseudo-randomness is needed."))
196+
197+
(extend-protocol TestContainer
198+
clojure.lang.Var
199+
(tests [v] (var-tests v)))
186200

187201
(defn find-vars-in-namespaces
188202
[& nses]
@@ -195,20 +209,16 @@
195209
(doseq [ns nses] (require ns))
196210
(apply find-vars-in-namespaces nses)))
197211

198-
(defn find-gentests-in-vars
199-
[& vars]
200-
(filter gentest? vars))
201-
202212
(defn run-generative-tests
203213
"Run generative tests."
204214
[nses nthreads msec]
205215
(let [c (count (->> (apply find-vars-in-namespaces nses)
206-
(filter gentest?)))]
216+
(mapcat tests)))]
207217
(when-not (zero? c)
208218
(let [test-msec (quot msec c)]
209219
(doseq [ns nses]
210220
(when-let [fs (->> (find-vars-in-namespaces ns)
211-
(filter gentest?)
221+
(mapcat tests)
212222
seq)]
213223
(event/report :test/group
214224
:name ns
@@ -300,11 +310,15 @@
300310
[& dirs]
301311
(if (seq dirs)
302312
(try
303-
(let [results (apply test-dirs dirs)]
313+
(let [results (apply test-dirs dirs)
314+
failed? (boolean (some failed? (vals results)))]
304315
(doseq [[k v] results]
305316
(println (str "\nFramework " k))
306317
(println v))
307-
(System/exit (if (some failed? (vals results)) 1 0)))
318+
(when failed?
319+
(binding [*out* *err*]
320+
(println "\n*** Some tests failed ***\n")))
321+
(System/exit (if failed? 1 0)))
308322
(catch Throwable t
309323
(.printStackTrace t)
310324
(System/exit -1))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
(ns clojure.test.generative.runner-test
2+
(:use [clojure.test :only (deftest) :as ctest]
3+
[clojure.test.generative :only (is) :as test]
4+
[clojure.test.generative.event :as event])
5+
(:require [clojure.test.generative.runner :as runner]))
6+
7+
(deftest zero-inputs
8+
(runner/run-for
9+
{:name 'test.generative.runner-test/zero-inputs-example
10+
:fn (fn [] (assert false "unreachable"))
11+
:inputs (fn [] nil)}
12+
1
13+
100))
14+
15+
(deftest finite-inputs
16+
(let [adder (atom 0)
17+
inputs [[1] [2] [3]]]
18+
(runner/run-for
19+
{:name 'test.generative.runner-test/finite-inputs-example
20+
:fn (fn [n] (swap! adder + n))
21+
:inputs (fn [] [[1] [2] [3]])}
22+
1
23+
100)
24+
(is (= @adder (apply + (map first inputs))))))
25+
26+
(defmethod runner/var-tests ::custom-type
27+
[v]
28+
[{:name (event/fqname v)
29+
:fn @v
30+
:inputs (fn [] [[::only-input]])}])
31+
32+
(defn ^{::test/type ::custom-type}
33+
roll-your-own
34+
[x]
35+
(is (= x ::only-input)))

0 commit comments

Comments
 (0)