1#![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; pub 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
43pub 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
88pub 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
94pub 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, };
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 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 std::thread::add_spawn_hook(|_| {
143 let output_capture = io::set_output_capture(None);
145 io::set_output_capture(output_capture.clone());
146 || {
148 io::set_output_capture(output_capture);
149 }
150 });
151 }
152 let res = console::run_tests_console(&opts, tests);
153 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
168pub fn test_main_env_args(tests: &[&TestDescAndFn]) -> ExitCode {
173 let args = env::args().collect::<Vec<_>>();
177 let tests = TestList::new(tests, TestListOrder::Sorted);
179 test_main_inner(&args, tests, None)
180}
181
182pub fn test_main_env_args_abort(tests: &[&TestDescAndFn]) -> ExitCode {
190 if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) {
193 unsafe {
202 env::remove_var(SECONDARY_TEST_INVOKER_VAR);
203 }
204
205 let mut tests = tests.iter().copied().cloned().collect::<Vec<_>>();
207 if env::var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR).is_ok() {
208 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 }
234
235 let args = env::args().collect::<Vec<_>>();
236 let tests = TestList::new(tests, TestListOrder::Sorted);
238 test_main_inner(&args, tests, Some(Options::new().panic_abort(true)))
239}
240
241pub 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
256pub 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 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 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 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)?; 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 }
458 _ => {
459 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 std::mem::forget(rx);
485 return Ok(());
486 }
487 }
488 }
489
490 if opts.bench_benchmarks {
491 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 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 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 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 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 if opts.exclude_should_panic {
557 filtered.retain(|test| test.desc.should_panic == ShouldPanic::No);
558 }
559
560 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
575fn filter_exact_match<'a>(tests: &[&'a TestDescAndFn], filters: &[String]) -> Vec<TestDescAndFn> {
578 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 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 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 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 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 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 runnable_bench.run(id, &desc, &monitor_ch, opts.nocapture);
693 None
694 }
695 }
696}
697
698#[inline(never)]
700fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
701 let result = f();
702
703 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 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 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 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}