• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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