rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.93k stars 1.57k forks source link

Proposal: Introducing the `assert_panic!` Macro for Testing Panic Messages in Rust #3479

Open dbsxdbsx opened 1 year ago

dbsxdbsx commented 1 year ago

Introduction

In Rust, the standard library provides macros like assert! and panic! for handling errors and testing code correctness. However, there is no built-in macro for capturing a panic and checking its message. This proposal aims to introduce a new macro, assert_panic!, which will allow developers to test if a specific panic message is triggered in their code.

Motivation

When writing tests in Rust, it is often necessary to ensure that a function or method panics under certain conditions. While the standard library provides macros like assert! and panic!, they do not offer a way to capture the panic message and check if it matches the expected message.

Having a macro like assert_panic! would simplify the process of testing panic messages and make it more convenient for developers to verify that their code behaves as expected in panic scenarios.

Proposed Solution

The assert_panic! macro will be designed to capture a panic and check if its message matches the expected message. Here's an example of how the macro can be used:

#[test]
fn test_permute_panic() {
    let data = &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
    let shape = &[2, 3];
    let tensor = Tensor::new(data, shape);
    assert_panic!(
        tensor.permute(&[1]),
        "The panic message inside permute()"
    );
}

The assert_panic! macro will be implemented as follows:

#[macro_export]
macro_rules! assert_panic {
    ($expr:expr) => {
        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| $expr)) {
            Ok(_) => panic!("Expression did not trigger panic"),
            Err(_) => (),
        }
    };
    ($expr:expr, $expected_msg:expr) => {
        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| $expr)) {
            Ok(_) => panic!("Expression did not trigger panic"),
            Err(err) => {
                let expected_msg_str = $expected_msg.to_string();
                if let Some(msg) = err.downcast_ref::<&'static str>() {
                    assert_eq!(*msg, expected_msg_str, "Panic message does not match expected");
                } else if let Some(msg) = err.downcast_ref::<String>() {
                    assert_eq!(*msg, expected_msg_str, "Panic message does not match expected");
                } else {
                    panic!("Expected panic message not found, expected panic message: {}", expected_msg_str);
                }
            }
        }
    };
}

Without this, users have to manually write code like this:

    let result = std::panic::catch_unwind(|| {
        tensor.permute(&[1]);
    });
    assert!(result.is_err(), "should trigger panic");
    let panic_info = result.unwrap_err();
    let panic_msg = panic_info
        .downcast_ref::<&'static str>()
        .expect("Expected panic message not found");
    assert_eq!(*panic_msg, "The panic message inside permute()");

Other thoughts

I don't know if this behavior is not encouraged or not, I see the macro assert_ne! is merged from crate assert_ne, while the similar assert-panic didn't.

Conclusion

Introducing the assert_panic! macro will provide a convenient way for Rust developers to test panic messages in their code. This macro will simplify the process of verifying that a function or method panics with the expected message, making it easier for developers to ensure that their code behaves correctly in panic scenarios.

Kixiron commented 1 year ago

Tests already have this ability through the #[should_panic] attribute

dbsxdbsx commented 1 year ago

Tests already have this ability through the #[should_panic] attribute

I know. But what if there needs more than one panic check in a unit test?

thomcc commented 1 year ago

This feels like something niche enough to be provided by an external crate.

tgross35 commented 1 year ago

This feels like something niche enough to be provided by an external crate.

Agreed - https://github.com/Tamschi/assert-panic looks like it does a lot of things pretty nice. It does look unmaintained though. @dbsxdbsx if you are interested in the topic, maybe you'd like to reach out to the owner and offer to help with maintenance

dbsxdbsx commented 1 year ago

This feels like something niche enough to be provided by an external crate.

Agreed - https://github.com/Tamschi/assert-panic looks like it does a lot of things pretty nice. It does look unmaintained though. @dbsxdbsx if you are interested in the topic, maybe you'd like to reach out to the owner and offer to help with maintenance

If possible, I'd like to see Rust's official team take over it.

jhpratt commented 1 year ago

I will note that #[should_panic] does not work with things like quickcheck. That necessitates custom handling similar to what this macro would provide. I incidentally ended up implementing something like this yesterday in an attempt to narrow down a bug. I'm not saying it should be used often, but it definitely has its use cases.