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