0 | module Evince.Reporter.JUnit
10 | import Evince.Reporter
12 | escape : String -> String
13 | escape = concatMap escChar . unpack
15 | escChar : Char -> String
16 | escChar '&' = "&"
17 | escChar '<' = "<"
18 | escChar '>' = ">"
19 | escChar '"' = """
20 | escChar '\'' = "'"
21 | escChar c = singleton c
23 | firstLine : String -> String
24 | firstLine s = case lines s of
28 | splitLast : List String -> (List String, String)
29 | splitLast [] = ([], "")
30 | splitLast [x] = ([], x)
31 | splitLast (x :: xs) = let (pre, l) = splitLast xs in (x :: pre, l)
33 | classname : List String -> String
34 | classname path = concat (intersperse "." (fst (splitLast path)))
36 | testName : List String -> String
37 | testName path = snd (splitLast path)
39 | locAttrs : Maybe SrcLoc -> String
40 | locAttrs Nothing = ""
41 | locAttrs (Just loc) = " file=\"" ++ escape loc.file ++ "\" line=\"" ++ show (loc.line + 1) ++ "\""
43 | renderTestCase : TestReport -> String
44 | renderTestCase report =
45 | let cn = escape (classname report.path)
46 | name = escape (testName report.path)
47 | loc = locAttrs report.loc
48 | in case report.outcome of
50 | " <testcase name=\"" ++ name ++ "\" classname=\"" ++ cn
51 | ++ "\"" ++ loc ++ " time=\"" ++ nanosToSeconds elapsed ++ "\"/>\n"
52 | Failed info elapsed =>
53 | let (msg, detail) = case failureDiff info of
54 | Just (reason, diffs) =>
55 | (reason, reason ++ "\n" ++ unlines (map renderLineDiffPlain diffs))
56 | Nothing => (firstLine (show info), show info)
57 | in " <testcase name=\"" ++ name ++ "\" classname=\"" ++ cn
58 | ++ "\"" ++ loc ++ " time=\"" ++ nanosToSeconds elapsed ++ "\">\n"
59 | ++ " <failure message=\"" ++ escape msg ++ "\">"
60 | ++ escape detail ++ "</failure>\n"
63 | " <testcase name=\"" ++ name ++ "\" classname=\"" ++ cn ++ "\"" ++ loc ++ ">\n"
65 | ++ maybe "/>\n" (\r => " message=\"" ++ escape r ++ "\"/>\n") reason
68 | countFailures : List TestReport -> Nat
69 | countFailures = foldl (\acc, r => case r.outcome of Failed _ _ => S acc;
_ => acc) 0
71 | countSkipped : List TestReport -> Nat
72 | countSkipped = foldl (\acc, r => case r.outcome of Skipped _ => S acc;
_ => acc) 0
74 | totalTime : List TestReport -> Integer
75 | totalTime = foldl (\acc, r => case r.outcome of
76 | Passed e => acc + e;
Failed _ e => acc + e;
Skipped _ => acc) 0
80 | renderXml : List TestReport -> String
82 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
84 | ++ " <testsuite name=\"evince\" tests=\"" ++ show (length reports)
85 | ++ "\" failures=\"" ++ show (countFailures reports)
87 | ++ "\" skipped=\"" ++ show (countSkipped reports)
88 | ++ "\" time=\"" ++ nanosToSeconds (totalTime reports) ++ "\">\n"
89 | ++ concatMap renderTestCase reports
90 | ++ " </testsuite>\n"
91 | ++ "</testsuites>\n"
93 | writeJUnitXml : String -> List TestReport -> IO ()
94 | writeJUnitXml filepath reports = do
95 | Right () <- writeFile filepath (renderXml reports)
96 | | Left err => ignore $
fPutStrLn stderr ("Error writing JUnit XML: " ++ show err)
102 | junitReporter : HasIO m => String -> m (Reporter m)
103 | junitReporter filepath = do
104 | ref <- newIORef {a = SnocList TestReport} [<]
105 | pure $
MkReporter $
\e => liftIO $
case e of
106 | TestDone report _ => modifyIORef ref (:< report)
108 | reports <- readIORef ref
109 | writeJUnitXml filepath (reports <>> [])