]> git.lizzy.rs Git - rust.git/blob - library/test/src/formatters/terse.rs
Rollup merge of #87346 - rylev:rename-force-warn, r=nikomatsakis
[rust.git] / library / test / src / formatters / terse.rs
1 use std::{io, io::prelude::Write};
2
3 use super::OutputFormatter;
4 use crate::{
5     bench::fmt_bench_samples,
6     console::{ConsoleTestState, OutputLocation},
7     term,
8     test_result::TestResult,
9     time,
10     types::NamePadding,
11     types::TestDesc,
12 };
13
14 // insert a '\n' after 100 tests in quiet mode
15 const QUIET_MODE_MAX_COLUMN: usize = 100;
16
17 pub(crate) struct TerseFormatter<T> {
18     out: OutputLocation<T>,
19     use_color: bool,
20     is_multithreaded: bool,
21     /// Number of columns to fill when aligning names
22     max_name_len: usize,
23
24     test_count: usize,
25     total_test_count: usize,
26 }
27
28 impl<T: Write> TerseFormatter<T> {
29     pub fn new(
30         out: OutputLocation<T>,
31         use_color: bool,
32         max_name_len: usize,
33         is_multithreaded: bool,
34     ) -> Self {
35         TerseFormatter {
36             out,
37             use_color,
38             max_name_len,
39             is_multithreaded,
40             test_count: 0,
41             total_test_count: 0, // initialized later, when write_run_start is called
42         }
43     }
44
45     pub fn write_ok(&mut self) -> io::Result<()> {
46         self.write_short_result(".", term::color::GREEN)
47     }
48
49     pub fn write_failed(&mut self) -> io::Result<()> {
50         self.write_short_result("F", term::color::RED)
51     }
52
53     pub fn write_ignored(&mut self) -> io::Result<()> {
54         self.write_short_result("i", term::color::YELLOW)
55     }
56
57     pub fn write_allowed_fail(&mut self) -> io::Result<()> {
58         self.write_short_result("a", term::color::YELLOW)
59     }
60
61     pub fn write_bench(&mut self) -> io::Result<()> {
62         self.write_pretty("bench", term::color::CYAN)
63     }
64
65     pub fn write_short_result(
66         &mut self,
67         result: &str,
68         color: term::color::Color,
69     ) -> io::Result<()> {
70         self.write_pretty(result, color)?;
71         if self.test_count % QUIET_MODE_MAX_COLUMN == QUIET_MODE_MAX_COLUMN - 1 {
72             // we insert a new line every 100 dots in order to flush the
73             // screen when dealing with line-buffered output (e.g., piping to
74             // `stamp` in the rust CI).
75             let out = format!(" {}/{}\n", self.test_count + 1, self.total_test_count);
76             self.write_plain(&out)?;
77         }
78
79         self.test_count += 1;
80         Ok(())
81     }
82
83     pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> {
84         match self.out {
85             OutputLocation::Pretty(ref mut term) => {
86                 if self.use_color {
87                     term.fg(color)?;
88                 }
89                 term.write_all(word.as_bytes())?;
90                 if self.use_color {
91                     term.reset()?;
92                 }
93                 term.flush()
94             }
95             OutputLocation::Raw(ref mut stdout) => {
96                 stdout.write_all(word.as_bytes())?;
97                 stdout.flush()
98             }
99         }
100     }
101
102     pub fn write_plain<S: AsRef<str>>(&mut self, s: S) -> io::Result<()> {
103         let s = s.as_ref();
104         self.out.write_all(s.as_bytes())?;
105         self.out.flush()
106     }
107
108     pub fn write_outputs(&mut self, state: &ConsoleTestState) -> io::Result<()> {
109         self.write_plain("\nsuccesses:\n")?;
110         let mut successes = Vec::new();
111         let mut stdouts = String::new();
112         for &(ref f, ref stdout) in &state.not_failures {
113             successes.push(f.name.to_string());
114             if !stdout.is_empty() {
115                 stdouts.push_str(&format!("---- {} stdout ----\n", f.name));
116                 let output = String::from_utf8_lossy(stdout);
117                 stdouts.push_str(&output);
118                 stdouts.push('\n');
119             }
120         }
121         if !stdouts.is_empty() {
122             self.write_plain("\n")?;
123             self.write_plain(&stdouts)?;
124         }
125
126         self.write_plain("\nsuccesses:\n")?;
127         successes.sort();
128         for name in &successes {
129             self.write_plain(&format!("    {}\n", name))?;
130         }
131         Ok(())
132     }
133
134     pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> {
135         self.write_plain("\nfailures:\n")?;
136         let mut failures = Vec::new();
137         let mut fail_out = String::new();
138         for &(ref f, ref stdout) in &state.failures {
139             failures.push(f.name.to_string());
140             if !stdout.is_empty() {
141                 fail_out.push_str(&format!("---- {} stdout ----\n", f.name));
142                 let output = String::from_utf8_lossy(stdout);
143                 fail_out.push_str(&output);
144                 fail_out.push('\n');
145             }
146         }
147         if !fail_out.is_empty() {
148             self.write_plain("\n")?;
149             self.write_plain(&fail_out)?;
150         }
151
152         self.write_plain("\nfailures:\n")?;
153         failures.sort();
154         for name in &failures {
155             self.write_plain(&format!("    {}\n", name))?;
156         }
157         Ok(())
158     }
159
160     fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> {
161         let name = desc.padded_name(self.max_name_len, desc.name.padding());
162         if let Some(test_mode) = desc.test_mode() {
163             self.write_plain(&format!("test {} - {} ... ", name, test_mode))?;
164         } else {
165             self.write_plain(&format!("test {} ... ", name))?;
166         }
167
168         Ok(())
169     }
170 }
171
172 impl<T: Write> OutputFormatter for TerseFormatter<T> {
173     fn write_run_start(&mut self, test_count: usize) -> io::Result<()> {
174         self.total_test_count = test_count;
175         let noun = if test_count != 1 { "tests" } else { "test" };
176         self.write_plain(&format!("\nrunning {} {}\n", test_count, noun))
177     }
178
179     fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
180         // Remnants from old libtest code that used the padding value
181         // in order to indicate benchmarks.
182         // When running benchmarks, terse-mode should still print their name as if
183         // it is the Pretty formatter.
184         if !self.is_multithreaded && desc.name.padding() == NamePadding::PadOnRight {
185             self.write_test_name(desc)?;
186         }
187
188         Ok(())
189     }
190
191     fn write_result(
192         &mut self,
193         desc: &TestDesc,
194         result: &TestResult,
195         _: Option<&time::TestExecTime>,
196         _: &[u8],
197         _: &ConsoleTestState,
198     ) -> io::Result<()> {
199         match *result {
200             TestResult::TrOk => self.write_ok(),
201             TestResult::TrFailed | TestResult::TrFailedMsg(_) | TestResult::TrTimedFail => {
202                 self.write_failed()
203             }
204             TestResult::TrIgnored => self.write_ignored(),
205             TestResult::TrAllowedFail => self.write_allowed_fail(),
206             TestResult::TrBench(ref bs) => {
207                 if self.is_multithreaded {
208                     self.write_test_name(desc)?;
209                 }
210                 self.write_bench()?;
211                 self.write_plain(&format!(": {}\n", fmt_bench_samples(bs)))
212             }
213         }
214     }
215
216     fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
217         self.write_plain(&format!(
218             "test {} has been running for over {} seconds\n",
219             desc.name,
220             time::TEST_WARN_TIMEOUT_S
221         ))
222     }
223
224     fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
225         if state.options.display_output {
226             self.write_outputs(state)?;
227         }
228         let success = state.failed == 0;
229         if !success {
230             self.write_failures(state)?;
231         }
232
233         self.write_plain("\ntest result: ")?;
234
235         if success {
236             // There's no parallelism at this point so it's safe to use color
237             self.write_pretty("ok", term::color::GREEN)?;
238         } else {
239             self.write_pretty("FAILED", term::color::RED)?;
240         }
241
242         let s = if state.allowed_fail > 0 {
243             format!(
244                 ". {} passed; {} failed ({} allowed); {} ignored; {} measured; {} filtered out",
245                 state.passed,
246                 state.failed + state.allowed_fail,
247                 state.allowed_fail,
248                 state.ignored,
249                 state.measured,
250                 state.filtered_out
251             )
252         } else {
253             format!(
254                 ". {} passed; {} failed; {} ignored; {} measured; {} filtered out",
255                 state.passed, state.failed, state.ignored, state.measured, state.filtered_out
256             )
257         };
258
259         self.write_plain(&s)?;
260
261         if let Some(ref exec_time) = state.exec_time {
262             let time_str = format!("; finished in {}", exec_time);
263             self.write_plain(&time_str)?;
264         }
265
266         self.write_plain("\n\n")?;
267
268         Ok(success)
269     }
270 }