1 //! Definition of the `Printer`.
2 //!
3 //! This is just an abstraction for everything that is printed to the screen
4 //! (or logfile, if specified). These parameters influence printing:
5 //! - `color`
6 //! - `format` (and `quiet`)
7 //! - `logfile`
8
9 use std::{fs::File, time::Duration};
10
11 use termcolor::{Ansi, Color, ColorChoice, ColorSpec, NoColor, StandardStream, WriteColor};
12
13 use crate::{
14 Arguments, ColorSetting, Conclusion, FormatSetting, Outcome, Trial, Failed,
15 Measurement, TestInfo,
16 };
17
18 pub(crate) struct Printer {
19 out: Box<dyn WriteColor>,
20 format: FormatSetting,
21 name_width: usize,
22 kind_width: usize,
23 }
24
25 impl Printer {
26 /// Creates a new printer configured by the given arguments (`format`,
27 /// `quiet`, `color` and `logfile` options).
new(args: &Arguments, tests: &[Trial]) -> Self28 pub(crate) fn new(args: &Arguments, tests: &[Trial]) -> Self {
29 let color_arg = args.color.unwrap_or(ColorSetting::Auto);
30
31 // Determine target of all output
32 let out = if let Some(logfile) = &args.logfile {
33 let f = File::create(logfile).expect("failed to create logfile");
34 if color_arg == ColorSetting::Always {
35 Box::new(Ansi::new(f)) as Box<dyn WriteColor>
36 } else {
37 Box::new(NoColor::new(f))
38 }
39 } else {
40 let choice = match color_arg {
41 ColorSetting::Auto => ColorChoice::Auto,
42 ColorSetting::Always => ColorChoice::Always,
43 ColorSetting::Never => ColorChoice::Never,
44 };
45 Box::new(StandardStream::stdout(choice))
46 };
47
48 // Determine correct format
49 let format = if args.quiet {
50 FormatSetting::Terse
51 } else {
52 args.format.unwrap_or(FormatSetting::Pretty)
53 };
54
55 // Determine max test name length to do nice formatting later.
56 //
57 // Unicode is hard and there is no way we can properly align/pad the
58 // test names and outcomes. Counting the number of code points is just
59 // a cheap way that works in most cases. Usually, these names are
60 // ASCII.
61 let name_width = tests.iter()
62 .map(|test| test.info.name.chars().count())
63 .max()
64 .unwrap_or(0);
65
66 let kind_width = tests.iter()
67 .map(|test| {
68 if test.info.kind.is_empty() {
69 0
70 } else {
71 // The two braces [] and one space
72 test.info.kind.chars().count() + 3
73 }
74 })
75 .max()
76 .unwrap_or(0);
77
78 Self {
79 out,
80 format,
81 name_width,
82 kind_width,
83 }
84 }
85
86 /// Prints the first line "running 3 tests".
print_title(&mut self, num_tests: u64)87 pub(crate) fn print_title(&mut self, num_tests: u64) {
88 match self.format {
89 FormatSetting::Pretty | FormatSetting::Terse => {
90 let plural_s = if num_tests == 1 { "" } else { "s" };
91
92 writeln!(self.out).unwrap();
93 writeln!(self.out, "running {} test{}", num_tests, plural_s).unwrap();
94 }
95 }
96 }
97
98 /// Prints the text announcing the test (e.g. "test foo::bar ... "). Prints
99 /// nothing in terse mode.
print_test(&mut self, info: &TestInfo)100 pub(crate) fn print_test(&mut self, info: &TestInfo) {
101 let TestInfo { name, kind, .. } = info;
102 match self.format {
103 FormatSetting::Pretty => {
104 let kind = if kind.is_empty() {
105 format!("")
106 } else {
107 format!("[{}] ", kind)
108 };
109
110 write!(
111 self.out,
112 "test {: <2$}{: <3$} ... ",
113 kind,
114 name,
115 self.kind_width,
116 self.name_width,
117 ).unwrap();
118 self.out.flush().unwrap();
119 }
120 FormatSetting::Terse => {
121 // In terse mode, nothing is printed before the job. Only
122 // `print_single_outcome` prints one character.
123 }
124 }
125 }
126
127 /// Prints the outcome of a single tests. `ok` or `FAILED` in pretty mode
128 /// and `.` or `F` in terse mode.
print_single_outcome(&mut self, outcome: &Outcome)129 pub(crate) fn print_single_outcome(&mut self, outcome: &Outcome) {
130 match self.format {
131 FormatSetting::Pretty => {
132 self.print_outcome_pretty(outcome);
133 writeln!(self.out).unwrap();
134 }
135 FormatSetting::Terse => {
136 let c = match outcome {
137 Outcome::Passed => '.',
138 Outcome::Failed { .. } => 'F',
139 Outcome::Ignored => 'i',
140 Outcome::Measured { .. } => {
141 // Benchmark are never printed in terse mode... for
142 // some reason.
143 self.print_outcome_pretty(outcome);
144 writeln!(self.out).unwrap();
145 return;
146 }
147 };
148
149 self.out.set_color(&color_of_outcome(outcome)).unwrap();
150 write!(self.out, "{}", c).unwrap();
151 self.out.reset().unwrap();
152 }
153 }
154 }
155
156 /// Prints the summary line after all tests have been executed.
print_summary(&mut self, conclusion: &Conclusion, execution_time: Duration)157 pub(crate) fn print_summary(&mut self, conclusion: &Conclusion, execution_time: Duration) {
158 match self.format {
159 FormatSetting::Pretty | FormatSetting::Terse => {
160 let outcome = if conclusion.has_failed() {
161 Outcome::Failed(Failed { msg: None })
162 } else {
163 Outcome::Passed
164 };
165
166 writeln!(self.out).unwrap();
167 write!(self.out, "test result: ").unwrap();
168 self.print_outcome_pretty(&outcome);
169 writeln!(
170 self.out,
171 ". {} passed; {} failed; {} ignored; {} measured; \
172 {} filtered out; finished in {:.2}s",
173 conclusion.num_passed,
174 conclusion.num_failed,
175 conclusion.num_ignored,
176 conclusion.num_measured,
177 conclusion.num_filtered_out,
178 execution_time.as_secs_f64()
179 ).unwrap();
180 writeln!(self.out).unwrap();
181 }
182 }
183 }
184
185 /// Prints a list of all tests. Used if `--list` is set.
print_list(&mut self, tests: &[Trial], ignored: bool)186 pub(crate) fn print_list(&mut self, tests: &[Trial], ignored: bool) {
187 Self::write_list(tests, ignored, &mut self.out).unwrap();
188 }
189
write_list( tests: &[Trial], ignored: bool, mut out: impl std::io::Write, ) -> std::io::Result<()>190 pub(crate) fn write_list(
191 tests: &[Trial],
192 ignored: bool,
193 mut out: impl std::io::Write,
194 ) -> std::io::Result<()> {
195 for test in tests {
196 // libtest prints out:
197 // * all tests without `--ignored`
198 // * just the ignored tests with `--ignored`
199 if ignored && !test.info.is_ignored {
200 continue;
201 }
202
203 let kind = if test.info.kind.is_empty() {
204 format!("")
205 } else {
206 format!("[{}] ", test.info.kind)
207 };
208
209 writeln!(
210 out,
211 "{}{}: {}",
212 kind,
213 test.info.name,
214 if test.info.is_bench { "bench" } else { "test" },
215 )?;
216 }
217
218 Ok(())
219 }
220
221 /// Prints a list of failed tests with their messages. This is only called
222 /// if there were any failures.
print_failures(&mut self, fails: &[(TestInfo, Option<String>)])223 pub(crate) fn print_failures(&mut self, fails: &[(TestInfo, Option<String>)]) {
224 writeln!(self.out).unwrap();
225 writeln!(self.out, "failures:").unwrap();
226 writeln!(self.out).unwrap();
227
228 // Print messages of all tests
229 for (test_info, msg) in fails {
230 writeln!(self.out, "---- {} ----", test_info.name).unwrap();
231 if let Some(msg) = msg {
232 writeln!(self.out, "{}", msg).unwrap();
233 }
234 writeln!(self.out).unwrap();
235 }
236
237 // Print summary list of failed tests
238 writeln!(self.out).unwrap();
239 writeln!(self.out, "failures:").unwrap();
240 for (test_info, _) in fails {
241 writeln!(self.out, " {}", test_info.name).unwrap();
242 }
243 }
244
245 /// Prints a colored 'ok'/'FAILED'/'ignored'/'bench'.
print_outcome_pretty(&mut self, outcome: &Outcome)246 fn print_outcome_pretty(&mut self, outcome: &Outcome) {
247 let s = match outcome {
248 Outcome::Passed => "ok",
249 Outcome::Failed { .. } => "FAILED",
250 Outcome::Ignored => "ignored",
251 Outcome::Measured { .. } => "bench",
252 };
253
254 self.out.set_color(&color_of_outcome(outcome)).unwrap();
255 write!(self.out, "{}", s).unwrap();
256 self.out.reset().unwrap();
257
258 if let Outcome::Measured(Measurement { avg, variance }) = outcome {
259 write!(
260 self.out,
261 ": {:>11} ns/iter (+/- {})",
262 fmt_with_thousand_sep(*avg),
263 fmt_with_thousand_sep(*variance),
264 ).unwrap();
265 }
266 }
267 }
268
269 /// Formats the given integer with `,` as thousand separator.
fmt_with_thousand_sep(mut v: u64) -> String270 pub fn fmt_with_thousand_sep(mut v: u64) -> String {
271 let mut out = String::new();
272 while v >= 1000 {
273 out = format!(",{:03}{}", v % 1000, out);
274 v /= 1000;
275 }
276 out = format!("{}{}", v, out);
277
278 out
279 }
280
281 /// Returns the `ColorSpec` associated with the given outcome.
color_of_outcome(outcome: &Outcome) -> ColorSpec282 fn color_of_outcome(outcome: &Outcome) -> ColorSpec {
283 let mut out = ColorSpec::new();
284 let color = match outcome {
285 Outcome::Passed => Color::Green,
286 Outcome::Failed { .. } => Color::Red,
287 Outcome::Ignored => Color::Yellow,
288 Outcome::Measured { .. } => Color::Cyan,
289 };
290 out.set_fg(Some(color));
291 out
292 }
293