1 //! Module providing interface for running tests in the console.
5 use std
::io
::prelude
::Write
;
6 use std
::time
::Instant
;
9 bench
::fmt_bench_samples
,
11 event
::{CompletedTest, TestEvent}
,
13 formatters
::{JsonFormatter, JunitFormatter, OutputFormatter, PrettyFormatter, TerseFormatter}
,
14 helpers
::{concurrency::get_concurrency, metrics::MetricMap}
,
15 options
::{Options, OutputFormat}
,
17 test_result
::TestResult
,
18 time
::{TestExecTime, TestSuiteExecTime}
,
19 types
::{NamePadding, TestDesc, TestDescAndFn}
,
22 /// Generic wrapper over stdout.
23 pub enum OutputLocation
<T
> {
24 Pretty(Box
<term
::StdoutTerminal
>),
28 impl<T
: Write
> Write
for OutputLocation
<T
> {
29 fn write(&mut self, buf
: &[u8]) -> io
::Result
<usize> {
31 OutputLocation
::Pretty(ref mut term
) => term
.write(buf
),
32 OutputLocation
::Raw(ref mut stdout
) => stdout
.write(buf
),
36 fn flush(&mut self) -> io
::Result
<()> {
38 OutputLocation
::Pretty(ref mut term
) => term
.flush(),
39 OutputLocation
::Raw(ref mut stdout
) => stdout
.flush(),
44 pub struct ConsoleTestState
{
45 pub log_out
: Option
<File
>,
50 pub filtered_out
: 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>)>,
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
)?
),
76 metrics
: MetricMap
::new(),
78 not_failures
: Vec
::new(),
79 time_failures
: Vec
::new(),
80 options
: opts
.options
,
84 pub fn write_log
<F
, S
>(&mut self, msg
: F
) -> io
::Result
<()>
93 let msg
= msg
.as_ref();
94 o
.write_all(msg
.as_bytes())
99 pub fn write_log_result(
103 exec_time
: Option
<&TestExecTime
>,
104 ) -> io
::Result
<()> {
106 let TestDesc { name, ignore_message, .. }
= test
;
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}")
120 TestResult
::TrBench(ref bs
) => fmt_bench_samples(bs
),
121 TestResult
::TrTimedFail
=> "failed (time limit exceeded)".to_owned(),
126 if let Some(exec_time
) = exec_time
{
127 self.write_log(|| format
!(" <{exec_time}>"))?
;
129 self.write_log(|| "\n")
132 fn current_test_count(&self) -> usize {
133 self.passed
+ self.failed
+ self.ignored
+ self.measured
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
),
144 let quiet
= opts
.format
== OutputFormat
::Terse
;
145 let mut st
= ConsoleTestState
::new(opts
)?
;
150 for test
in filter_tests(&opts
, tests
).into_iter() {
151 use crate::TestFn
::*;
153 let TestDescAndFn { desc: TestDesc { name, .. }
, testfn
} = test
;
155 let fntype
= match testfn
{
156 StaticTestFn(..) | DynTestFn(..) => {
160 StaticBenchFn(..) | DynBenchFn(..) => {
166 writeln
!(output
, "{name}: {fntype}")?
;
167 st
.write_log(|| format
!("{fntype} {name}\n"))?
;
170 fn plural(count
: u32, s
: &str) -> String
{
172 1 => format
!("1 {s}"),
173 n
=> format
!("{n} {s}s"),
178 if ntest
!= 0 || nbench
!= 0 {
182 writeln
!(output
, "{}, {}", plural(ntest
, "test"), plural(nbench
, "benchmark"))?
;
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
=> {
195 st
.not_failures
.push((test
, stdout
));
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
,
206 TestResult
::TrFailed
=> {
208 st
.failures
.push((test
, stdout
));
210 TestResult
::TrFailedMsg(msg
) => {
212 let mut stdout
= stdout
;
213 stdout
.extend_from_slice(format
!("note: {msg}").as_bytes());
214 st
.failures
.push((test
, stdout
));
216 TestResult
::TrTimedFail
=> {
218 st
.time_failures
.push((test
, stdout
));
223 // Handler for events that occur during test execution.
224 // It is provided as a callback to the `run_tests` function.
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
)?
;
235 TestEvent
::TeFilteredOut(filtered_out
) => {
236 st
.filtered_out
= filtered_out
;
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
;
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
);
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
),
263 let max_name_len
= tests
265 .max_by_key(|t
| len_if_padded(*t
))
266 .map(|t
| t
.desc
.name
.as_slice().len())
269 let is_multithreaded
= opts
.test_threads
.unwrap_or_else(get_concurrency
) > 1;
271 let mut out
: Box
<dyn OutputFormatter
> = match opts
.format
{
272 OutputFormat
::Pretty
=> Box
::new(PrettyFormatter
::new(
279 OutputFormat
::Terse
=> {
280 Box
::new(TerseFormatter
::new(output
, opts
.use_color(), max_name_len
, is_multithreaded
))
282 OutputFormat
::Json
=> Box
::new(JsonFormatter
::new(output
)),
283 OutputFormat
::Junit
=> Box
::new(JunitFormatter
::new(output
)),
285 let mut st
= ConsoleTestState
::new(opts
)?
;
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
);
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()));
296 assert
!(opts
.fail_fast
|| st
.current_test_count() == st
.total
);
298 out
.write_run_finish(&st
)
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(),