ic_testkit/pic/
lifecycle.rs1use std::panic::{AssertUnwindSafe, catch_unwind};
2use std::time::Duration;
3
4use candid::Principal;
5
6use super::{Pic, PicInstallError, startup};
7
8impl Pic {
9 #[must_use]
14 pub fn create_and_install_with_args(
15 &self,
16 wasm: Vec<u8>,
17 init_bytes: Vec<u8>,
18 install_cycles: u128,
19 ) -> Principal {
20 self.try_create_and_install_with_args(wasm, init_bytes, install_cycles)
21 .unwrap_or_else(|err| panic!("{err}"))
22 }
23
24 pub fn try_create_and_install_with_args(
26 &self,
27 wasm: Vec<u8>,
28 init_bytes: Vec<u8>,
29 install_cycles: u128,
30 ) -> Result<Principal, PicInstallError> {
31 self.try_create_funded_and_install(wasm, init_bytes, install_cycles)
32 }
33
34 pub fn wait_out_install_code_rate_limit(&self, cooldown: Duration) {
36 self.advance_time(cooldown);
37 self.tick_n(2);
38 }
39
40 pub fn retry_install_code_ok<T, F>(
42 &self,
43 retry_limit: usize,
44 cooldown: Duration,
45 mut op: F,
46 ) -> Result<T, String>
47 where
48 F: FnMut() -> Result<T, String>,
49 {
50 let mut last_err = None;
51
52 for _ in 0..retry_limit {
53 match op() {
54 Ok(value) => return Ok(value),
55 Err(err) if is_install_code_rate_limited(&err) => {
56 last_err = Some(err);
57 self.wait_out_install_code_rate_limit(cooldown);
58 }
59 Err(err) => return Err(err),
60 }
61 }
62
63 Err(last_err.unwrap_or_else(|| "install_code retry loop exhausted".to_string()))
64 }
65
66 pub fn retry_install_code_err<F>(
68 &self,
69 retry_limit: usize,
70 cooldown: Duration,
71 first: Result<(), String>,
72 mut op: F,
73 ) -> Result<(), String>
74 where
75 F: FnMut() -> Result<(), String>,
76 {
77 match first {
78 Ok(()) => return Ok(()),
79 Err(err) if !is_install_code_rate_limited(&err) => return Err(err),
80 Err(_) => {}
81 }
82
83 self.wait_out_install_code_rate_limit(cooldown);
84
85 for _ in 1..retry_limit {
86 match op() {
87 Ok(()) => return Ok(()),
88 Err(err) if is_install_code_rate_limited(&err) => {
89 self.wait_out_install_code_rate_limit(cooldown);
90 }
91 Err(err) => return Err(err),
92 }
93 }
94
95 op()
96 }
97
98 fn try_create_funded_and_install(
100 &self,
101 wasm: Vec<u8>,
102 init_bytes: Vec<u8>,
103 install_cycles: u128,
104 ) -> Result<Principal, PicInstallError> {
105 let canister_id = self.create_canister();
106 self.add_cycles(canister_id, install_cycles);
107
108 let install = catch_unwind(AssertUnwindSafe(|| {
109 self.inner
110 .install_canister(canister_id, wasm, init_bytes, None);
111 }));
112 if let Err(payload) = install {
113 eprintln!("install_canister trapped for {canister_id}");
114 if let Ok(status) = self.inner.canister_status(canister_id, None) {
115 eprintln!("canister_status for {canister_id}: {status:?}");
116 }
117 if let Ok(logs) = self
118 .inner
119 .fetch_canister_logs(canister_id, Principal::anonymous())
120 {
121 for record in logs {
122 eprintln!("canister_log {canister_id}: {record:?}");
123 }
124 }
125 return Err(PicInstallError::new(
126 canister_id,
127 startup::panic_payload_to_string(payload.as_ref()),
128 ));
129 }
130
131 Ok(canister_id)
132 }
133}
134
135fn is_install_code_rate_limited(message: &str) -> bool {
136 message.contains("CanisterInstallCodeRateLimited")
137}