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