1use std::{io, io::prelude::Write};
2
3use super::OutputFormatter;
4use crate::{
5 bench::fmt_bench_samples,
6 console::{ConsoleTestDiscoveryState, ConsoleTestState, OutputLocation},
7 term,
8 test_result::TestResult,
9 time,
10 types::NamePadding,
11 types::TestDesc,
12};
13
14// We insert a '\n' when the output hits 100 columns in quiet mode. 88 test
15// result chars leaves 12 chars for a progress count like " 11704/12853".
16const QUIET_MODE_MAX_COLUMN: usize = 88;
17
18pub(crate) struct TerseFormatter<T> {
19 out: OutputLocation<T>,
20 use_color: bool,
21 is_multithreaded: bool,
22 /// Number of columns to fill when aligning names
23 max_name_len: usize,
24
25 test_count: usize,
26 test_column: usize,
27 total_test_count: usize,
28}
29
30impl<T: Write> TerseFormatter<T> {
31 pub fn new(
32 out: OutputLocation<T>,
33 use_color: bool,
34 max_name_len: usize,
35 is_multithreaded: bool,
36 ) -> Self {
37 TerseFormatter {
38 out,
39 use_color,
40 max_name_len,
41 is_multithreaded,
42 test_count: 0,
43 test_column: 0,
44 total_test_count: 0, // initialized later, when write_run_start is called
45 }
46 }
47
48 pub fn write_ok(&mut self) -> io::Result<()> {
49 self.write_short_result(".", term::color::GREEN)
50 }
51
52 pub fn write_failed(&mut self, name: &str) -> io::Result<()> {
53 // Put failed tests on their own line and include the test name, so that it's faster
54 // to see which test failed without having to wait for them all to run.
55
56 // normally, we write the progress unconditionally, even if the previous line was cut short.
57 // but if this is the very first column, no short results will have been printed and we'll end up with *only* the progress on the line.
58 // avoid this.
59 if self.test_column != 0 {
60 self.write_progress()?;
61 }
62 self.test_count += 1;
63 self.write_plain(format!("{name} --- "))?;
64 self.write_pretty("FAILED", term::color::RED)?;
65 self.write_plain("\n")
66 }
67
68 pub fn write_ignored(&mut self) -> io::Result<()> {
69 self.write_short_result("i", term::color::YELLOW)
70 }
71
72 pub fn write_bench(&mut self) -> io::Result<()> {
73 self.write_pretty("bench", term::color::CYAN)
74 }
75
76 pub fn write_short_result(
77 &mut self,
78 result: &str,
79 color: term::color::Color,
80 ) -> io::Result<()> {
81 self.write_pretty(result, color)?;
82 self.test_count += 1;
83 self.test_column += 1;
84 if self.test_column % QUIET_MODE_MAX_COLUMN == QUIET_MODE_MAX_COLUMN - 1 {
85 // We insert a new line regularly in order to flush the
86 // screen when dealing with line-buffered output (e.g., piping to
87 // `stamp` in the Rust CI).
88 self.write_progress()?;
89 }
90
91 Ok(())
92 }
93
94 fn write_progress(&mut self) -> io::Result<()> {
95 let out = format!(" {}/{}\n", self.test_count, self.total_test_count);
96 self.write_plain(out)?;
97 self.test_column = 0;
98 Ok(())
99 }
100
101 pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> {
102 match self.out {
103 OutputLocation::Pretty(ref mut term) => {
104 if self.use_color {
105 term.fg(color)?;
106 }
107 term.write_all(word.as_bytes())?;
108 if self.use_color {
109 term.reset()?;
110 }
111 term.flush()
112 }
113 OutputLocation::Raw(ref mut stdout) => {
114 stdout.write_all(word.as_bytes())?;
115 stdout.flush()
116 }
117 }
118 }
119
120 pub fn write_plain<S: AsRef<str>>(&mut self, s: S) -> io::Result<()> {
121 let s = s.as_ref();
122 self.out.write_all(s.as_bytes())?;
123 self.out.flush()
124 }
125
126 pub fn write_outputs(&mut self, state: &ConsoleTestState) -> io::Result<()> {
127 self.write_plain("\nsuccesses:\n")?;
128 let mut successes = Vec::new();
129 let mut stdouts = String::new();
130 for (f, stdout) in &state.not_failures {
131 successes.push(f.name.to_string());
132 if !stdout.is_empty() {
133 stdouts.push_str(&format!("---- {} stdout ----\n", f.name));
134 let output = String::from_utf8_lossy(stdout);
135 stdouts.push_str(&output);
136 stdouts.push('\n');
137 }
138 }
139 if !stdouts.is_empty() {
140 self.write_plain("\n")?;
141 self.write_plain(&stdouts)?;
142 }
143
144 self.write_plain("\nsuccesses:\n")?;
145 successes.sort();
146 for name in &successes {
147 self.write_plain(&format!(" {name}\n"))?;
148 }
149 Ok(())
150 }
151
152 pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> {
153 self.write_plain("\nfailures:\n")?;
154 let mut failures = Vec::new();
155 let mut fail_out = String::new();
156 for (f, stdout) in &state.failures {
157 failures.push(f.name.to_string());
158 if !stdout.is_empty() {
159 fail_out.push_str(&format!("---- {} stdout ----\n", f.name));
160 let output = String::from_utf8_lossy(stdout);
161 fail_out.push_str(&output);
162 fail_out.push('\n');
163 }
164 }
165 if !fail_out.is_empty() {
166 self.write_plain("\n")?;
167 self.write_plain(&fail_out)?;
168 }
169
170 self.write_plain("\nfailures:\n")?;
171 failures.sort();
172 for name in &failures {
173 self.write_plain(&format!(" {name}\n"))?;
174 }
175 Ok(())
176 }
177
178 fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> {
179 let name = desc.padded_name(self.max_name_len, desc.name.padding());
180 if let Some(test_mode) = desc.test_mode() {
181 self.write_plain(format!("test {name} - {test_mode} ... "))?;
182 } else {
183 self.write_plain(format!("test {name} ... "))?;
184 }
185
186 Ok(())
187 }
188}
189
190impl<T: Write> OutputFormatter for TerseFormatter<T> {
191 fn write_discovery_start(&mut self) -> io::Result<()> {
192 Ok(())
193 }
194
195 fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> {
196 self.write_plain(format!("{}: {test_type}\n", desc.name))
197 }
198
199 fn write_discovery_finish(&mut self, _state: &ConsoleTestDiscoveryState) -> io::Result<()> {
200 Ok(())
201 }
202
203 fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> {
204 self.total_test_count = test_count;
205 let noun = if test_count != 1 { "tests" } else { "test" };
206 let shuffle_seed_msg = if let Some(shuffle_seed) = shuffle_seed {
207 format!(" (shuffle seed: {shuffle_seed})")
208 } else {
209 String::new()
210 };
211 self.write_plain(format!("\nrunning {test_count} {noun}{shuffle_seed_msg}\n"))
212 }
213
214 fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
215 // Remnants from old libtest code that used the padding value
216 // in order to indicate benchmarks.
217 // When running benchmarks, terse-mode should still print their name as if
218 // it is the Pretty formatter.
219 if !self.is_multithreaded && desc.name.padding() == NamePadding::PadOnRight {
220 self.write_test_name(desc)?;
221 }
222
223 Ok(())
224 }
225
226 fn write_result(
227 &mut self,
228 desc: &TestDesc,
229 result: &TestResult,
230 _: Option<&time::TestExecTime>,
231 _: &[u8],
232 _: &ConsoleTestState,
233 ) -> io::Result<()> {
234 match *result {
235 TestResult::TrOk => self.write_ok(),
236 TestResult::TrFailed | TestResult::TrFailedMsg(_) | TestResult::TrTimedFail => {
237 self.write_failed(desc.name.as_slice())
238 }
239 TestResult::TrIgnored => self.write_ignored(),
240 TestResult::TrBench(ref bs) => {
241 if self.is_multithreaded {
242 self.write_test_name(desc)?;
243 }
244 self.write_bench()?;
245 self.write_plain(format!(": {}\n", fmt_bench_samples(bs)))
246 }
247 }
248 }
249
250 fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
251 self.write_plain(format!(
252 "test {} has been running for over {} seconds\n",
253 desc.name,
254 time::TEST_WARN_TIMEOUT_S
255 ))
256 }
257
258 fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
259 if state.options.display_output {
260 self.write_outputs(state)?;
261 }
262 let success = state.failed == 0;
263 if !success {
264 self.write_failures(state)?;
265 }
266
267 self.write_plain("\ntest result: ")?;
268
269 if success {
270 // There's no parallelism at this point so it's safe to use color
271 self.write_pretty("ok", term::color::GREEN)?;
272 } else {
273 self.write_pretty("FAILED", term::color::RED)?;
274 }
275
276 let s = format!(
277 ". {} passed; {} failed; {} ignored; {} measured; {} filtered out",
278 state.passed, state.failed, state.ignored, state.measured, state.filtered_out
279 );
280
281 self.write_plain(s)?;
282
283 if let Some(ref exec_time) = state.exec_time {
284 let time_str = format!("; finished in {exec_time}");
285 self.write_plain(time_str)?;
286 }
287
288 self.write_plain("\n\n")?;
289
290 // Custom handling of cases where there is only 1 test to execute and that test was ignored.
291 // We want to show more detailed information(why was the test ignored) for investigation purposes.
292 if self.total_test_count == 1 && state.ignores.len() == 1 {
293 let test_desc = &state.ignores[0].0;
294 if let Some(im) = test_desc.ignore_message {
295 self.write_plain(format!("test: {}, ignore_message: {}\n\n", test_desc.name, im))?;
296 }
297 }
298
299 Ok(success)
300 }
301}
302