canic_testkit/pic/
startup.rs1use std::{any::Any, panic::catch_unwind};
2
3use pocket_ic::PocketIcBuilder;
4
5use super::Pic;
6
7#[derive(Debug, Eq, PartialEq)]
12pub enum PicStartError {
13 BinaryUnavailable { message: String },
14 BinaryInvalid { message: String },
15 DownloadFailed { message: String },
16 ServerStartFailed { message: String },
17 StartupTimedOut { message: String },
18 Panic { message: String },
19}
20
21pub(super) fn try_build_pic(builder: PocketIcBuilder) -> Result<Pic, PicStartError> {
22 let build = catch_unwind(|| builder.build());
23
24 match build {
25 Ok(inner) => Ok(Pic { inner }),
26 Err(payload) => Err(classify_pic_start_panic(payload)),
27 }
28}
29
30impl std::fmt::Display for PicStartError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::BinaryUnavailable { message }
34 | Self::BinaryInvalid { message }
35 | Self::DownloadFailed { message }
36 | Self::ServerStartFailed { message }
37 | Self::StartupTimedOut { message }
38 | Self::Panic { message } => f.write_str(message),
39 }
40 }
41}
42
43impl std::error::Error for PicStartError {}
44
45pub(super) fn panic_payload_to_string(payload: &(dyn Any + Send)) -> String {
47 if let Some(message) = payload.downcast_ref::<String>() {
48 return message.clone();
49 }
50 if let Some(message) = payload.downcast_ref::<&'static str>() {
51 return (*message).to_string();
52 }
53
54 "non-string panic payload".to_string()
55}
56
57fn classify_pic_start_panic(payload: Box<dyn Any + Send>) -> PicStartError {
59 let message = panic_payload_to_string(payload.as_ref());
60
61 if message.starts_with("Failed to validate PocketIC server binary") {
62 if message.contains("No such file or directory") || message.contains("os error 2") {
63 return PicStartError::BinaryUnavailable { message };
64 }
65
66 return PicStartError::BinaryInvalid { message };
67 }
68
69 if message.starts_with("Failed to download PocketIC server")
70 || message.starts_with("Failed to write PocketIC server binary")
71 {
72 return PicStartError::DownloadFailed { message };
73 }
74
75 if message.starts_with("Failed to start PocketIC binary")
76 || message.starts_with("Failed to create PocketIC server directory")
77 {
78 return PicStartError::ServerStartFailed { message };
79 }
80
81 if message.starts_with("Timed out waiting for PocketIC server being available") {
82 return PicStartError::StartupTimedOut { message };
83 }
84
85 PicStartError::Panic { message }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::{PicStartError, classify_pic_start_panic};
91
92 #[test]
93 fn pic_start_error_classifies_missing_binary() {
94 let error = classify_pic_start_panic(Box::new(
95 "Failed to validate PocketIC server binary `/tmp/pocket-ic`: `No such file or directory (os error 2)`.".to_string(),
96 ));
97
98 assert!(matches!(error, PicStartError::BinaryUnavailable { .. }));
99 }
100
101 #[test]
102 fn pic_start_error_classifies_failed_spawn() {
103 let error = classify_pic_start_panic(Box::new(
104 "Failed to start PocketIC binary (/tmp/pocket-ic)".to_string(),
105 ));
106
107 assert!(matches!(error, PicStartError::ServerStartFailed { .. }));
108 }
109}