Spawn one subprocess per unit test when panic=abort
This commit is contained in:
parent
76b12bd8fd
commit
8ae1ec60cc
@ -440,6 +440,7 @@ fn configure_and_expand_inner<'a>(
|
||||
&mut krate,
|
||||
sess.diagnostic(),
|
||||
&sess.features_untracked(),
|
||||
sess.panic_strategy(),
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user