libtest: Add --report-time flag to print test execution time

This commit is contained in:
Jakob Schikowski
2019-09-21 19:03:14 +02:00
parent 5349e69ae2
commit c16a547012
6 changed files with 162 additions and 48 deletions

View File

@@ -27,6 +27,7 @@ impl<T: Write> JsonFormatter<T> {
ty: &str, ty: &str,
name: &str, name: &str,
evt: &str, evt: &str,
exec_time: Option<&TestExecTime>,
stdout: Option<Cow<'_, str>>, stdout: Option<Cow<'_, str>>,
extra: Option<&str>, extra: Option<&str>,
) -> io::Result<()> { ) -> io::Result<()> {
@@ -34,6 +35,12 @@ impl<T: Write> JsonFormatter<T> {
r#"{{ "type": "{}", "name": "{}", "event": "{}""#, r#"{{ "type": "{}", "name": "{}", "event": "{}""#,
ty, name, evt ty, name, evt
))?; ))?;
if let Some(exec_time) = exec_time {
self.write_message(&*format!(
r#", "exec_time": "{}""#,
exec_time
))?;
}
if let Some(stdout) = stdout { if let Some(stdout) = stdout {
self.write_message(&*format!( self.write_message(&*format!(
r#", "stdout": "{}""#, r#", "stdout": "{}""#,
@@ -69,6 +76,7 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
&mut self, &mut self,
desc: &TestDesc, desc: &TestDesc,
result: &TestResult, result: &TestResult,
exec_time: Option<&TestExecTime>,
stdout: &[u8], stdout: &[u8],
state: &ConsoleTestState, state: &ConsoleTestState,
) -> io::Result<()> { ) -> io::Result<()> {
@@ -78,24 +86,36 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
None None
}; };
match *result { match *result {
TrOk => self.write_event("test", desc.name.as_slice(), "ok", stdout, None), TrOk => {
self.write_event("test", desc.name.as_slice(), "ok", exec_time, stdout, None)
}
TrFailed => self.write_event("test", desc.name.as_slice(), "failed", stdout, None), TrFailed => {
self.write_event("test", desc.name.as_slice(), "failed", exec_time, stdout, None)
}
TrFailedMsg(ref m) => self.write_event( TrFailedMsg(ref m) => self.write_event(
"test", "test",
desc.name.as_slice(), desc.name.as_slice(),
"failed", "failed",
exec_time,
stdout, stdout,
Some(&*format!(r#""message": "{}""#, EscapedString(m))), Some(&*format!(r#""message": "{}""#, EscapedString(m))),
), ),
TrIgnored => self.write_event("test", desc.name.as_slice(), "ignored", stdout, None), TrIgnored => {
self.write_event("test", desc.name.as_slice(), "ignored", exec_time, stdout, None)
TrAllowedFail => {
self.write_event("test", desc.name.as_slice(), "allowed_failure", stdout, None)
} }
TrAllowedFail => self.write_event(
"test",
desc.name.as_slice(),
"allowed_failure",
exec_time,
stdout,
None,
),
TrBench(ref bs) => { TrBench(ref bs) => {
let median = bs.ns_iter_summ.median as usize; let median = bs.ns_iter_summ.median as usize;
let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize; let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize;

View File

@@ -16,6 +16,7 @@ pub(crate) trait OutputFormatter {
&mut self, &mut self,
desc: &TestDesc, desc: &TestDesc,
result: &TestResult, result: &TestResult,
exec_time: Option<&TestExecTime>,
stdout: &[u8], stdout: &[u8],
state: &ConsoleTestState, state: &ConsoleTestState,
) -> io::Result<()>; ) -> io::Result<()>;

View File

@@ -30,20 +30,20 @@ impl<T: Write> PrettyFormatter<T> {
&self.out &self.out
} }
pub fn write_ok(&mut self) -> io::Result<()> { pub fn write_ok(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
self.write_short_result("ok", term::color::GREEN) self.write_short_result("ok", term::color::GREEN, exec_time)
} }
pub fn write_failed(&mut self) -> io::Result<()> { pub fn write_failed(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
self.write_short_result("FAILED", term::color::RED) self.write_short_result("FAILED", term::color::RED, exec_time)
} }
pub fn write_ignored(&mut self) -> io::Result<()> { pub fn write_ignored(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
self.write_short_result("ignored", term::color::YELLOW) self.write_short_result("ignored", term::color::YELLOW, exec_time)
} }
pub fn write_allowed_fail(&mut self) -> io::Result<()> { pub fn write_allowed_fail(&mut self, exec_time: Option<&TestExecTime>) -> io::Result<()> {
self.write_short_result("FAILED (allowed)", term::color::YELLOW) self.write_short_result("FAILED (allowed)", term::color::YELLOW, exec_time)
} }
pub fn write_bench(&mut self) -> io::Result<()> { pub fn write_bench(&mut self) -> io::Result<()> {
@@ -54,8 +54,12 @@ impl<T: Write> PrettyFormatter<T> {
&mut self, &mut self,
result: &str, result: &str,
color: term::color::Color, color: term::color::Color,
exec_time: Option<&TestExecTime>,
) -> io::Result<()> { ) -> io::Result<()> {
self.write_pretty(result, color)?; self.write_pretty(result, color)?;
if let Some(exec_time) = exec_time {
self.write_plain(format!(" {}", exec_time))?;
}
self.write_plain("\n") self.write_plain("\n")
} }
@@ -166,6 +170,7 @@ impl<T: Write> OutputFormatter for PrettyFormatter<T> {
&mut self, &mut self,
desc: &TestDesc, desc: &TestDesc,
result: &TestResult, result: &TestResult,
exec_time: Option<&TestExecTime>,
_: &[u8], _: &[u8],
_: &ConsoleTestState, _: &ConsoleTestState,
) -> io::Result<()> { ) -> io::Result<()> {
@@ -174,10 +179,10 @@ impl<T: Write> OutputFormatter for PrettyFormatter<T> {
} }
match *result { match *result {
TrOk => self.write_ok(), TrOk => self.write_ok(exec_time),
TrFailed | TrFailedMsg(_) => self.write_failed(), TrFailed | TrFailedMsg(_) => self.write_failed(exec_time),
TrIgnored => self.write_ignored(), TrIgnored => self.write_ignored(exec_time),
TrAllowedFail => self.write_allowed_fail(), TrAllowedFail => self.write_allowed_fail(exec_time),
TrBench(ref bs) => { TrBench(ref bs) => {
self.write_bench()?; self.write_bench()?;
self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) self.write_plain(&format!(": {}\n", fmt_bench_samples(bs)))

View File

@@ -174,6 +174,7 @@ impl<T: Write> OutputFormatter for TerseFormatter<T> {
&mut self, &mut self,
desc: &TestDesc, desc: &TestDesc,
result: &TestResult, result: &TestResult,
_: Option<&TestExecTime>,
_: &[u8], _: &[u8],
_: &ConsoleTestState, _: &ConsoleTestState,
) -> io::Result<()> { ) -> io::Result<()> {

View File

@@ -378,6 +378,7 @@ pub struct TestOpts {
pub format: OutputFormat, pub format: OutputFormat,
pub test_threads: Option<usize>, pub test_threads: Option<usize>,
pub skip: Vec<String>, pub skip: Vec<String>,
pub report_time: bool,
pub options: Options, pub options: Options,
} }
@@ -460,6 +461,11 @@ fn optgroups() -> getopts::Options {
"Enable nightly-only flags: "Enable nightly-only flags:
unstable-options = Allow use of experimental features", unstable-options = Allow use of experimental features",
"unstable-options", "unstable-options",
)
.optflag(
"",
"report-time",
"Show execution time of each test. Not available for --format=terse"
); );
return opts; return opts;
} }
@@ -573,6 +579,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
let quiet = matches.opt_present("quiet"); let quiet = matches.opt_present("quiet");
let exact = matches.opt_present("exact"); let exact = matches.opt_present("exact");
let list = matches.opt_present("list"); let list = matches.opt_present("list");
let report_time = matches.opt_present("report-time");
let logfile = matches.opt_str("logfile"); let logfile = matches.opt_str("logfile");
let logfile = logfile.map(|s| PathBuf::from(&s)); let logfile = logfile.map(|s| PathBuf::from(&s));
@@ -653,6 +660,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
format, format,
test_threads, test_threads,
skip: matches.opt_strs("skip"), skip: matches.opt_strs("skip"),
report_time,
options: Options::new().display_output(matches.opt_present("show-output")), options: Options::new().display_output(matches.opt_present("show-output")),
}; };
@@ -677,6 +685,16 @@ pub enum TestResult {
unsafe impl Send for TestResult {} unsafe impl Send for TestResult {}
/// The meassured execution time of a unit test.
#[derive(Clone, PartialEq)]
pub struct TestExecTime(Duration);
impl fmt::Display for TestExecTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.3}s", self.0.as_secs_f64())
}
}
enum OutputLocation<T> { enum OutputLocation<T> {
Pretty(Box<term::StdoutTerminal>), Pretty(Box<term::StdoutTerminal>),
Raw(T), Raw(T),
@@ -736,17 +754,30 @@ impl ConsoleTestState {
}) })
} }
pub fn write_log<S: AsRef<str>>(&mut self, msg: S) -> io::Result<()> { pub fn write_log<F, S>(
let msg = msg.as_ref(); &mut self,
msg: F,
) -> io::Result<()>
where
S: AsRef<str>,
F: FnOnce() -> S,
{
match self.log_out { match self.log_out {
None => Ok(()), None => Ok(()),
Some(ref mut o) => o.write_all(msg.as_bytes()), Some(ref mut o) => {
let msg = msg();
let msg = msg.as_ref();
o.write_all(msg.as_bytes())
},
} }
} }
pub fn write_log_result(&mut self, test: &TestDesc, result: &TestResult) -> io::Result<()> { pub fn write_log_result(&mut self,test: &TestDesc,
self.write_log(format!( result: &TestResult,
"{} {}\n", exec_time: Option<&TestExecTime>,
) -> io::Result<()> {
self.write_log(|| format!(
"{} {}",
match *result { match *result {
TrOk => "ok".to_owned(), TrOk => "ok".to_owned(),
TrFailed => "failed".to_owned(), TrFailed => "failed".to_owned(),
@@ -755,8 +786,12 @@ impl ConsoleTestState {
TrAllowedFail => "failed (allowed)".to_owned(), TrAllowedFail => "failed (allowed)".to_owned(),
TrBench(ref bs) => fmt_bench_samples(bs), TrBench(ref bs) => fmt_bench_samples(bs),
}, },
test.name test.name,
)) ))?;
if let Some(exec_time) = exec_time {
self.write_log(|| format!(" {}", exec_time))?;
}
self.write_log(|| "\n")
} }
fn current_test_count(&self) -> usize { fn current_test_count(&self) -> usize {
@@ -843,7 +878,7 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Res
}; };
writeln!(output, "{}: {}", name, fntype)?; writeln!(output, "{}: {}", name, fntype)?;
st.write_log(format!("{} {}\n", fntype, name))?; st.write_log(|| format!("{} {}\n", fntype, name))?;
} }
fn plural(count: u32, s: &str) -> String { fn plural(count: u32, s: &str) -> String {
@@ -884,9 +919,9 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
TeFilteredOut(filtered_out) => Ok(st.filtered_out = filtered_out), TeFilteredOut(filtered_out) => Ok(st.filtered_out = filtered_out),
TeWait(ref test) => out.write_test_start(test), TeWait(ref test) => out.write_test_start(test),
TeTimeout(ref test) => out.write_timeout(test), TeTimeout(ref test) => out.write_timeout(test),
TeResult(test, result, stdout) => { TeResult(test, result, exec_time, stdout) => {
st.write_log_result(&test, &result)?; st.write_log_result(&test, &result, exec_time.as_ref())?;
out.write_result(&test, &result, &*stdout, &st)?; out.write_result(&test, &result, exec_time.as_ref(), &*stdout, &st)?;
match result { match result {
TrOk => { TrOk => {
st.passed += 1; st.passed += 1;
@@ -1004,12 +1039,12 @@ fn stdout_isatty() -> bool {
pub enum TestEvent { pub enum TestEvent {
TeFiltered(Vec<TestDesc>), TeFiltered(Vec<TestDesc>),
TeWait(TestDesc), TeWait(TestDesc),
TeResult(TestDesc, TestResult, Vec<u8>), TeResult(TestDesc, TestResult, Option<TestExecTime>, Vec<u8>),
TeTimeout(TestDesc), TeTimeout(TestDesc),
TeFilteredOut(usize), TeFilteredOut(usize),
} }
pub type MonitorMsg = (TestDesc, TestResult, Vec<u8>); pub type MonitorMsg = (TestDesc, TestResult, Option<TestExecTime>, Vec<u8>);
struct Sink(Arc<Mutex<Vec<u8>>>); struct Sink(Arc<Mutex<Vec<u8>>>);
impl Write for Sink { impl Write for Sink {
@@ -1105,8 +1140,8 @@ where
let test = remaining.pop().unwrap(); let test = remaining.pop().unwrap();
callback(TeWait(test.desc.clone()))?; callback(TeWait(test.desc.clone()))?;
run_test(opts, !opts.run_tests, test, tx.clone(), Concurrent::No); run_test(opts, !opts.run_tests, test, tx.clone(), Concurrent::No);
let (test, result, stdout) = rx.recv().unwrap(); let (test, result, exec_time, stdout) = rx.recv().unwrap();
callback(TeResult(test, result, stdout))?; callback(TeResult(test, result, exec_time, stdout))?;
} }
} else { } else {
while pending > 0 || !remaining.is_empty() { while pending > 0 || !remaining.is_empty() {
@@ -1135,10 +1170,10 @@ where
} }
} }
let (desc, result, stdout) = res.unwrap(); let (desc, result, exec_time, stdout) = res.unwrap();
running_tests.remove(&desc); running_tests.remove(&desc);
callback(TeResult(desc, result, stdout))?; callback(TeResult(desc, result, exec_time, stdout))?;
pending -= 1; pending -= 1;
} }
} }
@@ -1148,8 +1183,8 @@ where
for b in filtered_benchs { for b in filtered_benchs {
callback(TeWait(b.desc.clone()))?; callback(TeWait(b.desc.clone()))?;
run_test(opts, false, b, tx.clone(), Concurrent::No); run_test(opts, false, b, tx.clone(), Concurrent::No);
let (test, result, stdout) = rx.recv().unwrap(); let (test, result, exec_time, stdout) = rx.recv().unwrap();
callback(TeResult(test, result, stdout))?; callback(TeResult(test, result, exec_time, stdout))?;
} }
} }
Ok(()) Ok(())
@@ -1384,7 +1419,7 @@ pub fn run_test(
&& desc.should_panic != ShouldPanic::No; && desc.should_panic != ShouldPanic::No;
if force_ignore || desc.ignore || ignore_because_panic_abort { if force_ignore || desc.ignore || ignore_because_panic_abort {
monitor_ch.send((desc, TrIgnored, Vec::new())).unwrap(); monitor_ch.send((desc, TrIgnored, None, Vec::new())).unwrap();
return; return;
} }
@@ -1392,6 +1427,7 @@ pub fn run_test(
desc: TestDesc, desc: TestDesc,
monitor_ch: Sender<MonitorMsg>, monitor_ch: Sender<MonitorMsg>,
nocapture: bool, nocapture: bool,
report_time: bool,
testfn: Box<dyn FnOnce() + Send>, testfn: Box<dyn FnOnce() + Send>,
concurrency: Concurrent, concurrency: Concurrent,
) { ) {
@@ -1410,7 +1446,16 @@ pub fn run_test(
None None
}; };
let start = if report_time {
Some(Instant::now())
} else {
None
};
let result = catch_unwind(AssertUnwindSafe(testfn)); let result = catch_unwind(AssertUnwindSafe(testfn));
let exec_time = start.map(|start| {
let duration = start.elapsed();
TestExecTime(duration)
});
if let Some((printio, panicio)) = oldio { if let Some((printio, panicio)) = oldio {
io::set_print(printio); io::set_print(printio);
@@ -1420,7 +1465,7 @@ pub fn run_test(
let test_result = calc_result(&desc, result); let test_result = calc_result(&desc, result);
let stdout = data.lock().unwrap().to_vec(); let stdout = data.lock().unwrap().to_vec();
monitor_ch monitor_ch
.send((desc.clone(), test_result, stdout)) .send((desc.clone(), test_result, exec_time, stdout))
.unwrap(); .unwrap();
}; };
@@ -1449,12 +1494,20 @@ pub fn run_test(
} }
DynTestFn(f) => { DynTestFn(f) => {
let cb = move || __rust_begin_short_backtrace(f); let cb = move || __rust_begin_short_backtrace(f);
run_test_inner(desc, monitor_ch, opts.nocapture, Box::new(cb), concurrency) run_test_inner(
desc,
monitor_ch,
opts.nocapture,
opts.report_time,
Box::new(cb),
concurrency,
)
} }
StaticTestFn(f) => run_test_inner( StaticTestFn(f) => run_test_inner(
desc, desc,
monitor_ch, monitor_ch,
opts.nocapture, opts.nocapture,
opts.report_time,
Box::new(move || __rust_begin_short_backtrace(f)), Box::new(move || __rust_begin_short_backtrace(f)),
concurrency, concurrency,
), ),
@@ -1702,7 +1755,7 @@ pub mod bench {
}; };
let stdout = data.lock().unwrap().to_vec(); let stdout = data.lock().unwrap().to_vec();
monitor_ch.send((desc, test_result, stdout)).unwrap(); monitor_ch.send((desc, test_result, None, stdout)).unwrap();
} }
pub fn run_once<F>(f: F) pub fn run_once<F>(f: F)

View File

@@ -23,6 +23,7 @@ impl TestOpts {
format: OutputFormat::Pretty, format: OutputFormat::Pretty,
test_threads: None, test_threads: None,
skip: vec![], skip: vec![],
report_time: false,
options: Options::new(), options: Options::new(),
} }
} }
@@ -67,7 +68,7 @@ pub fn do_not_run_ignored_tests() {
}; };
let (tx, rx) = channel(); let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
let (_, res, _) = rx.recv().unwrap(); let (_, res, _, _) = rx.recv().unwrap();
assert!(res != TrOk); assert!(res != TrOk);
} }
@@ -85,7 +86,7 @@ pub fn ignored_tests_result_in_ignored() {
}; };
let (tx, rx) = channel(); let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
let (_, res, _) = rx.recv().unwrap(); let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrIgnored); assert!(res == TrIgnored);
} }
@@ -105,7 +106,7 @@ fn test_should_panic() {
}; };
let (tx, rx) = channel(); let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
let (_, res, _) = rx.recv().unwrap(); let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrOk); assert!(res == TrOk);
} }
@@ -125,7 +126,7 @@ fn test_should_panic_good_message() {
}; };
let (tx, rx) = channel(); let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
let (_, res, _) = rx.recv().unwrap(); let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrOk); assert!(res == TrOk);
} }
@@ -147,7 +148,7 @@ fn test_should_panic_bad_message() {
}; };
let (tx, rx) = channel(); let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
let (_, res, _) = rx.recv().unwrap(); let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrFailedMsg(format!("{} '{}'", failed_msg, expected))); assert!(res == TrFailedMsg(format!("{} '{}'", failed_msg, expected)));
} }
@@ -165,10 +166,43 @@ fn test_should_panic_but_succeeds() {
}; };
let (tx, rx) = channel(); let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
let (_, res, _) = rx.recv().unwrap(); let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrFailed); assert!(res == TrFailed);
} }
fn report_time_test_template(report_time: bool) -> Option<TestExecTime> {
fn f() {}
let desc = TestDescAndFn {
desc: TestDesc {
name: StaticTestName("whatever"),
ignore: false,
should_panic: ShouldPanic::No,
allow_fail: false,
},
testfn: DynTestFn(Box::new(f)),
};
let test_opts = TestOpts {
report_time,
..TestOpts::new()
};
let (tx, rx) = channel();
run_test(&test_opts, false, desc, tx, Concurrent::No);
let (_, _, exec_time, _) = rx.recv().unwrap();
exec_time
}
#[test]
fn test_should_not_report_time() {
let exec_time = report_time_test_template(false);
assert!(exec_time.is_none());
}
#[test]
fn test_should_report_time() {
let exec_time = report_time_test_template(true);
assert!(exec_time.is_some());
}
#[test] #[test]
fn parse_ignored_flag() { fn parse_ignored_flag() {
let args = vec![ let args = vec![