Capturing panic message does not work when panic message has a string literal

Hey all,

I am writing a function that captures the string message of a panic.

First of all, I have a wrapper struct for my panic message:

pub struct PanicMessage {
    message: String,
}

impl PanicMessage {
    pub fn new(message: String) -> PanicMessage {
        PanicMessage {
            message
        }  
    }
}

Then I have a function that captures the message of the panic! command. The input argument is a closure and the output argument is the PanicMessage containing the error message coming from the panic! macro call. Here is the function:

pub fn get_panic_message<F,R>(f: F) -> PanicMessage where F: FnOnce() -> R + panic::UnwindSafe{
    let global_buffer = Arc::new(Mutex::new(String::new()));
    let old_hook = panic::take_hook();

    register_panic_hook_to_capture_output(&global_buffer);
    let result = panic::catch_unwind(f);
    panic::set_hook(old_hook);

    let panic_message;

    match result {
        Ok(_res) => {
            panic!("There was no panic, but it was expected!") 
        },
        Err(_) => {
            panic_message = global_buffer.lock().unwrap();
        }
    }
        
    PanicMessage {
        message: panic_message.to_string()
    }
}

fn register_panic_hook_to_capture_output(global_buffer: &Arc<Mutex<String>>) {
    panic::set_hook({
        let global_buffer = global_buffer.clone();
        Box::new(move |info| {
            let mut global_buffer = global_buffer.lock().unwrap();

            if let Some(s) = info.payload().downcast_ref::<&str>() {
                global_buffer.push_str(s);
            }
        })
    });
}

I have written two tests for this functionality. One is with using panic! without having string literal and the other is with using a string literal. Here are my tests:

#[cfg(test)]
mod test {
    use super::*;
    
    #[test]
    fn test_get_panic_message_with_simple_panic() {
        let panic_message = get_panic_message(||panic!("test"));

        assert_eq!(panic_message.message, "test")
    }

    #[test]
    fn test_get_panic_message_with_panic_having_string_literal() {
        let reason = String::from("Some failure reason");
        let panic_message = get_panic_message(||panic!("Error: {}", reason));

        assert_eq!(panic_message.message, "Error: Some failure reason")
    }


}

The test_get_panic_message_with_simple_panic passes. So far so good. On the other hand the test_get_panic_message_with_panic_having_string_literal fails with the following error:

thread 'test_ask::test::test_get_panic_message_with_panic_having_string_literal' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"Error: Some failure reason"`', src\test_ask.rs:72:9

It seems that if I use the panic! macro with string literal, then somehow it does not capture the error message. After hours of investigation, I could not figure out the root cause, could you please point out where the problem lies?

Thank you in advance!

That payload is not a literal, it's a dynamically created String.

             if let Some(s) = info.payload().downcast_ref::<&str>() {
                 global_buffer.push_str(s);
             }
+            if let Some(s) = info.payload().downcast_ref::<String>() {
+                global_buffer.push_str(s);
+            }

Playground.

1 Like

Works like charm, you are the best, thank you!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.