Crate panic_control [] [src]

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

CheckedJoinHandle

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

ThreadResultExt

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.