Skip to main content

ic_testkit/pic/
startup.rs

1use std::panic::{AssertUnwindSafe, catch_unwind};
2
3use pocket_ic::{PocketIc, PocketIcBuilder};
4
5use super::transport;
6
7/// A panic captured while PocketIC constructs an instance.
8///
9/// The message is preserved for diagnostics but deliberately remains
10/// unclassified because PocketIC does not yet expose structured startup errors.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct PocketIcStartupError {
13    message: String,
14}
15
16/// Fallible construction at PocketIC's currently panicking builder boundary.
17///
18/// This trait adds one operation rather than forwarding the builder API.
19pub trait PocketIcBuilderExt {
20    /// Build one PocketIC instance while capturing an upstream startup panic.
21    ///
22    /// This method deliberately does not classify panic text. It exists so a
23    /// test harness can apply its own bounded retry policy until PocketIC
24    /// provides a native fallible builder API. PocketIC's installed panic hook
25    /// may still print before unwinding reaches this boundary.
26    fn try_build(self) -> Result<PocketIc, PocketIcStartupError>;
27}
28
29impl PocketIcBuilderExt for PocketIcBuilder {
30    fn try_build(self) -> Result<PocketIc, PocketIcStartupError> {
31        catch_unwind(AssertUnwindSafe(|| self.build()))
32            .map_err(|payload| PocketIcStartupError::from_panic(payload.as_ref()))
33    }
34}
35
36impl PocketIcStartupError {
37    fn from_panic(payload: &(dyn std::any::Any + Send)) -> Self {
38        Self {
39            message: transport::panic_payload_to_string(payload),
40        }
41    }
42
43    /// Read the unclassified upstream panic message.
44    #[must_use]
45    pub fn message(&self) -> &str {
46        &self.message
47    }
48}
49
50impl std::fmt::Display for PocketIcStartupError {
51    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(formatter, "PocketIC startup panicked: {}", self.message)
53    }
54}
55
56impl std::error::Error for PocketIcStartupError {}
57
58#[cfg(test)]
59mod tests {
60    use super::PocketIcStartupError;
61
62    #[test]
63    fn startup_error_preserves_string_panic_message() {
64        let error = PocketIcStartupError::from_panic(&"startup failed");
65
66        assert_eq!(error.message(), "startup failed");
67        assert_eq!(
68            error.to_string(),
69            "PocketIC startup panicked: startup failed"
70        );
71    }
72}