Skip to content

Commit f198edc

Browse files
committed
middleware.test: offer fail-fast functionality
Fixes #709
1 parent 64bbf5c commit f198edc

File tree

5 files changed

+122
-58
lines changed

5 files changed

+122
-58
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
* [#773](https://github.com/clojure-emacs/cider-nrepl/pull/773) Add middleware to capture, debug, inspect and view log events emitted by Java logging frameworks.
88
* [#755](https://github.com/clojure-emacs/cider-nrepl/pull/755) `middleware.test`: now timing information is returned at var and ns level under the `:ms`/`:humanized` keys.
99
* `middleware.test`: only include `:diff` data when the expected/actual mismatch is deemed diffable.
10-
* i.e., maps and sequences are diffable, scalar values are not.
10+
* i.e., maps, sets and sequences are diffable, scalar values are not.
11+
* [#709](https://github.com/clojure-emacs/cider-nrepl/pull/709) `middleware.test`: offer fail-fast functionality.
1112

1213
### Changes
1314

doc/modules/ROOT/pages/nrepl-api/ops.adoc

+4
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ Optional parameters::
971971

972972
Returns::
973973
* `:elapsed-time` a report of the elapsed time spent running all the given namespaces. The structure is ``:elapsed-time {:ms <integer> :humanized <string>}``.
974+
* `:fail-fast` If true, the tests will be considered complete after the first test has failed or errored.
974975
* `:ns-elapsed-time` a report of the elapsed time spent running each namespace. The structure is ``:ns-elapsed-time {<ns as keyword> {:ms <integer> :humanized <string>}}``.
975976
* `:results` Misc information about the test result. The structure is ``:results {<ns as keyword> {<test var as keyword> [{,,, :elapsed-time {:ms <integer> :humanized <string>}}]}}``
976977
* `:status` Either done or indication of an error
@@ -1103,6 +1104,7 @@ Optional parameters::
11031104

11041105
Returns::
11051106
* `:elapsed-time` a report of the elapsed time spent running all the given namespaces. The structure is ``:elapsed-time {:ms <integer> :humanized <string>}``.
1107+
* `:fail-fast` If true, the tests will be considered complete after the first test has failed or errored.
11061108
* `:ns-elapsed-time` a report of the elapsed time spent running each namespace. The structure is ``:ns-elapsed-time {<ns as keyword> {:ms <integer> :humanized <string>}}``.
11071109
* `:results` Misc information about the test result. The structure is ``:results {<ns as keyword> {<test var as keyword> [{,,, :elapsed-time {:ms <integer> :humanized <string>}}]}}``
11081110
* `:status` Either done or indication of an error
@@ -1127,6 +1129,7 @@ Optional parameters::
11271129

11281130
Returns::
11291131
* `:elapsed-time` a report of the elapsed time spent running all the given namespaces. The structure is ``:elapsed-time {:ms <integer> :humanized <string>}``.
1132+
* `:fail-fast` If true, the tests will be considered complete after the first test has failed or errored.
11301133
* `:ns-elapsed-time` a report of the elapsed time spent running each namespace. The structure is ``:ns-elapsed-time {<ns as keyword> {:ms <integer> :humanized <string>}}``.
11311134
* `:results` Misc information about the test result. The structure is ``:results {<ns as keyword> {<test var as keyword> [{,,, :elapsed-time {:ms <integer> :humanized <string>}}]}}``
11321135
* `:status` Either done or indication of an error
@@ -1172,6 +1175,7 @@ Optional parameters::
11721175

11731176
Returns::
11741177
* `:elapsed-time` a report of the elapsed time spent running all the given namespaces. The structure is ``:elapsed-time {:ms <integer> :humanized <string>}``.
1178+
* `:fail-fast` If true, the tests will be considered complete after the first test has failed or errored.
11751179
* `:ns-elapsed-time` a report of the elapsed time spent running each namespace. The structure is ``:ns-elapsed-time {<ns as keyword> {:ms <integer> :humanized <string>}}``.
11761180
* `:results` Misc information about the test result. The structure is ``:results {<ns as keyword> {<test var as keyword> [{,,, :elapsed-time {:ms <integer> :humanized <string>}}]}}``
11771181
* `:status` Either done or indication of an error

src/cider/nrepl.clj

+7-5
Original file line numberDiff line numberDiff line change
@@ -612,29 +612,31 @@ stack frame of the most recent exception. This op is deprecated, please use the
612612
"ns-elapsed-time" "a report of the elapsed time spent running each namespace. The structure is `:ns-elapsed-time {<ns as keyword> {:ms <integer> :humanized <string>}}`."
613613
"results" "Misc information about the test result. The structure is `:results {<ns as keyword> {<test var as keyword> [{,,, :elapsed-time {:ms <integer> :humanized <string>}}]}}`"})
614614

615+
(def fail-fast-doc {"fail-fast" "If true, the tests will be considered complete after the first test has failed or errored."})
616+
615617
(def-wrapper wrap-test cider.nrepl.middleware.test/handle-test
616618
{:doc "Middleware that handles testing requests."
617619
:requires #{#'session #'wrap-print}
618620
:handles {"test-var-query"
619621
{:doc "Run tests specified by the `var-query` and return results. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
620622
:requires {"var-query" "A search query specifying the test vars to execute. See Orchard's var query documentation for more details."}
621-
:optional wrap-print-optional-arguments
622-
:returns timing-info-return-doc}
623+
:optional (merge wrap-print-optional-arguments)
624+
:returns (merge fail-fast-doc timing-info-return-doc)}
623625
"test"
624626
{:doc "[DEPRECATED - `use test-var-query` instead] Run tests in the specified namespace and return results. This accepts a set of `tests` to be run; if nil, runs all tests. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
625627
:optional wrap-print-optional-arguments
626-
:returns timing-info-return-doc}
628+
:returns (merge fail-fast-doc timing-info-return-doc)}
627629
"test-all"
628630
{:doc "Return exception cause and stack frame info for an erring test via the `stacktrace` middleware. The error to be retrieved is referenced by namespace, var name, and assertion index within the var."
629631
:optional wrap-print-optional-arguments
630-
:returns timing-info-return-doc}
632+
:returns (merge fail-fast-doc timing-info-return-doc)}
631633
"test-stacktrace"
632634
{:doc "Rerun all tests that did not pass when last run. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
633635
:optional wrap-print-optional-arguments}
634636
"retest"
635637
{:doc "[DEPRECATED - `use test-var-query` instead] Run all tests in the project. If `load?` is truthy, all project namespaces are loaded; otherwise, only tests in presently loaded namespaces are run. Results are cached for exception retrieval and to enable re-running of failed/erring tests."
636638
:optional wrap-print-optional-arguments
637-
:returns timing-info-return-doc}}})
639+
:returns (merge fail-fast-doc timing-info-return-doc)}}})
638640

639641
(def-wrapper wrap-trace cider.nrepl.middleware.trace/handle-trace
640642
{:doc "Toggle tracing of a given var."

src/cider/nrepl/middleware/test.clj

+85-52
Original file line numberDiff line numberDiff line change
@@ -304,69 +304,99 @@
304304
:message "Uncaught exception, not in assertion"})]
305305
(test/do-report (assoc report :var-elapsed-time @time-info))))))
306306

307+
(defn- current-test-run-failed? []
308+
(or (some-> @current-report :summary :fail pos?)
309+
(some-> @current-report :summary :error pos?)))
310+
307311
(defn test-vars
308312
"Call `test-var` on each var, with the fixtures defined for namespace object
309313
`ns`."
310-
[ns vars]
311-
(let [once-fixture-fn (test/join-fixtures (::test/once-fixtures (meta ns)))
312-
each-fixture-fn (test/join-fixtures (::test/each-fixtures (meta ns)))]
313-
(try (once-fixture-fn
314-
(fn []
315-
(doseq [v vars]
316-
(each-fixture-fn (fn [] (test-var v))))))
317-
(catch Throwable e
318-
(when (System/getProperty "cider.internal.testing")
319-
;; print stacktrace, in case it didn't have anything to do with fixtures
320-
;; (in which case, things would become very confusing)
321-
(.printStackTrace e))
322-
(report-fixture-error ns e)))))
314+
([ns vars]
315+
(test-vars ns vars false))
316+
317+
([ns vars fail-fast?]
318+
(let [once-fixture-fn (test/join-fixtures (::test/once-fixtures (meta ns)))
319+
each-fixture-fn (test/join-fixtures (::test/each-fixtures (meta ns)))]
320+
(try
321+
(once-fixture-fn
322+
(fn []
323+
(reduce (fn [_ v]
324+
(cond-> (each-fixture-fn (fn []
325+
(test-var v)))
326+
(and fail-fast? (current-test-run-failed?))
327+
reduced))
328+
nil
329+
vars)))
330+
(catch Throwable e
331+
(when (System/getProperty "cider.internal.testing")
332+
;; print stacktrace, in case it didn't have anything to do with fixtures
333+
;; (in which case, things would become very confusing)
334+
(.printStackTrace e))
335+
(report-fixture-error ns e))))))
323336

324337
(defn test-ns
325338
"If the namespace object defines a function named `test-ns-hook`, call that.
326339
Otherwise, test the specified vars. On completion, return a map of test
327340
results."
328-
[ns vars]
329-
(binding [test/report report]
330-
(test/do-report {:type :begin-test-ns, :ns ns})
331-
(let [time-info (atom nil)]
332-
(timing time-info
333-
(if-let [test-hook (ns-resolve ns 'test-ns-hook)]
334-
(test-hook)
335-
(test-vars ns vars)))
336-
(test/do-report {:type :end-test-ns
337-
:ns ns
338-
:ns-elapsed-time @time-info})
339-
@current-report)))
341+
([ns vars]
342+
(test-ns ns vars false))
343+
344+
([ns vars fail-fast?]
345+
(binding [test/report report]
346+
(test/do-report {:type :begin-test-ns, :ns ns})
347+
(let [time-info (atom nil)]
348+
(timing time-info
349+
(if-let [test-hook (ns-resolve ns 'test-ns-hook)]
350+
(test-hook)
351+
(test-vars ns vars fail-fast?)))
352+
(test/do-report {:type :end-test-ns
353+
:ns ns
354+
:ns-elapsed-time @time-info})
355+
@current-report))))
340356

341357
(defn test-var-query
342358
"Call `test-ns` for each var found via var-query."
343-
[var-query]
344-
(report-reset!)
345-
(let [elapsed-time (atom nil)
346-
corpus (group-by
347-
(comp :ns meta)
348-
(query/vars var-query))]
349-
(timing elapsed-time
350-
(doseq [[ns vars] corpus]
351-
(test-ns ns vars)))
352-
(assoc @current-report :elapsed-time @elapsed-time)))
359+
([var-query]
360+
(test-var-query var-query false))
361+
362+
([var-query fail-fast?]
363+
(report-reset!)
364+
(let [elapsed-time (atom nil)
365+
corpus (group-by
366+
(comp :ns meta)
367+
(query/vars var-query))]
368+
(timing elapsed-time
369+
(reduce (fn [_ [ns vars]]
370+
(cond-> (test-ns ns vars fail-fast?)
371+
(and fail-fast? (current-test-run-failed?))
372+
reduced))
373+
nil
374+
corpus))
375+
(assoc @current-report :elapsed-time @elapsed-time))))
353376

354377
(defn test-nss
355378
"Call `test-ns` for each entry in map `m`, in which keys are namespace
356379
symbols and values are var symbols to be tested in that namespace (or `nil`
357380
to test all vars). Symbols are first resolved to their corresponding
358381
objects."
359-
[m]
360-
(report-reset!)
361-
(let [elapsed-time (atom nil)
362-
corpus (mapv (fn [[ns vars]]
363-
[(the-ns ns)
364-
(keep (partial ns-resolve ns) vars)])
365-
m)]
366-
(timing elapsed-time
367-
(doseq [[ns vars] corpus]
368-
(test-ns ns vars)))
369-
(assoc @current-report :elapsed-time @elapsed-time)))
382+
([m]
383+
(test-nss m false))
384+
385+
([m fail-fast?]
386+
(report-reset!)
387+
(let [elapsed-time (atom nil)
388+
corpus (mapv (fn [[ns vars]]
389+
[(the-ns ns)
390+
(keep (partial ns-resolve ns) vars)])
391+
m)]
392+
(timing elapsed-time
393+
(reduce (fn [_ [ns vars]]
394+
(cond-> (test-ns ns vars fail-fast?)
395+
(and fail-fast? (current-test-run-failed?))
396+
reduced))
397+
nil
398+
corpus))
399+
(assoc @current-report :elapsed-time @elapsed-time))))
370400

371401
;;; ## Middleware
372402

@@ -378,8 +408,9 @@
378408
(atom {}))
379409

380410
(defn handle-test-var-query-op
381-
[{:keys [var-query transport session id] :as msg}]
382-
(let [{:keys [exec]} (meta session)]
411+
[{:keys [fail-fast var-query transport session id] :as msg}]
412+
(let [fail-fast? (#{true "true"} fail-fast)
413+
{:keys [exec]} (meta session)]
383414
(exec id
384415
(fn []
385416
(with-bindings (assoc @session #'ie/*msg* msg)
@@ -394,7 +425,7 @@
394425
(assoc-in [:ns-query :has-tests?] true)
395426
(assoc :test? true)
396427
(util.coerce/var-query)
397-
test-var-query
428+
(test-var-query fail-fast?)
398429
stringify-msg)]
399430
(reset! results (:results report))
400431
(t/send transport (response-for msg (util/transform-value report))))
@@ -424,7 +455,7 @@
424455
:exclude-meta-key exclude}})))
425456

426457
(defn handle-retest-op
427-
[{:keys [transport session id] :as msg}]
458+
[{:keys [transport session id fail-fast] :as msg}]
428459
(let [{:keys [exec]} (meta session)]
429460
(exec id
430461
(fn []
@@ -433,9 +464,11 @@
433464
(let [problems (filter (comp #{:fail :error} :type)
434465
(mapcat val tests))
435466
vars (distinct (map :var problems))]
436-
(if (seq vars) (assoc ret ns vars) ret)))
467+
(if (seq vars)
468+
(assoc ret ns vars)
469+
ret)))
437470
{} @results)
438-
report (test-nss nss)]
471+
report (test-nss nss (#{true "true"} fail-fast))]
439472
(reset! results (:results report))
440473
(t/send transport (response-for msg (util/transform-value report))))))
441474
(fn []

test/clj/cider/nrepl/middleware/test_test.clj

+24
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,30 @@ The `988` value reflects that it times things correctly for a slow test.")
203203
(is (-> test-result :results :failing-test-ns :fast-failing-test (get 0) :elapsed-time :humanized string?)
204204
"Timing also works for the `retest` op (var level)")))
205205

206+
(deftest fail-fast-test
207+
(require 'failing-test-ns)
208+
(let [test-result (session/message {:op "test-var-query"
209+
:var-query {:ns-query {:exactly ["failing-test-ns"]}}
210+
:fail-fast "true"})]
211+
(is (= 1
212+
(count (:failing-test-ns (:results test-result))))))
213+
214+
(let [test-result (session/message {:op "test-var-query"
215+
:var-query {:ns-query {:exactly ["failing-test-ns"]}}
216+
:fail-fast "false"})]
217+
(is (= 2
218+
(count (:failing-test-ns (:results test-result))))))
219+
220+
(let [test-result (session/message {:op "retest"
221+
:fail-fast "false"})]
222+
(is (= 2
223+
(count (:failing-test-ns (:results test-result))))))
224+
225+
(let [test-result (session/message {:op "retest"
226+
:fail-fast "true"})]
227+
(is (= 1
228+
(count (:failing-test-ns (:results test-result)))))))
229+
206230
(deftest print-object-test
207231
(testing "uses println for matcher-combinators results, otherwise invokes pprint"
208232
(is (= "{no quotes}\n"

0 commit comments

Comments
 (0)