Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
dbsxdbsx opened this issue Aug 24, 2023 · 6 comments

Comments

@dbsxdbsx
Copy link

dbsxdbsx commented Aug 24, 2023

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
Copy link
Member

Kixiron commented Aug 24, 2023

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

@dbsxdbsx
Copy link
Author

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
Copy link
Member

thomcc commented Aug 25, 2023

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

@tgross35
Copy link
Contributor

tgross35 commented Sep 6, 2023

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
Copy link
Author

dbsxdbsx commented Sep 6, 2023

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
Copy link
Member

jhpratt commented Sep 19, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants