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