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