Skip to content

Commit e19a98c

Browse files
committed
Do not panic when a test function returns Result::Err.
Rust's test library allows test functions to return a Result, so that the test is deemed to have failed if the function returns a Result::Err variant. Currently, this works by having Result implement the Termination trait and asserting in assert_test_result that Termination::report() indicates successful completion. This turns a Result::Err into a panic, which is caught and unwound in the test library. This approach is problematic in certain environments where one wishes to save on both binary size and compute resources when running tests by: * Compiling all code with --panic=abort to avoid having to generate unwinding tables, and * Running most tests in-process to avoid the overhead of spawning new processes. This change removes the intermediate panic step and passes a Result::Err directly through to the test runner. To do this, it modifies assert_test_result to return a Result<(), String> where the Err variant holds what was previously the panic message. It changes the types in the TestFn enum to return Result<(), String>. This tries to minimise the changes to benchmark tests, so it calls unwrap() on the Result returned by assert_test_result, effectively keeping the same behaviour as before.
1 parent 2d1aa57 commit e19a98c

File tree

7 files changed

+135
-60
lines changed

7 files changed

+135
-60
lines changed

library/test/src/bench.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ impl Bencher {
4949
self.summary = Some(iter(&mut inner));
5050
}
5151

52-
pub fn bench<F>(&mut self, mut f: F) -> Option<stats::Summary>
52+
pub fn bench<F>(&mut self, mut f: F) -> Result<Option<stats::Summary>, String>
5353
where
54-
F: FnMut(&mut Bencher),
54+
F: FnMut(&mut Bencher) -> Result<(), String>,
5555
{
56-
f(self);
57-
self.summary
56+
let result = f(self);
57+
result.map(|_| self.summary)
5858
}
5959
}
6060

@@ -195,7 +195,7 @@ pub fn benchmark<F>(
195195
nocapture: bool,
196196
f: F,
197197
) where
198-
F: FnMut(&mut Bencher),
198+
F: FnMut(&mut Bencher) -> Result<(), String>,
199199
{
200200
let mut bs = Bencher { mode: BenchMode::Auto, summary: None, bytes: 0 };
201201

@@ -211,32 +211,33 @@ pub fn benchmark<F>(
211211

212212
let test_result = match result {
213213
//bs.bench(f) {
214-
Ok(Some(ns_iter_summ)) => {
214+
Ok(Ok(Some(ns_iter_summ))) => {
215215
let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
216216
let mb_s = bs.bytes * 1000 / ns_iter;
217217

218218
let bs = BenchSamples { ns_iter_summ, mb_s: mb_s as usize };
219219
TestResult::TrBench(bs)
220220
}
221-
Ok(None) => {
221+
Ok(Ok(None)) => {
222222
// iter not called, so no data.
223223
// FIXME: error in this case?
224224
let samples: &mut [f64] = &mut [0.0_f64; 1];
225225
let bs = BenchSamples { ns_iter_summ: stats::Summary::new(samples), mb_s: 0 };
226226
TestResult::TrBench(bs)
227227
}
228228
Err(_) => TestResult::TrFailed,
229+
Ok(Err(_)) => TestResult::TrFailed,
229230
};
230231

231232
let stdout = data.lock().unwrap().to_vec();
232233
let message = CompletedTest::new(id, desc, test_result, None, stdout);
233234
monitor_ch.send(message).unwrap();
234235
}
235236

236-
pub fn run_once<F>(f: F)
237+
pub fn run_once<F>(f: F) -> Result<(), String>
237238
where
238-
F: FnMut(&mut Bencher),
239+
F: FnMut(&mut Bencher) -> Result<(), String>,
239240
{
240241
let mut bs = Bencher { mode: BenchMode::Single, summary: None, bytes: 0 };
241-
bs.bench(f);
242+
bs.bench(f).map(|_| ())
242243
}

library/test/src/lib.rs

+42-19
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
//! benchmarks themselves) should be done via the `#[test]` and
77
//! `#[bench]` attributes.
88
//!
9-
//! See the [Testing Chapter](../book/ch11-00-testing.html) of the book for more details.
9+
//! See the [Testing Chapter](../book/ch11-00-testing.html) of the book for more
10+
//! details.
1011
1112
// Currently, not much of this is meant for users. It is intended to
1213
// support the simplest interface possible for representing and
@@ -77,6 +78,7 @@ mod types;
7778
#[cfg(test)]
7879
mod tests;
7980

81+
use core::any::Any;
8082
use event::{CompletedTest, TestEvent};
8183
use helpers::concurrency::get_concurrency;
8284
use helpers::exit_code::get_exit_code;
@@ -176,17 +178,20 @@ fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
176178
}
177179
}
178180

179-
/// Invoked when unit tests terminate. Should panic if the unit
180-
/// Tests is considered a failure. By default, invokes `report()`
181-
/// and checks for a `0` result.
182-
pub fn assert_test_result<T: Termination>(result: T) {
181+
/// Invoked when unit tests terminate. Returns `Result::Err` if the test is
182+
/// considered a failure. By default, invokes `report() and checks for a `0`
183+
/// result.
184+
pub fn assert_test_result<T: Termination>(result: T) -> Result<(), String> {
183185
let code = result.report().to_i32();
184-
assert_eq!(
185-
code, 0,
186-
"the test returned a termination value with a non-zero status code ({}) \
187-
which indicates a failure",
188-
code
189-
);
186+
if code == 0 {
187+
Ok(())
188+
} else {
189+
Err(format!(
190+
"the test returned a termination value with a non-zero status code \
191+
({}) which indicates a failure",
192+
code
193+
))
194+
}
190195
}
191196

192197
pub fn run_tests<F>(
@@ -479,7 +484,7 @@ pub fn run_test(
479484
id: TestId,
480485
desc: TestDesc,
481486
monitor_ch: Sender<CompletedTest>,
482-
testfn: Box<dyn FnOnce() + Send>,
487+
testfn: Box<dyn FnOnce() -> Result<(), String> + Send>,
483488
opts: TestRunOpts,
484489
) -> Option<thread::JoinHandle<()>> {
485490
let concurrency = opts.concurrency;
@@ -568,19 +573,19 @@ pub fn run_test(
568573

569574
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
570575
#[inline(never)]
571-
fn __rust_begin_short_backtrace<F: FnOnce()>(f: F) {
572-
f();
576+
fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
577+
let result = f();
573578

574579
// prevent this frame from being tail-call optimised away
575-
black_box(());
580+
black_box(result)
576581
}
577582

578583
fn run_test_in_process(
579584
id: TestId,
580585
desc: TestDesc,
581586
nocapture: bool,
582587
report_time: bool,
583-
testfn: Box<dyn FnOnce() + Send>,
588+
testfn: Box<dyn FnOnce() -> Result<(), String> + Send>,
584589
monitor_ch: Sender<CompletedTest>,
585590
time_opts: Option<time::TestTimeOptions>,
586591
) {
@@ -592,7 +597,7 @@ fn run_test_in_process(
592597
}
593598

594599
let start = report_time.then(Instant::now);
595-
let result = catch_unwind(AssertUnwindSafe(testfn));
600+
let result = fold_err(catch_unwind(AssertUnwindSafe(testfn)));
596601
let exec_time = start.map(|start| {
597602
let duration = start.elapsed();
598603
TestExecTime(duration)
@@ -609,6 +614,19 @@ fn run_test_in_process(
609614
monitor_ch.send(message).unwrap();
610615
}
611616

617+
fn fold_err<T, E>(
618+
result: Result<Result<T, E>, Box<dyn Any + Send>>,
619+
) -> Result<T, Box<dyn Any + Send>>
620+
where
621+
E: Send + 'static,
622+
{
623+
match result {
624+
Ok(Err(e)) => Err(Box::new(e)),
625+
Ok(Ok(v)) => Ok(v),
626+
Err(e) => Err(e),
627+
}
628+
}
629+
612630
fn spawn_test_subprocess(
613631
id: TestId,
614632
desc: TestDesc,
@@ -664,7 +682,10 @@ fn spawn_test_subprocess(
664682
monitor_ch.send(message).unwrap();
665683
}
666684

667-
fn run_test_in_spawned_subprocess(desc: TestDesc, testfn: Box<dyn FnOnce() + Send>) -> ! {
685+
fn run_test_in_spawned_subprocess(
686+
desc: TestDesc,
687+
testfn: Box<dyn FnOnce() -> Result<(), String> + Send>,
688+
) -> ! {
668689
let builtin_panic_hook = panic::take_hook();
669690
let record_result = Arc::new(move |panic_info: Option<&'_ PanicInfo<'_>>| {
670691
let test_result = match panic_info {
@@ -690,7 +711,9 @@ fn run_test_in_spawned_subprocess(desc: TestDesc, testfn: Box<dyn FnOnce() + Sen
690711
});
691712
let record_result2 = record_result.clone();
692713
panic::set_hook(Box::new(move |info| record_result2(Some(&info))));
693-
testfn();
714+
if let Err(message) = testfn() {
715+
panic!("{}", message);
716+
}
694717
record_result(None);
695718
unreachable!("panic=abort callback should have exited the process")
696719
}

0 commit comments

Comments
 (0)