Expand description
Controlled panics using dynamic type checking.
Sometimes there is a need to test how Rust code behaves on occurrence of a panic. A panic can be invoked on purpose in a thread spawned by the test and the effects observed after the thread is joined. The problem with “benign” panics is that it may be cumbersome to tell them apart from panics indicating actual errors, such as assertion failures.
Another issue is the behavior of the default panic hook. It is very useful for getting information about the cause of an unexpected thread panic, but for tests causing panics on purpose it produces annoying output noise. The panic hook can be overridden, but custom panic hooks affect the entire program, which in typical usage is the test runner; it is easy to misuse them causing important error information to go unreported.
The simplest way, as provided by the standard library, to propagate
a panic that occurred in a child thread to the thread that spawned it
is to call unwrap
on the result of JoinHandle::join
. Unfortunately,
due to an issue with
the implementation of Any
, the resulting panic message does not relay
information from the child thread’s panic.
This crate provides utilities and an ergonomic interface for testing panics in a controlled and output-friendly way using dynamic type checks to discern between expected and unexpected panics.
§Expected Panic Type
The recommended way to designate panics as expected is by using values of
a custom type as the parameter for panic!
. The type could be as simple
as a token unit-like struct, or it can be equipped to carry additional
information from the panic site.
Any panic value type shall be Sized
, 'static
, and Send
.
For the value to be usable in testing, it should also implement
at least Debug
and PartialEq
.
§Examples
use panic_control::{Context, Outcome};
use panic_control::{chain_hook_ignoring, spawn_quiet};
use panic_control::ThreadResultExt;
use std::thread;
#[derive(Debug, PartialEq, Eq)]
enum Expected {
Token,
Int(i32),
String(String)
}
// Rust's stock test runner does not provide a way to do global
// initialization and the tests are run in parallel in a random
// order by default. So this is our solution, to be called at
// the beginning of every test exercising a panic with an
// Expected value.
fn silence_expected_panics() {
use std::sync::{Once, ONCE_INIT};
static HOOK_ONCE: Once = ONCE_INIT;
HOOK_ONCE.call_once(|| {
chain_hook_ignoring::<Expected>()
});
}
// ...
silence_expected_panics();
let thread_builder = thread::Builder::new()
.name("My panicky thread".into());
let ctx = Context::<Expected>::from(thread_builder);
let h = ctx.spawn(|| {
let unwind_me = TypeUnderTest::new();
assert!(unwind_me.doing_fine());
// ^-- If this fails, join() will return Err
panic!(Expected::String("Rainbows and unicorns!".into()));
});
let outcome = h.join().unwrap_or_propagate();
match outcome {
Outcome::Panicked(Expected::String(s)) => {
println!("thread panicked as expected: {}", s);
}
_ => panic!("unexpected value returned from join()")
}
let ctx = Context::<Expected>::new();
let h = ctx.spawn_quiet(|| {
let h = spawn_quiet(|| {
panic!("Sup dawg, we heard you like panics \
so we put a panic in your panic!");
});
h.join().unwrap_or_propagate();
});
let res = h.join();
let msg = res.panic_value_as_str().unwrap();
assert!(msg.contains("panic in your panic"));
Structs§
- Checked
Join Handle - Wraps
std::thread::JoinHandle
for panic value discrimination. - Context
- The launch pad for a thread checked for the expected panic type.
Enums§
- Outcome
- Enumerates the expected outcomes from joining a panic-checked thread.
Traits§
- Thread
Result Ext - Helpful extension methods for
std::thread::Result
.
Functions§
- chain_
hook_ ignoring - Augments the panic hook, filtering out panics of a particular type.
- chain_
hook_ ignoring_ full - Augments the panic hook, filtering out panics with a free-form check.
- chain_
hook_ ignoring_ if - Augments the panic hook, filtering out panics with a statically typed closure.
- disable_
hook_ in_ current_ thread - Disables the panic hook for the current thread.
- enable_
hook_ in_ current_ thread - Enables the panic hook for the current thread.
- spawn_
quiet - Like
std::thread::spawn()
, but disables the panic hook if the spawned thread panics.