]> git.lizzy.rs Git - rust.git/blob - library/test/src/lib.rs
1e8c63b69dbaf2ae39a9e2b5ea51dde8da4fc493
[rust.git] / library / test / src / 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 details.
10
11 // Currently, not much of this is meant for users. It is intended to
12 // support the simplest interface possible for representing and
13 // running tests while providing a base that other test frameworks may
14 // build off of.
15
16 #![unstable(feature = "test", issue = "50297")]
17 #![doc(test(attr(deny(warnings))))]
18 #![feature(libc)]
19 #![feature(rustc_private)]
20 #![feature(nll)]
21 #![feature(available_parallelism)]
22 #![feature(bench_black_box)]
23 #![feature(internal_output_capture)]
24 #![feature(panic_unwind)]
25 #![feature(staged_api)]
26 #![feature(termination_trait_lib)]
27 #![feature(test)]
28 #![feature(total_cmp)]
29
30 // Public reexports
31 pub use self::bench::{black_box, Bencher};
32 pub use self::console::run_tests_console;
33 pub use self::options::{ColorConfig, Options, OutputFormat, RunIgnored, ShouldPanic};
34 pub use self::types::TestName::*;
35 pub use self::types::*;
36 pub use self::ColorConfig::*;
37 pub use cli::TestOpts;
38
39 // Module to be used by rustc to compile tests in libtest
40 pub mod test {
41     pub use crate::{
42         assert_test_result,
43         bench::Bencher,
44         cli::{parse_opts, TestOpts},
45         filter_tests,
46         helpers::metrics::{Metric, MetricMap},
47         options::{Concurrent, Options, RunIgnored, RunStrategy, ShouldPanic},
48         run_test, test_main, test_main_static,
49         test_result::{TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk},
50         time::{TestExecTime, TestTimeOptions},
51         types::{
52             DynTestFn, DynTestName, StaticBenchFn, StaticTestFn, StaticTestName, TestDesc,
53             TestDescAndFn, TestId, TestName, TestType,
54         },
55     };
56 }
57
58 use std::{
59     collections::VecDeque,
60     env, io,
61     io::prelude::Write,
62     panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo},
63     process::{self, Command, Termination},
64     sync::mpsc::{channel, Sender},
65     sync::{Arc, Mutex},
66     thread,
67     time::{Duration, Instant},
68 };
69
70 pub mod bench;
71 mod cli;
72 mod console;
73 mod event;
74 mod formatters;
75 mod helpers;
76 mod options;
77 pub mod stats;
78 mod term;
79 mod test_result;
80 mod time;
81 mod types;
82
83 #[cfg(test)]
84 mod tests;
85
86 use event::{CompletedTest, TestEvent};
87 use helpers::concurrency::get_concurrency;
88 use helpers::exit_code::get_exit_code;
89 use helpers::shuffle::{get_shuffle_seed, shuffle_tests};
90 use options::{Concurrent, RunStrategy};
91 use test_result::*;
92 use time::TestExecTime;
93
94 // Process exit code to be used to indicate test failures.
95 const ERROR_EXIT_CODE: i32 = 101;
96
97 const SECONDARY_TEST_INVOKER_VAR: &str = "__RUST_TEST_INVOKE";
98
99 // The default console test runner. It accepts the command line
100 // arguments and a vector of test_descs.
101 pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Option<Options>) {
102     let mut opts = match cli::parse_opts(args) {
103         Some(Ok(o)) => o,
104         Some(Err(msg)) => {
105             eprintln!("error: {}", msg);
106             process::exit(ERROR_EXIT_CODE);
107         }
108         None => return,
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             process::exit(ERROR_EXIT_CODE);
117         }
118     } else {
119         match console::run_tests_console(&opts, tests) {
120             Ok(true) => {}
121             Ok(false) => process::exit(ERROR_EXIT_CODE),
122             Err(e) => {
123                 eprintln!("error: io error when listing tests: {:?}", e);
124                 process::exit(ERROR_EXIT_CODE);
125             }
126         }
127     }
128 }
129
130 /// A variant optimized for invocation with a static test vector.
131 /// This will panic (intentionally) when fed any dynamic tests.
132 ///
133 /// This is the entry point for the main function generated by `rustc --test`
134 /// when panic=unwind.
135 pub fn test_main_static(tests: &[&TestDescAndFn]) {
136     let args = env::args().collect::<Vec<_>>();
137     let owned_tests: Vec<_> = tests.iter().map(make_owned_test).collect();
138     test_main(&args, owned_tests, None)
139 }
140
141 /// A variant optimized for invocation with a static test vector.
142 /// This will panic (intentionally) when fed any dynamic tests.
143 ///
144 /// Runs tests in panic=abort mode, which involves spawning subprocesses for
145 /// tests.
146 ///
147 /// This is the entry point for the main function generated by `rustc --test`
148 /// when panic=abort.
149 pub fn test_main_static_abort(tests: &[&TestDescAndFn]) {
150     // If we're being run in SpawnedSecondary mode, run the test here. run_test
151     // will then exit the process.
152     if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) {
153         env::remove_var(SECONDARY_TEST_INVOKER_VAR);
154         let test = tests
155             .iter()
156             .filter(|test| test.desc.name.as_slice() == name)
157             .map(make_owned_test)
158             .next()
159             .unwrap_or_else(|| panic!("couldn't find a test with the provided name '{}'", name));
160         let TestDescAndFn { desc, testfn } = test;
161         let testfn = match testfn {
162             StaticTestFn(f) => f,
163             _ => panic!("only static tests are supported"),
164         };
165         run_test_in_spawned_subprocess(desc, Box::new(testfn));
166     }
167
168     let args = env::args().collect::<Vec<_>>();
169     let owned_tests: Vec<_> = tests.iter().map(make_owned_test).collect();
170     test_main(&args, owned_tests, Some(Options::new().panic_abort(true)))
171 }
172
173 /// Clones static values for putting into a dynamic vector, which test_main()
174 /// needs to hand out ownership of tests to parallel test runners.
175 ///
176 /// This will panic when fed any dynamic tests, because they cannot be cloned.
177 fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
178     match test.testfn {
179         StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: test.desc.clone() },
180         StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: test.desc.clone() },
181         _ => panic!("non-static tests passed to test::test_main_static"),
182     }
183 }
184
185 /// Invoked when unit tests terminate. Should panic if the unit
186 /// Tests is considered a failure. By default, invokes `report()`
187 /// and checks for a `0` result.
188 pub fn assert_test_result<T: Termination>(result: T) {
189     let code = result.report();
190     assert_eq!(
191         code, 0,
192         "the test returned a termination value with a non-zero status code ({}) \
193          which indicates a failure",
194         code
195     );
196 }
197
198 pub fn run_tests<F>(
199     opts: &TestOpts,
200     tests: Vec<TestDescAndFn>,
201     mut notify_about_test_event: F,
202 ) -> io::Result<()>
203 where
204     F: FnMut(TestEvent) -> io::Result<()>,
205 {
206     use std::collections::{self, HashMap};
207     use std::hash::BuildHasherDefault;
208     use std::sync::mpsc::RecvTimeoutError;
209
210     struct RunningTest {
211         join_handle: Option<thread::JoinHandle<()>>,
212     }
213
214     // Use a deterministic hasher
215     type TestMap =
216         HashMap<TestId, RunningTest, BuildHasherDefault<collections::hash_map::DefaultHasher>>;
217
218     struct TimeoutEntry {
219         id: TestId,
220         desc: TestDesc,
221         timeout: Instant,
222     }
223
224     let tests_len = tests.len();
225
226     let mut filtered_tests = filter_tests(opts, tests);
227     if !opts.bench_benchmarks {
228         filtered_tests = convert_benchmarks_to_tests(filtered_tests);
229     }
230
231     let filtered_tests = {
232         let mut filtered_tests = filtered_tests;
233         for test in filtered_tests.iter_mut() {
234             test.desc.name = test.desc.name.with_padding(test.testfn.padding());
235         }
236
237         filtered_tests
238     };
239
240     let filtered_out = tests_len - filtered_tests.len();
241     let event = TestEvent::TeFilteredOut(filtered_out);
242     notify_about_test_event(event)?;
243
244     let filtered_descs = filtered_tests.iter().map(|t| t.desc.clone()).collect();
245
246     let shuffle_seed = get_shuffle_seed(opts);
247
248     let event = TestEvent::TeFiltered(filtered_descs, shuffle_seed);
249     notify_about_test_event(event)?;
250
251     let (filtered_tests, filtered_benchs): (Vec<_>, _) = filtered_tests
252         .into_iter()
253         .enumerate()
254         .map(|(i, e)| (TestId(i), e))
255         .partition(|(_, e)| matches!(e.testfn, StaticTestFn(_) | DynTestFn(_)));
256
257     let concurrency = opts.test_threads.unwrap_or_else(get_concurrency);
258
259     let mut remaining = filtered_tests;
260     if let Some(shuffle_seed) = shuffle_seed {
261         shuffle_tests(shuffle_seed, &mut remaining);
262     } else {
263         remaining.reverse();
264     }
265     let mut pending = 0;
266
267     let (tx, rx) = channel::<CompletedTest>();
268     let run_strategy = if opts.options.panic_abort && !opts.force_run_in_process {
269         RunStrategy::SpawnPrimary
270     } else {
271         RunStrategy::InProcess
272     };
273
274     let mut running_tests: TestMap = HashMap::default();
275     let mut timeout_queue: VecDeque<TimeoutEntry> = VecDeque::new();
276
277     fn get_timed_out_tests(
278         running_tests: &TestMap,
279         timeout_queue: &mut VecDeque<TimeoutEntry>,
280     ) -> Vec<TestDesc> {
281         let now = Instant::now();
282         let mut timed_out = Vec::new();
283         while let Some(timeout_entry) = timeout_queue.front() {
284             if now < timeout_entry.timeout {
285                 break;
286             }
287             let timeout_entry = timeout_queue.pop_front().unwrap();
288             if running_tests.contains_key(&timeout_entry.id) {
289                 timed_out.push(timeout_entry.desc);
290             }
291         }
292         timed_out
293     }
294
295     fn calc_timeout(timeout_queue: &VecDeque<TimeoutEntry>) -> Option<Duration> {
296         timeout_queue.front().map(|&TimeoutEntry { timeout: next_timeout, .. }| {
297             let now = Instant::now();
298             if next_timeout >= now { next_timeout - now } else { Duration::new(0, 0) }
299         })
300     }
301
302     if concurrency == 1 {
303         while !remaining.is_empty() {
304             let (id, test) = remaining.pop().unwrap();
305             let event = TestEvent::TeWait(test.desc.clone());
306             notify_about_test_event(event)?;
307             let join_handle =
308                 run_test(opts, !opts.run_tests, id, test, run_strategy, tx.clone(), Concurrent::No);
309             assert!(join_handle.is_none());
310             let completed_test = rx.recv().unwrap();
311
312             let event = TestEvent::TeResult(completed_test);
313             notify_about_test_event(event)?;
314         }
315     } else {
316         while pending > 0 || !remaining.is_empty() {
317             while pending < concurrency && !remaining.is_empty() {
318                 let (id, test) = remaining.pop().unwrap();
319                 let timeout = time::get_default_test_timeout();
320                 let desc = test.desc.clone();
321
322                 let event = TestEvent::TeWait(desc.clone());
323                 notify_about_test_event(event)?; //here no pad
324                 let join_handle = run_test(
325                     opts,
326                     !opts.run_tests,
327                     id,
328                     test,
329                     run_strategy,
330                     tx.clone(),
331                     Concurrent::Yes,
332                 );
333                 running_tests.insert(id, RunningTest { join_handle });
334                 timeout_queue.push_back(TimeoutEntry { id, desc, timeout });
335                 pending += 1;
336             }
337
338             let mut res;
339             loop {
340                 if let Some(timeout) = calc_timeout(&timeout_queue) {
341                     res = rx.recv_timeout(timeout);
342                     for test in get_timed_out_tests(&running_tests, &mut timeout_queue) {
343                         let event = TestEvent::TeTimeout(test);
344                         notify_about_test_event(event)?;
345                     }
346
347                     match res {
348                         Err(RecvTimeoutError::Timeout) => {
349                             // Result is not yet ready, continue waiting.
350                         }
351                         _ => {
352                             // We've got a result, stop the loop.
353                             break;
354                         }
355                     }
356                 } else {
357                     res = rx.recv().map_err(|_| RecvTimeoutError::Disconnected);
358                     break;
359                 }
360             }
361
362             let mut completed_test = res.unwrap();
363             let running_test = running_tests.remove(&completed_test.id).unwrap();
364             if let Some(join_handle) = running_test.join_handle {
365                 if let Err(_) = join_handle.join() {
366                     if let TrOk = completed_test.result {
367                         completed_test.result =
368                             TrFailedMsg("panicked after reporting success".to_string());
369                     }
370                 }
371             }
372
373             let event = TestEvent::TeResult(completed_test);
374             notify_about_test_event(event)?;
375             pending -= 1;
376         }
377     }
378
379     if opts.bench_benchmarks {
380         // All benchmarks run at the end, in serial.
381         for (id, b) in filtered_benchs {
382             let event = TestEvent::TeWait(b.desc.clone());
383             notify_about_test_event(event)?;
384             run_test(opts, false, id, b, run_strategy, tx.clone(), Concurrent::No);
385             let completed_test = rx.recv().unwrap();
386
387             let event = TestEvent::TeResult(completed_test);
388             notify_about_test_event(event)?;
389         }
390     }
391     Ok(())
392 }
393
394 pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
395     let mut filtered = tests;
396     let matches_filter = |test: &TestDescAndFn, filter: &str| {
397         let test_name = test.desc.name.as_slice();
398
399         match opts.filter_exact {
400             true => test_name == filter,
401             false => test_name.contains(filter),
402         }
403     };
404
405     // Remove tests that don't match the test filter
406     if !opts.filters.is_empty() {
407         filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
408     }
409
410     // Skip tests that match any of the skip filters
411     filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
412
413     // Excludes #[should_panic] tests
414     if opts.exclude_should_panic {
415         filtered.retain(|test| test.desc.should_panic == ShouldPanic::No);
416     }
417
418     // maybe unignore tests
419     match opts.run_ignored {
420         RunIgnored::Yes => {
421             filtered.iter_mut().for_each(|test| test.desc.ignore = false);
422         }
423         RunIgnored::Only => {
424             filtered.retain(|test| test.desc.ignore);
425             filtered.iter_mut().for_each(|test| test.desc.ignore = false);
426         }
427         RunIgnored::No => {}
428     }
429
430     // Sort the tests alphabetically
431     filtered.sort_by(|t1, t2| t1.desc.name.as_slice().cmp(t2.desc.name.as_slice()));
432
433     filtered
434 }
435
436 pub fn convert_benchmarks_to_tests(tests: Vec<TestDescAndFn>) -> Vec<TestDescAndFn> {
437     // convert benchmarks to tests, if we're not benchmarking them
438     tests
439         .into_iter()
440         .map(|x| {
441             let testfn = match x.testfn {
442                 DynBenchFn(benchfn) => DynTestFn(Box::new(move || {
443                     bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b)))
444                 })),
445                 StaticBenchFn(benchfn) => DynTestFn(Box::new(move || {
446                     bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b)))
447                 })),
448                 f => f,
449             };
450             TestDescAndFn { desc: x.desc, testfn }
451         })
452         .collect()
453 }
454
455 pub fn run_test(
456     opts: &TestOpts,
457     force_ignore: bool,
458     id: TestId,
459     test: TestDescAndFn,
460     strategy: RunStrategy,
461     monitor_ch: Sender<CompletedTest>,
462     concurrency: Concurrent,
463 ) -> Option<thread::JoinHandle<()>> {
464     let TestDescAndFn { desc, testfn } = test;
465
466     // Emscripten can catch panics but other wasm targets cannot
467     let ignore_because_no_process_support = desc.should_panic != ShouldPanic::No
468         && cfg!(target_family = "wasm")
469         && !cfg!(target_os = "emscripten");
470
471     if force_ignore || desc.ignore || ignore_because_no_process_support {
472         let message = CompletedTest::new(id, desc, TrIgnored, None, Vec::new());
473         monitor_ch.send(message).unwrap();
474         return None;
475     }
476
477     struct TestRunOpts {
478         pub strategy: RunStrategy,
479         pub nocapture: bool,
480         pub concurrency: Concurrent,
481         pub time: Option<time::TestTimeOptions>,
482     }
483
484     fn run_test_inner(
485         id: TestId,
486         desc: TestDesc,
487         monitor_ch: Sender<CompletedTest>,
488         testfn: Box<dyn FnOnce() + Send>,
489         opts: TestRunOpts,
490     ) -> Option<thread::JoinHandle<()>> {
491         let concurrency = opts.concurrency;
492         let name = desc.name.clone();
493
494         let runtest = move || match opts.strategy {
495             RunStrategy::InProcess => run_test_in_process(
496                 id,
497                 desc,
498                 opts.nocapture,
499                 opts.time.is_some(),
500                 testfn,
501                 monitor_ch,
502                 opts.time,
503             ),
504             RunStrategy::SpawnPrimary => spawn_test_subprocess(
505                 id,
506                 desc,
507                 opts.nocapture,
508                 opts.time.is_some(),
509                 monitor_ch,
510                 opts.time,
511             ),
512         };
513
514         // If the platform is single-threaded we're just going to run
515         // the test synchronously, regardless of the concurrency
516         // level.
517         let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_family = "wasm");
518         if concurrency == Concurrent::Yes && supports_threads {
519             let cfg = thread::Builder::new().name(name.as_slice().to_owned());
520             let mut runtest = Arc::new(Mutex::new(Some(runtest)));
521             let runtest2 = runtest.clone();
522             match cfg.spawn(move || runtest2.lock().unwrap().take().unwrap()()) {
523                 Ok(handle) => Some(handle),
524                 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
525                     // `ErrorKind::WouldBlock` means hitting the thread limit on some
526                     // platforms, so run the test synchronously here instead.
527                     Arc::get_mut(&mut runtest).unwrap().get_mut().unwrap().take().unwrap()();
528                     None
529                 }
530                 Err(e) => panic!("failed to spawn thread to run test: {}", e),
531             }
532         } else {
533             runtest();
534             None
535         }
536     }
537
538     let test_run_opts =
539         TestRunOpts { strategy, nocapture: opts.nocapture, concurrency, time: opts.time_options };
540
541     match testfn {
542         DynBenchFn(benchfn) => {
543             // Benchmarks aren't expected to panic, so we run them all in-process.
544             crate::bench::benchmark(id, desc, monitor_ch, opts.nocapture, benchfn);
545             None
546         }
547         StaticBenchFn(benchfn) => {
548             // Benchmarks aren't expected to panic, so we run them all in-process.
549             crate::bench::benchmark(id, desc, monitor_ch, opts.nocapture, benchfn);
550             None
551         }
552         DynTestFn(f) => {
553             match strategy {
554                 RunStrategy::InProcess => (),
555                 _ => panic!("Cannot run dynamic test fn out-of-process"),
556             };
557             run_test_inner(
558                 id,
559                 desc,
560                 monitor_ch,
561                 Box::new(move || __rust_begin_short_backtrace(f)),
562                 test_run_opts,
563             )
564         }
565         StaticTestFn(f) => run_test_inner(
566             id,
567             desc,
568             monitor_ch,
569             Box::new(move || __rust_begin_short_backtrace(f)),
570             test_run_opts,
571         ),
572     }
573 }
574
575 /// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
576 #[inline(never)]
577 fn __rust_begin_short_backtrace<F: FnOnce()>(f: F) {
578     f();
579
580     // prevent this frame from being tail-call optimised away
581     black_box(());
582 }
583
584 fn run_test_in_process(
585     id: TestId,
586     desc: TestDesc,
587     nocapture: bool,
588     report_time: bool,
589     testfn: Box<dyn FnOnce() + Send>,
590     monitor_ch: Sender<CompletedTest>,
591     time_opts: Option<time::TestTimeOptions>,
592 ) {
593     // Buffer for capturing standard I/O
594     let data = Arc::new(Mutex::new(Vec::new()));
595
596     if !nocapture {
597         io::set_output_capture(Some(data.clone()));
598     }
599
600     let start = report_time.then(Instant::now);
601     let result = catch_unwind(AssertUnwindSafe(testfn));
602     let exec_time = start.map(|start| {
603         let duration = start.elapsed();
604         TestExecTime(duration)
605     });
606
607     io::set_output_capture(None);
608
609     let test_result = match result {
610         Ok(()) => calc_result(&desc, Ok(()), &time_opts, &exec_time),
611         Err(e) => calc_result(&desc, Err(e.as_ref()), &time_opts, &exec_time),
612     };
613     let stdout = data.lock().unwrap_or_else(|e| e.into_inner()).to_vec();
614     let message = CompletedTest::new(id, desc, test_result, exec_time, stdout);
615     monitor_ch.send(message).unwrap();
616 }
617
618 fn spawn_test_subprocess(
619     id: TestId,
620     desc: TestDesc,
621     nocapture: bool,
622     report_time: bool,
623     monitor_ch: Sender<CompletedTest>,
624     time_opts: Option<time::TestTimeOptions>,
625 ) {
626     let (result, test_output, exec_time) = (|| {
627         let args = env::args().collect::<Vec<_>>();
628         let current_exe = &args[0];
629
630         let mut command = Command::new(current_exe);
631         command.env(SECONDARY_TEST_INVOKER_VAR, desc.name.as_slice());
632         if nocapture {
633             command.stdout(process::Stdio::inherit());
634             command.stderr(process::Stdio::inherit());
635         }
636
637         let start = report_time.then(Instant::now);
638         let output = match command.output() {
639             Ok(out) => out,
640             Err(e) => {
641                 let err = format!("Failed to spawn {} as child for test: {:?}", args[0], e);
642                 return (TrFailed, err.into_bytes(), None);
643             }
644         };
645         let exec_time = start.map(|start| {
646             let duration = start.elapsed();
647             TestExecTime(duration)
648         });
649
650         let std::process::Output { stdout, stderr, status } = output;
651         let mut test_output = stdout;
652         formatters::write_stderr_delimiter(&mut test_output, &desc.name);
653         test_output.extend_from_slice(&stderr);
654
655         let result = match (|| -> Result<TestResult, String> {
656             let exit_code = get_exit_code(status)?;
657             Ok(get_result_from_exit_code(&desc, exit_code, &time_opts, &exec_time))
658         })() {
659             Ok(r) => r,
660             Err(e) => {
661                 write!(&mut test_output, "Unexpected error: {}", e).unwrap();
662                 TrFailed
663             }
664         };
665
666         (result, test_output, exec_time)
667     })();
668
669     let message = CompletedTest::new(id, desc, result, exec_time, test_output);
670     monitor_ch.send(message).unwrap();
671 }
672
673 fn run_test_in_spawned_subprocess(desc: TestDesc, testfn: Box<dyn FnOnce() + Send>) -> ! {
674     let builtin_panic_hook = panic::take_hook();
675     let record_result = Arc::new(move |panic_info: Option<&'_ PanicInfo<'_>>| {
676         let test_result = match panic_info {
677             Some(info) => calc_result(&desc, Err(info.payload()), &None, &None),
678             None => calc_result(&desc, Ok(()), &None, &None),
679         };
680
681         // We don't support serializing TrFailedMsg, so just
682         // print the message out to stderr.
683         if let TrFailedMsg(msg) = &test_result {
684             eprintln!("{}", msg);
685         }
686
687         if let Some(info) = panic_info {
688             builtin_panic_hook(info);
689         }
690
691         if let TrOk = test_result {
692             process::exit(test_result::TR_OK);
693         } else {
694             process::exit(test_result::TR_FAILED);
695         }
696     });
697     let record_result2 = record_result.clone();
698     panic::set_hook(Box::new(move |info| record_result2(Some(&info))));
699     testfn();
700     record_result(None);
701     unreachable!("panic=abort callback should have exited the process")
702 }