【问题标题】:Is it possible to change the whole panic message?是否可以更改整个恐慌信息?
【发布时间】:2018-07-20 02:39:30
【问题描述】:

NUnit 是一个 C# 单元测试框架,允许您编写如下代码:

Assert.That(someInt, Is.EqualTo(42));
Assert.That(someList, Has.Member(someMember));

我喜欢这种代码,因为它看起来像英文,很容易阅读。


我正在玩 Rust,看看我是否可以创建一个提供相同感觉的库:

use std::fmt::Debug;

struct Is;

enum Verb<T> {
    EqualTo(T),
}

impl Is {
    fn equal_to<T>(&self, obj: T) -> Verb<T> {
        Verb::EqualTo(obj)
    }
}

#[allow(non_upper_case_globals)]
const is: Is = Is{};

fn assert_that<T: Eq + Debug>(obj: T, verb: Verb<T>) {
    match verb {
        Verb::EqualTo(rhs)    => assert_eq!(obj, rhs),
    }
}

fn main() {
    assert_that(42, is.equal_to(42));
    assert_that(42, is.equal_to(0));
}

这很好,但一方面:当代码在assert_that(42, is.equal_to(0)) 发生恐慌时,恐慌给出的行是assert_eq!(obj, rhs) 的行(在库中而不是用户的代码)。我知道这种行为是正常的,但我会收到更有用的信息。

panic中如何指示正确的行号?

【问题讨论】:

  • 我认为如果您将assert_that 更改为宏,它可能会显示正确的行。不确定。

标签: unit-testing rust


【解决方案1】:

没有直接的方法可以调整panic! 打印的行号。

a proto-RFC 添加了一个属性,允许某些方法从回溯中“隐藏”。有可能这样的属性也会影响行号,但目前还不清楚。

How to write a panic! like macro in Rust? 描述了如何编写自己的panic! 宏,但它选择拆除整个进程,而不仅仅是当前线程。


重要的是您只想控制消息,这可以通过panic::set_hook 实现。您可以通过线程本地将侧通道信息从测试传递到恐慌处理程序。

use std::cell::Cell;

thread_local! {
    static ASSERT_LOCATION: Cell<Option<(&'static str, u32)>> = Cell::new(None)
}

fn report_my_error(info: &std::panic::PanicInfo) {
    match info.location() {
        Some(location) => {
            let file = location.file();
            let line = location.line();
            println!("The panic actually happened at: {}, {}", file, line);
        }
        None => println!("I don't know where the panic actually happened"),
    }

    ASSERT_LOCATION.with(|location| if let Some((file, line)) = location.get() {
        println!(
            "But I'm going to tell you it happened at {}, {}",
            file,
            line
        );
    });

    if let Some(msg) = info.payload().downcast_ref::<&str>() {
        println!("The error message was: {}", msg);
    }
}

#[test]
fn alpha() {
    std::panic::set_hook(Box::new(report_my_error));

    ASSERT_LOCATION.with(|location| {
        location.set(Some((file!(), line!())));
    });

    panic!("This was only a test")
}

您需要确保在每个测试中都设置了恐慌处理程序,然后设置位置信息。您可能还需要更新紧急处理程序以将位置信息设置回None,以避免线程之间的位置信息泄漏。

您可能希望编写自己的宏,用户可以在测试中使用它来隐式设置行号。与此类似的语法可以为这个设置代码提供一个存在的地方:

assert_that!(42, is.equal_to(0));

可以扩展为:

assert_that(file!(), line!(), 42, is.equal_to(0));

我可能会在 assert_that 中设置恐慌处理程序。

【讨论】:

    【解决方案2】:

    您可能对使用 spectral 感兴趣,这是一个为 Rust 提供流畅测试断言的库。如果您查看他们的implementation,正如其他人所建议的那样,他们使用宏而不是函数,因此line!()file!() 宏在您放置库中定义的assert_that! 宏的位置展开。

    用法如下:

    #[test]
    fn example() {
        assert_that!(2).is_equal_to(4);
    }
    

    正如预期的那样,输出指向我库中的正确行:

    failures:
    
    ---- utils::tests::example stdout ----
            thread 'utils::tests::example' panicked at '
            expected: <4>
            but was: <2>
    
            at location: src/utils.rs:344
    ', /home/example/.cargo/registry/src/github.com-1ecc6299db9ec823/spectral-0.6.0/src/lib.rs:343
    note: Run with `RUST_BACKTRACE=1` for a backtrace.
    
    
    failures:
        utils::tests::example
    
    test result: FAILED. 61 passed; 1 failed; 0 ignored; 0 measured
    

    【讨论】:

    • 这不会改变恐慌信息的位置——它只会增加另一个位置,可能会使理解复杂化:location: src/utils.rs:344; /src/lib.rs:343.
    猜你喜欢
    • 1970-01-01
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 2023-02-04
    • 1970-01-01
    相关资源
    最近更新 更多