From 457084ad8c76d1c6d96d9ec3bff5e81203a7f44b Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Thu, 25 Apr 2019 11:52:41 +0300 Subject: [PATCH] Added very basic JUnit output --- libtest/Cargo.toml | 3 +- libtest/formatters/junit.rs | 123 ++++++++++++++++++++++++++++++++++++ libtest/formatters/mod.rs | 2 + libtest/lib.rs | 29 +++++++-- 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 libtest/formatters/junit.rs diff --git a/libtest/Cargo.toml b/libtest/Cargo.toml index c1ce6b4..cab9631 100644 --- a/libtest/Cargo.toml +++ b/libtest/Cargo.toml @@ -17,4 +17,5 @@ crate-type = ["dylib", "rlib"] [dependencies] getopts = "0.2" -term = "0.5" \ No newline at end of file +term = "0.5" +chrono = "0.4" \ No newline at end of file diff --git a/libtest/formatters/junit.rs b/libtest/formatters/junit.rs new file mode 100644 index 0000000..057f779 --- /dev/null +++ b/libtest/formatters/junit.rs @@ -0,0 +1,123 @@ +use super::*; +use ::chrono::prelude::*; +use chrono::SecondsFormat; + +pub(crate) struct JUnitFormatter { + out: OutputLocation, + results: Vec<(TestDesc, TestResult)>, +} + +impl JUnitFormatter { + pub fn new(out: OutputLocation) -> Self { + Self { + out, + results: Vec::new(), + } + } + + fn write_message(&mut self, s: &str) -> io::Result<()> { + assert!(!s.contains('\n')); + + self.out.write_all(s.as_ref())?; + self.out.write_all(b"\n") + } +} + +impl OutputFormatter for JUnitFormatter { + fn write_run_start(&mut self, _test_count: usize) -> io::Result<()> { + self.write_message(&"") + } + + fn write_test_start(&mut self, _desc: &TestDesc) -> io::Result<()> { + // We do not output anything on test start. + Ok(()) + } + + fn write_timeout(&mut self, _desc: &TestDesc) -> io::Result<()> { + Ok(()) + } + + fn write_result( + &mut self, + desc: &TestDesc, + result: &TestResult, + _stdout: &[u8], + ) -> io::Result<()> { + self.results.push((desc.clone(), result.clone())); + Ok(()) + } + + fn write_run_finish( + &mut self, + state: &ConsoleTestState, + ) -> io::Result { + self.write_message("")?; + self.write_message("")?; + + // JUnit expects time in the ISO8601, which was proposed in RFC 3339. + let timestamp = + Local::now().to_rfc3339_opts(SecondsFormat::Secs, false); + let elapsed_time = + state.start_time.elapsed().as_millis() as f32 / 1000.0; + self.write_message(&*format!( + "", + state.failed, state.total, elapsed_time, timestamp + ))?; + for (desc, result) in std::mem::replace(&mut self.results, Vec::new()) + { + match result { + TestResult::TrFailed => { + self.write_message(&*format!( + "", + desc.name.as_slice() + ))?; + self.write_message("")?; + self.write_message("")?; + } + + TestResult::TrFailedMsg(ref m) => { + self.write_message(&*format!( + "", + desc.name.as_slice() + ))?; + self.write_message(&*format!( + "", + m + ))?; + self.write_message("")?; + } + + TestResult::TrBench(ref b) => { + self.write_message(&*format!( + "", + desc.name.as_slice(), + b.ns_iter_summ.sum + ))?; + } + + _ => { + self.write_message(&*format!( + "", + desc.name.as_slice() + ))?; + } + } + } + self.write_message("")?; + self.write_message("")?; + self.write_message("")?; + self.write_message("")?; + + Ok(state.failed == 0) + } +} diff --git a/libtest/formatters/mod.rs b/libtest/formatters/mod.rs index a5582a3..9622810 100644 --- a/libtest/formatters/mod.rs +++ b/libtest/formatters/mod.rs @@ -1,10 +1,12 @@ use super::*; mod json; +mod junit; mod pretty; mod terse; pub(crate) use self::json::JsonFormatter; +pub(crate) use self::junit::JUnitFormatter; pub(crate) use self::pretty::PrettyFormatter; pub(crate) use self::terse::TerseFormatter; diff --git a/libtest/lib.rs b/libtest/lib.rs index 1617baf..b3f28cc 100644 --- a/libtest/lib.rs +++ b/libtest/lib.rs @@ -1,5 +1,8 @@ //! Rust's built-in unit-test and micro-benchmarking framework. -#![cfg_attr(any(unix, target_os = "cloudabi", target_os = "fuchsia"), feature(libc, rustc_private))] +#![cfg_attr( + any(unix, target_os = "cloudabi", target_os = "fuchsia"), + feature(libc, rustc_private) +)] #![feature(fnbox)] #![feature(set_stdio)] #![feature(panic_unwind)] @@ -56,7 +59,8 @@ mod formatters; pub mod stats; use crate::formatters::{ - JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter, + JUnitFormatter, JsonFormatter, OutputFormatter, PrettyFormatter, + TerseFormatter, }; /// Whether to execute tests concurrently or not @@ -327,6 +331,7 @@ pub enum OutputFormat { Pretty, Terse, Json, + JUnit, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -441,8 +446,9 @@ fn optgroups() -> getopts::Options { "Configure formatting of output: pretty = Print verbose output; terse = Display one character per test; - json = Output a json document", - "pretty|terse|json", + json = Output a json document; + junit = Output a JUnit document", + "pretty|terse|json|junit", ) .optopt( "Z", @@ -622,10 +628,18 @@ pub fn parse_opts(args: &[String]) -> Option { } OutputFormat::Json } + Some("junit") => { + if !allow_unstable { + return Some(Err( + "The \"junit\" format is only accepted on the nightly compiler".into(), + )); + } + OutputFormat::JUnit + } Some(v) => { return Some(Err(format!( - "argument for --format must be pretty, terse, or json (was \ + "argument for --format must be pretty, terse, json, or junit (was \ {})", v ))); @@ -704,6 +718,7 @@ struct ConsoleTestState { failures: Vec<(TestDesc, Vec)>, not_failures: Vec<(TestDesc, Vec)>, options: Options, + start_time: Instant, } impl ConsoleTestState { @@ -726,6 +741,7 @@ impl ConsoleTestState { failures: Vec::new(), not_failures: Vec::new(), options: opts.options, + start_time: Instant::now(), }) } @@ -962,9 +978,9 @@ pub fn run_tests_console( is_multithreaded, )), OutputFormat::Json => Box::new(JsonFormatter::new(output)), + OutputFormat::JUnit => Box::new(JUnitFormatter::new(output)), }; let mut st = ConsoleTestState::new(opts)?; - run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?; assert!(st.current_test_count() == st.total); @@ -1008,6 +1024,7 @@ fn should_sort_failures_before_printing_them() { failures: vec![(test_b, Vec::new()), (test_a, Vec::new())], options: Options::new(), not_failures: Vec::new(), + start_time: Instant::now(), }; out.write_failures(&st).unwrap();