canic_testkit/pic/
standalone.rs1use candid::encode_args;
2use canic::{
3 dto::{
4 abi::v1::CanisterInitPayload,
5 env::EnvBootstrapArgs,
6 topology::{AppIndexArgs, SubnetIndexArgs},
7 },
8 ids::{CanisterRole, SubnetRole},
9};
10use std::{
11 path::{Path, PathBuf},
12 sync::Mutex,
13};
14
15use crate::{
16 Fake,
17 artifacts::{
18 WasmBuildProfile, build_internal_test_wasm_canisters, read_wasm, test_target_dir,
19 workspace_root_for,
20 },
21};
22
23use super::{
24 Pic, PicSerialGuard, StandaloneCanisterFixtureError, try_acquire_pic_serial_guard, try_pic,
25};
26
27const STANDALONE_INSTALL_CYCLES: u128 = 1_000_000_000_000;
28const STANDALONE_READY_TICK_LIMIT: usize = 60;
29static STANDALONE_BUILD_SERIAL: Mutex<()> = Mutex::new(());
30
31pub struct StandaloneCanisterFixture {
36 pic: Pic,
37 canister_id: canic::cdk::types::Principal,
38 _serial_guard: PicSerialGuard,
39}
40
41impl StandaloneCanisterFixture {
42 #[must_use]
44 pub const fn pic(&self) -> &Pic {
45 &self.pic
46 }
47
48 #[must_use]
50 pub const fn pic_mut(&mut self) -> &mut Pic {
51 &mut self.pic
52 }
53
54 #[must_use]
56 pub const fn canister_id(&self) -> canic::cdk::types::Principal {
57 self.canister_id
58 }
59
60 #[must_use]
62 pub fn into_parts(self) -> (Pic, canic::cdk::types::Principal) {
63 (self.pic, self.canister_id)
64 }
65}
66
67#[must_use]
70pub fn install_prebuilt_canister(wasm: Vec<u8>, init_bytes: Vec<u8>) -> StandaloneCanisterFixture {
71 try_install_prebuilt_canister(wasm, init_bytes)
72 .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
73}
74
75pub fn try_install_prebuilt_canister(
78 wasm: Vec<u8>,
79 init_bytes: Vec<u8>,
80) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
81 try_install_prebuilt_canister_with_cycles(wasm, init_bytes, STANDALONE_INSTALL_CYCLES)
82}
83
84#[must_use]
87pub fn install_prebuilt_canister_with_cycles(
88 wasm: Vec<u8>,
89 init_bytes: Vec<u8>,
90 install_cycles: u128,
91) -> StandaloneCanisterFixture {
92 try_install_prebuilt_canister_with_cycles(wasm, init_bytes, install_cycles)
93 .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
94}
95
96pub fn try_install_prebuilt_canister_with_cycles(
99 wasm: Vec<u8>,
100 init_bytes: Vec<u8>,
101 install_cycles: u128,
102) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
103 let serial_guard =
104 try_acquire_pic_serial_guard().map_err(StandaloneCanisterFixtureError::SerialGuard)?;
105 let pic = try_pic().map_err(StandaloneCanisterFixtureError::Start)?;
106 let canister_id = pic
107 .try_create_and_install_with_args(wasm, init_bytes, install_cycles)
108 .map_err(StandaloneCanisterFixtureError::Install)?;
109
110 Ok(StandaloneCanisterFixture {
111 pic,
112 canister_id,
113 _serial_guard: serial_guard,
114 })
115}
116
117#[must_use]
121pub fn install_standalone_canister(
122 crate_name: &str,
123 role: CanisterRole,
124 profile: WasmBuildProfile,
125) -> StandaloneCanisterFixture {
126 assert!(
127 !role.is_root(),
128 "standalone helper is for non-root canisters"
129 );
130
131 let workspace_root = workspace_root();
132 let target_name = format!("standalone-{crate_name}");
133 let target_dir = test_target_dir(&workspace_root, &target_name);
134 ensure_canister_wasm_ready(&workspace_root, &target_dir, crate_name, profile);
135
136 let wasm = read_wasm(&target_dir, crate_name, profile);
137 let fixture = install_prebuilt_canister(wasm, standalone_init_args(role));
138 let canister_id = fixture.canister_id();
139 let pic = fixture.pic();
140 pic.wait_for_ready(
141 canister_id,
142 STANDALONE_READY_TICK_LIMIT,
143 "standalone canister bootstrap",
144 );
145
146 fixture
147}
148
149fn ensure_canister_wasm_ready(
153 workspace_root: &Path,
154 target_dir: &Path,
155 crate_name: &str,
156 profile: WasmBuildProfile,
157) {
158 let _build_guard = STANDALONE_BUILD_SERIAL
159 .lock()
160 .unwrap_or_else(std::sync::PoisonError::into_inner);
161
162 build_internal_test_wasm_canisters(workspace_root, target_dir, &[crate_name], profile);
163}
164
165fn standalone_init_args(role: CanisterRole) -> Vec<u8> {
168 let root_pid = Fake::principal(1);
169 let payload = CanisterInitPayload {
170 env: EnvBootstrapArgs {
171 prime_root_pid: Some(root_pid),
172 subnet_role: Some(SubnetRole::PRIME),
173 subnet_pid: Some(Fake::principal(2)),
174 root_pid: Some(root_pid),
175 canister_role: Some(role),
176 parent_pid: Some(root_pid),
177 },
178 app_index: AppIndexArgs(Vec::new()),
179 subnet_index: SubnetIndexArgs(Vec::new()),
180 };
181
182 encode_args::<(CanisterInitPayload, Option<Vec<u8>>)>((payload, None))
183 .expect("encode standalone init args")
184}
185
186fn workspace_root() -> PathBuf {
187 workspace_root_for(env!("CARGO_MANIFEST_DIR"))
188}