|
25 | 25 | read-string |
26 | 26 | 10000]]) |
27 | 27 |
|
28 | | -(defn config |
| 28 | +(defn- config |
29 | 29 | [] |
30 | 30 | (reduce |
31 | 31 | (fn [m [prop path coerce default]] |
|
43 | 43 | (locking rnd |
44 | 44 | (.nextInt rnd))) |
45 | 45 |
|
46 | | -(defmulti var-tests |
47 | | - "TestContainer.tests support for vars. To create custom test |
48 | | - types, define vars that have :c.t.g/type metadata, and then add |
49 | | - a matching var-tests method that returns a collection of tests." |
50 | | - (fn [v] (:clojure.test.generative/type (meta v)))) |
51 | | - |
52 | | -(defmethod var-tests :defspec [^clojure.lang.Var v] |
53 | | - [{:name (-> (when-let [ns (.ns v)] |
54 | | - (str ns "/" (.sym v)) |
55 | | - (.sym v)) |
56 | | - symbol) |
57 | | - :f @v |
58 | | - :inputs (fn [] |
59 | | - (repeatedly |
60 | | - (fn [] |
61 | | - (into [] (map #(%) (:clojure.test.generative/arg-fns (meta v)))))))}]) |
62 | | - |
63 | | -(defmethod var-tests nil [v] nil) |
64 | | - |
65 | 46 | (defprotocol TestContainer |
66 | | - (tests |
67 | | - [_] |
68 | | - "Returns a collection of generative tests, where a test is a map with |
69 | | - :name ns-qualified symbol |
70 | | - :f fn to test |
71 | | - :inputs fn returning a (possibly infinite!) sequence of inputs |
72 | | -
|
73 | | - All input generation should use and gen/*rnd* |
74 | | - if a source of pseudo-randomness is needed.")) |
| 47 | + (get-tests [_])) |
75 | 48 |
|
76 | 49 | (extend-protocol TestContainer |
77 | | - clojure.lang.Var |
78 | | - (tests [v] (var-tests v))) |
| 50 | + clojure.lang.Var |
| 51 | + (get-tests |
| 52 | + [v] |
| 53 | + (when-let [arg-fns (:clojure.test.generative/arg-fns (meta v))] |
| 54 | + [{:test (-> (if-let [ns (.ns v)] |
| 55 | + (str ns "/" (.sym v)) |
| 56 | + (.sym v)) |
| 57 | + symbol) |
| 58 | + :input-gen (fn [] |
| 59 | + (repeatedly |
| 60 | + (fn [] |
| 61 | + (into [] (map #(%) arg-fns)))))}])) |
| 62 | + |
| 63 | + clojure.lang.MapEquivalence |
| 64 | + (get-tests |
| 65 | + [m] m)) |
| 66 | + |
79 | 67 |
|
80 | 68 | (defn find-vars-in-namespaces |
81 | 69 | [& nses] |
|
92 | 80 | "Run f (presumably for side effects) repeatedly on n threads, |
93 | 81 | until msec has passed or somebody throws an exception. |
94 | 82 | Returns as many status maps as seeds passed in." |
95 | | - [{:keys [name f inputs]} msec seeds] |
96 | | - (print (str "\n" name)) (flush) |
97 | | - (let [start (System/currentTimeMillis) |
| 83 | + [{:keys [test input-gen]} msec seeds] |
| 84 | + (prn) (prn test) |
| 85 | + (let [f (eval test) |
| 86 | + start (System/currentTimeMillis) |
98 | 87 | futs (mapv |
99 | 88 | #(future |
100 | 89 | (try |
101 | 90 | (binding [gen/*rnd* (java.util.Random. %)] |
102 | 91 | (loop [iter 0 |
103 | | - [input & more] (inputs)] |
104 | | - (let [status {:iter iter :seed % :name name :input input}] |
| 92 | + [input & more] (input-gen)] |
| 93 | + (let [status {:iter iter :seed % :test test :input input}] |
105 | 94 | (if input |
106 | 95 | (let [failure (try |
107 | 96 | (apply f input) |
|
112 | 101 | (cond |
113 | 102 | failure failure |
114 | 103 | (< now (+ start msec)) (recur (inc iter) more) |
115 | | - :else status)) |
| 104 | + :else (select-keys status [:test :seed :iter]))) |
116 | 105 | (assoc status :exhausted true))))))) |
117 | 106 | seeds)] |
118 | 107 | (map deref futs))) |
119 | 108 |
|
120 | 109 | (defn run-n |
121 | 110 | "Run tests in parallel on nthreads, dividing msec equally between the tests." |
122 | | - [tests msec nthreads] |
| 111 | + [nthreads msec tests] |
123 | 112 | (mapcat #(run-one % (/ msec (count tests)) (repeatedly nthreads next-seed)) tests)) |
124 | 113 |
|
125 | | -(defn run-var |
126 | | - [var msec nthreads] |
127 | | - (run-n (var-tests var) msec nthreads)) |
| 114 | +(defn failed? |
| 115 | + "Does test result indicate a failure?" |
| 116 | + [result] |
| 117 | + (contains? result :exception)) |
| 118 | + |
| 119 | +(defn run-vars |
| 120 | + "Designed for interactive use. Prints results to *out* and throws |
| 121 | + on first failure encountered." |
| 122 | + [nthreads msec & test-containers] |
| 123 | + (doseq [result (run-n nthreads msec (mapcat get-tests test-containers))] |
| 124 | + (if (failed? result) |
| 125 | + (throw (ex-info "Generative test failed" result)) |
| 126 | + (prn result)))) |
128 | 127 |
|
129 | 128 | (defn dir-tests |
130 | 129 | "Returns all tests in dirs" |
131 | | - [& dirs] |
| 130 | + [dirs] |
132 | 131 | (let [load (fn [s] (require s) s)] |
133 | 132 | (->> (mapcat #(ns/find-namespaces-in-dir (java.io.File. ^String %)) dirs) |
134 | 133 | (map load) |
135 | 134 | (apply find-vars-in-namespaces) |
136 | | - (mapcat tests)))) |
| 135 | + (mapcat get-tests)))) |
137 | 136 |
|
138 | | -(defn run-tests-in-dirs |
139 | | - [{:keys [threads msec verbose]} & dirs] |
| 137 | +(defn run-suite |
| 138 | + "Designed for test suite use." |
| 139 | + [{:keys [threads msec verbose]} tests] |
140 | 140 | (reduce |
141 | 141 | (fn [{:keys [failures iters tests]} result] |
142 | 142 | (if (or verbose (:exception result)) |
143 | | - (pprint/pprint result) |
| 143 | + (do (prn) (prn result)) |
144 | 144 | (print ".")) |
145 | 145 | (when (:exception result) |
146 | 146 | (.printStackTrace ^Throwable (:exception result))) |
|
149 | 149 | :iters (+ iters (:iter result)) |
150 | 150 | :tests (inc tests)}) |
151 | 151 | {:failures 0 :iters 0 :tests 0} |
152 | | - (run-n (apply dir-tests dirs) msec threads))) |
| 152 | + (run-n threads msec tests))) |
153 | 153 |
|
154 | 154 | (defn -main |
155 | 155 | "Command line entry point. Calls System.exit!" |
156 | 156 | [& dirs] |
157 | 157 | (if (seq dirs) |
158 | 158 | (try |
159 | | - (let [result (apply run-tests-in-dirs (config) dirs)] |
| 159 | + (let [result (run-suite (config) (dir-tests dirs))] |
160 | 160 | (println "\n" result) |
161 | 161 | (System/exit (:failures result))) |
162 | 162 | (catch Throwable t |
|
0 commit comments