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 test_result::TestResult, 8 time, 9 types::NamePadding, 10 types::TestDesc, 11 }; 12 13 // insert a '\n' after 100 tests in quiet mode 14 const QUIET_MODE_MAX_COLUMN: usize = 100; 15 16 pub(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 27 impl<T: Write> TerseFormatter<T> { new( out: OutputLocation<T>, use_color: bool, max_name_len: usize, is_multithreaded: bool, ) -> Self28 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 write_ok(&mut self) -> io::Result<()>44 pub fn write_ok(&mut self) -> io::Result<()> { 45 self.write_short_result(".", term::color::GREEN) 46 } 47 write_failed(&mut self) -> io::Result<()>48 pub fn write_failed(&mut self) -> io::Result<()> { 49 self.write_short_result("F", term::color::RED) 50 } 51 write_ignored(&mut self) -> io::Result<()>52 pub fn write_ignored(&mut self) -> io::Result<()> { 53 self.write_short_result("i", term::color::YELLOW) 54 } 55 write_allowed_fail(&mut self) -> io::Result<()>56 pub fn write_allowed_fail(&mut self) -> io::Result<()> { 57 self.write_short_result("a", term::color::YELLOW) 58 } 59 write_bench(&mut self) -> io::Result<()>60 pub fn write_bench(&mut self) -> io::Result<()> { 61 self.write_pretty("bench", term::color::CYAN) 62 } 63 write_short_result( &mut self, result: &str, color: term::color::Color, ) -> io::Result<()>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 write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()>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 write_plain<S: AsRef<str>>(&mut self, s: S) -> io::Result<()>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 write_outputs(&mut self, state: &ConsoleTestState) -> io::Result<()>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 write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()>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 write_test_name(&mut self, desc: &TestDesc) -> io::Result<()>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 167 impl<T: Write> OutputFormatter for TerseFormatter<T> { write_run_start(&mut self, test_count: usize) -> io::Result<()>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 write_test_start(&mut self, desc: &TestDesc) -> io::Result<()>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 write_result( &mut self, desc: &TestDesc, result: &TestResult, _: Option<&time::TestExecTime>, _: &[u8], _: &ConsoleTestState, ) -> io::Result<()>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 write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>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 write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool>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