Skip to content

Commit c36198f

Browse files
committed
Create clojure-test-junit-output
Make it easier to output your test results in JUnit format. Yes, there's `clojure.test.junit` but it doesn't do timing and it requires you to choose between the normal `clojure.test` output (read by humans) and JUnit XML which is not read by most people if they have anything to say about it. Usage Add this library to your dependencies. You can put it in your `:test` profile if you'd like. Then all you need to do is wrap your tests. You can doe this with a `:once` fixture like so: ```clojure ; Add this to your :require vector in your ns declaration. [clojure-test-junit-output.core :refer (with-junit-output)] (clojure.test/use-fixtures :once (with-junit-output "/where/you/want.xml")) ```
0 parents  commit c36198f

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/target
2+
/classes
3+
/checkouts
4+
pom.xml
5+
pom.xml.asc
6+
*.jar
7+
*.class
8+
/.lein-*
9+
/.nrepl-port
10+
.hgignore
11+
.hg/

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Copyright (c) 2016, Conor McDermottroe
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
2. Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
13+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# clojure-test-junit-output
2+
3+
Make it easier to output your test results in JUnit format. Yes, there's
4+
`clojure.test.junit` but it doesn't do timing and it requires you to choose
5+
between the normal `clojure.test` output (read by humans) and JUnit XML which
6+
is not read by most people if they have anything to say about it.
7+
8+
## Usage
9+
10+
Add this library to your dependencies. You can put it in your `:test` profile
11+
if you'd like.
12+
13+
Then all you need to do is wrap your tests. You can doe this with a `:once`
14+
fixture like so:
15+
16+
```clojure
17+
; Add this to your :require vector in your ns declaration.
18+
[clojure-test-junit-output.core :refer (with-junit-output)]
19+
20+
(clojure.test/use-fixtures :once (with-junit-output "/where/you/want.xml"))
21+
```
22+
23+
## License
24+
25+
This library is distributed under a two clause BSD-style license. See the
26+
`LICENSE` file for details.

project.clj

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(defproject clojure-test-junit-output "0.1.0-SNAPSHOT"
2+
:description "Make it easier to output your test results in JUnit format."
3+
:url "https://github.com/conormcd/clojure-test-junit-output"
4+
:license {:name "BSD"
5+
:url "https://github.com/conormcd/clojure-test-junit-output/blob/master/LICENSE"}
6+
:dependencies [[org.clojure/clojure "1.7.0"]
7+
[robert/hooke "1.3.0"]])
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
(ns clojure-test-junit-output.core
2+
(:require [clojure.stacktrace :refer (print-cause-trace)]
3+
[clojure.string :as str]
4+
[clojure.test]
5+
[clojure.xml :as xml]
6+
[robert.hooke]))
7+
8+
(def ^:private testcase-initial-state {:tag :testcase
9+
:attrs {:name ""
10+
:time 0}})
11+
12+
(def ^:private testsuite-initial-state {:tag :testsuite
13+
:attrs {:name ""
14+
:tests 0
15+
:failures 0
16+
:errors 0
17+
:time 0}})
18+
19+
(def ^:private current-testcase (atom testcase-initial-state))
20+
21+
(def ^:private current-testsuite (atom testsuite-initial-state))
22+
23+
(def ^:private testsuites (atom {:tag :testsuites}))
24+
25+
(def ^:private junit-file (atom nil))
26+
27+
(defn- xml-str
28+
"Escape some troublesome characters in strings destined for XML output."
29+
[string]
30+
(str/replace string
31+
#"[<>\"']"
32+
(fn [x] (str "&#" (str (int (first x))) ";"))))
33+
34+
(defn- elapsed-seconds
35+
"Given the value from a previous call to System/nanoTime, compute the number
36+
of seconds which have elapsed since then."
37+
[start-nanotime]
38+
(/ (- (System/nanoTime) start-nanotime)
39+
1000000000.0))
40+
41+
(defmulti do-report
42+
"A mirror of clojure.test/do-report but which gathers information to format
43+
as JUnit output (and does the output, in the :summary action)."
44+
:type)
45+
46+
(defmethod do-report :end-test-ns
47+
[data]
48+
(swap! testsuites
49+
update :content conj
50+
(-> @current-testsuite
51+
(assoc-in [:attrs :name] (-> data :ns ns-name xml-str))
52+
(assoc-in [:attrs :time] (->> @current-testsuite
53+
:content
54+
(map (comp :time :attrs))
55+
(reduce +)))))
56+
(reset! current-testsuite testsuite-initial-state))
57+
58+
(defmethod do-report :begin-test-var
59+
[data]
60+
(swap! current-testcase assoc-in [:attrs :time] (System/nanoTime)))
61+
62+
(defmethod do-report :end-test-var
63+
[data]
64+
(swap! current-testsuite
65+
update :content conj
66+
(-> @current-testcase
67+
(assoc-in [:attrs :name] (-> data :var meta :name xml-str))
68+
(update-in [:attrs :time] elapsed-seconds)))
69+
(reset! current-testcase testcase-initial-state))
70+
71+
(defmethod do-report :summary
72+
[data]
73+
(spit @junit-file (with-out-str (xml/emit @testsuites))))
74+
75+
(defmethod do-report :default
76+
[data]
77+
(when (contains? #{:pass :fail :error} (:type data))
78+
(swap! current-testsuite update-in [:attrs :tests] inc)
79+
(when (= :error (:type data))
80+
(swap! current-testsuite update-in [:attrs :errors] inc)
81+
(swap! current-testcase
82+
update :content conj
83+
{:tag :error
84+
:attrs {:type (-> data :actual type str)
85+
:message (-> data :actual .getMessage)}
86+
:content ["<![CDATA["
87+
(with-out-str (-> data :actual print-cause-trace))
88+
"]]>"]}))
89+
(when (= :fail (:type data))
90+
(swap! current-testsuite update-in [:attrs :failures] inc)
91+
(swap! current-testcase
92+
update :content conj
93+
{:tag :failure
94+
:attrs {:type "assertion failure"
95+
:message (format "expected: %s actual: %s"
96+
(-> data :expected xml-str)
97+
(-> data :actual xml-str))}}))))
98+
99+
(defn do-report-hook
100+
"This is the robert.hooke hook function that is used to tap the input to
101+
clojure.test/do-report."
102+
[f & args]
103+
(do-report (into {} args))
104+
(apply f args))
105+
106+
(defn with-junit-output
107+
"Return a clojure.test fixture function which taps clojure.test/do-report in
108+
order to provide JUnit XML output to a file without affecting the normal
109+
output of the test runner.."
110+
[output-file]
111+
(fn [f]
112+
(reset! junit-file output-file)
113+
(robert.hooke/add-hook #'clojure.test/do-report #'do-report-hook)
114+
(f)))

0 commit comments

Comments
 (0)