1use candid::{Principal, encode_args, encode_one};
2use canic::{
3 Error,
4 cdk::types::TC,
5 dto::{
6 abi::v1::CanisterInitPayload,
7 env::EnvBootstrapArgs,
8 subnet::SubnetIdentity,
9 topology::{AppDirectoryArgs, SubnetDirectoryArgs},
10 },
11 ids::CanisterRole,
12};
13use pocket_ic::{
14 CanisterStatusResult, PocketIc, PocketIcBuilder, RejectResponse, common::rest::RawMessageId,
15};
16use std::time::Duration;
17
18mod baseline;
19mod calls;
20mod diagnostics;
21mod errors;
22mod lifecycle;
23mod process_lock;
24mod readiness;
25mod snapshot;
26mod standalone;
27mod startup;
28
29pub use baseline::{
30 CachedPicBaseline, CachedPicBaselineGuard, ControllerSnapshots,
31 restore_or_rebuild_cached_pic_baseline,
32};
33pub use errors::{PicInstallError, StandaloneCanisterFixtureError};
34pub use process_lock::{
35 PicSerialGuard, PicSerialGuardError, acquire_pic_serial_guard, try_acquire_pic_serial_guard,
36};
37pub use readiness::{role_pid, wait_until_ready};
38pub use startup::PicStartError;
39const INSTALL_CYCLES: u128 = 500 * TC;
40
41pub use standalone::{
42 StandaloneCanisterFixture, install_prebuilt_canister, install_prebuilt_canister_with_cycles,
43 install_standalone_canister, try_install_prebuilt_canister,
44 try_install_prebuilt_canister_with_cycles,
45};
46
47#[must_use]
56pub fn pic() -> Pic {
57 try_pic().unwrap_or_else(|err| panic!("failed to start PocketIC: {err}"))
58}
59
60pub fn try_pic() -> Result<Pic, PicStartError> {
62 PicBuilder::new().with_application_subnet().try_build()
63}
64
65pub struct PicBuilder(PocketIcBuilder);
76
77#[expect(clippy::new_without_default)]
78impl PicBuilder {
79 #[must_use]
81 pub fn new() -> Self {
82 Self(PocketIcBuilder::new())
83 }
84
85 #[must_use]
87 pub fn with_application_subnet(mut self) -> Self {
88 self.0 = self.0.with_application_subnet();
89 self
90 }
91
92 #[must_use]
94 pub fn with_ii_subnet(mut self) -> Self {
95 self.0 = self.0.with_ii_subnet();
96 self
97 }
98
99 #[must_use]
101 pub fn with_nns_subnet(mut self) -> Self {
102 self.0 = self.0.with_nns_subnet();
103 self
104 }
105
106 #[must_use]
108 pub fn build(self) -> Pic {
109 self.try_build()
110 .unwrap_or_else(|err| panic!("failed to start PocketIC: {err}"))
111 }
112
113 pub fn try_build(self) -> Result<Pic, PicStartError> {
115 startup::try_build_pic(self.0)
116 }
117}
118pub struct Pic {
127 inner: PocketIc,
128}
129
130impl Pic {
131 pub fn tick(&self) {
133 self.inner.tick();
134 }
135
136 pub fn advance_time(&self, duration: Duration) {
138 self.inner.advance_time(duration);
139 }
140
141 #[must_use]
143 pub fn create_canister(&self) -> Principal {
144 self.inner.create_canister()
145 }
146
147 pub fn add_cycles(&self, canister_id: Principal, amount: u128) {
149 let _ = self.inner.add_cycles(canister_id, amount);
150 }
151
152 pub fn install_canister(
154 &self,
155 canister_id: Principal,
156 wasm_module: Vec<u8>,
157 arg: Vec<u8>,
158 sender: Option<Principal>,
159 ) {
160 self.inner
161 .install_canister(canister_id, wasm_module, arg, sender);
162 }
163
164 pub fn upgrade_canister(
166 &self,
167 canister_id: Principal,
168 wasm_module: Vec<u8>,
169 arg: Vec<u8>,
170 sender: Option<Principal>,
171 ) -> Result<(), RejectResponse> {
172 self.inner
173 .upgrade_canister(canister_id, wasm_module, arg, sender)
174 }
175
176 pub fn reinstall_canister(
178 &self,
179 canister_id: Principal,
180 wasm_module: Vec<u8>,
181 arg: Vec<u8>,
182 sender: Option<Principal>,
183 ) -> Result<(), RejectResponse> {
184 self.inner
185 .reinstall_canister(canister_id, wasm_module, arg, sender)
186 }
187
188 pub fn submit_call(
190 &self,
191 canister_id: Principal,
192 sender: Principal,
193 method: &str,
194 payload: Vec<u8>,
195 ) -> Result<RawMessageId, RejectResponse> {
196 self.inner.submit_call(canister_id, sender, method, payload)
197 }
198
199 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
201 self.inner.await_call(message_id)
202 }
203
204 pub fn canister_status(
206 &self,
207 canister_id: Principal,
208 sender: Option<Principal>,
209 ) -> Result<CanisterStatusResult, RejectResponse> {
210 self.inner.canister_status(canister_id, sender)
211 }
212
213 pub fn fetch_canister_logs(
215 &self,
216 canister_id: Principal,
217 sender: Principal,
218 ) -> Result<Vec<pocket_ic::CanisterLogRecord>, RejectResponse> {
219 self.inner.fetch_canister_logs(canister_id, sender)
220 }
221
222 #[must_use]
224 pub fn current_time_nanos(&self) -> u64 {
225 self.inner.get_time().as_nanos_since_unix_epoch()
226 }
227
228 pub fn restore_time_nanos(&self, nanos_since_epoch: u64) {
230 let restored = pocket_ic::Time::from_nanos_since_unix_epoch(nanos_since_epoch);
231 self.inner.set_time(restored);
232 self.inner.set_certified_time(restored);
233 }
234}
235
236fn install_args(role: CanisterRole) -> Result<Vec<u8>, Error> {
251 if role.is_root() {
252 install_root_args()
253 } else {
254 let env = EnvBootstrapArgs {
257 prime_root_pid: None,
258 subnet_role: None,
259 subnet_pid: None,
260 root_pid: None,
261 canister_role: Some(role),
262 parent_pid: None,
263 };
264
265 let payload = CanisterInitPayload {
268 env,
269 app_directory: AppDirectoryArgs(Vec::new()),
270 subnet_directory: SubnetDirectoryArgs(Vec::new()),
271 };
272
273 encode_args::<(CanisterInitPayload, Option<Vec<u8>>)>((payload, None))
274 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))
275 }
276}
277
278fn install_root_args() -> Result<Vec<u8>, Error> {
279 encode_one(SubnetIdentity::Manual)
280 .map_err(|err| Error::internal(format!("encode_one failed: {err}")))
281}
282
283