]> git.proxmox.com Git - rustc.git/blame - src/libtest/console.rs
New upstream version 1.44.1+dfsg1
[rustc.git] / src / libtest / console.rs
CommitLineData
e74abb32
XL
1//! Module providing interface for running tests in the console.
2
3use std::fs::File;
e74abb32 4use std::io;
dfeec247 5use std::io::prelude::Write;
e74abb32 6
e74abb32
XL
7use super::{
8 bench::fmt_bench_samples,
9 cli::TestOpts,
dfeec247
XL
10 event::{CompletedTest, TestEvent},
11 filter_tests,
e74abb32 12 formatters::{JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter},
dfeec247 13 helpers::{concurrency::get_concurrency, metrics::MetricMap},
e74abb32 14 options::{Options, OutputFormat},
dfeec247 15 run_tests,
e74abb32
XL
16 test_result::TestResult,
17 time::TestExecTime,
dfeec247 18 types::{NamePadding, TestDesc, TestDescAndFn},
e74abb32
XL
19};
20
21/// Generic wrapper over stdout.
22pub enum OutputLocation<T> {
23 Pretty(Box<term::StdoutTerminal>),
24 Raw(T),
25}
26
27impl<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
43pub 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
59impl 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
dfeec247 83 pub fn write_log<F, S>(&mut self, msg: F) -> io::Result<()>
e74abb32
XL
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())
dfeec247 94 }
e74abb32
XL
95 }
96 }
97
dfeec247
XL
98 pub fn write_log_result(
99 &mut self,
100 test: &TestDesc,
e74abb32
XL
101 result: &TestResult,
102 exec_time: Option<&TestExecTime>,
103 ) -> io::Result<()> {
dfeec247
XL
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 })?;
e74abb32
XL
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.
131pub 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
dfeec247 146 let TestDescAndFn { desc: TestDesc { name, .. }, testfn } = test;
e74abb32
XL
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 {
ba9703b0 172 writeln!(output)?;
e74abb32
XL
173 }
174
dfeec247 175 writeln!(output, "{}, {}", plural(ntest, "test"), plural(nbench, "benchmark"))?;
e74abb32
XL
176 }
177
178 Ok(())
179}
180
181// Updates `ConsoleTestState` depending on result of the test execution.
182fn 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.
219fn 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.
251pub 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 )),
dfeec247
XL
273 OutputFormat::Terse => {
274 Box::new(TerseFormatter::new(output, opts.use_color(), max_name_len, is_multithreaded))
275 }
e74abb32
XL
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.
288fn 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}