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 {
pub(crate) fn new(args: &Arguments, tests: &[Trial]) -> Self {
let color_arg = args.color.unwrap_or(ColorSetting::Auto);
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))
};
let format = if args.quiet {
FormatSetting::Terse
} else {
args.format.unwrap_or(FormatSetting::Pretty)
};
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 {
test.info.kind.chars().count() + 3
}
})
.max()
.unwrap_or(0);
Self {
out,
format,
name_width,
kind_width,
}
}
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();
}
}
}
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 => {
}
}
}
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 { .. } => {
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();
}
}
}
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();
}
}
}
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 {
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(())
}
pub(crate) fn print_failures(&mut self, fails: &[(TestInfo, Option<String>)]) {
writeln!(self.out).unwrap();
writeln!(self.out, "failures:").unwrap();
writeln!(self.out).unwrap();
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();
}
writeln!(self.out).unwrap();
writeln!(self.out, "failures:").unwrap();
for (test_info, _) in fails {
writeln!(self.out, " {}", test_info.name).unwrap();
}
}
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();
}
}
}
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
}
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
}