diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:50 +0000 |
commit | 9835e2ae736235810b4ea1c162ca5e65c547e770 (patch) | |
tree | 3fcebf40ed70e581d776a8a4c65923e8ec20e026 /library/test/src/formatters | |
parent | Releasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff) | |
download | rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/test/src/formatters')
-rw-r--r-- | library/test/src/formatters/json.rs | 146 | ||||
-rw-r--r-- | library/test/src/formatters/junit.rs | 40 |
2 files changed, 96 insertions, 90 deletions
diff --git a/library/test/src/formatters/json.rs b/library/test/src/formatters/json.rs index 40976ec5e..47c4e7757 100644 --- a/library/test/src/formatters/json.rs +++ b/library/test/src/formatters/json.rs @@ -18,14 +18,10 @@ impl<T: Write> JsonFormatter<T> { } fn writeln_message(&mut self, s: &str) -> io::Result<()> { - assert!(!s.contains('\n')); - - self.out.write_all(s.as_ref())?; - self.out.write_all(b"\n") - } - - fn write_message(&mut self, s: &str) -> io::Result<()> { - assert!(!s.contains('\n')); + // self.out will take a lock, but that lock is released when write_all returns. This + // results in a race condition and json output may not end with a new line. We avoid this + // by issuing `write_all` calls line-by-line. + assert_eq!(s.chars().last(), Some('\n')); self.out.write_all(s.as_ref()) } @@ -34,34 +30,35 @@ impl<T: Write> JsonFormatter<T> { &mut self, ty: &str, name: &str, - evt: &str, + event: &str, exec_time: Option<&time::TestExecTime>, stdout: Option<Cow<'_, str>>, extra: Option<&str>, ) -> io::Result<()> { // A doc test's name includes a filename which must be escaped for correct json. - self.write_message(&format!( - r#"{{ "type": "{}", "name": "{}", "event": "{}""#, - ty, - EscapedString(name), - evt - ))?; - if let Some(exec_time) = exec_time { - self.write_message(&format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64()))?; - } - if let Some(stdout) = stdout { - self.write_message(&format!(r#", "stdout": "{}""#, EscapedString(stdout)))?; - } - if let Some(extra) = extra { - self.write_message(&format!(r#", {extra}"#))?; - } - self.writeln_message(" }") + let name = EscapedString(name); + let exec_time_json = if let Some(exec_time) = exec_time { + format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64()) + } else { + String::from("") + }; + let stdout_json = if let Some(stdout) = stdout { + format!(r#", "stdout": "{}""#, EscapedString(stdout)) + } else { + String::from("") + }; + let extra_json = + if let Some(extra) = extra { format!(r#", {extra}"#) } else { String::from("") }; + let newline = "\n"; + + self.writeln_message(&format!( + r#"{{ "type": "{ty}", "name": "{name}", "event": "{event}"{exec_time_json}{stdout_json}{extra_json} }}{newline}"#)) } } impl<T: Write> OutputFormatter for JsonFormatter<T> { fn write_discovery_start(&mut self) -> io::Result<()> { - self.writeln_message(&format!(r#"{{ "type": "suite", "event": "discovery" }}"#)) + self.writeln_message(concat!(r#"{ "type": "suite", "event": "discovery" }"#, "\n")) } fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> { @@ -69,35 +66,21 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> { name, ignore, ignore_message, - #[cfg(not(bootstrap))] source_file, - #[cfg(not(bootstrap))] start_line, - #[cfg(not(bootstrap))] start_col, - #[cfg(not(bootstrap))] end_line, - #[cfg(not(bootstrap))] end_col, .. } = desc; - #[cfg(bootstrap)] - let source_file = ""; - #[cfg(bootstrap)] - let start_line = 0; - #[cfg(bootstrap)] - let start_col = 0; - #[cfg(bootstrap)] - let end_line = 0; - #[cfg(bootstrap)] - let end_col = 0; + let name = EscapedString(name.as_slice()); + let ignore_message = ignore_message.unwrap_or(""); + let source_path = EscapedString(source_file); + let newline = "\n"; self.writeln_message(&format!( - r#"{{ "type": "{test_type}", "event": "discovered", "name": "{}", "ignore": {ignore}, "ignore_message": "{}", "source_path": "{}", "start_line": {start_line}, "start_col": {start_col}, "end_line": {end_line}, "end_col": {end_col} }}"#, - EscapedString(name.as_slice()), - ignore_message.unwrap_or(""), - EscapedString(source_file), + r#"{{ "type": "{test_type}", "event": "discovered", "name": "{name}", "ignore": {ignore}, "ignore_message": "{ignore_message}", "source_path": "{source_path}", "start_line": {start_line}, "start_col": {start_col}, "end_line": {end_line}, "end_col": {end_col} }}{newline}"# )) } @@ -105,9 +88,10 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> { let ConsoleTestDiscoveryState { tests, benchmarks, ignored, .. } = state; let total = tests + benchmarks; + let newline = "\n"; self.writeln_message(&format!( - r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}"# - )) + r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}{newline}"# + )) } fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> { @@ -116,15 +100,17 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> { } else { String::new() }; + let newline = "\n"; self.writeln_message(&format!( - r#"{{ "type": "suite", "event": "started", "test_count": {test_count}{shuffle_seed_json} }}"# - )) + r#"{{ "type": "suite", "event": "started", "test_count": {test_count}{shuffle_seed_json} }}{newline}"# + )) } fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { + let name = EscapedString(desc.name.as_slice()); + let newline = "\n"; self.writeln_message(&format!( - r#"{{ "type": "test", "event": "started", "name": "{}" }}"#, - EscapedString(desc.name.as_slice()) + r#"{{ "type": "test", "event": "started", "name": "{name}" }}{newline}"# )) } @@ -189,53 +175,43 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> { } else { format!(r#", "mib_per_second": {}"#, bs.mb_s) }; + let name = EscapedString(desc.name.as_slice()); - let line = format!( + self.writeln_message(&format!( "{{ \"type\": \"bench\", \ - \"name\": \"{}\", \ - \"median\": {}, \ - \"deviation\": {}{} }}", - EscapedString(desc.name.as_slice()), - median, - deviation, - mbps - ); - - self.writeln_message(&line) + \"name\": \"{name}\", \ + \"median\": {median}, \ + \"deviation\": {deviation}{mbps} }}\n", + )) } } } fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + let name = EscapedString(desc.name.as_slice()); + let newline = "\n"; self.writeln_message(&format!( - r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#, - EscapedString(desc.name.as_slice()) + r#"{{ "type": "test", "event": "timeout", "name": "{name}" }}{newline}"#, )) } fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> { - self.write_message(&format!( - "{{ \"type\": \"suite\", \ - \"event\": \"{}\", \ - \"passed\": {}, \ - \"failed\": {}, \ - \"ignored\": {}, \ - \"measured\": {}, \ - \"filtered_out\": {}", - if state.failed == 0 { "ok" } else { "failed" }, - state.passed, - state.failed, - state.ignored, - state.measured, - state.filtered_out, - ))?; - - if let Some(ref exec_time) = state.exec_time { - let time_str = format!(", \"exec_time\": {}", exec_time.0.as_secs_f64()); - self.write_message(&time_str)?; - } + let event = if state.failed == 0 { "ok" } else { "failed" }; + let passed = state.passed; + let failed = state.failed; + let ignored = state.ignored; + let measured = state.measured; + let filtered_out = state.filtered_out; + let exec_time_json = if let Some(ref exec_time) = state.exec_time { + format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64()) + } else { + String::from("") + }; + let newline = "\n"; - self.writeln_message(" }")?; + self.writeln_message(&format!( + r#"{{ "type": "suite", "event": "{event}", "passed": {passed}, "failed": {failed}, "ignored": {ignored}, "measured": {measured}, "filtered_out": {filtered_out}{exec_time_json} }}{newline}"# + ))?; Ok(state.failed == 0) } diff --git a/library/test/src/formatters/junit.rs b/library/test/src/formatters/junit.rs index 2e07ce3c0..9f5bf2436 100644 --- a/library/test/src/formatters/junit.rs +++ b/library/test/src/formatters/junit.rs @@ -11,7 +11,7 @@ use crate::{ pub struct JunitFormatter<T> { out: OutputLocation<T>, - results: Vec<(TestDesc, TestResult, Duration)>, + results: Vec<(TestDesc, TestResult, Duration, Vec<u8>)>, } impl<T: Write> JunitFormatter<T> { @@ -26,6 +26,18 @@ impl<T: Write> JunitFormatter<T> { } } +fn str_to_cdata(s: &str) -> String { + // Drop the stdout in a cdata. Unfortunately, you can't put either of `]]>` or + // `<?'` in a CDATA block, so the escaping gets a little weird. + let escaped_output = s.replace("]]>", "]]]]><![CDATA[>"); + let escaped_output = escaped_output.replace("<?", "<]]><![CDATA[?"); + // We also smuggle newlines as 
 so as to keep all the output on one line + let escaped_output = escaped_output.replace("\n", "]]>
<![CDATA["); + // Prune empty CDATA blocks resulting from any escaping + let escaped_output = escaped_output.replace("<![CDATA[]]>", ""); + format!("<![CDATA[{}]]>", escaped_output) +} + impl<T: Write> OutputFormatter for JunitFormatter<T> { fn write_discovery_start(&mut self) -> io::Result<()> { Err(io::Error::new(io::ErrorKind::NotFound, "Not yet implemented!")) @@ -63,14 +75,14 @@ impl<T: Write> OutputFormatter for JunitFormatter<T> { desc: &TestDesc, result: &TestResult, exec_time: Option<&time::TestExecTime>, - _stdout: &[u8], + stdout: &[u8], _state: &ConsoleTestState, ) -> io::Result<()> { // Because the testsuite node holds some of the information as attributes, we can't write it // until all of the tests have finished. Instead of writing every result as they come in, we add // them to a Vec and write them all at once when run is complete. let duration = exec_time.map(|t| t.0).unwrap_or_default(); - self.results.push((desc.clone(), result.clone(), duration)); + self.results.push((desc.clone(), result.clone(), duration, stdout.to_vec())); Ok(()) } fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> { @@ -85,7 +97,7 @@ impl<T: Write> OutputFormatter for JunitFormatter<T> { >", state.failed, state.total, state.ignored ))?; - for (desc, result, duration) in std::mem::take(&mut self.results) { + for (desc, result, duration, stdout) in std::mem::take(&mut self.results) { let (class_name, test_name) = parse_class_name(&desc); match result { TestResult::TrIgnored => { /* no-op */ } @@ -98,6 +110,11 @@ impl<T: Write> OutputFormatter for JunitFormatter<T> { duration.as_secs_f64() ))?; self.write_message("<failure type=\"assert\"/>")?; + if !stdout.is_empty() { + self.write_message("<system-out>")?; + self.write_message(&str_to_cdata(&String::from_utf8_lossy(&stdout)))?; + self.write_message("</system-out>")?; + } self.write_message("</testcase>")?; } @@ -110,6 +127,11 @@ impl<T: Write> OutputFormatter for JunitFormatter<T> { duration.as_secs_f64() ))?; self.write_message(&format!("<failure message=\"{m}\" type=\"assert\"/>"))?; + if !stdout.is_empty() { + self.write_message("<system-out>")?; + self.write_message(&str_to_cdata(&String::from_utf8_lossy(&stdout)))?; + self.write_message("</system-out>")?; + } self.write_message("</testcase>")?; } @@ -136,11 +158,19 @@ impl<T: Write> OutputFormatter for JunitFormatter<T> { TestResult::TrOk => { self.write_message(&format!( "<testcase classname=\"{}\" \ - name=\"{}\" time=\"{}\"/>", + name=\"{}\" time=\"{}\"", class_name, test_name, duration.as_secs_f64() ))?; + if stdout.is_empty() || !state.options.display_output { + self.write_message("/>")?; + } else { + self.write_message("><system-out>")?; + self.write_message(&str_to_cdata(&String::from_utf8_lossy(&stdout)))?; + self.write_message("</system-out>")?; + self.write_message("</testcase>")?; + } } } } |