#panic-hook #panic #thread #panic-message #control #join

panic-control

用于测试有意引发panic的代码的工具

5个版本

使用旧的Rust 2015

0.1.4 2018年4月14日
0.1.3 2017年12月1日
0.1.2 2017年11月29日
0.1.1 2017年11月28日
0.1.0 2017年11月28日

#565 in 测试

Download history 71/week @ 2024-03-12 95/week @ 2024-03-19 114/week @ 2024-03-26 136/week @ 2024-04-02 94/week @ 2024-04-09 88/week @ 2024-04-16 98/week @ 2024-04-23 86/week @ 2024-04-30 89/week @ 2024-05-07 88/week @ 2024-05-14 112/week @ 2024-05-21 90/week @ 2024-05-28 62/week @ 2024-06-04 70/week @ 2024-06-11 84/week @ 2024-06-18 62/week @ 2024-06-25

每月294次下载
用于sarek

Apache-2.0/MIT

37KB
358

使用动态类型检查控制Rust panic

panic_control crate提供工具,用于在有意引发的panic中测试代码行为,同时区分预期和意外的panic,以便能够捕获断言失败等。

许可证

许可协议为以下之一

您可选择。

贡献

除非您明确声明,否则根据Apache-2.0许可证定义,您提交的任何有意包含在本作品中的贡献,均应双许可,如上所述,没有任何附加条款或条件。


lib.rs:

使用动态类型检查控制panic

有时需要测试Rust代码在panic发生时的行为。可以在测试生成的线程中故意引发panic,并在线程连接后观察其效果。问题在于,区分“良性的”panic和表示实际错误的panic(如断言失败)可能很麻烦。

另一个问题是默认panic hook的行为。它对于获取意外线程panic的原因信息非常有用,但对于故意引发panic的测试,它会产生讨厌的输出噪声。panic hook可以被覆盖,但自定义panic hook会影响整个程序,在典型用法中是测试运行器;很容易误用它们,导致重要的错误信息未报告。

将子线程中发生的panic传播到其父线程的最简单方法,由标准库提供,是在JoinHandle::join的结果上调用unwrap。遗憾的是,由于Any实现中存在一个问题,导致最终的panic消息没有从子线程的panic中传递信息。

此crate提供了一些工具和用户友好的接口,通过动态类型检查来区分预期panic和非预期panic,以在可控和输出友好的方式测试panic。

预期panic类型

将panic指定为预期的推荐方法是使用自定义类型的值作为panic!的参数。这个类型可以是简单的单元-like结构体,也可以携带来自panic点的额外信息。任何panic值类型都应该是Sized'staticSend。为了使值可用于测试,它还应该实现至少DebugPartialEq

示例

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"));

无运行时依赖