]> git.lizzy.rs Git - rust.git/blob - src/libtest/console.rs
Auto merge of #65733 - Centril:rollup-0zth66f, r=Centril
[rust.git] / src / libtest / console.rs
1 //! Module providing interface for running tests in the console.
2
3 use std::fs::File;
4 use std::io::prelude::Write;
5 use std::io;
6
7 use term;
8
9 use super::{
10     bench::fmt_bench_samples,
11     cli::TestOpts,
12     event::{TestEvent, CompletedTest},
13     formatters::{JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter},
14     helpers::{
15         concurrency::get_concurrency,
16         metrics::MetricMap,
17     },
18     types::{TestDesc, TestDescAndFn, NamePadding},
19     options::{Options, OutputFormat},
20     test_result::TestResult,
21     time::TestExecTime,
22     run_tests,
23     filter_tests,
24 };
25
26 /// Generic wrapper over stdout.
27 pub enum OutputLocation<T> {
28     Pretty(Box<term::StdoutTerminal>),
29     Raw(T),
30 }
31
32 impl<T: Write> Write for OutputLocation<T> {
33     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
34         match *self {
35             OutputLocation::Pretty(ref mut term) => term.write(buf),
36             OutputLocation::Raw(ref mut stdout) => stdout.write(buf),
37         }
38     }
39
40     fn flush(&mut self) -> io::Result<()> {
41         match *self {
42             OutputLocation::Pretty(ref mut term) => term.flush(),
43             OutputLocation::Raw(ref mut stdout) => stdout.flush(),
44         }
45     }
46 }
47
48 pub struct ConsoleTestState {
49     pub log_out: Option<File>,
50     pub total: usize,
51     pub passed: usize,
52     pub failed: usize,
53     pub ignored: usize,
54     pub allowed_fail: usize,
55     pub filtered_out: usize,
56     pub measured: usize,
57     pub metrics: MetricMap,
58     pub failures: Vec<(TestDesc, Vec<u8>)>,
59     pub not_failures: Vec<(TestDesc, Vec<u8>)>,
60     pub time_failures: Vec<(TestDesc, Vec<u8>)>,
61     pub options: Options,
62 }
63
64 impl ConsoleTestState {
65     pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState> {
66         let log_out = match opts.logfile {
67             Some(ref path) => Some(File::create(path)?),
68             None => None,
69         };
70
71         Ok(ConsoleTestState {
72             log_out,
73             total: 0,
74             passed: 0,
75             failed: 0,
76             ignored: 0,
77             allowed_fail: 0,
78             filtered_out: 0,
79             measured: 0,
80             metrics: MetricMap::new(),
81             failures: Vec::new(),
82             not_failures: Vec::new(),
83             time_failures: Vec::new(),
84             options: opts.options,
85         })
86     }
87
88     pub fn write_log<F, S>(
89         &mut self,
90         msg: F,
91     ) -> io::Result<()>
92     where
93         S: AsRef<str>,
94         F: FnOnce() -> S,
95     {
96         match self.log_out {
97             None => Ok(()),
98             Some(ref mut o) => {
99                 let msg = msg();
100                 let msg = msg.as_ref();
101                 o.write_all(msg.as_bytes())
102             },
103         }
104     }
105
106     pub fn write_log_result(&mut self,test: &TestDesc,
107         result: &TestResult,
108         exec_time: Option<&TestExecTime>,
109     ) -> io::Result<()> {
110         self.write_log(|| format!(
111             "{} {}",
112             match *result {
113                 TestResult::TrOk => "ok".to_owned(),
114                 TestResult::TrFailed => "failed".to_owned(),
115                 TestResult::TrFailedMsg(ref msg) => format!("failed: {}", msg),
116                 TestResult::TrIgnored => "ignored".to_owned(),
117                 TestResult::TrAllowedFail => "failed (allowed)".to_owned(),
118                 TestResult::TrBench(ref bs) => fmt_bench_samples(bs),
119                 TestResult::TrTimedFail => "failed (time limit exceeded)".to_owned(),
120             },
121             test.name,
122         ))?;
123         if let Some(exec_time) = exec_time {
124             self.write_log(|| format!(" <{}>", exec_time))?;
125         }
126         self.write_log(|| "\n")
127     }
128
129     fn current_test_count(&self) -> usize {
130         self.passed + self.failed + self.ignored + self.measured + self.allowed_fail
131     }
132 }
133
134 // List the tests to console, and optionally to logfile. Filters are honored.
135 pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<()> {
136     let mut output = match term::stdout() {
137         None => OutputLocation::Raw(io::stdout()),
138         Some(t) => OutputLocation::Pretty(t),
139     };
140
141     let quiet = opts.format == OutputFormat::Terse;
142     let mut st = ConsoleTestState::new(opts)?;
143
144     let mut ntest = 0;
145     let mut nbench = 0;
146
147     for test in filter_tests(&opts, tests) {
148         use crate::TestFn::*;
149
150         let TestDescAndFn {
151             desc: TestDesc { name, .. },
152             testfn,
153         } = test;
154
155         let fntype = match testfn {
156             StaticTestFn(..) | DynTestFn(..) => {
157                 ntest += 1;
158                 "test"
159             }
160             StaticBenchFn(..) | DynBenchFn(..) => {
161                 nbench += 1;
162                 "benchmark"
163             }
164         };
165
166         writeln!(output, "{}: {}", name, fntype)?;
167         st.write_log(|| format!("{} {}\n", fntype, name))?;
168     }
169
170     fn plural(count: u32, s: &str) -> String {
171         match count {
172             1 => format!("{} {}", 1, s),
173             n => format!("{} {}s", n, s),
174         }
175     }
176
177     if !quiet {
178         if ntest != 0 || nbench != 0 {
179             writeln!(output, "")?;
180         }
181
182         writeln!(
183             output,
184             "{}, {}",
185             plural(ntest, "test"),
186             plural(nbench, "benchmark")
187         )?;
188     }
189
190     Ok(())
191 }
192
193 // Updates `ConsoleTestState` depending on result of the test execution.
194 fn handle_test_result(st: &mut ConsoleTestState, completed_test: CompletedTest) {
195     let test = completed_test.desc;
196     let stdout = completed_test.stdout;
197     match completed_test.result {
198         TestResult::TrOk => {
199             st.passed += 1;
200             st.not_failures.push((test, stdout));
201         }
202         TestResult::TrIgnored => st.ignored += 1,
203         TestResult::TrAllowedFail => st.allowed_fail += 1,
204         TestResult::TrBench(bs) => {
205             st.metrics.insert_metric(
206                 test.name.as_slice(),
207                 bs.ns_iter_summ.median,
208                 bs.ns_iter_summ.max - bs.ns_iter_summ.min,
209             );
210             st.measured += 1
211         }
212         TestResult::TrFailed => {
213             st.failed += 1;
214             st.failures.push((test, stdout));
215         }
216         TestResult::TrFailedMsg(msg) => {
217             st.failed += 1;
218             let mut stdout = stdout;
219             stdout.extend_from_slice(format!("note: {}", msg).as_bytes());
220             st.failures.push((test, stdout));
221         }
222         TestResult::TrTimedFail => {
223             st.failed += 1;
224             st.time_failures.push((test, stdout));
225         }
226     }
227 }
228
229 // Handler for events that occur during test execution.
230 // It is provided as a callback to the `run_tests` function.
231 fn on_test_event(
232     event: &TestEvent,
233     st: &mut ConsoleTestState,
234     out: &mut dyn OutputFormatter,
235 ) -> io::Result<()> {
236     match (*event).clone() {
237         TestEvent::TeFiltered(ref filtered_tests) => {
238             st.total = filtered_tests.len();
239             out.write_run_start(filtered_tests.len())?;
240         }
241         TestEvent::TeFilteredOut(filtered_out) => {
242             st.filtered_out = filtered_out;
243         }
244         TestEvent::TeWait(ref test) => out.write_test_start(test)?,
245         TestEvent::TeTimeout(ref test) => out.write_timeout(test)?,
246         TestEvent::TeResult(completed_test) => {
247             let test = &completed_test.desc;
248             let result = &completed_test.result;
249             let exec_time = &completed_test.exec_time;
250             let stdout = &completed_test.stdout;
251
252             st.write_log_result(test, result, exec_time.as_ref())?;
253             out.write_result(test, result, exec_time.as_ref(), &*stdout, st)?;
254             handle_test_result(st, completed_test);
255         }
256     }
257
258     Ok(())
259 }
260
261 /// A simple console test runner.
262 /// Runs provided tests reporting process and results to the stdout.
263 pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> {
264     let output = match term::stdout() {
265         None => OutputLocation::Raw(io::stdout()),
266         Some(t) => OutputLocation::Pretty(t),
267     };
268
269     let max_name_len = tests
270         .iter()
271         .max_by_key(|t| len_if_padded(*t))
272         .map(|t| t.desc.name.as_slice().len())
273         .unwrap_or(0);
274
275     let is_multithreaded = opts.test_threads.unwrap_or_else(get_concurrency) > 1;
276
277     let mut out: Box<dyn OutputFormatter> = match opts.format {
278         OutputFormat::Pretty => Box::new(PrettyFormatter::new(
279             output,
280             opts.use_color(),
281             max_name_len,
282             is_multithreaded,
283             opts.time_options,
284         )),
285         OutputFormat::Terse => Box::new(TerseFormatter::new(
286             output,
287             opts.use_color(),
288             max_name_len,
289             is_multithreaded,
290         )),
291         OutputFormat::Json => Box::new(JsonFormatter::new(output)),
292     };
293     let mut st = ConsoleTestState::new(opts)?;
294
295     run_tests(opts, tests, |x| on_test_event(&x, &mut st, &mut *out))?;
296
297     assert!(st.current_test_count() == st.total);
298
299     out.write_run_finish(&st)
300 }
301
302 // Calculates padding for given test description.
303 fn len_if_padded(t: &TestDescAndFn) -> usize {
304     match t.testfn.padding() {
305         NamePadding::PadNone => 0,
306         NamePadding::PadOnRight => t.desc.name.as_slice().len(),
307     }
308 }