blob: d0766f03c09f74dacd1afc72eff08d282d28f691 [file] [log] [blame] [edit]
//! Definition of the `Printer`.
//!
//! This is just an abstraction for everything that is printed to the screen
//! (or logfile, if specified). These parameters influence printing:
//! - `color`
//! - `format` (and `quiet`)
//! - `logfile`
use std::{fs::File, time::Duration};
use termcolor::{Ansi, Color, ColorChoice, ColorSpec, NoColor, StandardStream, WriteColor};
use crate::{
Arguments, ColorSetting, Conclusion, FormatSetting, Outcome, Trial, Failed,
Measurement, TestInfo,
};
pub(crate) struct Printer {
out: Box<dyn WriteColor>,
format: FormatSetting,
name_width: usize,
kind_width: usize,
}
impl Printer {
/// Creates a new printer configured by the given arguments (`format`,
/// `quiet`, `color` and `logfile` options).
pub(crate) fn new(args: &Arguments, tests: &[Trial]) -> Self {
let color_arg = args.color.unwrap_or(ColorSetting::Auto);
// Determine target of all output
let out = if let Some(logfile) = &args.logfile {
let f = File::create(logfile).expect("failed to create logfile");
if color_arg == ColorSetting::Always {
Box::new(Ansi::new(f)) as Box<dyn WriteColor>
} else {
Box::new(NoColor::new(f))
}
} else {
let choice = match color_arg {
ColorSetting::Auto => ColorChoice::Auto,
ColorSetting::Always => ColorChoice::Always,
ColorSetting::Never => ColorChoice::Never,
};
Box::new(StandardStream::stdout(choice))
};
// Determine correct format
let format = if args.quiet {
FormatSetting::Terse
} else {
args.format.unwrap_or(FormatSetting::Pretty)
};
// Determine max test name length to do nice formatting later.
//
// Unicode is hard and there is no way we can properly align/pad the
// test names and outcomes. Counting the number of code points is just
// a cheap way that works in most cases. Usually, these names are
// ASCII.
let name_width = tests.iter()
.map(|test| test.info.name.chars().count())
.max()
.unwrap_or(0);
let kind_width = tests.iter()
.map(|test| {
if test.info.kind.is_empty() {
0
} else {
// The two braces [] and one space
test.info.kind.chars().count() + 3
}
})
.max()
.unwrap_or(0);
Self {
out,
format,
name_width,
kind_width,
}
}
/// Prints the first line "running 3 tests".
pub(crate) fn print_title(&mut self, num_tests: u64) {
match self.format {
FormatSetting::Pretty | FormatSetting::Terse => {
let plural_s = if num_tests == 1 { "" } else { "s" };
writeln!(self.out).unwrap();
writeln!(self.out, "running {} test{}", num_tests, plural_s).unwrap();
}
}
}
/// Prints the text announcing the test (e.g. "test foo::bar ... "). Prints
/// nothing in terse mode.
pub(crate) fn print_test(&mut self, info: &TestInfo) {
let TestInfo { name, kind, .. } = info;
match self.format {
FormatSetting::Pretty => {
let kind = if kind.is_empty() {
format!("")
} else {
format!("[{}] ", kind)
};
write!(
self.out,
"test {: <2$}{: <3$} ... ",
kind,
name,
self.kind_width,
self.name_width,
).unwrap();
self.out.flush().unwrap();
}
FormatSetting::Terse => {
// In terse mode, nothing is printed before the job. Only
// `print_single_outcome` prints one character.
}
}
}
/// Prints the outcome of a single tests. `ok` or `FAILED` in pretty mode
/// and `.` or `F` in terse mode.
pub(crate) fn print_single_outcome(&mut self, outcome: &Outcome) {
match self.format {
FormatSetting::Pretty => {
self.print_outcome_pretty(outcome);
writeln!(self.out).unwrap();
}
FormatSetting::Terse => {
let c = match outcome {
Outcome::Passed => '.',
Outcome::Failed { .. } => 'F',
Outcome::Ignored => 'i',
Outcome::Measured { .. } => {
// Benchmark are never printed in terse mode... for
// some reason.
self.print_outcome_pretty(outcome);
writeln!(self.out).unwrap();
return;
}
};
self.out.set_color(&color_of_outcome(outcome)).unwrap();
write!(self.out, "{}", c).unwrap();
self.out.reset().unwrap();
}
}
}
/// Prints the summary line after all tests have been executed.
pub(crate) fn print_summary(&mut self, conclusion: &Conclusion, execution_time: Duration) {
match self.format {
FormatSetting::Pretty | FormatSetting::Terse => {
let outcome = if conclusion.has_failed() {
Outcome::Failed(Failed { msg: None })
} else {
Outcome::Passed
};
writeln!(self.out).unwrap();
write!(self.out, "test result: ").unwrap();
self.print_outcome_pretty(&outcome);
writeln!(
self.out,
". {} passed; {} failed; {} ignored; {} measured; \
{} filtered out; finished in {:.2}s",
conclusion.num_passed,
conclusion.num_failed,
conclusion.num_ignored,
conclusion.num_measured,
conclusion.num_filtered_out,
execution_time.as_secs_f64()
).unwrap();
writeln!(self.out).unwrap();
}
}
}
/// Prints a list of all tests. Used if `--list` is set.
pub(crate) fn print_list(&mut self, tests: &[Trial], ignored: bool) {
Self::write_list(tests, ignored, &mut self.out).unwrap();
}
pub(crate) fn write_list(
tests: &[Trial],
ignored: bool,
mut out: impl std::io::Write,
) -> std::io::Result<()> {
for test in tests {
// libtest prints out:
// * all tests without `--ignored`
// * just the ignored tests with `--ignored`
if ignored && !test.info.is_ignored {
continue;
}
let kind = if test.info.kind.is_empty() {
format!("")
} else {
format!("[{}] ", test.info.kind)
};
writeln!(
out,
"{}{}: {}",
kind,
test.info.name,
if test.info.is_bench { "bench" } else { "test" },
)?;
}
Ok(())
}
/// Prints a list of failed tests with their messages. This is only called
/// if there were any failures.
pub(crate) fn print_failures(&mut self, fails: &[(TestInfo, Option<String>)]) {
writeln!(self.out).unwrap();
writeln!(self.out, "failures:").unwrap();
writeln!(self.out).unwrap();
// Print messages of all tests
for (test_info, msg) in fails {
writeln!(self.out, "---- {} ----", test_info.name).unwrap();
if let Some(msg) = msg {
writeln!(self.out, "{}", msg).unwrap();
}
writeln!(self.out).unwrap();
}
// Print summary list of failed tests
writeln!(self.out).unwrap();
writeln!(self.out, "failures:").unwrap();
for (test_info, _) in fails {
writeln!(self.out, " {}", test_info.name).unwrap();
}
}
/// Prints a colored 'ok'/'FAILED'/'ignored'/'bench'.
fn print_outcome_pretty(&mut self, outcome: &Outcome) {
let s = match outcome {
Outcome::Passed => "ok",
Outcome::Failed { .. } => "FAILED",
Outcome::Ignored => "ignored",
Outcome::Measured { .. } => "bench",
};
self.out.set_color(&color_of_outcome(outcome)).unwrap();
write!(self.out, "{}", s).unwrap();
self.out.reset().unwrap();
if let Outcome::Measured(Measurement { avg, variance }) = outcome {
write!(
self.out,
": {:>11} ns/iter (+/- {})",
fmt_with_thousand_sep(*avg),
fmt_with_thousand_sep(*variance),
).unwrap();
}
}
}
/// Formats the given integer with `,` as thousand separator.
pub fn fmt_with_thousand_sep(mut v: u64) -> String {
let mut out = String::new();
while v >= 1000 {
out = format!(",{:03}{}", v % 1000, out);
v /= 1000;
}
out = format!("{}{}", v, out);
out
}
/// Returns the `ColorSpec` associated with the given outcome.
fn color_of_outcome(outcome: &Outcome) -> ColorSpec {
let mut out = ColorSpec::new();
let color = match outcome {
Outcome::Passed => Color::Green,
Outcome::Failed { .. } => Color::Red,
Outcome::Ignored => Color::Yellow,
Outcome::Measured { .. } => Color::Cyan,
};
out.set_fg(Some(color));
out
}