Skip to content
This repository was archived by the owner on Aug 12, 2021. It is now read-only.

Added very basic JUnit output #16

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion libtest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ crate-type = ["dylib", "rlib"]

[dependencies]
getopts = "0.2"
term = "0.5"
term = "0.5"
chrono = "0.4"
123 changes: 123 additions & 0 deletions libtest/formatters/junit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use super::*;
use ::chrono::prelude::*;
use chrono::SecondsFormat;

pub(crate) struct JUnitFormatter<T> {
out: OutputLocation<T>,
results: Vec<(TestDesc, TestResult)>,
}

impl<T: Write> JUnitFormatter<T> {
pub fn new(out: OutputLocation<T>) -> 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<T: Write> OutputFormatter for JUnitFormatter<T> {
fn write_run_start(&mut self, _test_count: usize) -> io::Result<()> {
self.write_message(&"<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
}

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<bool> {
self.write_message("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
self.write_message("<testsuites>")?;

// 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!(
"<testsuite name=\"test\" package=\"test\" id=\"0\" \
hostname=\"localhost\" \
errors=\"0\" \
failures=\"{}\" \
tests=\"{}\" \
time=\"{}\" \
timestamp=\"{}\">",
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!(
"<testcase classname=\"test.global\" \
name=\"{}\" time=\"0\">",
desc.name.as_slice()
))?;
self.write_message("<failure type=\"assert\"/>")?;
self.write_message("</testcase>")?;
}

TestResult::TrFailedMsg(ref m) => {
self.write_message(&*format!(
"<testcase classname=\"test.global\" \
name=\"{}\" time=\"0\">",
desc.name.as_slice()
))?;
self.write_message(&*format!(
"<failure message=\"{}\" type=\"assert\"/>",
m
))?;
self.write_message("</testcase>")?;
}

TestResult::TrBench(ref b) => {
self.write_message(&*format!(
"<testcase classname=\"test.global\" \
name=\"{}\" time=\"{}\" />",
desc.name.as_slice(),
b.ns_iter_summ.sum
))?;
}

_ => {
self.write_message(&*format!(
"<testcase classname=\"test.global\" \
name=\"{}\" time=\"0\"/>",
desc.name.as_slice()
))?;
}
}
}
self.write_message("<system-out/>")?;
self.write_message("<system-err/>")?;
self.write_message("</testsuite>")?;
self.write_message("</testsuites>")?;

Ok(state.failed == 0)
}
}
2 changes: 2 additions & 0 deletions libtest/formatters/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
29 changes: 23 additions & 6 deletions libtest/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -327,6 +331,7 @@ pub enum OutputFormat {
Pretty,
Terse,
Json,
JUnit,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -622,10 +628,18 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
}
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
)));
Expand Down Expand Up @@ -704,6 +718,7 @@ struct ConsoleTestState {
failures: Vec<(TestDesc, Vec<u8>)>,
not_failures: Vec<(TestDesc, Vec<u8>)>,
options: Options,
start_time: Instant,
}

impl ConsoleTestState {
Expand All @@ -726,6 +741,7 @@ impl ConsoleTestState {
failures: Vec::new(),
not_failures: Vec::new(),
options: opts.options,
start_time: Instant::now(),
})
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down