Commits

Paudi Moriarty committed 0b35559

Add support for JUnit output to clojureTest

* Added junit boolean field to ClojureTestTask to enable/disable additional junit output (disabled by default)
* Added junitOutputDir field to ClojureTestTask (defaults to "build/test-results") (only used if junit = true)
* Added test-junit namespace to tasks directory
** Defines test-namespaces task which enables additional JUnit reporting of test results
** XML escapes everything sent to *out* to prevent invalid XML result files
** Collects results for each namespace tested for detailed reporting of failures to System/out

  • Participants
  • Parent commits 3d3e06f

Comments (0)

Files changed (2)

File clojuresque-runtime/src/main/resources/clojuresque/tasks/test_junit.clj

+(ns clojuresque.tasks.test-junit
+  (:use
+    [clojure.test :only (run-tests report successful? *test-out*) :as t]
+    [clojure.test.junit :only (junit-report with-junit-output) :as j]
+    [clojuresque.cli :only (deftask)]
+    [clojuresque.util :only (namespaces)]))
+
+
+(def escape-xml-map
+  (zipmap "'<>\"&" (map #(str \& % \;) '[apos lt gt quot amp])))
+
+(defn- escape-xml [text]
+  (apply str (map #(escape-xml-map % %) text)))
+
+(defn xml-escaping-writer
+  [writer]
+  (proxy
+    [java.io.FilterWriter] [writer]
+    (write [text]
+      (if (string? text)
+        (.write writer (escape-xml text))
+        (.write writer text)))))
+
+
+(defn add-counters [results counters]
+  (merge-with + results counters))
+
+(defn check-result
+  [result]
+  (when (or (pos? (:fail result)) (pos? (:error result)))
+    (System/exit 1)))
+
+(defn escape-file-path 
+  "Escapes the given file path so that it's safe for inclusion in a Clojure string literal."
+  [directory file]
+  (-> (java.io.File. directory file)
+    (.getPath)
+    (.replace "\\" "\\\\")))
+
+(defn test-namespace-with-junit-output
+  "Run all tests in the namespace with junit output.
+   Writes test output to a file called <namespace>.xml in <output-dir>
+   XML escapes *out* so that it's safe for inclusion in the JUnit XML report file." 
+  [namespace output-dir]
+  (with-open [writer (clojure.java.io/writer (str (escape-file-path output-dir (str namespace ".xml"))))
+              escaped (xml-escaping-writer writer)]
+    (binding [*test-out* writer *out* escaped]
+      (with-junit-output
+        (run-tests namespace)))))
+  
+(deftask test-namespaces
+  "Run all tests in the namespaces of the given files by virtue of clojure.test with additional junit output.
+   Writes test output to a file called <namespace>.xml in <output-dir>
+   XML escapes *out* so that it's safe for inclusion in the JUnit XML report file." 
+  [[output-dir o "Directory to wirite JUnit XML result files."]
+   files]
+  (let [namespaces (namespaces files)]
+    (apply require namespaces)
+    (let [results (atom {:type :summary})
+          current-ns (atom nil)
+          failed (atom [])
+          report-orig report
+          junit-report-orig junit-report]
+      ; Change junit-report so that it also prints to System/out and records summaries for each namespace tested 
+      (binding [junit-report (fn [x] 
+                               (junit-report-orig x)
+                               (when (or (= :begin-test-ns (:type x)) (= :summary (:type x)))
+                                 (binding [*test-out* (java.io.OutputStreamWriter. System/out)]
+                                   (report-orig x)))
+                               ; Record results for each namespace for later result checking and reporting
+                               (when (= :begin-test-ns (:type x))
+                                 (reset! current-ns (ns-name (:ns x))))
+                               (when (= :summary (:type x))
+                                 (when (or (pos? (:fail x)) (pos? (:error x)))
+                                   (swap! failed conj [@current-ns x]))
+                                 (swap! results add-counters (dissoc x :type))))]
+        ; test each namespace individually to allow per ns reporting of failures at the end
+        (doseq [namespace namespaces]
+          (test-namespace-with-junit-output namespace output-dir))
+        (shutdown-agents)
+        (when (:test @results)
+          (println "\nTotals:")
+          (report @results)
+          (println)
+          (if (successful? @results)
+            (do 
+              (println "Success!!!")
+              (System/exit 0))
+            (do
+              (println "\n!!! There were test failures:")
+              ; Print results for each namespace which was unsuccessful
+              (doseq [[ns summary] @failed]
+                (println ns ": " (:fail summary) "failures," (:error summary) "errors."))
+              (println)
+              (System/exit 1))))
+        (System/exit 0)))))
+

File clojuresque/src/main/groovy/clojuresque/ClojureTestTask.groovy

     def FileCollection classpath
     def SourceDirectorySet testRoots
     def Closure jvmOptions = {}
+    def boolean junit = false
+    def String junitOutputDir = "build/test-results" 
     def List<String> tests = []
 
     @InputFiles
                 this.classesDir,
                 this.classpath
             )
-            if (tests.size() == 0) {
-                main = "clojuresque.tasks.test/test-namespaces"
-                args = this.source.files
+            if (junit) {
+                main = "clojuresque.tasks.test-junit/test-namespaces"
+                args = ["-o", junitOutputDir] + this.source.files
             } else {
-                main = "clojuresque.tasks.test/test-vars"
-                args = tests
+                if (tests.size() == 0) {
+                    main = "clojuresque.tasks.test/test-namespaces"
+                    args = this.source.files
+                } else {
+                    main = "clojuresque.tasks.test/test-vars"
+                    args = tests
+                }
             }
         }
     }