我如何在 QuickCheck 测试中悄悄捕捉恐慌?

How can I silently catch panics in QuickCheck tests?

在我的 overflower_support 箱子的测试中,我发现我收到了很多虚假的恐慌报告,这些报告已经使用 std::panic::catch_unwind(_) 处理了。这有点不幸,因为它掩盖了可能发生的真正错误。消息如下所示:

thread 'safe' panicked at 'arithmetic overflow', src/lib.rs:56

为了平息那些分散注意力的消息,我引入了 dont_panic(..) 函数,它劫持了恐慌处理程序,调用闭包并在完成时重置恐慌处理程序,返回闭包结果。它看起来像这样:

fn dont_panic<F, A, R>(args: A, f: F) -> R
    where F: Fn(A) -> R
{
    let p = panic::take_hook();
    panic::set_hook(Box::new(|_| ()));
    let result = f(args);
    panic::set_hook(p);
    result
}

然而,在函数中使用这个函数来检查有点令人惊讶,不仅可以平息所需的消息,而且还可以快速检查错误输出,这对我来说显然很有价值。即使将测试限制为一个线程也会发生这种情况。

#[test]
fn test_some_panic() {
    fn check(x: usize) -> bool {
        let expected = if x < 256 { Some(x) } else { None };
        let actual = dont_panic(|| panic::catch_unwind(|| { assert!(x < 256); x }).ok());
        expected == actual
    }
    quickcheck(check as fn(usize) -> bool);
}

如何在保持 QuickCheck 的恐慌可见的同时从我的代码中隐藏捕获的恐慌?

默认的恐慌处理程序正在 stderr 上无条件地打印恐慌信息。

您想register your own handler.

我的方法有两个问题:

  1. 并行测试 运行(而且 quickcheck 似乎增加了一些并行性 它自己的,因为 -j 1 似乎无法平息恐慌消息)。
  2. 消息已写入(或被 set_hook(_) 抑制)否 不管有没有catch_unwind(_)

然而,dpc.pw在恐慌处理程序中根据文件进行区分的想法是 发现。我之前改变了调用 install_handler() 函数的方法 调用 quickcheck(_),我在此处完整复制:

use std::panic;
use std::sync::{Once, ONCE_INIT};

static HANDLER : Once = ONCE_INIT;

fn install_handler() {
    HANDLER.call_once(|| {
        let p = panic::take_hook();
        panic::set_hook(Box::new(move|info| {
            if info.location().map_or(false, |l| l.file() != "src/lib.rs" &&
                    !l.file().ends_with("/num/mod.rs")) {
                p(info);
            }
        }));
    })
}

如果恐慌来自 src/lib.rs(这 是我的 overflower_support 代码)或来自 /num/mod.rs 的某处(因为 Rust libcore 代码也可能会崩溃。

请注意,您可以省略 Once,但这会增加多个处理程序 时间并显着增加堆栈跟踪的大小,同时加剧 测试性能。

我遇到了同样的问题和其他一些问题,最后我写了一个 crate 来解决它们:

panic-control

有了它,您的示例可能会在 "quiet" 线程中由 运行 解决(假设您对具体使用 catch_unwind 不感兴趣):

use panic_control::spawn_quiet;

#[test]
fn test_some_panic() {
    fn check(x: usize) -> bool {
        let expected = if x < 256 { Some(x) } else { None };
        let h = spawn_quiet(|| { assert!(x < 256); x });
        let actual = h.join().ok();
        expected == actual
    }
    quickcheck(check as fn(usize) -> bool);
}