]> git.lizzy.rs Git - rust.git/blob - library/test/src/formatters/junit.rs
Merge commit '8d14c94b5c0a66241b4244f1c60ac5859cec1d97' into clippyup
[rust.git] / library / test / src / formatters / junit.rs
1 use std::io::{self, prelude::Write};
2 use std::time::Duration;
3
4 use super::OutputFormatter;
5 use crate::{
6     console::{ConsoleTestState, OutputLocation},
7     test_result::TestResult,
8     time,
9     types::{TestDesc, TestType},
10 };
11
12 pub struct JunitFormatter<T> {
13     out: OutputLocation<T>,
14     results: Vec<(TestDesc, TestResult, Duration)>,
15 }
16
17 impl<T: Write> JunitFormatter<T> {
18     pub fn new(out: OutputLocation<T>) -> Self {
19         Self { out, results: Vec::new() }
20     }
21
22     fn write_message(&mut self, s: &str) -> io::Result<()> {
23         assert!(!s.contains('\n'));
24
25         self.out.write_all(s.as_ref())
26     }
27 }
28
29 impl<T: Write> OutputFormatter for JunitFormatter<T> {
30     fn write_run_start(
31         &mut self,
32         _test_count: usize,
33         _shuffle_seed: Option<u64>,
34     ) -> io::Result<()> {
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\"?>")
38     }
39
40     fn write_test_start(&mut self, _desc: &TestDesc) -> io::Result<()> {
41         // We do not output anything on test start.
42         Ok(())
43     }
44
45     fn write_timeout(&mut self, _desc: &TestDesc) -> io::Result<()> {
46         // We do not output anything on test timeout.
47         Ok(())
48     }
49
50     fn write_result(
51         &mut self,
52         desc: &TestDesc,
53         result: &TestResult,
54         exec_time: Option<&time::TestExecTime>,
55         _stdout: &[u8],
56         _state: &ConsoleTestState,
57     ) -> io::Result<()> {
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));
63         Ok(())
64     }
65     fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
66         self.write_message("<testsuites>")?;
67
68         self.write_message(&*format!(
69             "<testsuite name=\"test\" package=\"test\" id=\"0\" \
70              errors=\"0\" \
71              failures=\"{}\" \
72              tests=\"{}\" \
73              skipped=\"{}\" \
74              >",
75             state.failed, state.total, state.ignored
76         ))?;
77         for (desc, result, duration) in std::mem::replace(&mut self.results, Vec::new()) {
78             let (class_name, test_name) = parse_class_name(&desc);
79             match result {
80                 TestResult::TrIgnored => { /* no-op */ }
81                 TestResult::TrFailed => {
82                     self.write_message(&*format!(
83                         "<testcase classname=\"{}\" \
84                          name=\"{}\" time=\"{}\">",
85                         class_name,
86                         test_name,
87                         duration.as_secs_f64()
88                     ))?;
89                     self.write_message("<failure type=\"assert\"/>")?;
90                     self.write_message("</testcase>")?;
91                 }
92
93                 TestResult::TrFailedMsg(ref m) => {
94                     self.write_message(&*format!(
95                         "<testcase classname=\"{}\" \
96                          name=\"{}\" time=\"{}\">",
97                         class_name,
98                         test_name,
99                         duration.as_secs_f64()
100                     ))?;
101                     self.write_message(&*format!("<failure message=\"{}\" type=\"assert\"/>", m))?;
102                     self.write_message("</testcase>")?;
103                 }
104
105                 TestResult::TrTimedFail => {
106                     self.write_message(&*format!(
107                         "<testcase classname=\"{}\" \
108                          name=\"{}\" time=\"{}\">",
109                         class_name,
110                         test_name,
111                         duration.as_secs_f64()
112                     ))?;
113                     self.write_message("<failure type=\"timeout\"/>")?;
114                     self.write_message("</testcase>")?;
115                 }
116
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
122                     ))?;
123                 }
124
125                 TestResult::TrOk | TestResult::TrAllowedFail => {
126                     self.write_message(&*format!(
127                         "<testcase classname=\"{}\" \
128                          name=\"{}\" time=\"{}\"/>",
129                         class_name,
130                         test_name,
131                         duration.as_secs_f64()
132                     ))?;
133                 }
134             }
135         }
136         self.write_message("<system-out/>")?;
137         self.write_message("<system-err/>")?;
138         self.write_message("</testsuite>")?;
139         self.write_message("</testsuites>")?;
140
141         self.out.write_all(b"\n\n")?;
142
143         Ok(state.failed == 0)
144     }
145 }
146
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())),
153     }
154 }
155
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!(),
164     };
165     (class_name, test_name)
166 }
167
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!(),
175     };
176     (class_name, test_name)
177 }
178
179 fn parse_class_name_integration(desc: &TestDesc) -> (String, String) {
180     (String::from("integration"), String::from(desc.name.as_slice()))
181 }