]> git.proxmox.com Git - rustc.git/blob - library/test/src/formatters/junit.rs
New upstream version 1.58.1+dfsg1
[rustc.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 }