use std::fs::File;
use termcolor::{Ansi, Color, ColorChoice, ColorSpec, NoColor, StandardStream, WriteColor};
use ::{Arguments, ColorSetting, Conclusion, FormatSetting, Outcome, Test};
pub(crate) struct Printer {
out: Box<dyn WriteColor>,
format: FormatSetting,
name_width: usize,
kind_width: usize,
}
impl Printer {
pub(crate) fn new<D>(args: &Arguments, tests: &[Test<D>]) -> 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.name.chars().count())
.max()
.unwrap_or(0);
let kind_width = tests.iter()
.map(|test| {
if test.kind.is_empty() {
0
} else {
test.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();
}
FormatSetting::Json => unimplemented!(),
}
}
pub(crate) fn print_test(&mut self, name: &str, kind: &str) {
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 => {
}
FormatSetting::Json => unimplemented!(),
}
}
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();
}
FormatSetting::Json => unimplemented!(),
}
}
pub(crate) fn print_summary(
&mut self,
conclusion: &Conclusion,
) {
match self.format {
FormatSetting::Pretty | FormatSetting::Terse => {
let outcome = if conclusion.has_failed() {
Outcome::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",
conclusion.num_passed(),
conclusion.num_failed(),
conclusion.num_ignored(),
conclusion.num_benches(),
conclusion.num_filtered_out(),
).unwrap();
writeln!(self.out).unwrap();
}
FormatSetting::Json => unimplemented!(),
}
}
pub(crate) fn print_list<D>(&mut self, tests: &[Test<D>]) {
for test in tests {
let kind = if test.kind.is_empty() {
format!("")
} else {
format!("[{}] ", test.kind)
};
writeln!(
self.out,
"{}{}: {}",
kind,
test.name,
if test.is_bench { "bench" } else { "test" },
).unwrap();
}
}
pub(crate) fn print_failures<D>(&mut self, fails: &[(Test<D>, Option<String>)]) {
writeln!(self.out).unwrap();
writeln!(self.out, "failures:").unwrap();
writeln!(self.out).unwrap();
for (test, msg) in fails {
writeln!(self.out, "---- {} ----", test.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, _) in fails {
writeln!(self.out, " {}", test.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 { 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
}