1 use std
::io
::{self, prelude::Write}
;
2 use std
::time
::Duration
;
4 use super::OutputFormatter
;
6 console
::{ConsoleTestState, OutputLocation}
,
7 test_result
::TestResult
,
9 types
::{TestDesc, TestType}
,
12 pub struct JunitFormatter
<T
> {
13 out
: OutputLocation
<T
>,
14 results
: Vec
<(TestDesc
, TestResult
, Duration
)>,
17 impl<T
: Write
> JunitFormatter
<T
> {
18 pub fn new(out
: OutputLocation
<T
>) -> Self {
19 Self { out, results: Vec::new() }
22 fn write_message(&mut self, s
: &str) -> io
::Result
<()> {
23 assert
!(!s
.contains('
\n'
));
25 self.out
.write_all(s
.as_ref())
29 impl<T
: Write
> OutputFormatter
for JunitFormatter
<T
> {
33 _shuffle_seed
: Option
<u64>,
35 // We write xml header on run start
36 self.out
.write_all(b
"\n")?
;
37 self.write_message("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
40 fn write_test_start(&mut self, _desc
: &TestDesc
) -> io
::Result
<()> {
41 // We do not output anything on test start.
45 fn write_timeout(&mut self, _desc
: &TestDesc
) -> io
::Result
<()> {
46 // We do not output anything on test timeout.
54 exec_time
: Option
<&time
::TestExecTime
>,
56 _state
: &ConsoleTestState
,
58 // Because the testsuite node holds some of the information as attributes, we can't write it
59 // until all of the tests have finished. Instead of writing every result as they come in, we add
60 // them to a Vec and write them all at once when run is complete.
61 let duration
= exec_time
.map(|t
| t
.0).unwrap_or_default();
62 self.results
.push((desc
.clone(), result
.clone(), duration
));
65 fn write_run_finish(&mut self, state
: &ConsoleTestState
) -> io
::Result
<bool
> {
66 self.write_message("<testsuites>")?
;
68 self.write_message(&*format
!(
69 "<testsuite name=\"test\" package=\"test\" id=\"0\" \
75 state
.failed
, state
.total
, state
.ignored
77 for (desc
, result
, duration
) in std
::mem
::replace(&mut self.results
, Vec
::new()) {
78 let (class_name
, test_name
) = parse_class_name(&desc
);
80 TestResult
::TrIgnored
=> { /* no-op */ }
81 TestResult
::TrFailed
=> {
82 self.write_message(&*format
!(
83 "<testcase classname=\"{}\" \
84 name=\"{}\" time=\"{}\">",
87 duration
.as_secs_f64()
89 self.write_message("<failure type=\"assert\"/>")?
;
90 self.write_message("</testcase>")?
;
93 TestResult
::TrFailedMsg(ref m
) => {
94 self.write_message(&*format
!(
95 "<testcase classname=\"{}\" \
96 name=\"{}\" time=\"{}\">",
99 duration
.as_secs_f64()
101 self.write_message(&*format
!("<failure message=\"{}\" type=\"assert\"/>", m
))?
;
102 self.write_message("</testcase>")?
;
105 TestResult
::TrTimedFail
=> {
106 self.write_message(&*format
!(
107 "<testcase classname=\"{}\" \
108 name=\"{}\" time=\"{}\">",
111 duration
.as_secs_f64()
113 self.write_message("<failure type=\"timeout\"/>")?
;
114 self.write_message("</testcase>")?
;
117 TestResult
::TrBench(ref b
) => {
118 self.write_message(&*format
!(
119 "<testcase classname=\"benchmark::{}\" \
120 name=\"{}\" time=\"{}\" />",
121 class_name
, test_name
, b
.ns_iter_summ
.sum
125 TestResult
::TrOk
| TestResult
::TrAllowedFail
=> {
126 self.write_message(&*format
!(
127 "<testcase classname=\"{}\" \
128 name=\"{}\" time=\"{}\"/>",
131 duration
.as_secs_f64()
136 self.write_message("<system-out/>")?
;
137 self.write_message("<system-err/>")?
;
138 self.write_message("</testsuite>")?
;
139 self.write_message("</testsuites>")?
;
141 self.out
.write_all(b
"\n\n")?
;
143 Ok(state
.failed
== 0)
147 fn parse_class_name(desc
: &TestDesc
) -> (String
, String
) {
148 match desc
.test_type
{
149 TestType
::UnitTest
=> parse_class_name_unit(desc
),
150 TestType
::DocTest
=> parse_class_name_doc(desc
),
151 TestType
::IntegrationTest
=> parse_class_name_integration(desc
),
152 TestType
::Unknown
=> (String
::from("unknown"), String
::from(desc
.name
.as_slice())),
156 fn parse_class_name_unit(desc
: &TestDesc
) -> (String
, String
) {
157 // Module path => classname
158 // Function name => name
159 let module_segments
: Vec
<&str> = desc
.name
.as_slice().split("::").collect();
160 let (class_name
, test_name
) = match module_segments
[..] {
161 [test
] => (String
::from("crate"), String
::from(test
)),
162 [ref path @
.., test
] => (path
.join("::"), String
::from(test
)),
163 [..] => unreachable
!(),
165 (class_name
, test_name
)
168 fn parse_class_name_doc(desc
: &TestDesc
) -> (String
, String
) {
169 // File path => classname
170 // Line # => test name
171 let segments
: Vec
<&str> = desc
.name
.as_slice().split(" - ").collect();
172 let (class_name
, test_name
) = match segments
[..] {
173 [file
, line
] => (String
::from(file
.trim()), String
::from(line
.trim())),
174 [..] => unreachable
!(),
176 (class_name
, test_name
)
179 fn parse_class_name_integration(desc
: &TestDesc
) -> (String
, String
) {
180 (String
::from("integration"), String
::from(desc
.name
.as_slice()))