]> git.lizzy.rs Git - rust.git/blob - library/test/src/formatters/junit.rs
Rollup merge of #103570 - lukas-code:stabilize-ilog, r=scottmcm
[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.write_message("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
37     }
38
39     fn write_test_start(&mut self, _desc: &TestDesc) -> io::Result<()> {
40         // We do not output anything on test start.
41         Ok(())
42     }
43
44     fn write_timeout(&mut self, _desc: &TestDesc) -> io::Result<()> {
45         // We do not output anything on test timeout.
46         Ok(())
47     }
48
49     fn write_result(
50         &mut self,
51         desc: &TestDesc,
52         result: &TestResult,
53         exec_time: Option<&time::TestExecTime>,
54         _stdout: &[u8],
55         _state: &ConsoleTestState,
56     ) -> io::Result<()> {
57         // Because the testsuite node holds some of the information as attributes, we can't write it
58         // until all of the tests have finished. Instead of writing every result as they come in, we add
59         // them to a Vec and write them all at once when run is complete.
60         let duration = exec_time.map(|t| t.0).unwrap_or_default();
61         self.results.push((desc.clone(), result.clone(), duration));
62         Ok(())
63     }
64     fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
65         self.write_message("<testsuites>")?;
66
67         self.write_message(&*format!(
68             "<testsuite name=\"test\" package=\"test\" id=\"0\" \
69              errors=\"0\" \
70              failures=\"{}\" \
71              tests=\"{}\" \
72              skipped=\"{}\" \
73              >",
74             state.failed, state.total, state.ignored
75         ))?;
76         for (desc, result, duration) in std::mem::replace(&mut self.results, Vec::new()) {
77             let (class_name, test_name) = parse_class_name(&desc);
78             match result {
79                 TestResult::TrIgnored => { /* no-op */ }
80                 TestResult::TrFailed => {
81                     self.write_message(&*format!(
82                         "<testcase classname=\"{}\" \
83                          name=\"{}\" time=\"{}\">",
84                         class_name,
85                         test_name,
86                         duration.as_secs_f64()
87                     ))?;
88                     self.write_message("<failure type=\"assert\"/>")?;
89                     self.write_message("</testcase>")?;
90                 }
91
92                 TestResult::TrFailedMsg(ref m) => {
93                     self.write_message(&*format!(
94                         "<testcase classname=\"{}\" \
95                          name=\"{}\" time=\"{}\">",
96                         class_name,
97                         test_name,
98                         duration.as_secs_f64()
99                     ))?;
100                     self.write_message(&*format!("<failure message=\"{m}\" type=\"assert\"/>"))?;
101                     self.write_message("</testcase>")?;
102                 }
103
104                 TestResult::TrTimedFail => {
105                     self.write_message(&*format!(
106                         "<testcase classname=\"{}\" \
107                          name=\"{}\" time=\"{}\">",
108                         class_name,
109                         test_name,
110                         duration.as_secs_f64()
111                     ))?;
112                     self.write_message("<failure type=\"timeout\"/>")?;
113                     self.write_message("</testcase>")?;
114                 }
115
116                 TestResult::TrBench(ref b) => {
117                     self.write_message(&*format!(
118                         "<testcase classname=\"benchmark::{}\" \
119                          name=\"{}\" time=\"{}\" />",
120                         class_name, test_name, b.ns_iter_summ.sum
121                     ))?;
122                 }
123
124                 TestResult::TrOk => {
125                     self.write_message(&*format!(
126                         "<testcase classname=\"{}\" \
127                          name=\"{}\" time=\"{}\"/>",
128                         class_name,
129                         test_name,
130                         duration.as_secs_f64()
131                     ))?;
132                 }
133             }
134         }
135         self.write_message("<system-out/>")?;
136         self.write_message("<system-err/>")?;
137         self.write_message("</testsuite>")?;
138         self.write_message("</testsuites>")?;
139
140         self.out.write_all(b"\n")?;
141
142         Ok(state.failed == 0)
143     }
144 }
145
146 fn parse_class_name(desc: &TestDesc) -> (String, String) {
147     match desc.test_type {
148         TestType::UnitTest => parse_class_name_unit(desc),
149         TestType::DocTest => parse_class_name_doc(desc),
150         TestType::IntegrationTest => parse_class_name_integration(desc),
151         TestType::Unknown => (String::from("unknown"), String::from(desc.name.as_slice())),
152     }
153 }
154
155 fn parse_class_name_unit(desc: &TestDesc) -> (String, String) {
156     // Module path => classname
157     // Function name => name
158     let module_segments: Vec<&str> = desc.name.as_slice().split("::").collect();
159     let (class_name, test_name) = match module_segments[..] {
160         [test] => (String::from("crate"), String::from(test)),
161         [ref path @ .., test] => (path.join("::"), String::from(test)),
162         [..] => unreachable!(),
163     };
164     (class_name, test_name)
165 }
166
167 fn parse_class_name_doc(desc: &TestDesc) -> (String, String) {
168     // File path => classname
169     // Line # => test name
170     let segments: Vec<&str> = desc.name.as_slice().split(" - ").collect();
171     let (class_name, test_name) = match segments[..] {
172         [file, line] => (String::from(file.trim()), String::from(line.trim())),
173         [..] => unreachable!(),
174     };
175     (class_name, test_name)
176 }
177
178 fn parse_class_name_integration(desc: &TestDesc) -> (String, String) {
179     (String::from("integration"), String::from(desc.name.as_slice()))
180 }