Skip to content

Commit

Permalink
Catch unhandled exceptions in fixtures
Browse files Browse the repository at this point in the history
  • Loading branch information
tonsky committed Apr 30, 2018
1 parent 4f637d3 commit 916b593
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 35 deletions.
2 changes: 1 addition & 1 deletion eftest/src/eftest/output_capture.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
(def ^:dynamic *test-buffer* nil)

(defn read-test-buffer []
(String. (.toByteArray *test-buffer*)))
(some-> *test-buffer* (.toByteArray) (String.)))

(def active-buffers (atom #{}))

Expand Down
5 changes: 5 additions & 0 deletions eftest/src/eftest/report.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
additional statistics and information during the tests."
nil)

(def ^:dynamic *testing-path*
"2-element vector [ns scope] where scope is either :clojure.test/once-fixtures,
:clojure.test/each-fixtures or var under test"
nil)

(defn report-to-file
"Wrap a report function so that its output is directed to a file. output-file
should be something that can be coerced into a Writer."
Expand Down
51 changes: 30 additions & 21 deletions eftest/src/eftest/report/pretty.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[puget.printer :as puget]
[fipp.engine :as fipp]
[eftest.output-capture :as capture]
[eftest.report :as report]
[clojure.string :as str]))

(def ^:dynamic *fonts*
Expand All @@ -30,12 +31,19 @@
"The divider to use between test failure and error reports."
"\n")

(defn- testing-vars-str [{:keys [file line]}]
(let [test-var (first test/*testing-vars*)]
(str (:clojure-frame *fonts*) (-> test-var meta :ns ns-name) "/"
(:function-name *fonts*) (-> test-var meta :name) (:reset *fonts*)
(when (or file line)
(str " (" (:source *fonts*) file ":" line (:reset *fonts*) ")")))))
(defn- testing-scope-str [{:keys [file line]}]
(let [[ns scope] report/*testing-path*]
(str
(cond
(keyword? scope)
(str (:clojure-frame *fonts*) (ns-name ns) (:reset *fonts*) " during "
(:function-name *fonts*) scope (:reset *fonts*))

(var? scope)
(str (:clojure-frame *fonts*) (ns-name ns) "/"
(:function-name *fonts*) (:name (meta scope)) (:reset *fonts*)))
(when (or file line)
(str " (" (:source *fonts*) file ":" line (:reset *fonts*) ")")))))

(defn- diff-all [expected actuals]
(map vector actuals (map #(take 2 (data/diff expected %)) actuals)))
Expand Down Expand Up @@ -71,18 +79,19 @@
[:span "expected: " (puget/format-doc p expected) :break]
[:span " actual: " (puget/format-doc p actual)]])))

(defn- print-stacktrace [t]
(binding [exception/*traditional* true
exception/*fonts* *fonts*]
(repl/pretty-print-stack-trace t test/*stack-trace-depth*)))

(defn- error-report [{:keys [expected actual]}]
(let [p (pretty-printer)]
(pprint-document
[:group
[:span "expected: " (puget/format-doc p expected) :break]
[:span " actual: "
(if (instance? Throwable actual)
(binding [exception/*traditional* true
exception/*fonts* *fonts*]
(with-out-str
(repl/pretty-print-stack-trace actual test/*stack-trace-depth*)))
(puget/format-doc p actual))]])))
(if expected
(let [p (pretty-printer)]
(pprint-document
[:group
[:span "expected: " (puget/format-doc p expected) :break]
[:span " actual: " (with-out-str (print-stacktrace actual))]]))
(print-stacktrace actual)))

(defn- print-output [output]
(let [c (:divider *fonts*)
Expand All @@ -106,7 +115,7 @@
(test/with-test-out
(test/inc-report-counter :fail)
(print *divider*)
(println (str (:fail *fonts*) "FAIL" (:reset *fonts*) " in") (testing-vars-str m))
(println (str (:fail *fonts*) "FAIL" (:reset *fonts*) " in") (testing-scope-str m))
(when (seq test/*testing-contexts*) (println (test/testing-contexts-str)))
(when message (println message))
(if (and (sequential? expected)
Expand All @@ -119,11 +128,11 @@
(test/with-test-out
(test/inc-report-counter :error)
(print *divider*)
(println (str (:error *fonts*) "ERROR" (:reset *fonts*) " in") (testing-vars-str m))
(println (str (:error *fonts*) "ERROR" (:reset *fonts*) " in") (testing-scope-str m))
(when (seq test/*testing-contexts*) (println (test/testing-contexts-str)))
(when message (println message))
(error-report m)
(print-output (capture/read-test-buffer))))
(some-> (capture/read-test-buffer) (print-output))))

(defn- pluralize [word count]
(if (= count 1) word (str word "s")))
Expand All @@ -134,7 +143,7 @@
(defmethod report :long-test [{:keys [duration] :as m}]
(test/with-test-out
(print *divider*)
(println (str (:fail *fonts*) "LONG TEST" (:reset *fonts*) " in") (testing-vars-str m))
(println (str (:fail *fonts*) "LONG TEST" (:reset *fonts*) " in") (testing-scope-str m))
(when duration (println "Test took" (format-interval duration) "seconds to run"))))

(defmethod report :summary [{:keys [test pass fail error duration]}]
Expand Down
41 changes: 28 additions & 13 deletions eftest/src/eftest/runner.clj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
(defn- multithread-namespaces? [{:keys [multithread?] :or {multithread? true}}]
(or (true? multithread?) (= multithread? :namespaces)))

(defn- fixture-exception [throwable]
{:type :error
:message "Uncaught exception during fixture initialization."
:actual throwable})

(defn- test-vars
[ns vars report
{:as opts :keys [fail-fast? capture-output? test-warn-time]
Expand All @@ -75,20 +80,30 @@
each-fixtures (-> ns meta ::test/each-fixtures test/join-fixtures)
test-var (-> (fn [v]
(when-not (and fail-fast? (failed-test?))
(each-fixtures
(if capture-output?
#(binding [test/report report]
(capture/with-test-buffer
(test/test-var v)))
#(binding [test/report report]
(test/test-var v))))))
(binding [report/*testing-path* [ns ::test/each-fixtures]]
(try
(each-fixtures
(if capture-output?
#(binding [test/report report
report/*testing-path* [ns v]]
(capture/with-test-buffer
(test/test-var v)))
#(binding [test/report report
report/*testing-path* [ns v]]
(test/test-var v))))
(catch Throwable t
(test/do-report (fixture-exception t)))))))
(wrap-test-with-timer test-warn-time))]
(once-fixtures
(fn []
(if (multithread-vars? opts)
(do (->> vars (filter synchronized?) (map test-var) (dorun))
(->> vars (remove synchronized?) (pmap* test-var) (dorun)))
(doseq [v vars] (test-var v)))))))
(binding [report/*testing-path* [ns ::test/once-fixtures]]
(try
(once-fixtures
(fn []
(if (multithread-vars? opts)
(do (->> vars (filter synchronized?) (map test-var) (dorun))
(->> vars (remove synchronized?) (pmap* test-var) (dorun)))
(doseq [v vars] (test-var v)))))
(catch Throwable t
(test/do-report (fixture-exception t)))))))

(defn- test-ns [ns vars report opts]
(let [ns (the-ns ns)]
Expand Down

0 comments on commit 916b593

Please sign in to comment.