canic_testkit/pic/
lifecycle.rs1use std::panic::{AssertUnwindSafe, catch_unwind};
2use std::time::Duration;
3
4use candid::Principal;
5use canic::{Error, ids::CanisterRole};
6
7use super::{INSTALL_CYCLES, Pic, PicInstallError, install_args, install_root_args, startup};
8
9impl Pic {
10 pub fn create_and_install_root_canister(&self, wasm: Vec<u8>) -> Result<Principal, Error> {
12 let init_bytes = install_root_args()?;
13
14 Ok(self.create_and_install_with_args(wasm, init_bytes, INSTALL_CYCLES))
15 }
16
17 pub fn create_and_install_canister(
21 &self,
22 role: CanisterRole,
23 wasm: Vec<u8>,
24 ) -> Result<Principal, Error> {
25 let init_bytes = install_args(role)?;
26
27 Ok(self.create_and_install_with_args(wasm, init_bytes, INSTALL_CYCLES))
28 }
29
30 #[must_use]
35 pub fn create_and_install_with_args(
36 &self,
37 wasm: Vec<u8>,
38 init_bytes: Vec<u8>,
39 install_cycles: u128,
40 ) -> Principal {
41 self.try_create_and_install_with_args(wasm, init_bytes, install_cycles)
42 .unwrap_or_else(|err| panic!("{err}"))
43 }
44
45 pub fn try_create_and_install_with_args(
47 &self,
48 wasm: Vec<u8>,
49 init_bytes: Vec<u8>,
50 install_cycles: u128,
51 ) -> Result<Principal, PicInstallError> {
52 self.try_create_funded_and_install(wasm, init_bytes, install_cycles)
53 }
54
55 pub fn wait_for_ready(&self, canister_id: Principal, tick_limit: usize, context: &str) {
57 for _ in 0..tick_limit {
58 self.tick();
59 if self.fetch_ready(canister_id) {
60 return;
61 }
62 }
63
64 self.dump_canister_debug(canister_id, context);
65 panic!("{context}: canister {canister_id} did not become ready after {tick_limit} ticks");
66 }
67
68 pub fn wait_for_all_ready<I>(&self, canister_ids: I, tick_limit: usize, context: &str)
70 where
71 I: IntoIterator<Item = Principal>,
72 {
73 let canister_ids = canister_ids.into_iter().collect::<Vec<_>>();
74
75 for _ in 0..tick_limit {
76 self.tick();
77 if canister_ids
78 .iter()
79 .copied()
80 .all(|canister_id| self.fetch_ready(canister_id))
81 {
82 return;
83 }
84 }
85
86 for canister_id in &canister_ids {
87 self.dump_canister_debug(*canister_id, context);
88 }
89 panic!("{context}: canisters did not become ready after {tick_limit} ticks");
90 }
91
92 pub fn wait_out_install_code_rate_limit(&self, cooldown: Duration) {
94 self.advance_time(cooldown);
95 self.tick_n(2);
96 }
97
98 pub fn retry_install_code_ok<T, F>(
100 &self,
101 retry_limit: usize,
102 cooldown: Duration,
103 mut op: F,
104 ) -> Result<T, String>
105 where
106 F: FnMut() -> Result<T, String>,
107 {
108 let mut last_err = None;
109
110 for _ in 0..retry_limit {
111 match op() {
112 Ok(value) => return Ok(value),
113 Err(err) if is_install_code_rate_limited(&err) => {
114 last_err = Some(err);
115 self.wait_out_install_code_rate_limit(cooldown);
116 }
117 Err(err) => return Err(err),
118 }
119 }
120
121 Err(last_err.unwrap_or_else(|| "install_code retry loop exhausted".to_string()))
122 }
123
124 pub fn retry_install_code_err<F>(
126 &self,
127 retry_limit: usize,
128 cooldown: Duration,
129 first: Result<(), String>,
130 mut op: F,
131 ) -> Result<(), String>
132 where
133 F: FnMut() -> Result<(), String>,
134 {
135 match first {
136 Ok(()) => return Ok(()),
137 Err(err) if !is_install_code_rate_limited(&err) => return Err(err),
138 Err(_) => {}
139 }
140
141 self.wait_out_install_code_rate_limit(cooldown);
142
143 for _ in 1..retry_limit {
144 match op() {
145 Ok(()) => return Ok(()),
146 Err(err) if is_install_code_rate_limited(&err) => {
147 self.wait_out_install_code_rate_limit(cooldown);
148 }
149 Err(err) => return Err(err),
150 }
151 }
152
153 op()
154 }
155
156 fn try_create_funded_and_install(
158 &self,
159 wasm: Vec<u8>,
160 init_bytes: Vec<u8>,
161 install_cycles: u128,
162 ) -> Result<Principal, PicInstallError> {
163 let canister_id = self.create_canister();
164 self.add_cycles(canister_id, install_cycles);
165
166 let install = catch_unwind(AssertUnwindSafe(|| {
167 self.inner
168 .install_canister(canister_id, wasm, init_bytes, None);
169 }));
170 if let Err(payload) = install {
171 eprintln!("install_canister trapped for {canister_id}");
172 if let Ok(status) = self.inner.canister_status(canister_id, None) {
173 eprintln!("canister_status for {canister_id}: {status:?}");
174 }
175 if let Ok(logs) = self
176 .inner
177 .fetch_canister_logs(canister_id, Principal::anonymous())
178 {
179 for record in logs {
180 eprintln!("canister_log {canister_id}: {record:?}");
181 }
182 }
183 return Err(PicInstallError::new(
184 canister_id,
185 startup::panic_payload_to_string(payload.as_ref()),
186 ));
187 }
188
189 Ok(canister_id)
190 }
191}
192
193fn is_install_code_rate_limited(message: &str) -> bool {
194 message.contains("CanisterInstallCodeRateLimited")
195}