asserting/panic/
mod.rs

1//! Implementation of assertions for code that should or should not panic.
2
3use crate::assertions::AssertCodePanics;
4use crate::colored::{mark_missing_substr, mark_unexpected_substr};
5use crate::expectations::{DoesNotPanic, DoesPanic};
6use crate::spec::{Code, DiffFormat, Expectation, Expression, FailingStrategy, Spec};
7use crate::std::any::Any;
8use crate::std::panic;
9
10const ONLY_ONE_EXPECTATION: &str = "only one expectation allowed when asserting closures!";
11const UNKNOWN_PANIC_MESSAGE: &str = "<unknown panic message>";
12
13impl<S, R> AssertCodePanics for Spec<'_, Code<S>, R>
14where
15    S: FnOnce(),
16    R: FailingStrategy,
17{
18    fn does_not_panic(self) -> Self {
19        self.expecting(DoesNotPanic::default())
20    }
21
22    fn panics(self) -> Self {
23        self.expecting(DoesPanic::default())
24    }
25
26    fn panics_with_message(self, message: impl Into<String>) -> Self {
27        self.expecting(DoesPanic::with_message(message))
28    }
29}
30
31impl<S> Expectation<Code<S>> for DoesNotPanic
32where
33    S: FnOnce(),
34{
35    fn test(&mut self, subject: &Code<S>) -> bool {
36        if let Some(function) = subject.take() {
37            let result = panic::catch_unwind(panic::AssertUnwindSafe(function));
38            match result {
39                Ok(()) => true,
40                Err(panic_message) => {
41                    self.actual_message = Some(panic_message);
42                    false
43                },
44            }
45        } else {
46            self.actual_message = Some(Box::new(ONLY_ONE_EXPECTATION));
47            false
48        }
49    }
50
51    fn message(
52        &self,
53        expression: Expression<'_>,
54        _actual: &Code<S>,
55        format: &DiffFormat,
56    ) -> String {
57        let panic_message = read_panic_message(self.actual_message.as_ref())
58            .unwrap_or_else(|| UNKNOWN_PANIC_MESSAGE.to_string());
59
60        if panic_message == ONLY_ONE_EXPECTATION {
61            format!("error in test assertion: {ONLY_ONE_EXPECTATION}")
62        } else {
63            let marked_did_panic = mark_unexpected_substr("did panic", format);
64            let marked_panic_message = mark_unexpected_substr(&panic_message, format);
65            format!(
66                "expected {expression} to not panic, but {marked_did_panic}\n  with message: \"{marked_panic_message}\""
67            )
68        }
69    }
70}
71
72impl<S> Expectation<Code<S>> for DoesPanic
73where
74    S: FnOnce(),
75{
76    fn test(&mut self, subject: &Code<S>) -> bool {
77        if let Some(function) = subject.take() {
78            let result = panic::catch_unwind(panic::AssertUnwindSafe(function));
79            match result {
80                Ok(()) => false,
81                Err(panic_message) => {
82                    let panic_message = read_panic_message(Some(panic_message).as_ref())
83                        .unwrap_or_else(|| UNKNOWN_PANIC_MESSAGE.to_string());
84                    let test_result = if let Some(expected_message) = &self.expected_message {
85                        &panic_message == expected_message
86                    } else {
87                        // did panic - panic message should not be asserted
88                        true
89                    };
90                    self.actual_message = Some(panic_message);
91                    test_result
92                },
93            }
94        } else {
95            self.actual_message = Some(ONLY_ONE_EXPECTATION.to_string());
96            false
97        }
98    }
99
100    fn message(
101        &self,
102        expression: Expression<'_>,
103        _actual: &Code<S>,
104        format: &DiffFormat,
105    ) -> String {
106        if let Some(actual_message) = self.actual_message.as_ref() {
107            if actual_message == ONLY_ONE_EXPECTATION {
108                format!("error in test assertion: {ONLY_ONE_EXPECTATION}")
109            } else if let Some(expected_message) = &self.expected_message {
110                let marked_expected_message = mark_missing_substr(expected_message, format);
111                let marked_actual_message = mark_unexpected_substr(actual_message, format);
112                format!("expected {expression} to panic with message {expected_message:?}\n   but was: \"{marked_actual_message}\"\n  expected: \"{marked_expected_message}\"")
113            } else {
114                // should be unreachable
115                format!("expected {expression} to panic, but did not panic")
116            }
117        } else if let Some(expected_message) = &self.expected_message {
118            let marked_did_not_panic = mark_unexpected_substr("did not panic", format);
119            format!("expected {expression} to panic with message {expected_message:?},\n  but {marked_did_not_panic}")
120        } else {
121            let marked_did_not_panic = mark_unexpected_substr("did not panic", format);
122            format!("expected {expression} to panic, but {marked_did_not_panic}")
123        }
124    }
125}
126
127fn read_panic_message(error: Option<&Box<dyn Any + Send>>) -> Option<String> {
128    error.and_then(|message| {
129        message
130            .downcast_ref::<String>()
131            .cloned()
132            .or_else(|| message.downcast_ref::<&str>().map(ToString::to_string))
133    })
134}
135
136#[cfg(test)]
137mod tests;