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