Skip to main content

test/
lib.rs

1//! Support code for rustc's built in unit-test and micro-benchmarking
2//! framework.
3//!
4//! Almost all user code will only be interested in `Bencher` and
5//! `black_box`. All other interactions (such as writing tests and
6//! benchmarks themselves) should be done via the `#[test]` and
7//! `#[bench]` attributes.
8//!
9//! See the [Testing Chapter](../book/ch11-00-testing.html) of the book for more
10//! details.
11
12// Currently, not much of this is meant for users. It is intended to
13// support the simplest interface possible for representing and
14// running tests while providing a base that other test frameworks may
15// build off of.
16
17#![unstable(feature = "test", issue = "50297")]
18#![doc(test(attr(deny(warnings))))]
19#![doc(rust_logo)]
20#![feature(rustdoc_internals)]
21#![feature(file_buffered)]
22#![feature(internal_output_capture)]
23#![feature(io_const_error)]
24#![feature(staged_api)]
25#![feature(process_exitcode_internals)]
26#![feature(panic_can_unwind)]
27#![cfg_attr(test, feature(test))]
28#![feature(thread_spawn_hook)]
29#![allow(internal_features)]
30#![warn(rustdoc::unescaped_backticks)]
31#![warn(unreachable_pub)]
32
33pub use std::process::ExitCode; // used by rustc-generated test harness
34
35pub use cli::TestOpts;
36
37pub use self::ColorConfig::*;
38pub use self::bench::{Bencher, black_box};
39pub use self::console::run_tests_console;
40pub use self::options::{ColorConfig, Options, OutputFormat, RunIgnored, ShouldPanic};
41pub use self::types::*;
42
43// Make some items publicly available for our own tests.
44pub mod test {
45    pub use crate::bench::Bencher;
46    pub use crate::cli::{TestOpts, parse_opts};
47    pub use crate::helpers::metrics::{Metric, MetricMap};
48    pub use crate::options::{Options, RunIgnored, RunStrategy, ShouldPanic};
49    pub use crate::test_result::{TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk};
50    pub use crate::time::{TestExecTime, TestTimeOptions};
51}
52
53use std::collections::VecDeque;
54use std::io::prelude::Write;
55use std::mem::ManuallyDrop;
56use std::panic::{self, AssertUnwindSafe, PanicHookInfo, catch_unwind};
57use std::process::{self, Command, Termination};
58use std::sync::mpsc::{Sender, channel};
59use std::sync::{Arc, Mutex};
60use std::time::{Duration, Instant};
61use std::{env, io, thread};
62
63pub mod bench;
64mod cli;
65mod console;
66mod event;
67mod formatters;
68mod helpers;
69mod options;
70pub mod stats;
71mod term;
72mod test_result;
73mod time;
74mod types;
75
76#[cfg(test)]
77mod tests;
78
79use core::any::Any;
80
81use event::{CompletedTest, TestEvent};
82use helpers::concurrency::get_concurrency;
83use helpers::shuffle::{get_shuffle_seed, shuffle_tests};
84use options::RunStrategy;
85use test_result::*;
86use time::TestExecTime;
87
88/// Process exit code to be used to indicate test failures.
89pub const ERROR_EXIT_CODE: u8 = 101;
90
91const SECONDARY_TEST_INVOKER_VAR: &str = "__RUST_TEST_INVOKE";
92const SECONDARY_TEST_BENCH_BENCHMARKS_VAR: &str = "__RUST_TEST_BENCH_BENCHMARKS";
93
94// The default console test runner. It accepts the command line
95// arguments and a vector of test_descs.
96pub fn test_main(args: &[String], tests: &[&TestDescAndFn]) -> ExitCode {
97    let tests = TestList::new(tests, TestListOrder::Unsorted);
98    test_main_inner(args, tests, None)
99}
100
101fn test_main_inner(args: &[String], tests: TestList<'_>, options: Option<Options>) -> ExitCode {
102    let mut opts = match cli::parse_opts(args) {
103        Some(Ok(o)) => o,
104        Some(Err(msg)) => {
105            eprintln!("error: {msg}");
106            return ERROR_EXIT_CODE.into();
107        }
108        None => return ExitCode::SUCCESS, // help was shown
109    };
110    if let Some(options) = options {
111        opts.options = options;
112    }
113    if opts.list {
114        if let Err(e) = console::list_tests_console(&opts, tests) {
115            eprintln!("error: io error when listing tests: {e:?}");
116            return ERROR_EXIT_CODE.into();
117        }
118    } else {
119        if !opts.nocapture {
120            // If we encounter a non-unwinding panic, flush any captured output from the current test,
121            // and stop capturing output to ensure that the non-unwinding panic message is visible.
122            // We also acquire the locks for both output streams to prevent output from other threads
123            // from interleaving with the panic message or appearing after it.
124            let builtin_panic_hook = panic::take_hook();
125            let hook = Box::new({
126                move |info: &'_ PanicHookInfo<'_>| {
127                    if !info.can_unwind() {
128                        std::mem::forget(std::io::stderr().lock());
129                        let mut stdout = ManuallyDrop::new(std::io::stdout().lock());
130                        if let Some(captured) = io::set_output_capture(None) {
131                            if let Ok(data) = captured.lock() {
132                                let _ = stdout.write_all(&data);
133                                let _ = stdout.flush();
134                            }
135                        }
136                    }
137                    builtin_panic_hook(info);
138                }
139            });
140            panic::set_hook(hook);
141            // Use a thread spawning hook to make new threads inherit output capturing.
142            std::thread::add_spawn_hook(|_| {
143                // Get and clone the output capture of the current thread.
144                let output_capture = io::set_output_capture(None);
145                io::set_output_capture(output_capture.clone());
146                // Set the output capture of the new thread.
147                || {
148                    io::set_output_capture(output_capture);
149                }
150            });
151        }
152        let res = console::run_tests_console(&opts, tests);
153        // Prevent Valgrind from reporting reachable blocks in users' unit tests.
154        drop(panic::take_hook());
155        match res {
156            Ok(true) => {}
157            Ok(false) => return ExitCode::from(ERROR_EXIT_CODE),
158            Err(e) => {
159                eprintln!("error: io error when listing tests: {e:?}");
160                return ExitCode::from(ERROR_EXIT_CODE);
161            }
162        }
163    }
164
165    ExitCode::SUCCESS
166}
167
168/// A variant that takes the arguments from the command line.
169///
170/// This is the entry point for the main function generated by `rustc --test`
171/// when panic=unwind.
172pub fn test_main_env_args(tests: &[&TestDescAndFn]) -> ExitCode {
173    // This is supposed to be reasonably fast even in Miri. In particular, when invoked via `--exact
174    // test`, we want the entire invocation to be `O(log n)` in the number of tests: never iterate
175    // the entire test list (as that list could be big)!
176    let args = env::args().collect::<Vec<_>>();
177    // Tests are sorted by name at compile time by mk_tests_slice.
178    let tests = TestList::new(tests, TestListOrder::Sorted);
179    test_main_inner(&args, tests, None)
180}
181
182/// A variant that takes the arguments from the command line.
183///
184/// Runs tests in panic=abort mode, which involves spawning subprocesses for
185/// tests. If we are invoked as subprocess, this function does not return.
186///
187/// This is the entry point for the main function generated by `rustc --test`
188/// when panic=abort.
189pub fn test_main_env_args_abort(tests: &[&TestDescAndFn]) -> ExitCode {
190    // If we're being run in SpawnedSecondary mode, run the test here. run_test
191    // will then exit the process.
192    if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) {
193        // SAFETY: Technically, this is a racy access that we probably shouldn't do?
194        // In practice, this is completely fine as long as the test harness is made of Rust,
195        // as std will synchronize the racy accesses that occur when they happen through std::env.
196        // Any unsoundness can only be exposed in practice if e.g. C code also takes an interest
197        // in these variables.
198        //
199        // If we ever grow an actual story for libtest and start documenting custom harness reqs,
200        // we should either fix this being racy or say "write it in Rust, please".
201        unsafe {
202            env::remove_var(SECONDARY_TEST_INVOKER_VAR);
203        }
204
205        // Convert benchmarks to tests if we're not benchmarking.
206        let mut tests = tests.iter().copied().cloned().collect::<Vec<_>>();
207        if env::var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR).is_ok() {
208            // SAFETY: Same as for SECONDARY_TEST_INVOKER_VAR
209            unsafe {
210                env::remove_var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR);
211            }
212        } else {
213            tests = convert_benchmarks_to_tests(tests);
214        };
215
216        let test = tests
217            .into_iter()
218            .find(|test| test.desc.name.as_slice() == name)
219            .unwrap_or_else(|| panic!("couldn't find a test with the provided name '{name}'"));
220        let TestDescAndFn { desc, testfn } = test;
221        match testfn.into_runnable() {
222            Runnable::Test(runnable_test) => {
223                if runnable_test.is_dynamic() {
224                    panic!("only static tests are supported");
225                }
226                run_test_in_spawned_subprocess(desc, runnable_test);
227            }
228            Runnable::Bench(_) => {
229                panic!("benchmarks should not be executed into child processes")
230            }
231        }
232        // Unreachable
233    }
234
235    let args = env::args().collect::<Vec<_>>();
236    // Tests are sorted by name at compile time by mk_tests_slice.
237    let tests = TestList::new(tests, TestListOrder::Sorted);
238    test_main_inner(&args, tests, Some(Options::new().panic_abort(true)))
239}
240
241/// Public API used by rustdoc to display the `total` and `compilation` times in the expected
242/// format.
243pub fn print_merged_doctests_times(args: &[String], total_time: f64, compilation_time: f64) {
244    let opts = match cli::parse_opts(args) {
245        Some(Ok(o)) => o,
246        Some(Err(msg)) => {
247            eprintln!("error: {msg}");
248            process::exit(ERROR_EXIT_CODE.into());
249        }
250        None => return,
251    };
252    let mut formatter = console::get_formatter(&opts, 0);
253    formatter.write_merged_doctests_times(total_time, compilation_time).unwrap();
254}
255
256/// Invoked when unit tests terminate. Returns `Result::Err` if the test is
257/// considered a failure. By default, invokes `report()` and checks for a `0`
258/// result.
259pub fn assert_test_result<T: Termination>(result: T) -> Result<(), String> {
260    let code = result.report().to_i32();
261    if code == 0 {
262        Ok(())
263    } else {
264        Err(format!(
265            "the test returned a termination value with a non-zero status code \
266             ({code}) which indicates a failure"
267        ))
268    }
269}
270
271struct FilteredTests {
272    tests: Vec<(TestId, TestDescAndFn)>,
273    benches: Vec<(TestId, TestDescAndFn)>,
274    next_id: usize,
275}
276
277impl FilteredTests {
278    fn add_bench(&mut self, desc: TestDesc, testfn: TestFn) {
279        let test = TestDescAndFn { desc, testfn };
280        self.benches.push((TestId(self.next_id), test));
281        self.next_id += 1;
282    }
283    fn add_test(&mut self, desc: TestDesc, testfn: TestFn) {
284        let test = TestDescAndFn { desc, testfn };
285        self.tests.push((TestId(self.next_id), test));
286        self.next_id += 1;
287    }
288    fn total_len(&self) -> usize {
289        self.tests.len() + self.benches.len()
290    }
291}
292
293pub fn run_tests<F>(
294    opts: &TestOpts,
295    mut filtered_tests: Vec<TestDescAndFn>,
296    all_tests_len: usize,
297    mut notify_about_test_event: F,
298) -> io::Result<()>
299where
300    F: FnMut(TestEvent) -> io::Result<()>,
301{
302    use std::collections::HashMap;
303    use std::hash::{BuildHasherDefault, DefaultHasher};
304    use std::sync::mpsc::RecvTimeoutError;
305
306    struct RunningTest {
307        join_handle: Option<thread::JoinHandle<()>>,
308    }
309
310    impl RunningTest {
311        fn join(self, completed_test: &mut CompletedTest) {
312            if let Some(join_handle) = self.join_handle {
313                if let Err(_) = join_handle.join() {
314                    if let TrOk = completed_test.result {
315                        completed_test.result =
316                            TrFailedMsg("panicked after reporting success".to_string());
317                    }
318                }
319            }
320        }
321    }
322
323    // Use a deterministic hasher
324    type TestMap = HashMap<TestId, RunningTest, BuildHasherDefault<DefaultHasher>>;
325
326    struct TimeoutEntry {
327        id: TestId,
328        desc: TestDesc,
329        timeout: Instant,
330    }
331
332    let mut filtered = FilteredTests { tests: Vec::new(), benches: Vec::new(), next_id: 0 };
333
334    if !opts.bench_benchmarks {
335        filtered_tests = convert_benchmarks_to_tests(filtered_tests);
336    }
337
338    for test in filtered_tests {
339        let mut desc = test.desc;
340        desc.name = desc.name.with_padding(test.testfn.padding());
341
342        match test.testfn {
343            DynBenchFn(_) | StaticBenchFn(_) => {
344                filtered.add_bench(desc, test.testfn);
345            }
346            testfn => {
347                filtered.add_test(desc, testfn);
348            }
349        };
350    }
351
352    let filtered_out = all_tests_len - filtered.total_len();
353    let event = TestEvent::TeFilteredOut(filtered_out);
354    notify_about_test_event(event)?;
355
356    let shuffle_seed = get_shuffle_seed(opts);
357
358    let event = TestEvent::TeFiltered(filtered.total_len(), shuffle_seed);
359    notify_about_test_event(event)?;
360
361    let concurrency = opts.test_threads.unwrap_or_else(get_concurrency);
362
363    let mut remaining = filtered.tests;
364    if let Some(shuffle_seed) = shuffle_seed {
365        shuffle_tests(shuffle_seed, &mut remaining);
366    }
367    // Store the tests in a VecDeque so we can efficiently remove the first element to run the
368    // tests in the order they were passed (unless shuffled).
369    let mut remaining = VecDeque::from(remaining);
370    let mut pending = 0;
371
372    let (tx, rx) = channel::<CompletedTest>();
373    let run_strategy = if opts.options.panic_abort && !opts.force_run_in_process {
374        RunStrategy::SpawnPrimary
375    } else {
376        RunStrategy::InProcess
377    };
378
379    let mut running_tests: TestMap = HashMap::default();
380    let mut timeout_queue: VecDeque<TimeoutEntry> = VecDeque::new();
381
382    fn get_timed_out_tests(
383        running_tests: &TestMap,
384        timeout_queue: &mut VecDeque<TimeoutEntry>,
385    ) -> Vec<TestDesc> {
386        let now = Instant::now();
387        let mut timed_out = Vec::new();
388        while let Some(timeout_entry) = timeout_queue.front() {
389            if now < timeout_entry.timeout {
390                break;
391            }
392            let timeout_entry = timeout_queue.pop_front().unwrap();
393            if running_tests.contains_key(&timeout_entry.id) {
394                timed_out.push(timeout_entry.desc);
395            }
396        }
397        timed_out
398    }
399
400    fn calc_timeout(timeout_queue: &VecDeque<TimeoutEntry>) -> Option<Duration> {
401        timeout_queue.front().map(|&TimeoutEntry { timeout: next_timeout, .. }| {
402            let now = Instant::now();
403            if next_timeout >= now { next_timeout - now } else { Duration::new(0, 0) }
404        })
405    }
406
407    if concurrency == 1 {
408        while !remaining.is_empty() {
409            let (id, test) = remaining.pop_front().unwrap();
410            let event = TestEvent::TeWait(test.desc.clone());
411            notify_about_test_event(event)?;
412            let join_handle = run_test(opts, !opts.run_tests, id, test, run_strategy, tx.clone());
413            // Wait for the test to complete.
414            let mut completed_test = rx.recv().unwrap();
415            RunningTest { join_handle }.join(&mut completed_test);
416
417            let fail_fast = match completed_test.result {
418                TrIgnored | TrOk | TrBench(_) => false,
419                TrFailed | TrFailedMsg(_) | TrTimedFail => opts.fail_fast,
420            };
421
422            let event = TestEvent::TeResult(completed_test);
423            notify_about_test_event(event)?;
424
425            if fail_fast {
426                return Ok(());
427            }
428        }
429    } else {
430        while pending > 0 || !remaining.is_empty() {
431            while pending < concurrency && !remaining.is_empty() {
432                let (id, test) = remaining.pop_front().unwrap();
433                let timeout = time::get_default_test_timeout();
434                let desc = test.desc.clone();
435
436                let event = TestEvent::TeWait(desc.clone());
437                notify_about_test_event(event)?; //here no pad
438                let join_handle =
439                    run_test(opts, !opts.run_tests, id, test, run_strategy, tx.clone());
440                running_tests.insert(id, RunningTest { join_handle });
441                timeout_queue.push_back(TimeoutEntry { id, desc, timeout });
442                pending += 1;
443            }
444
445            let mut res;
446            loop {
447                if let Some(timeout) = calc_timeout(&timeout_queue) {
448                    res = rx.recv_timeout(timeout);
449                    for test in get_timed_out_tests(&running_tests, &mut timeout_queue) {
450                        let event = TestEvent::TeTimeout(test);
451                        notify_about_test_event(event)?;
452                    }
453
454                    match res {
455                        Err(RecvTimeoutError::Timeout) => {
456                            // Result is not yet ready, continue waiting.
457                        }
458                        _ => {
459                            // We've got a result, stop the loop.
460                            break;
461                        }
462                    }
463                } else {
464                    res = rx.recv().map_err(|_| RecvTimeoutError::Disconnected);
465                    break;
466                }
467            }
468
469            let mut completed_test = res.unwrap();
470            let running_test = running_tests.remove(&completed_test.id).unwrap();
471            running_test.join(&mut completed_test);
472
473            let fail_fast = match completed_test.result {
474                TrIgnored | TrOk | TrBench(_) => false,
475                TrFailed | TrFailedMsg(_) | TrTimedFail => opts.fail_fast,
476            };
477
478            let event = TestEvent::TeResult(completed_test);
479            notify_about_test_event(event)?;
480            pending -= 1;
481
482            if fail_fast {
483                // Prevent remaining test threads from panicking
484                std::mem::forget(rx);
485                return Ok(());
486            }
487        }
488    }
489
490    if opts.bench_benchmarks {
491        // All benchmarks run at the end, in serial.
492        for (id, b) in filtered.benches {
493            let event = TestEvent::TeWait(b.desc.clone());
494            notify_about_test_event(event)?;
495            let join_handle = run_test(opts, false, id, b, run_strategy, tx.clone());
496            // Wait for the test to complete.
497            let mut completed_test = rx.recv().unwrap();
498            RunningTest { join_handle }.join(&mut completed_test);
499
500            let event = TestEvent::TeResult(completed_test);
501            notify_about_test_event(event)?;
502        }
503    }
504    Ok(())
505}
506
507pub fn filter_tests(opts: &TestOpts, tests: TestList<'_>) -> Vec<TestDescAndFn> {
508    let TestList { tests, order } = tests;
509
510    // Initial filtering: Remove tests that don't match the test filter.
511    let mut filtered = if opts.filters.is_empty() {
512        tests.iter().copied().cloned().collect::<Vec<_>>()
513    } else if opts.filter_exact && order == TestListOrder::Sorted {
514        // Let's say that `f` is the number of filters and `n` is the number
515        // of tests.
516        //
517        // The test array is sorted by name (guaranteed by the caller via
518        // TestListOrder::Sorted), so use binary search for O(f log n)
519        // exact-match lookups instead of an O(n) linear scan.
520        //
521        // This is important for Miri, where the interpreted execution makes
522        // the linear scan very expensive.
523        filter_exact_match(tests, &opts.filters)
524    } else {
525        tests
526            .iter()
527            .copied()
528            .filter(|test| {
529                let test_name = test.desc.name.as_slice();
530                opts.filters.iter().any(|filter| {
531                    if opts.filter_exact {
532                        test_name == filter.as_str()
533                    } else {
534                        test_name.contains(filter.as_str())
535                    }
536                })
537            })
538            .cloned()
539            .collect::<Vec<_>>()
540    };
541
542    // Skip tests that match any of the skip filters
543    //
544    // After exact positive filtering above, the filtered set is small, so a
545    // linear scan is acceptable even under Miri.
546    if !opts.skip.is_empty() {
547        filtered.retain(|test| {
548            let name = test.desc.name.as_slice();
549            !opts.skip.iter().any(|sf| {
550                if opts.filter_exact { name == sf.as_str() } else { name.contains(sf.as_str()) }
551            })
552        });
553    }
554
555    // Excludes #[should_panic] tests
556    if opts.exclude_should_panic {
557        filtered.retain(|test| test.desc.should_panic == ShouldPanic::No);
558    }
559
560    // maybe unignore tests
561    match opts.run_ignored {
562        RunIgnored::Yes => {
563            filtered.iter_mut().for_each(|test| test.desc.ignore = false);
564        }
565        RunIgnored::Only => {
566            filtered.retain(|test| test.desc.ignore);
567            filtered.iter_mut().for_each(|test| test.desc.ignore = false);
568        }
569        RunIgnored::No => {}
570    }
571
572    filtered
573}
574
575/// Extract tests whose names exactly match one of the given `filters`, using
576/// binary search on the (assumed sorted) test list.
577fn filter_exact_match<'a>(tests: &[&'a TestDescAndFn], filters: &[String]) -> Vec<TestDescAndFn> {
578    // Binary search for each filter in the sorted test list.
579    let mut indexes: Vec<usize> = filters
580        .iter()
581        .filter_map(|f| tests.binary_search_by(|t| t.desc.name.as_slice().cmp(f.as_str())).ok())
582        .collect();
583    indexes.sort_unstable();
584    indexes.dedup();
585
586    // Extract matching tests.
587    let mut result = Vec::with_capacity(indexes.len());
588    for &idx in indexes.iter() {
589        result.push(tests[idx].clone());
590    }
591    result
592}
593
594pub fn convert_benchmarks_to_tests(tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
595    // convert benchmarks to tests, if we're not benchmarking them
596    tests
597        .into_iter()
598        .map(|x| {
599            let testfn = match x.testfn {
600                DynBenchFn(benchfn) => DynBenchAsTestFn(benchfn),
601                StaticBenchFn(benchfn) => StaticBenchAsTestFn(benchfn),
602                f => f,
603            };
604            TestDescAndFn { desc: x.desc, testfn }
605        })
606        .collect()
607}
608
609pub fn run_test(
610    opts: &TestOpts,
611    force_ignore: bool,
612    id: TestId,
613    test: TestDescAndFn,
614    strategy: RunStrategy,
615    monitor_ch: Sender<CompletedTest>,
616) -> Option<thread::JoinHandle<()>> {
617    let TestDescAndFn { desc, testfn } = test;
618
619    // Emscripten can catch panics but other wasm targets cannot
620    let ignore_because_no_process_support = desc.should_panic != ShouldPanic::No
621        && (cfg!(target_family = "wasm") || cfg!(target_os = "zkvm"))
622        && !cfg!(target_os = "emscripten");
623
624    if force_ignore || desc.ignore || ignore_because_no_process_support {
625        let message = CompletedTest::new(id, desc, TrIgnored, None, Vec::new());
626        monitor_ch.send(message).unwrap();
627        return None;
628    }
629
630    match testfn.into_runnable() {
631        Runnable::Test(runnable_test) => {
632            if runnable_test.is_dynamic() {
633                match strategy {
634                    RunStrategy::InProcess => (),
635                    _ => panic!("Cannot run dynamic test fn out-of-process"),
636                };
637            }
638
639            let name = desc.name.clone();
640            let nocapture = opts.nocapture;
641            let time_options = opts.time_options;
642            let bench_benchmarks = opts.bench_benchmarks;
643
644            let runtest = move || match strategy {
645                RunStrategy::InProcess => run_test_in_process(
646                    id,
647                    desc,
648                    nocapture,
649                    time_options.is_some(),
650                    runnable_test,
651                    monitor_ch,
652                    time_options,
653                ),
654                RunStrategy::SpawnPrimary => spawn_test_subprocess(
655                    id,
656                    desc,
657                    nocapture,
658                    time_options.is_some(),
659                    monitor_ch,
660                    time_options,
661                    bench_benchmarks,
662                ),
663            };
664
665            // If the platform is single-threaded we're just going to run
666            // the test synchronously, regardless of the concurrency
667            // level.
668            let supports_threads = !cfg!(target_os = "emscripten")
669                && !cfg!(target_family = "wasm")
670                && !cfg!(target_os = "zkvm");
671            if supports_threads {
672                let cfg = thread::Builder::new().name(name.as_slice().to_owned());
673                let mut runtest = Arc::new(Mutex::new(Some(runtest)));
674                let runtest2 = runtest.clone();
675                match cfg.spawn(move || runtest2.lock().unwrap().take().unwrap()()) {
676                    Ok(handle) => Some(handle),
677                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
678                        // `ErrorKind::WouldBlock` means hitting the thread limit on some
679                        // platforms, so run the test synchronously here instead.
680                        Arc::get_mut(&mut runtest).unwrap().get_mut().unwrap().take().unwrap()();
681                        None
682                    }
683                    Err(e) => panic!("failed to spawn thread to run test: {e}"),
684                }
685            } else {
686                runtest();
687                None
688            }
689        }
690        Runnable::Bench(runnable_bench) => {
691            // Benchmarks aren't expected to panic, so we run them all in-process.
692            runnable_bench.run(id, &desc, &monitor_ch, opts.nocapture);
693            None
694        }
695    }
696}
697
698/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
699#[inline(never)]
700fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
701    let result = f();
702
703    // prevent this frame from being tail-call optimised away
704    black_box(result)
705}
706
707fn run_test_in_process(
708    id: TestId,
709    desc: TestDesc,
710    nocapture: bool,
711    report_time: bool,
712    runnable_test: RunnableTest,
713    monitor_ch: Sender<CompletedTest>,
714    time_opts: Option<time::TestTimeOptions>,
715) {
716    // Buffer for capturing standard I/O
717    let data = Arc::new(Mutex::new(Vec::new()));
718
719    if !nocapture {
720        io::set_output_capture(Some(data.clone()));
721    }
722
723    let start = report_time.then(Instant::now);
724    let result = fold_err(catch_unwind(AssertUnwindSafe(|| runnable_test.run())));
725    let exec_time = start.map(|start| {
726        let duration = start.elapsed();
727        TestExecTime(duration)
728    });
729
730    io::set_output_capture(None);
731
732    // Determine whether the test passed or failed, by comparing its panic
733    // payload (if any) with its `ShouldPanic` value, and by checking for
734    // fatal timeout.
735    let test_result =
736        calc_result(&desc, result.err().as_deref(), time_opts.as_ref(), exec_time.as_ref());
737    let stdout = data.lock().unwrap_or_else(|e| e.into_inner()).to_vec();
738    let message = CompletedTest::new(id, desc, test_result, exec_time, stdout);
739    monitor_ch.send(message).unwrap();
740}
741
742fn fold_err<T, E>(
743    result: Result<Result<T, E>, Box<dyn Any + Send>>,
744) -> Result<T, Box<dyn Any + Send>>
745where
746    E: Send + 'static,
747{
748    match result {
749        Ok(Err(e)) => Err(Box::new(e)),
750        Ok(Ok(v)) => Ok(v),
751        Err(e) => Err(e),
752    }
753}
754
755fn spawn_test_subprocess(
756    id: TestId,
757    desc: TestDesc,
758    nocapture: bool,
759    report_time: bool,
760    monitor_ch: Sender<CompletedTest>,
761    time_opts: Option<time::TestTimeOptions>,
762    bench_benchmarks: bool,
763) {
764    let (result, test_output, exec_time) = (|| {
765        let args = env::args().collect::<Vec<_>>();
766        let current_exe = &args[0];
767
768        let mut command = Command::new(current_exe);
769        command.env(SECONDARY_TEST_INVOKER_VAR, desc.name.as_slice());
770        if bench_benchmarks {
771            command.env(SECONDARY_TEST_BENCH_BENCHMARKS_VAR, "1");
772        }
773        if nocapture {
774            command.stdout(process::Stdio::inherit());
775            command.stderr(process::Stdio::inherit());
776        }
777
778        let start = report_time.then(Instant::now);
779        let output = match command.output() {
780            Ok(out) => out,
781            Err(e) => {
782                let err = format!("Failed to spawn {} as child for test: {:?}", args[0], e);
783                return (TrFailed, err.into_bytes(), None);
784            }
785        };
786        let exec_time = start.map(|start| {
787            let duration = start.elapsed();
788            TestExecTime(duration)
789        });
790
791        let std::process::Output { stdout, stderr, status } = output;
792        let mut test_output = stdout;
793        formatters::write_stderr_delimiter(&mut test_output, &desc.name);
794        test_output.extend_from_slice(&stderr);
795
796        let result =
797            get_result_from_exit_code(&desc, status, time_opts.as_ref(), exec_time.as_ref());
798        (result, test_output, exec_time)
799    })();
800
801    let message = CompletedTest::new(id, desc, result, exec_time, test_output);
802    monitor_ch.send(message).unwrap();
803}
804
805fn run_test_in_spawned_subprocess(desc: TestDesc, runnable_test: RunnableTest) -> ! {
806    let builtin_panic_hook = panic::take_hook();
807    let record_result = Arc::new(move |panic_info: Option<&'_ PanicHookInfo<'_>>| {
808        let test_result = calc_result(&desc, panic_info.map(|info| info.payload()), None, None);
809
810        // We don't support serializing TrFailedMsg, so just
811        // print the message out to stderr.
812        if let TrFailedMsg(msg) = &test_result {
813            eprintln!("{msg}");
814        }
815
816        if let Some(info) = panic_info {
817            builtin_panic_hook(info);
818        }
819
820        if let TrOk = test_result {
821            process::exit(test_result::TR_OK);
822        } else {
823            process::abort();
824        }
825    });
826    let record_result2 = record_result.clone();
827    panic::set_hook(Box::new(move |info| record_result2(Some(info))));
828    if let Err(message) = runnable_test.run() {
829        panic!("{}", message);
830    }
831    record_result(None);
832    unreachable!("panic=abort callback should have exited the process")
833}