Spawn one subprocess per unit test when panic=abort

This commit is contained in:
Tyler Mandry 2019-09-11 17:13:34 -07:00
parent 76b12bd8fd
commit 8ae1ec60cc
5 changed files with 304 additions and 100 deletions

View File

@ -440,6 +440,7 @@ fn configure_and_expand_inner<'a>(
&mut krate,
sess.diagnostic(),
&sess.features_untracked(),
sess.panic_strategy(),
)
});

View File

@ -2,6 +2,7 @@
use log::debug;
use smallvec::{smallvec, SmallVec};
use rustc_target::spec::PanicStrategy;
use syntax::ast::{self, Ident};
use syntax::attr;
use syntax::entry::{self, EntryPointType};
@ -25,6 +26,7 @@ struct Test {
struct TestCtxt<'a> {
ext_cx: ExtCtxt<'a>,
panic_strategy: PanicStrategy,
def_site: Span,
test_cases: Vec<Test>,
reexport_test_harness_main: Option<Symbol>,
@ -40,6 +42,7 @@ pub fn inject(
krate: &mut ast::Crate,
span_diagnostic: &errors::Handler,
features: &Features,
panic_strategy: PanicStrategy,
) {
// Check for #![reexport_test_harness_main = "some_name"] which gives the
// main test function the name `some_name` without hygiene. This needs to be
@ -54,7 +57,7 @@ pub fn inject(
if should_test {
generate_test_harness(sess, resolver, reexport_test_harness_main,
krate, features, test_runner)
krate, features, panic_strategy, test_runner)
}
}
@ -183,6 +186,7 @@ fn generate_test_harness(sess: &ParseSess,
reexport_test_harness_main: Option<Symbol>,
krate: &mut ast::Crate,
features: &Features,
panic_strategy: PanicStrategy,
test_runner: Option<ast::Path>) {
let mut econfig = ExpansionConfig::default("test".to_string());
econfig.features = Some(features);
@ -203,6 +207,7 @@ fn generate_test_harness(sess: &ParseSess,
let cx = TestCtxt {
ext_cx,
panic_strategy,
def_site,
test_cases: Vec::new(),
reexport_test_harness_main,
@ -248,9 +253,14 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
let ecx = &cx.ext_cx;
let test_id = Ident::new(sym::test, sp);
let runner_name = match cx.panic_strategy {
PanicStrategy::Unwind => "test_main_static",
PanicStrategy::Abort => "test_main_static_abort",
};
// test::test_main_static(...)
let mut test_runner = cx.test_runner.clone().unwrap_or(
ecx.path(sp, vec![test_id, ecx.ident_of("test_main_static", sp)]));
ecx.path(sp, vec![test_id, ecx.ident_of(runner_name, sp)]));
test_runner.span = sp;

View File

@ -22,3 +22,12 @@ pub(crate) trait OutputFormatter {
) -> io::Result<()>;
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool>;
}
pub(crate) fn write_stderr_delimiter(test_output: &mut Vec<u8>, test_name: &TestName) {
match test_output.last() {
Some(b'\n') => (),
Some(_) => test_output.push(b'\n'),
None => (),
}
write!(test_output, "---- {} stderr ----\n", test_name).unwrap();
}

View File

@ -21,7 +21,8 @@
#![unstable(feature = "test", issue = "50297")]
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))]
#![feature(asm)]
#![cfg_attr(any(unix, target_os = "cloudabi"), feature(libc, rustc_private))]
#![cfg_attr(any(unix, target_os = "cloudabi"), feature(libc))]
#![feature(rustc_private)]
#![feature(nll)]
#![feature(set_stdio)]
#![feature(panic_unwind)]
@ -34,16 +35,6 @@ use getopts;
extern crate libc;
use term;
// FIXME(#54291): rustc and/or LLVM don't yet support building with panic-unwind
// on aarch64-pc-windows-msvc, or thumbv7a-pc-windows-msvc
// so we don't link libtest against libunwind (for the time being)
// even though it means that libtest won't be fully functional on
// these platforms.
//
// See also: https://github.com/rust-lang/rust/issues/54190#issuecomment-422904437
#[cfg(not(all(windows, any(target_arch = "aarch64", target_arch = "arm"))))]
extern crate panic_unwind;
pub use self::ColorConfig::*;
use self::NamePadding::*;
use self::OutputLocation::*;
@ -61,10 +52,10 @@ use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo};
use std::path::PathBuf;
use std::process;
use std::process::Termination;
use std::process::{ExitStatus, Command, Termination};
use std::sync::mpsc::{channel, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
@ -76,13 +67,21 @@ mod tests;
const TEST_WARN_TIMEOUT_S: u64 = 60;
const QUIET_MODE_MAX_COLUMN: usize = 100; // insert a '\n' after 100 tests in quiet mode
const SECONDARY_TEST_INVOKER_VAR: &'static str = "__RUST_TEST_INVOKE";
// Return codes for secondary process.
// Start somewhere other than 0 so we know the return code means what we think
// it means.
const TR_OK: i32 = 50;
const TR_FAILED: i32 = 51;
// to be used by rustc to compile tests in libtest
pub mod test {
pub use crate::{
assert_test_result, filter_tests, parse_opts, run_test, test_main, test_main_static,
Bencher, DynTestFn, DynTestName, Metric, MetricMap, Options, RunIgnored, ShouldPanic,
StaticBenchFn, StaticTestFn, StaticTestName, TestDesc, TestDescAndFn, TestName, TestOpts,
TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk,
Bencher, DynTestFn, DynTestName, Metric, MetricMap, Options, RunIgnored, RunStrategy,
ShouldPanic, StaticBenchFn, StaticTestFn, StaticTestName, TestDesc, TestDescAndFn, TestName,
TestOpts, TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk,
};
}
@ -257,12 +256,14 @@ impl Metric {
#[derive(Copy, Clone, Debug)]
pub struct Options {
display_output: bool,
panic_abort: bool,
}
impl Options {
pub fn new() -> Options {
Options {
display_output: false,
panic_abort: false,
}
}
@ -270,6 +271,11 @@ impl Options {
self.display_output = display_output;
self
}
pub fn panic_abort(mut self, panic_abort: bool) -> Options {
self.panic_abort = panic_abort;
self
}
}
// The default console test runner. It accepts the command line
@ -303,32 +309,66 @@ pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Option<Opt
}
}
// A variant optimized for invocation with a static test vector.
// This will panic (intentionally) when fed any dynamic tests, because
// it is copying the static values out into a dynamic vector and cannot
// copy dynamic values. It is doing this because from this point on
// a Vec<TestDescAndFn> is used in order to effect ownership-transfer
// semantics into parallel test runners, which in turn requires a Vec<>
// rather than a &[].
/// A variant optimized for invocation with a static test vector.
/// This will panic (intentionally) when fed any dynamic tests.
///
/// This is the entry point for the main function generated by `rustc --test`
/// when panic=unwind.
pub fn test_main_static(tests: &[&TestDescAndFn]) {
let args = env::args().collect::<Vec<_>>();
let owned_tests = tests
.iter()
.map(|t| match t.testfn {
StaticTestFn(f) => TestDescAndFn {
testfn: StaticTestFn(f),
desc: t.desc.clone(),
},
StaticBenchFn(f) => TestDescAndFn {
testfn: StaticBenchFn(f),
desc: t.desc.clone(),
},
_ => panic!("non-static tests passed to test::test_main_static"),
})
.collect();
let owned_tests: Vec<_> = tests.iter().map(make_owned_test).collect();
test_main(&args, owned_tests, None)
}
/// A variant optimized for invocation with a static test vector.
/// This will panic (intentionally) when fed any dynamic tests.
///
/// Runs tests in panic=abort mode, which involves spawning subprocesses for
/// tests.
///
/// This is the entry point for the main function generated by `rustc --test`
/// when panic=abort.
pub fn test_main_static_abort(tests: &[&TestDescAndFn]) {
// If we're being run in SpawnedSecondary mode, run the test here. run_test
// will then exit the process.
if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) {
let test = tests
.iter()
.filter(|test| test.desc.name.as_slice() == name)
.map(make_owned_test)
.next()
.expect("couldn't find a test with the provided name");
let TestDescAndFn { desc, testfn } = test;
let testfn = match testfn {
StaticTestFn(f) => f,
_ => panic!("only static tests are supported"),
};
run_test_in_spawned_subprocess(desc, Box::new(testfn));
}
let args = env::args().collect::<Vec<_>>();
let owned_tests: Vec<_> = tests.iter().map(make_owned_test).collect();
test_main(&args, owned_tests, Some(Options::new().panic_abort(true)))
}
/// Clones static values for putting into a dynamic vector, which test_main()
/// needs to hand out ownership of tests to parallel test runners.
///
/// This will panic when fed any dynamic tests, because they cannot be cloned.
fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
match test.testfn {
StaticTestFn(f) => TestDescAndFn {
testfn: StaticTestFn(f),
desc: test.desc.clone(),
},
StaticBenchFn(f) => TestDescAndFn {
testfn: StaticBenchFn(f),
desc: test.desc.clone(),
},
_ => panic!("non-static tests passed to test::test_main_static"),
}
}
/// Invoked when unit tests terminate. Should panic if the unit
/// Tests is considered a failure. By default, invokes `report()`
/// and checks for a `0` result.
@ -1062,6 +1102,18 @@ impl Write for Sink {
}
}
#[derive(Clone, Copy)]
pub enum RunStrategy {
/// Runs the test in the current process, and sends the result back over the
/// supplied channel.
InProcess,
/// Spawns a subprocess to run the test, and sends the result back over the
/// supplied channel. Requires argv[0] to exist and point to the binary
/// that's currently running.
SpawnPrimary,
}
pub fn run_tests<F>(opts: &TestOpts, tests: Vec<TestDescAndFn>, mut callback: F) -> io::Result<()>
where
F: FnMut(TestEvent) -> io::Result<()>,
@ -1109,6 +1161,11 @@ where
let mut pending = 0;
let (tx, rx) = channel::<MonitorMsg>();
let run_strategy = if opts.options.panic_abort {
RunStrategy::SpawnPrimary
} else {
RunStrategy::InProcess
};
let mut running_tests: TestMap = HashMap::default();
@ -1145,7 +1202,7 @@ where
while !remaining.is_empty() {
let test = remaining.pop().unwrap();
callback(TeWait(test.desc.clone()))?;
run_test(opts, !opts.run_tests, test, tx.clone(), Concurrent::No);
run_test(opts, !opts.run_tests, test, run_strategy, tx.clone(), Concurrent::No);
let (test, result, exec_time, stdout) = rx.recv().unwrap();
callback(TeResult(test, result, exec_time, stdout))?;
}
@ -1156,7 +1213,7 @@ where
let timeout = Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S);
running_tests.insert(test.desc.clone(), timeout);
callback(TeWait(test.desc.clone()))?; //here no pad
run_test(opts, !opts.run_tests, test, tx.clone(), Concurrent::Yes);
run_test(opts, !opts.run_tests, test, run_strategy, tx.clone(), Concurrent::Yes);
pending += 1;
}
@ -1188,7 +1245,7 @@ where
// All benchmarks run at the end, in serial.
for b in filtered_benchs {
callback(TeWait(b.desc.clone()))?;
run_test(opts, false, b, tx.clone(), Concurrent::No);
run_test(opts, false, b, run_strategy, tx.clone(), Concurrent::No);
let (test, result, exec_time, stdout) = rx.recv().unwrap();
callback(TeResult(test, result, exec_time, stdout))?;
}
@ -1415,64 +1472,38 @@ pub fn run_test(
opts: &TestOpts,
force_ignore: bool,
test: TestDescAndFn,
strategy: RunStrategy,
monitor_ch: Sender<MonitorMsg>,
concurrency: Concurrent,
) {
let TestDescAndFn { desc, testfn } = test;
let ignore_because_panic_abort = cfg!(target_arch = "wasm32")
let ignore_because_no_process_support = cfg!(target_arch = "wasm32")
&& !cfg!(target_os = "emscripten")
&& desc.should_panic != ShouldPanic::No;
if force_ignore || desc.ignore || ignore_because_panic_abort {
if force_ignore || desc.ignore || ignore_because_no_process_support {
monitor_ch.send((desc, TrIgnored, None, Vec::new())).unwrap();
return;
}
fn run_test_inner(
desc: TestDesc,
monitor_ch: Sender<MonitorMsg>,
nocapture: bool,
report_time: bool,
strategy: RunStrategy,
monitor_ch: Sender<MonitorMsg>,
testfn: Box<dyn FnOnce() + Send>,
concurrency: Concurrent,
) {
// Buffer for capturing standard I/O
let data = Arc::new(Mutex::new(Vec::new()));
let data2 = data.clone();
let name = desc.name.clone();
let runtest = move || {
let oldio = if !nocapture {
Some((
io::set_print(Some(Box::new(Sink(data2.clone())))),
io::set_panic(Some(Box::new(Sink(data2)))),
))
} else {
None
};
let start = if report_time {
Some(Instant::now())
} else {
None
};
let result = catch_unwind(AssertUnwindSafe(testfn));
let exec_time = start.map(|start| {
let duration = start.elapsed();
TestExecTime(duration)
});
if let Some((printio, panicio)) = oldio {
io::set_print(printio);
io::set_panic(panicio);
};
let test_result = calc_result(&desc, result);
let stdout = data.lock().unwrap().to_vec();
monitor_ch
.send((desc.clone(), test_result, exec_time, stdout))
.unwrap();
match strategy {
RunStrategy::InProcess =>
run_test_in_process(desc, nocapture, report_time, testfn, monitor_ch),
RunStrategy::SpawnPrimary => spawn_test_subprocess(desc, report_time, monitor_ch),
}
};
// If the platform is single-threaded we're just going to run
@ -1489,31 +1520,38 @@ pub fn run_test(
match testfn {
DynBenchFn(bencher) => {
// Benchmarks aren't expected to panic, so we run them all in-process.
crate::bench::benchmark(desc, monitor_ch, opts.nocapture, |harness| {
bencher.run(harness)
});
}
StaticBenchFn(benchfn) => {
// Benchmarks aren't expected to panic, so we run them all in-process.
crate::bench::benchmark(desc, monitor_ch, opts.nocapture, |harness| {
(benchfn.clone())(harness)
});
}
DynTestFn(f) => {
let cb = move || __rust_begin_short_backtrace(f);
match strategy {
RunStrategy::InProcess => (),
_ => panic!("Cannot run dynamic test fn out-of-process"),
};
run_test_inner(
desc,
monitor_ch,
opts.nocapture,
opts.report_time,
Box::new(cb),
concurrency,
)
strategy,
monitor_ch,
Box::new(move || __rust_begin_short_backtrace(f)),
concurrency
);
}
StaticTestFn(f) => run_test_inner(
desc,
monitor_ch,
opts.nocapture,
opts.report_time,
strategy,
monitor_ch,
Box::new(move || __rust_begin_short_backtrace(f)),
concurrency,
),
@ -1526,7 +1564,9 @@ fn __rust_begin_short_backtrace<F: FnOnce()>(f: F) {
f()
}
fn calc_result(desc: &TestDesc, task_result: Result<(), Box<dyn Any + Send>>) -> TestResult {
fn calc_result<'a>(desc: &TestDesc,
task_result: Result<(), &'a (dyn Any + 'static + Send)>)
-> TestResult {
match (&desc.should_panic, task_result) {
(&ShouldPanic::No, Ok(())) | (&ShouldPanic::Yes, Err(_)) => TrOk,
(&ShouldPanic::YesWithMessage(msg), Err(ref err)) => {
@ -1552,6 +1592,150 @@ fn calc_result(desc: &TestDesc, task_result: Result<(), Box<dyn Any + Send>>) ->
}
}
fn get_result_from_exit_code(desc: &TestDesc, code: i32) -> TestResult {
match (desc.allow_fail, code) {
(_, TR_OK) => TrOk,
(true, TR_FAILED) => TrAllowedFail,
(false, TR_FAILED) => TrFailed,
(_, _) => TrFailedMsg(format!("got unexpected return code {}", code)),
}
}
fn run_test_in_process(desc: TestDesc,
nocapture: bool,
report_time: bool,
testfn: Box<dyn FnOnce() + Send>,
monitor_ch: Sender<MonitorMsg>) {
// Buffer for capturing standard I/O
let data = Arc::new(Mutex::new(Vec::new()));
let oldio = if !nocapture {
Some((
io::set_print(Some(Box::new(Sink(data.clone())))),
io::set_panic(Some(Box::new(Sink(data.clone())))),
))
} else {
None
};
let start = if report_time {
Some(Instant::now())
} else {
None
};
let result = catch_unwind(AssertUnwindSafe(testfn));
let exec_time = start.map(|start| {
let duration = start.elapsed();
TestExecTime(duration)
});
if let Some((printio, panicio)) = oldio {
io::set_print(printio);
io::set_panic(panicio);
}
let test_result = match result {
Ok(()) => calc_result(&desc, Ok(())),
Err(e) => calc_result(&desc, Err(e.as_ref())),
};
let stdout = data.lock().unwrap().to_vec();
monitor_ch.send((desc.clone(), test_result, exec_time, stdout)).unwrap();
}
fn spawn_test_subprocess(desc: TestDesc, report_time: bool, monitor_ch: Sender<MonitorMsg>) {
let (result, test_output, exec_time) = (|| {
let args = env::args().collect::<Vec<_>>();
let current_exe = &args[0];
let start = if report_time {
Some(Instant::now())
} else {
None
};
let output = match Command::new(current_exe)
.env(SECONDARY_TEST_INVOKER_VAR, desc.name.as_slice())
.output() {
Ok(out) => out,
Err(e) => {
let err = format!("Failed to spawn {} as child for test: {:?}", args[0], e);
return (TrFailed, err.into_bytes(), None);
}
};
let exec_time = start.map(|start| {
let duration = start.elapsed();
TestExecTime(duration)
});
let std::process::Output { stdout, stderr, status } = output;
let mut test_output = stdout;
formatters::write_stderr_delimiter(&mut test_output, &desc.name);
test_output.extend_from_slice(&stderr);
let result = match (|| -> Result<TestResult, String> {
let exit_code = get_exit_code(status)?;
Ok(get_result_from_exit_code(&desc, exit_code))
})() {
Ok(r) => r,
Err(e) => {
write!(&mut test_output, "Unexpected error: {}", e).unwrap();
TrFailed
}
};
(result, test_output, exec_time)
})();
monitor_ch.send((desc.clone(), result, exec_time, test_output)).unwrap();
}
fn run_test_in_spawned_subprocess(desc: TestDesc, testfn: Box<dyn FnOnce() + Send>) -> ! {
let builtin_panic_hook = panic::take_hook();
let record_result = Arc::new(move |panic_info: Option<&'_ PanicInfo<'_>>| {
let test_result = match panic_info {
Some(info) => calc_result(&desc, Err(info.payload())),
None => calc_result(&desc, Ok(())),
};
// We don't support serializing TrFailedMsg, so just
// print the message out to stderr.
if let TrFailedMsg(msg) = &test_result {
eprintln!("{}", msg);
}
if let Some(info) = panic_info {
builtin_panic_hook(info);
}
if let TrOk = test_result {
process::exit(TR_OK);
} else {
process::exit(TR_FAILED);
}
});
let record_result2 = record_result.clone();
panic::set_hook(Box::new(move |info| record_result2(Some(&info))));
testfn();
record_result(None);
unreachable!("panic=abort callback should have exited the process")
}
#[cfg(not(unix))]
fn get_exit_code(status: ExitStatus) -> Result<i32, String> {
status.code().ok_or("received no exit code from child process".into())
}
#[cfg(unix)]
fn get_exit_code(status: ExitStatus) -> Result<i32, String> {
use std::os::unix::process::ExitStatusExt;
match status.code() {
Some(code) => Ok(code),
None => match status.signal() {
Some(signal) => Err(format!("child process exited with signal {}", signal)),
None => Err("child process exited with unknown signal".into()),
}
}
}
#[derive(Clone, PartialEq)]
pub struct MetricMap(BTreeMap<String, Metric>);
@ -1700,7 +1884,9 @@ where
}
pub mod bench {
use super::{BenchMode, BenchSamples, Bencher, MonitorMsg, Sender, Sink, TestDesc, TestResult};
use super::{
BenchMode, BenchSamples, Bencher, MonitorMsg, Sender, Sink, TestDesc, TestResult
};
use crate::stats;
use std::cmp;
use std::io;
@ -1718,12 +1904,10 @@ pub mod bench {
};
let data = Arc::new(Mutex::new(Vec::new()));
let data2 = data.clone();
let oldio = if !nocapture {
Some((
io::set_print(Some(Box::new(Sink(data2.clone())))),
io::set_panic(Some(Box::new(Sink(data2)))),
io::set_print(Some(Box::new(Sink(data.clone())))),
io::set_panic(Some(Box::new(Sink(data.clone())))),
))
} else {
None
@ -1734,7 +1918,7 @@ pub mod bench {
if let Some((printio, panicio)) = oldio {
io::set_print(printio);
io::set_panic(panicio);
};
}
let test_result = match result {
//bs.bench(f) {

View File

@ -1,7 +1,7 @@
use super::*;
use crate::test::{
filter_tests, parse_opts, run_test, DynTestFn, DynTestName, MetricMap, RunIgnored,
filter_tests, parse_opts, run_test, DynTestFn, DynTestName, MetricMap, RunIgnored, RunStrategy,
ShouldPanic, StaticTestName, TestDesc, TestDescAndFn, TestOpts, TrFailedMsg,
TrIgnored, TrOk,
};
@ -67,7 +67,7 @@ pub fn do_not_run_ignored_tests() {
testfn: DynTestFn(Box::new(f)),
};
let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
run_test(&TestOpts::new(), false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, res, _, _) = rx.recv().unwrap();
assert!(res != TrOk);
}
@ -85,7 +85,7 @@ pub fn ignored_tests_result_in_ignored() {
testfn: DynTestFn(Box::new(f)),
};
let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
run_test(&TestOpts::new(), false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrIgnored);
}
@ -105,7 +105,7 @@ fn test_should_panic() {
testfn: DynTestFn(Box::new(f)),
};
let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
run_test(&TestOpts::new(), false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrOk);
}
@ -125,7 +125,7 @@ fn test_should_panic_good_message() {
testfn: DynTestFn(Box::new(f)),
};
let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
run_test(&TestOpts::new(), false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrOk);
}
@ -147,7 +147,7 @@ fn test_should_panic_bad_message() {
testfn: DynTestFn(Box::new(f)),
};
let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
run_test(&TestOpts::new(), false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrFailedMsg(format!("{} '{}'", failed_msg, expected)));
}
@ -165,7 +165,7 @@ fn test_should_panic_but_succeeds() {
testfn: DynTestFn(Box::new(f)),
};
let (tx, rx) = channel();
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
run_test(&TestOpts::new(), false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, res, _, _) = rx.recv().unwrap();
assert!(res == TrFailedMsg("test did not panic as expected".to_string()));
}
@ -186,7 +186,7 @@ fn report_time_test_template(report_time: bool) -> Option<TestExecTime> {
..TestOpts::new()
};
let (tx, rx) = channel();
run_test(&test_opts, false, desc, tx, Concurrent::No);
run_test(&test_opts, false, desc, RunStrategy::InProcess, tx, Concurrent::No);
let (_, _, exec_time, _) = rx.recv().unwrap();
exec_time
}