1#![allow(clippy::test_attr_in_doctest)]
2#![doc = include_str!("../README.md")]
3use crate::{
57 common::rest::{
58 AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet,
59 HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceHttpGatewayConfig, InstanceId,
60 MockCanisterHttpResponse, RawEffectivePrincipal, RawMessageId, RawSubnetBlockmakers,
61 RawTickConfigs, RawTime, SubnetId, SubnetKind, SubnetSpec, Topology,
62 },
63 nonblocking::PocketIc as PocketIcAsync,
64};
65use candid::{
66 Principal, decode_args, encode_args,
67 utils::{ArgumentDecoder, ArgumentEncoder},
68};
69use flate2::read::GzDecoder;
70pub use ic_management_canister_types::{
71 CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
72 Snapshot,
73};
74pub use ic_transport_types::SubnetMetrics;
75use reqwest::Url;
76use schemars::JsonSchema;
77use semver::{Version, VersionReq};
78use serde::{Deserialize, Serialize};
79use slog::Level;
80#[cfg(unix)]
81use std::os::unix::fs::OpenOptionsExt;
82#[cfg(windows)]
83use std::sync::Once;
84use std::{
85 fs::OpenOptions,
86 net::{IpAddr, SocketAddr},
87 path::PathBuf,
88 process::{Child, Command},
89 sync::{Arc, mpsc::channel},
90 thread,
91 thread::JoinHandle,
92 time::{Duration, SystemTime, UNIX_EPOCH},
93};
94use strum_macros::EnumIter;
95use tempfile::{NamedTempFile, TempDir};
96use thiserror::Error;
97use tokio::runtime::Runtime;
98use tracing::{instrument, warn};
99#[cfg(windows)]
100use wslpath::windows_to_wsl;
101
102pub mod common;
103pub mod nonblocking;
104
105const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
106
107const MIN_SERVER_VERSION: &str = "13.0.0";
108const MAX_SERVER_VERSION: &str = "14";
109
110pub const LATEST_SERVER_VERSION: &str = "13.0.0";
112
113const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
115
116const LOCALHOST: &str = "127.0.0.1";
117
118enum PocketIcStateKind {
119 StateDir(PathBuf),
121 TempDir(TempDir),
127}
128
129pub struct PocketIcState {
130 state: PocketIcStateKind,
131}
132
133impl PocketIcState {
134 #[allow(clippy::new_without_default)]
135 pub fn new() -> Self {
136 let temp_dir = TempDir::new().unwrap();
137 Self {
138 state: PocketIcStateKind::TempDir(temp_dir),
139 }
140 }
141
142 pub fn new_from_path(state_dir: PathBuf) -> Self {
143 Self {
144 state: PocketIcStateKind::StateDir(state_dir),
145 }
146 }
147
148 pub fn into_path(self) -> PathBuf {
149 match self.state {
150 PocketIcStateKind::StateDir(state_dir) => state_dir,
151 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
152 }
153 }
154
155 pub(crate) fn state_dir(&self) -> PathBuf {
156 match &self.state {
157 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
158 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
159 }
160 }
161}
162
163pub struct PocketIcBuilder {
164 config: Option<ExtendedSubnetConfigSet>,
165 http_gateway_config: Option<InstanceHttpGatewayConfig>,
166 server_binary: Option<PathBuf>,
167 server_url: Option<Url>,
168 max_request_time_ms: Option<u64>,
169 read_only_state_dir: Option<PathBuf>,
170 state_dir: Option<PocketIcState>,
171 icp_config: IcpConfig,
172 log_level: Option<Level>,
173 bitcoind_addr: Option<Vec<SocketAddr>>,
174 dogecoind_addr: Option<Vec<SocketAddr>>,
175 icp_features: IcpFeatures,
176 initial_time: Option<InitialTime>,
177 mainnet_nns_subnet_id: Option<bool>,
178}
179
180#[allow(clippy::new_without_default)]
181impl PocketIcBuilder {
182 pub fn new() -> Self {
183 Self {
184 config: None,
185 http_gateway_config: None,
186 server_binary: None,
187 server_url: None,
188 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
189 read_only_state_dir: None,
190 state_dir: None,
191 icp_config: IcpConfig::default(),
192 log_level: None,
193 bitcoind_addr: None,
194 dogecoind_addr: None,
195 icp_features: IcpFeatures::default(),
196 initial_time: None,
197 mainnet_nns_subnet_id: None,
198 }
199 }
200
201 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
202 let mut builder = Self::new();
203 builder.config = Some(config.into());
204 builder
205 }
206
207 pub fn build(self) -> PocketIc {
208 PocketIc::from_components(
209 self.config.unwrap_or_default(),
210 self.server_url,
211 self.server_binary,
212 self.max_request_time_ms,
213 self.read_only_state_dir,
214 self.state_dir,
215 self.icp_config,
216 self.log_level,
217 self.bitcoind_addr,
218 self.dogecoind_addr,
219 self.icp_features,
220 self.initial_time,
221 self.http_gateway_config,
222 self.mainnet_nns_subnet_id,
223 )
224 }
225
226 pub async fn build_async(self) -> PocketIcAsync {
227 PocketIcAsync::from_components(
228 self.config.unwrap_or_default(),
229 self.server_url,
230 self.server_binary,
231 self.max_request_time_ms,
232 self.read_only_state_dir,
233 self.state_dir,
234 self.icp_config,
235 self.log_level,
236 self.bitcoind_addr,
237 self.dogecoind_addr,
238 self.icp_features,
239 self.initial_time,
240 self.http_gateway_config,
241 self.mainnet_nns_subnet_id,
242 )
243 .await
244 }
245
246 pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
248 self.server_binary = Some(server_binary);
249 self
250 }
251
252 pub fn with_server_url(mut self, server_url: Url) -> Self {
254 self.server_url = Some(server_url);
255 self
256 }
257
258 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
259 self.max_request_time_ms = max_request_time_ms;
260 self
261 }
262
263 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
264 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
265 self
266 }
267
268 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
269 self.state_dir = Some(state_dir);
270 self
271 }
272
273 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
274 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
275 self
276 }
277
278 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
279 self.icp_config = icp_config;
280 self
281 }
282
283 pub fn with_log_level(mut self, log_level: Level) -> Self {
284 self.log_level = Some(log_level);
285 self
286 }
287
288 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
289 self.with_bitcoind_addrs(vec![bitcoind_addr])
290 }
291
292 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
293 Self {
294 bitcoind_addr: Some(bitcoind_addrs),
295 ..self
296 }
297 }
298
299 pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
300 Self {
301 dogecoind_addr: Some(dogecoind_addrs),
302 ..self
303 }
304 }
305
306 pub fn with_nns_subnet(mut self) -> Self {
308 let mut config = self.config.unwrap_or_default();
309 config.nns = Some(config.nns.unwrap_or_default());
310 self.config = Some(config);
311 self
312 }
313
314 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
331 self.with_subnet_state(SubnetKind::NNS, path_to_state)
332 }
333
334 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
351 let mut config = self.config.unwrap_or_default();
352 #[cfg(not(windows))]
353 let state_dir = path_to_state;
354 #[cfg(windows)]
355 let state_dir = wsl_path(&path_to_state, "subnet state").into();
356 let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
357 match subnet_kind {
358 SubnetKind::NNS => config.nns = Some(subnet_spec),
359 SubnetKind::SNS => config.sns = Some(subnet_spec),
360 SubnetKind::II => config.ii = Some(subnet_spec),
361 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
362 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
363 SubnetKind::Application => config.application.push(subnet_spec),
364 SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
365 SubnetKind::System => config.system.push(subnet_spec),
366 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
367 };
368 self.config = Some(config);
369 self
370 }
371
372 pub fn with_sns_subnet(mut self) -> Self {
374 let mut config = self.config.unwrap_or_default();
375 config.sns = Some(config.sns.unwrap_or_default());
376 self.config = Some(config);
377 self
378 }
379
380 pub fn with_ii_subnet(mut self) -> Self {
382 let mut config = self.config.unwrap_or_default();
383 config.ii = Some(config.ii.unwrap_or_default());
384 self.config = Some(config);
385 self
386 }
387
388 pub fn with_fiduciary_subnet(mut self) -> Self {
390 let mut config = self.config.unwrap_or_default();
391 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
392 self.config = Some(config);
393 self
394 }
395
396 pub fn with_bitcoin_subnet(mut self) -> Self {
398 let mut config = self.config.unwrap_or_default();
399 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
400 self.config = Some(config);
401 self
402 }
403
404 pub fn with_system_subnet(mut self) -> Self {
406 let mut config = self.config.unwrap_or_default();
407 config.system.push(SubnetSpec::default());
408 self.config = Some(config);
409 self
410 }
411
412 pub fn with_application_subnet(mut self) -> Self {
414 let mut config = self.config.unwrap_or_default();
415 config.application.push(SubnetSpec::default());
416 self.config = Some(config);
417 self
418 }
419
420 pub fn with_verified_application_subnet(mut self) -> Self {
422 let mut config = self.config.unwrap_or_default();
423 config.verified_application.push(SubnetSpec::default());
424 self.config = Some(config);
425 self
426 }
427
428 pub fn with_benchmarking_application_subnet(mut self) -> Self {
430 let mut config = self.config.unwrap_or_default();
431 config
432 .application
433 .push(SubnetSpec::default().with_benchmarking_instruction_config());
434 self.config = Some(config);
435 self
436 }
437
438 pub fn with_benchmarking_system_subnet(mut self) -> Self {
440 let mut config = self.config.unwrap_or_default();
441 config
442 .system
443 .push(SubnetSpec::default().with_benchmarking_instruction_config());
444 self.config = Some(config);
445 self
446 }
447
448 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
453 self.icp_features = icp_features;
454 self
455 }
456
457 #[deprecated(note = "Use `with_initial_time` instead")]
461 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
462 self.initial_time = Some(InitialTime::Timestamp(RawTime {
463 nanos_since_epoch: initial_timestamp_nanos,
464 }));
465 self
466 }
467
468 pub fn with_initial_time(mut self, initial_time: Time) -> Self {
472 self.initial_time = Some(InitialTime::Timestamp(RawTime {
473 nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
474 }));
475 self
476 }
477
478 pub fn with_auto_progress(mut self) -> Self {
482 let config = AutoProgressConfig {
483 artificial_delay_ms: None,
484 };
485 self.initial_time = Some(InitialTime::AutoProgress(config));
486 self
487 }
488
489 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
490 self.http_gateway_config = Some(http_gateway_config);
491 self
492 }
493
494 pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
495 self.mainnet_nns_subnet_id = Some(true);
496 self
497 }
498}
499
500#[derive(Copy, Clone, PartialEq, PartialOrd)]
503pub struct Time(Duration);
504
505impl Time {
506 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
508 self.0.as_nanos().try_into().unwrap()
509 }
510
511 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
512 Time(Duration::from_nanos(nanos))
513 }
514}
515
516impl std::fmt::Debug for Time {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
519 write!(f, "{nanos_since_unix_epoch}")
520 }
521}
522
523impl std::ops::Add<Duration> for Time {
524 type Output = Time;
525 fn add(self, dur: Duration) -> Time {
526 Time(self.0 + dur)
527 }
528}
529
530impl From<SystemTime> for Time {
531 fn from(time: SystemTime) -> Self {
532 Self::from_nanos_since_unix_epoch(
533 time.duration_since(UNIX_EPOCH)
534 .unwrap()
535 .as_nanos()
536 .try_into()
537 .unwrap(),
538 )
539 }
540}
541
542impl TryFrom<Time> for SystemTime {
543 type Error = String;
544
545 fn try_from(time: Time) -> Result<SystemTime, String> {
546 let nanos = time.as_nanos_since_unix_epoch();
547 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
548 let roundtrip: Time = system_time.into();
549 if roundtrip.as_nanos_since_unix_epoch() == nanos {
550 Ok(system_time)
551 } else {
552 Err(format!(
553 "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
554 ))
555 }
556 }
557}
558
559pub struct PocketIc {
561 pocket_ic: PocketIcAsync,
562 runtime: Arc<tokio::runtime::Runtime>,
563 thread: Option<JoinHandle<()>>,
564}
565
566impl PocketIc {
567 pub fn new() -> Self {
570 PocketIcBuilder::new().with_application_subnet().build()
571 }
572
573 pub fn new_from_existing_instance(
578 server_url: Url,
579 instance_id: InstanceId,
580 max_request_time_ms: Option<u64>,
581 ) -> Self {
582 let (tx, rx) = channel();
583 let thread = thread::spawn(move || {
584 let rt = tokio::runtime::Builder::new_current_thread()
585 .enable_all()
586 .build()
587 .unwrap();
588 tx.send(rt).unwrap();
589 });
590 let runtime = rx.recv().unwrap();
591
592 let pocket_ic =
593 PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
594
595 Self {
596 pocket_ic,
597 runtime: Arc::new(runtime),
598 thread: Some(thread),
599 }
600 }
601
602 #[allow(clippy::too_many_arguments)]
603 pub(crate) fn from_components(
604 subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
605 server_url: Option<Url>,
606 server_binary: Option<PathBuf>,
607 max_request_time_ms: Option<u64>,
608 read_only_state_dir: Option<PathBuf>,
609 state_dir: Option<PocketIcState>,
610 icp_config: IcpConfig,
611 log_level: Option<Level>,
612 bitcoind_addr: Option<Vec<SocketAddr>>,
613 dogecoind_addr: Option<Vec<SocketAddr>>,
614 icp_features: IcpFeatures,
615 initial_time: Option<InitialTime>,
616 http_gateway_config: Option<InstanceHttpGatewayConfig>,
617 mainnet_nns_subnet_id: Option<bool>,
618 ) -> Self {
619 let (tx, rx) = channel();
620 let thread = thread::spawn(move || {
621 let rt = tokio::runtime::Builder::new_current_thread()
622 .enable_all()
623 .build()
624 .unwrap();
625 tx.send(rt).unwrap();
626 });
627 let runtime = rx.recv().unwrap();
628
629 let pocket_ic = runtime.block_on(async {
630 PocketIcAsync::from_components(
631 subnet_config_set,
632 server_url,
633 server_binary,
634 max_request_time_ms,
635 read_only_state_dir,
636 state_dir,
637 icp_config,
638 log_level,
639 bitcoind_addr,
640 dogecoind_addr,
641 icp_features,
642 initial_time,
643 http_gateway_config,
644 mainnet_nns_subnet_id,
645 )
646 .await
647 });
648
649 Self {
650 pocket_ic,
651 runtime: Arc::new(runtime),
652 thread: Some(thread),
653 }
654 }
655
656 pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
657 self.pocket_ic.take_state_internal()
658 }
659
660 pub fn get_server_url(&self) -> Url {
662 self.pocket_ic.get_server_url()
663 }
664
665 pub fn instance_id(&self) -> InstanceId {
667 self.pocket_ic.instance_id
668 }
669
670 pub fn topology(&self) -> Topology {
672 let runtime = self.runtime.clone();
673 runtime.block_on(async { self.pocket_ic.topology().await })
674 }
675
676 #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
678 pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
679 let runtime = self.runtime.clone();
680 runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
681 }
682
683 #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
686 pub fn set_stable_memory(
687 &self,
688 canister_id: CanisterId,
689 data: Vec<u8>,
690 compression: BlobCompression,
691 ) {
692 let runtime = self.runtime.clone();
693 runtime.block_on(async {
694 self.pocket_ic
695 .set_stable_memory(canister_id, data, compression)
696 .await
697 })
698 }
699
700 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
702 pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
703 let runtime = self.runtime.clone();
704 runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
705 }
706
707 #[instrument(ret)]
709 pub fn list_instances() -> Vec<String> {
710 let runtime = tokio::runtime::Builder::new_current_thread()
711 .build()
712 .unwrap();
713 let url = runtime.block_on(async {
714 let (_, server_url) = start_server(StartServerParams {
715 reuse: true,
716 ..Default::default()
717 })
718 .await;
719 server_url.join("instances").unwrap()
720 });
721 let instances: Vec<String> = reqwest::blocking::Client::new()
722 .get(url)
723 .send()
724 .expect("Failed to get result")
725 .json()
726 .expect("Failed to get json");
727 instances
728 }
729
730 #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
732 pub fn verify_canister_signature(
733 &self,
734 msg: Vec<u8>,
735 sig: Vec<u8>,
736 pubkey: Vec<u8>,
737 root_pubkey: Vec<u8>,
738 ) -> Result<(), String> {
739 let runtime = self.runtime.clone();
740 runtime.block_on(async {
741 self.pocket_ic
742 .verify_canister_signature(msg, sig, pubkey, root_pubkey)
743 .await
744 })
745 }
746
747 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
752 pub fn tick(&self) {
753 let runtime = self.runtime.clone();
754 runtime.block_on(async { self.pocket_ic.tick().await })
755 }
756
757 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
760 pub fn tick_with_configs(&self, configs: TickConfigs) {
761 let runtime = self.runtime.clone();
762 runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
763 }
764
765 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
771 pub fn auto_progress(&self) -> Url {
772 let runtime = self.runtime.clone();
773 runtime.block_on(async { self.pocket_ic.auto_progress().await })
774 }
775
776 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
778 pub fn auto_progress_enabled(&self) -> bool {
779 let runtime = self.runtime.clone();
780 runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
781 }
782
783 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
785 pub fn stop_progress(&self) {
786 let runtime = self.runtime.clone();
787 runtime.block_on(async { self.pocket_ic.stop_progress().await })
788 }
789
790 pub fn url(&self) -> Option<Url> {
794 self.pocket_ic.url()
795 }
796
797 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
806 pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
807 let runtime = self.runtime.clone();
808 runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
809 }
810
811 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
822 pub fn make_live_with_params(
823 &mut self,
824 ip_addr: Option<IpAddr>,
825 listen_at: Option<u16>,
826 domains: Option<Vec<String>>,
827 https_config: Option<HttpsConfig>,
828 ) -> Url {
829 let runtime = self.runtime.clone();
830 runtime.block_on(async {
831 self.pocket_ic
832 .make_live_with_params(ip_addr, listen_at, domains, https_config)
833 .await
834 })
835 }
836
837 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
840 pub fn stop_live(&mut self) {
841 let runtime = self.runtime.clone();
842 runtime.block_on(async { self.pocket_ic.stop_live().await })
843 }
844
845 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
847 pub fn root_key(&self) -> Option<Vec<u8>> {
848 let runtime = self.runtime.clone();
849 runtime.block_on(async { self.pocket_ic.root_key().await })
850 }
851
852 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
854 pub fn get_time(&self) -> Time {
855 let runtime = self.runtime.clone();
856 runtime.block_on(async { self.pocket_ic.get_time().await })
857 }
858
859 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
861 pub fn set_time(&self, time: Time) {
862 let runtime = self.runtime.clone();
863 runtime.block_on(async { self.pocket_ic.set_time(time).await })
864 }
865
866 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
868 pub fn set_certified_time(&self, time: Time) {
869 let runtime = self.runtime.clone();
870 runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
871 }
872
873 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
875 pub fn advance_time(&self, duration: Duration) {
876 let runtime = self.runtime.clone();
877 runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
878 }
879
880 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
883 pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
884 let runtime = self.runtime.clone();
885 runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
886 }
887
888 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
890 pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
891 let runtime = self.runtime.clone();
892 runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
893 }
894
895 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
897 pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
898 let runtime = self.runtime.clone();
899 runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
900 }
901
902 pub fn submit_call(
904 &self,
905 canister_id: CanisterId,
906 sender: Principal,
907 method: &str,
908 payload: Vec<u8>,
909 ) -> Result<RawMessageId, RejectResponse> {
910 let runtime = self.runtime.clone();
911 runtime.block_on(async {
912 self.pocket_ic
913 .submit_call(canister_id, sender, method, payload)
914 .await
915 })
916 }
917
918 pub fn submit_call_with_effective_principal(
920 &self,
921 canister_id: CanisterId,
922 effective_principal: RawEffectivePrincipal,
923 sender: Principal,
924 method: &str,
925 payload: Vec<u8>,
926 ) -> Result<RawMessageId, RejectResponse> {
927 let runtime = self.runtime.clone();
928 runtime.block_on(async {
929 self.pocket_ic
930 .submit_call_with_effective_principal(
931 canister_id,
932 effective_principal,
933 sender,
934 method,
935 payload,
936 )
937 .await
938 })
939 }
940
941 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
943 let runtime = self.runtime.clone();
944 runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
945 }
946
947 pub fn ingress_status(
951 &self,
952 message_id: RawMessageId,
953 ) -> Option<Result<Vec<u8>, RejectResponse>> {
954 let runtime = self.runtime.clone();
955 runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
956 }
957
958 pub fn ingress_status_as(
963 &self,
964 message_id: RawMessageId,
965 caller: Principal,
966 ) -> IngressStatusResult {
967 let runtime = self.runtime.clone();
968 runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
969 }
970
971 pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
975 let runtime = self.runtime.clone();
976 runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
977 }
978
979 #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
981 pub fn update_call(
982 &self,
983 canister_id: CanisterId,
984 sender: Principal,
985 method: &str,
986 payload: Vec<u8>,
987 ) -> Result<Vec<u8>, RejectResponse> {
988 let runtime = self.runtime.clone();
989 runtime.block_on(async {
990 self.pocket_ic
991 .update_call(canister_id, sender, method, payload)
992 .await
993 })
994 }
995
996 #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
998 pub fn query_call(
999 &self,
1000 canister_id: CanisterId,
1001 sender: Principal,
1002 method: &str,
1003 payload: Vec<u8>,
1004 ) -> Result<Vec<u8>, RejectResponse> {
1005 let runtime = self.runtime.clone();
1006 runtime.block_on(async {
1007 self.pocket_ic
1008 .query_call(canister_id, sender, method, payload)
1009 .await
1010 })
1011 }
1012
1013 pub fn fetch_canister_logs(
1015 &self,
1016 canister_id: CanisterId,
1017 sender: Principal,
1018 ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
1019 let runtime = self.runtime.clone();
1020 runtime.block_on(async {
1021 self.pocket_ic
1022 .fetch_canister_logs(canister_id, sender)
1023 .await
1024 })
1025 }
1026
1027 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1029 pub fn canister_status(
1030 &self,
1031 canister_id: CanisterId,
1032 sender: Option<Principal>,
1033 ) -> Result<CanisterStatusResult, RejectResponse> {
1034 let runtime = self.runtime.clone();
1035 runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1036 }
1037
1038 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1040 pub fn create_canister(&self) -> CanisterId {
1041 let runtime = self.runtime.clone();
1042 runtime.block_on(async { self.pocket_ic.create_canister().await })
1043 }
1044
1045 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1047 pub fn create_canister_with_settings(
1048 &self,
1049 sender: Option<Principal>,
1050 settings: Option<CanisterSettings>,
1051 ) -> CanisterId {
1052 let runtime = self.runtime.clone();
1053 runtime.block_on(async {
1054 self.pocket_ic
1055 .create_canister_with_settings(sender, settings)
1056 .await
1057 })
1058 }
1059
1060 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, canister_id = %canister_id.to_string()))]
1068 pub fn create_canister_with_id(
1069 &self,
1070 sender: Option<Principal>,
1071 settings: Option<CanisterSettings>,
1072 canister_id: CanisterId,
1073 ) -> Result<CanisterId, String> {
1074 let runtime = self.runtime.clone();
1075 runtime.block_on(async {
1076 self.pocket_ic
1077 .create_canister_with_id(sender, settings, canister_id)
1078 .await
1079 })
1080 }
1081
1082 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, subnet_id = %subnet_id.to_string()))]
1084 pub fn create_canister_on_subnet(
1085 &self,
1086 sender: Option<Principal>,
1087 settings: Option<CanisterSettings>,
1088 subnet_id: SubnetId,
1089 ) -> CanisterId {
1090 let runtime = self.runtime.clone();
1091 runtime.block_on(async {
1092 self.pocket_ic
1093 .create_canister_on_subnet(sender, settings, subnet_id)
1094 .await
1095 })
1096 }
1097
1098 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1101 pub fn upload_chunk(
1102 &self,
1103 canister_id: CanisterId,
1104 sender: Option<Principal>,
1105 chunk: Vec<u8>,
1106 ) -> Result<Vec<u8>, RejectResponse> {
1107 let runtime = self.runtime.clone();
1108 runtime.block_on(async {
1109 self.pocket_ic
1110 .upload_chunk(canister_id, sender, chunk)
1111 .await
1112 })
1113 }
1114
1115 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1117 pub fn stored_chunks(
1118 &self,
1119 canister_id: CanisterId,
1120 sender: Option<Principal>,
1121 ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1122 let runtime = self.runtime.clone();
1123 runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1124 }
1125
1126 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1128 pub fn clear_chunk_store(
1129 &self,
1130 canister_id: CanisterId,
1131 sender: Option<Principal>,
1132 ) -> Result<(), RejectResponse> {
1133 let runtime = self.runtime.clone();
1134 runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1135 }
1136
1137 #[instrument(skip(self, mode, chunk_hashes_list, wasm_module_hash, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string(), store_canister_id = %store_canister_id.to_string(), arg_len = %arg.len()))]
1139 pub fn install_chunked_canister(
1140 &self,
1141 canister_id: CanisterId,
1142 sender: Option<Principal>,
1143 mode: CanisterInstallMode,
1144 store_canister_id: CanisterId,
1145 chunk_hashes_list: Vec<Vec<u8>>,
1146 wasm_module_hash: Vec<u8>,
1147 arg: Vec<u8>,
1148 ) -> Result<(), RejectResponse> {
1149 let runtime = self.runtime.clone();
1150 runtime.block_on(async {
1151 self.pocket_ic
1152 .install_chunked_canister(
1153 canister_id,
1154 sender,
1155 mode,
1156 store_canister_id,
1157 chunk_hashes_list,
1158 wasm_module_hash,
1159 arg,
1160 )
1161 .await
1162 })
1163 }
1164
1165 #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1167 pub fn install_canister(
1168 &self,
1169 canister_id: CanisterId,
1170 wasm_module: Vec<u8>,
1171 arg: Vec<u8>,
1172 sender: Option<Principal>,
1173 ) {
1174 let runtime = self.runtime.clone();
1175 runtime.block_on(async {
1176 self.pocket_ic
1177 .install_canister(canister_id, wasm_module, arg, sender)
1178 .await
1179 })
1180 }
1181
1182 #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1184 pub fn upgrade_canister(
1185 &self,
1186 canister_id: CanisterId,
1187 wasm_module: Vec<u8>,
1188 arg: Vec<u8>,
1189 sender: Option<Principal>,
1190 ) -> Result<(), RejectResponse> {
1191 let runtime = self.runtime.clone();
1192 runtime.block_on(async {
1193 self.pocket_ic
1194 .upgrade_canister(canister_id, wasm_module, arg, sender)
1195 .await
1196 })
1197 }
1198
1199 #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1201 pub fn upgrade_eop_canister(
1202 &self,
1203 canister_id: CanisterId,
1204 wasm_module: Vec<u8>,
1205 arg: Vec<u8>,
1206 sender: Option<Principal>,
1207 ) -> Result<(), RejectResponse> {
1208 let runtime = self.runtime.clone();
1209 runtime.block_on(async {
1210 self.pocket_ic
1211 .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1212 .await
1213 })
1214 }
1215
1216 #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1218 pub fn reinstall_canister(
1219 &self,
1220 canister_id: CanisterId,
1221 wasm_module: Vec<u8>,
1222 arg: Vec<u8>,
1223 sender: Option<Principal>,
1224 ) -> Result<(), RejectResponse> {
1225 let runtime = self.runtime.clone();
1226 runtime.block_on(async {
1227 self.pocket_ic
1228 .reinstall_canister(canister_id, wasm_module, arg, sender)
1229 .await
1230 })
1231 }
1232
1233 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1235 pub fn uninstall_canister(
1236 &self,
1237 canister_id: CanisterId,
1238 sender: Option<Principal>,
1239 ) -> Result<(), RejectResponse> {
1240 let runtime = self.runtime.clone();
1241 runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1242 }
1243
1244 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1246 pub fn take_canister_snapshot(
1247 &self,
1248 canister_id: CanisterId,
1249 sender: Option<Principal>,
1250 replace_snapshot: Option<Vec<u8>>,
1251 ) -> Result<Snapshot, RejectResponse> {
1252 let runtime = self.runtime.clone();
1253 runtime.block_on(async {
1254 self.pocket_ic
1255 .take_canister_snapshot(canister_id, sender, replace_snapshot)
1256 .await
1257 })
1258 }
1259
1260 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1262 pub fn load_canister_snapshot(
1263 &self,
1264 canister_id: CanisterId,
1265 sender: Option<Principal>,
1266 snapshot_id: Vec<u8>,
1267 ) -> Result<(), RejectResponse> {
1268 let runtime = self.runtime.clone();
1269 runtime.block_on(async {
1270 self.pocket_ic
1271 .load_canister_snapshot(canister_id, sender, snapshot_id)
1272 .await
1273 })
1274 }
1275
1276 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1278 pub fn list_canister_snapshots(
1279 &self,
1280 canister_id: CanisterId,
1281 sender: Option<Principal>,
1282 ) -> Result<Vec<Snapshot>, RejectResponse> {
1283 let runtime = self.runtime.clone();
1284 runtime.block_on(async {
1285 self.pocket_ic
1286 .list_canister_snapshots(canister_id, sender)
1287 .await
1288 })
1289 }
1290
1291 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1293 pub fn delete_canister_snapshot(
1294 &self,
1295 canister_id: CanisterId,
1296 sender: Option<Principal>,
1297 snapshot_id: Vec<u8>,
1298 ) -> Result<(), RejectResponse> {
1299 let runtime = self.runtime.clone();
1300 runtime.block_on(async {
1301 self.pocket_ic
1302 .delete_canister_snapshot(canister_id, sender, snapshot_id)
1303 .await
1304 })
1305 }
1306
1307 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1309 pub fn update_canister_settings(
1310 &self,
1311 canister_id: CanisterId,
1312 sender: Option<Principal>,
1313 settings: CanisterSettings,
1314 ) -> Result<(), RejectResponse> {
1315 let runtime = self.runtime.clone();
1316 runtime.block_on(async {
1317 self.pocket_ic
1318 .update_canister_settings(canister_id, sender, settings)
1319 .await
1320 })
1321 }
1322
1323 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1325 pub fn set_controllers(
1326 &self,
1327 canister_id: CanisterId,
1328 sender: Option<Principal>,
1329 new_controllers: Vec<Principal>,
1330 ) -> Result<(), RejectResponse> {
1331 let runtime = self.runtime.clone();
1332 runtime.block_on(async {
1333 self.pocket_ic
1334 .set_controllers(canister_id, sender, new_controllers)
1335 .await
1336 })
1337 }
1338
1339 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1341 pub fn start_canister(
1342 &self,
1343 canister_id: CanisterId,
1344 sender: Option<Principal>,
1345 ) -> Result<(), RejectResponse> {
1346 let runtime = self.runtime.clone();
1347 runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1348 }
1349
1350 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1352 pub fn stop_canister(
1353 &self,
1354 canister_id: CanisterId,
1355 sender: Option<Principal>,
1356 ) -> Result<(), RejectResponse> {
1357 let runtime = self.runtime.clone();
1358 runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1359 }
1360
1361 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1363 pub fn delete_canister(
1364 &self,
1365 canister_id: CanisterId,
1366 sender: Option<Principal>,
1367 ) -> Result<(), RejectResponse> {
1368 let runtime = self.runtime.clone();
1369 runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1370 }
1371
1372 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1374 pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1375 let runtime = self.runtime.clone();
1376 runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1377 }
1378
1379 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1381 pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1382 let runtime = self.runtime.clone();
1383 runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1384 }
1385
1386 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1388 pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1389 let runtime = self.runtime.clone();
1390 runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1391 }
1392
1393 pub fn update_call_with_effective_principal(
1394 &self,
1395 canister_id: CanisterId,
1396 effective_principal: RawEffectivePrincipal,
1397 sender: Principal,
1398 method: &str,
1399 payload: Vec<u8>,
1400 ) -> Result<Vec<u8>, RejectResponse> {
1401 let runtime = self.runtime.clone();
1402 runtime.block_on(async {
1403 self.pocket_ic
1404 .update_call_with_effective_principal(
1405 canister_id,
1406 effective_principal,
1407 sender,
1408 method,
1409 payload,
1410 )
1411 .await
1412 })
1413 }
1414
1415 #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), effective_principal = %effective_principal.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1419 pub fn query_call_with_effective_principal(
1420 &self,
1421 canister_id: CanisterId,
1422 effective_principal: RawEffectivePrincipal,
1423 sender: Principal,
1424 method: &str,
1425 payload: Vec<u8>,
1426 ) -> Result<Vec<u8>, RejectResponse> {
1427 let runtime = self.runtime.clone();
1428 runtime.block_on(async {
1429 self.pocket_ic
1430 .query_call_with_effective_principal(
1431 canister_id,
1432 effective_principal,
1433 sender,
1434 method,
1435 payload,
1436 )
1437 .await
1438 })
1439 }
1440
1441 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1453 pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1454 let runtime = self.runtime.clone();
1455 runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1456 }
1457
1458 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1460 pub fn mock_canister_http_response(
1461 &self,
1462 mock_canister_http_response: MockCanisterHttpResponse,
1463 ) {
1464 let runtime = self.runtime.clone();
1465 runtime.block_on(async {
1466 self.pocket_ic
1467 .mock_canister_http_response(mock_canister_http_response)
1468 .await
1469 })
1470 }
1471
1472 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1476 pub fn canister_snapshot_download(
1477 &self,
1478 canister_id: CanisterId,
1479 sender: Principal,
1480 snapshot_id: Vec<u8>,
1481 snapshot_dir: PathBuf,
1482 ) {
1483 let runtime = self.runtime.clone();
1484 runtime.block_on(async {
1485 self.pocket_ic
1486 .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1487 .await
1488 })
1489 }
1490
1491 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1495 pub fn canister_snapshot_upload(
1496 &self,
1497 canister_id: CanisterId,
1498 sender: Principal,
1499 replace_snapshot: Option<Vec<u8>>,
1500 snapshot_dir: PathBuf,
1501 ) -> Vec<u8> {
1502 let runtime = self.runtime.clone();
1503 runtime.block_on(async {
1504 self.pocket_ic
1505 .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1506 .await
1507 })
1508 }
1509}
1510
1511impl Default for PocketIc {
1512 fn default() -> Self {
1513 Self::new()
1514 }
1515}
1516
1517impl Drop for PocketIc {
1518 fn drop(&mut self) {
1519 self.runtime.block_on(async {
1520 self.pocket_ic.do_drop().await;
1521 });
1522 if let Some(thread) = self.thread.take() {
1523 thread.join().unwrap();
1524 }
1525 }
1526}
1527
1528pub fn call_candid_as<Input, Output>(
1532 env: &PocketIc,
1533 canister_id: CanisterId,
1534 effective_principal: RawEffectivePrincipal,
1535 sender: Principal,
1536 method: &str,
1537 input: Input,
1538) -> Result<Output, RejectResponse>
1539where
1540 Input: ArgumentEncoder,
1541 Output: for<'a> ArgumentDecoder<'a>,
1542{
1543 with_candid(input, |payload| {
1544 env.update_call_with_effective_principal(
1545 canister_id,
1546 effective_principal,
1547 sender,
1548 method,
1549 payload,
1550 )
1551 })
1552}
1553
1554pub fn call_candid<Input, Output>(
1557 env: &PocketIc,
1558 canister_id: CanisterId,
1559 effective_principal: RawEffectivePrincipal,
1560 method: &str,
1561 input: Input,
1562) -> Result<Output, RejectResponse>
1563where
1564 Input: ArgumentEncoder,
1565 Output: for<'a> ArgumentDecoder<'a>,
1566{
1567 call_candid_as(
1568 env,
1569 canister_id,
1570 effective_principal,
1571 Principal::anonymous(),
1572 method,
1573 input,
1574 )
1575}
1576
1577pub fn query_candid<Input, Output>(
1579 env: &PocketIc,
1580 canister_id: CanisterId,
1581 method: &str,
1582 input: Input,
1583) -> Result<Output, RejectResponse>
1584where
1585 Input: ArgumentEncoder,
1586 Output: for<'a> ArgumentDecoder<'a>,
1587{
1588 query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1589}
1590
1591pub fn query_candid_as<Input, Output>(
1594 env: &PocketIc,
1595 canister_id: CanisterId,
1596 sender: Principal,
1597 method: &str,
1598 input: Input,
1599) -> Result<Output, RejectResponse>
1600where
1601 Input: ArgumentEncoder,
1602 Output: for<'a> ArgumentDecoder<'a>,
1603{
1604 with_candid(input, |bytes| {
1605 env.query_call(canister_id, sender, method, bytes)
1606 })
1607}
1608
1609pub fn update_candid<Input, Output>(
1611 env: &PocketIc,
1612 canister_id: CanisterId,
1613 method: &str,
1614 input: Input,
1615) -> Result<Output, RejectResponse>
1616where
1617 Input: ArgumentEncoder,
1618 Output: for<'a> ArgumentDecoder<'a>,
1619{
1620 update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1621}
1622
1623pub fn update_candid_as<Input, Output>(
1626 env: &PocketIc,
1627 canister_id: CanisterId,
1628 sender: Principal,
1629 method: &str,
1630 input: Input,
1631) -> Result<Output, RejectResponse>
1632where
1633 Input: ArgumentEncoder,
1634 Output: for<'a> ArgumentDecoder<'a>,
1635{
1636 with_candid(input, |bytes| {
1637 env.update_call(canister_id, sender, method, bytes)
1638 })
1639}
1640
1641pub fn with_candid<Input, Output>(
1644 input: Input,
1645 f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1646) -> Result<Output, RejectResponse>
1647where
1648 Input: ArgumentEncoder,
1649 Output: for<'a> ArgumentDecoder<'a>,
1650{
1651 let in_bytes = encode_args(input).expect("failed to encode args");
1652 f(in_bytes).map(|out_bytes| {
1653 decode_args(&out_bytes).unwrap_or_else(|e| {
1654 panic!(
1655 "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1656 std::any::type_name::<Output>(),
1657 e,
1658 out_bytes,
1659 String::from_utf8_lossy(&out_bytes),
1660 )
1661 })
1662 })
1663}
1664
1665#[derive(Clone, Copy, Debug)]
1667pub enum TryFromError {
1668 ValueOutOfRange(u64),
1669}
1670
1671#[derive(
1678 PartialOrd,
1679 Ord,
1680 Clone,
1681 Copy,
1682 Debug,
1683 PartialEq,
1684 Eq,
1685 Hash,
1686 Serialize,
1687 Deserialize,
1688 JsonSchema,
1689 EnumIter,
1690)]
1691pub enum ErrorCode {
1692 SubnetOversubscribed = 101,
1694 MaxNumberOfCanistersReached = 102,
1695 CanisterQueueFull = 201,
1697 IngressMessageTimeout = 202,
1698 CanisterQueueNotEmpty = 203,
1699 IngressHistoryFull = 204,
1700 CanisterIdAlreadyExists = 205,
1701 StopCanisterRequestTimeout = 206,
1702 CanisterOutOfCycles = 207,
1703 CertifiedStateUnavailable = 208,
1704 CanisterInstallCodeRateLimited = 209,
1705 CanisterHeapDeltaRateLimited = 210,
1706 CanisterNotFound = 301,
1708 CanisterSnapshotNotFound = 305,
1709 InsufficientMemoryAllocation = 402,
1711 InsufficientCyclesForCreateCanister = 403,
1712 SubnetNotFound = 404,
1713 CanisterNotHostedBySubnet = 405,
1714 CanisterRejectedMessage = 406,
1715 UnknownManagementMessage = 407,
1716 InvalidManagementPayload = 408,
1717 CanisterSnapshotImmutable = 409,
1718 InvalidSubnetAdmin = 410,
1719 CanisterTrapped = 502,
1721 CanisterCalledTrap = 503,
1722 CanisterContractViolation = 504,
1723 CanisterInvalidWasm = 505,
1724 CanisterDidNotReply = 506,
1725 CanisterOutOfMemory = 507,
1726 CanisterStopped = 508,
1727 CanisterStopping = 509,
1728 CanisterNotStopped = 510,
1729 CanisterStoppingCancelled = 511,
1730 CanisterInvalidController = 512,
1731 CanisterFunctionNotFound = 513,
1732 CanisterNonEmpty = 514,
1733 QueryCallGraphLoopDetected = 517,
1734 InsufficientCyclesInCall = 520,
1735 CanisterWasmEngineError = 521,
1736 CanisterInstructionLimitExceeded = 522,
1737 CanisterMemoryAccessLimitExceeded = 524,
1738 QueryCallGraphTooDeep = 525,
1739 QueryCallGraphTotalInstructionLimitExceeded = 526,
1740 CompositeQueryCalledInReplicatedMode = 527,
1741 QueryTimeLimitExceeded = 528,
1742 QueryCallGraphInternal = 529,
1743 InsufficientCyclesInComputeAllocation = 530,
1744 InsufficientCyclesInMemoryAllocation = 531,
1745 InsufficientCyclesInMemoryGrow = 532,
1746 ReservedCyclesLimitExceededInMemoryAllocation = 533,
1747 ReservedCyclesLimitExceededInMemoryGrow = 534,
1748 InsufficientCyclesInMessageMemoryGrow = 535,
1749 CanisterMethodNotFound = 536,
1750 CanisterWasmModuleNotFound = 537,
1751 CanisterAlreadyInstalled = 538,
1752 CanisterWasmMemoryLimitExceeded = 539,
1753 ReservedCyclesLimitIsTooLow = 540,
1754 CanisterInvalidControllerOrSubnetAdmin = 541,
1755 DeadlineExpired = 601,
1757 ResponseDropped = 602,
1758}
1759
1760impl TryFrom<u64> for ErrorCode {
1761 type Error = TryFromError;
1762 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1763 match err {
1764 101 => Ok(ErrorCode::SubnetOversubscribed),
1766 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1767 201 => Ok(ErrorCode::CanisterQueueFull),
1769 202 => Ok(ErrorCode::IngressMessageTimeout),
1770 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1771 204 => Ok(ErrorCode::IngressHistoryFull),
1772 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1773 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1774 207 => Ok(ErrorCode::CanisterOutOfCycles),
1775 208 => Ok(ErrorCode::CertifiedStateUnavailable),
1776 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1777 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1778 301 => Ok(ErrorCode::CanisterNotFound),
1780 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1781 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1783 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1784 404 => Ok(ErrorCode::SubnetNotFound),
1785 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1786 406 => Ok(ErrorCode::CanisterRejectedMessage),
1787 407 => Ok(ErrorCode::UnknownManagementMessage),
1788 408 => Ok(ErrorCode::InvalidManagementPayload),
1789 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1790 410 => Ok(ErrorCode::InvalidSubnetAdmin),
1791 502 => Ok(ErrorCode::CanisterTrapped),
1793 503 => Ok(ErrorCode::CanisterCalledTrap),
1794 504 => Ok(ErrorCode::CanisterContractViolation),
1795 505 => Ok(ErrorCode::CanisterInvalidWasm),
1796 506 => Ok(ErrorCode::CanisterDidNotReply),
1797 507 => Ok(ErrorCode::CanisterOutOfMemory),
1798 508 => Ok(ErrorCode::CanisterStopped),
1799 509 => Ok(ErrorCode::CanisterStopping),
1800 510 => Ok(ErrorCode::CanisterNotStopped),
1801 511 => Ok(ErrorCode::CanisterStoppingCancelled),
1802 512 => Ok(ErrorCode::CanisterInvalidController),
1803 513 => Ok(ErrorCode::CanisterFunctionNotFound),
1804 514 => Ok(ErrorCode::CanisterNonEmpty),
1805 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
1806 520 => Ok(ErrorCode::InsufficientCyclesInCall),
1807 521 => Ok(ErrorCode::CanisterWasmEngineError),
1808 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
1809 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
1810 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
1811 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
1812 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
1813 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
1814 529 => Ok(ErrorCode::QueryCallGraphInternal),
1815 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
1816 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
1817 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
1818 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
1819 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
1820 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
1821 536 => Ok(ErrorCode::CanisterMethodNotFound),
1822 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
1823 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
1824 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
1825 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
1826 541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
1827 601 => Ok(ErrorCode::DeadlineExpired),
1829 602 => Ok(ErrorCode::ResponseDropped),
1830 _ => Err(TryFromError::ValueOutOfRange(err)),
1831 }
1832 }
1833}
1834
1835impl std::fmt::Display for ErrorCode {
1836 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1837 write!(f, "IC{:04}", *self as i32)
1839 }
1840}
1841
1842#[derive(
1847 PartialOrd,
1848 Ord,
1849 Clone,
1850 Copy,
1851 Debug,
1852 PartialEq,
1853 Eq,
1854 Hash,
1855 Serialize,
1856 Deserialize,
1857 JsonSchema,
1858 EnumIter,
1859)]
1860pub enum RejectCode {
1861 SysFatal = 1,
1862 SysTransient = 2,
1863 DestinationInvalid = 3,
1864 CanisterReject = 4,
1865 CanisterError = 5,
1866 SysUnknown = 6,
1867}
1868
1869impl TryFrom<u64> for RejectCode {
1870 type Error = TryFromError;
1871 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
1872 match err {
1873 1 => Ok(RejectCode::SysFatal),
1874 2 => Ok(RejectCode::SysTransient),
1875 3 => Ok(RejectCode::DestinationInvalid),
1876 4 => Ok(RejectCode::CanisterReject),
1877 5 => Ok(RejectCode::CanisterError),
1878 6 => Ok(RejectCode::SysUnknown),
1879 _ => Err(TryFromError::ValueOutOfRange(err)),
1880 }
1881 }
1882}
1883
1884#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1886pub struct RejectResponse {
1887 pub reject_code: RejectCode,
1888 pub reject_message: String,
1889 pub error_code: ErrorCode,
1890 pub certified: bool,
1891}
1892
1893impl std::fmt::Display for RejectResponse {
1894 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1895 write!(
1897 f,
1898 "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
1899 self.reject_code, self.reject_message, self.error_code
1900 )
1901 }
1902}
1903
1904#[derive(Debug, Serialize, Deserialize)]
1910pub enum IngressStatusResult {
1911 NotAvailable,
1912 Forbidden(String),
1913 Success(Result<Vec<u8>, RejectResponse>),
1914}
1915
1916#[derive(Clone, Debug, Default)]
1917pub struct TickConfigs {
1918 pub blockmakers: Option<Vec<SubnetBlockmakers>>,
1919}
1920
1921impl From<TickConfigs> for RawTickConfigs {
1922 fn from(tick_configs: TickConfigs) -> Self {
1923 Self {
1924 blockmakers: tick_configs.blockmakers.map(|blockmakers| {
1925 blockmakers
1926 .into_iter()
1927 .map(|blockmaker| blockmaker.into())
1928 .collect()
1929 }),
1930 }
1931 }
1932}
1933
1934#[derive(Clone, Debug)]
1935pub struct SubnetBlockmakers {
1936 pub subnet: Principal,
1937 pub blockmaker: Principal,
1938 pub failed_blockmakers: Vec<Principal>,
1939}
1940
1941impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
1942 fn from(blockmaker: SubnetBlockmakers) -> Self {
1943 Self {
1944 subnet: blockmaker.subnet.into(),
1945 blockmaker: blockmaker.blockmaker.into(),
1946 failed_blockmakers: blockmaker
1947 .failed_blockmakers
1948 .into_iter()
1949 .map(|p| p.into())
1950 .collect(),
1951 }
1952 }
1953}
1954
1955#[cfg(windows)]
1956fn wsl_path(path: &PathBuf, desc: &str) -> String {
1957 windows_to_wsl(
1958 path.as_os_str()
1959 .to_str()
1960 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
1961 )
1962 .unwrap_or_else(|e| {
1963 panic!(
1964 "Could not convert {} path ({:?}) to WSL path: {:?}",
1965 desc, path, e
1966 )
1967 })
1968}
1969
1970#[cfg(windows)]
1971static WSL_WARM_UP: Once = Once::new();
1972
1973#[cfg(windows)]
1974fn warm_up_wsl() {
1975 WSL_WARM_UP.call_once(|| {
1976 let output = Command::new("wsl")
1977 .arg("bash")
1978 .arg("-c")
1979 .arg("true")
1980 .output()
1981 .expect("Failed to warm up WSL");
1982 if !output.status.success() {
1983 panic!(
1984 "Failed to warm up WSL.\nStatus: {}\nStdout: {}\nStderr: {}",
1985 output.status,
1986 String::from_utf8_lossy(&output.stdout),
1987 String::from_utf8_lossy(&output.stderr),
1988 );
1989 }
1990 });
1991}
1992
1993#[cfg(windows)]
1994fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1995 warm_up_wsl();
1996 let mut cmd = Command::new("wsl");
1997 cmd.arg(wsl_path(bin_path, "PocketIC binary"));
1998 cmd
1999}
2000
2001#[cfg(not(windows))]
2002fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2003 Command::new(bin_path)
2004}
2005
2006fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
2007 let unexpected_version = format!(
2008 "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
2009 );
2010 let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
2011 return Err(unexpected_version);
2012 };
2013 if pocket_ic_server != POCKET_IC_SERVER_NAME {
2014 return Err(unexpected_version);
2015 }
2016 let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
2017 let version = Version::parse(version)
2018 .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
2019 if !req.matches(&version) {
2020 return Err(format!(
2021 "Incompatible PocketIC server version: got {version}; expected {req}."
2022 ));
2023 }
2024
2025 Ok(())
2026}
2027
2028fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
2029 let mut cmd = pocket_ic_server_cmd(server_binary);
2030 cmd.arg("--version");
2031 let output = cmd.output().map_err(|e| e.to_string())?;
2032 if !output.status.success() {
2033 return Err(format!(
2034 "PocketIC server failed to print its version.\nStatus: {}\nStdout: {}\nStderr: {}",
2035 output.status,
2036 String::from_utf8_lossy(&output.stdout),
2037 String::from_utf8_lossy(&output.stderr),
2038 ));
2039 }
2040 let version_str = String::from_utf8(output.stdout)
2041 .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
2042 let version_line = version_str.trim_end_matches('\n');
2043 check_pocketic_server_version(version_line)
2044}
2045
2046async fn download_pocketic_server(
2047 server_url: String,
2048 mut out: std::fs::File,
2049) -> Result<(), String> {
2050 let binary = reqwest::get(server_url)
2051 .await
2052 .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2053 .bytes()
2054 .await
2055 .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2056 .to_vec();
2057 let mut gz = GzDecoder::new(&binary[..]);
2058 let _ = std::io::copy(&mut gz, &mut out)
2059 .map_err(|e| format!("Failed to write PocketIC server binary: {e}"));
2060 Ok(())
2061}
2062
2063#[derive(Default)]
2064pub struct StartServerParams {
2065 pub server_binary: Option<PathBuf>,
2066 pub reuse: bool,
2068 pub ttl: Option<Duration>,
2075 pub hard_ttl: Option<Duration>,
2083}
2084
2085pub async fn start_server(params: StartServerParams) -> (Child, Url) {
2087 let default_bin_dir =
2088 std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
2089 let default_bin_path = default_bin_dir.join("pocket-ic");
2090 let bin_path_provided =
2091 params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
2092 let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
2093 std::env::var_os("POCKET_IC_BIN")
2094 .unwrap_or_else(|| default_bin_path.clone().into())
2095 .into()
2096 });
2097
2098 if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
2099 if bin_path_provided {
2100 panic!(
2101 "Failed to validate PocketIC server binary `{}`: `{}`.",
2102 bin_path.display(),
2103 e
2104 );
2105 }
2106 bin_path = default_bin_path.clone();
2107 std::fs::create_dir_all(&default_bin_dir)
2108 .expect("Failed to create PocketIC server directory");
2109 let mut options = OpenOptions::new();
2110 options.write(true).create_new(true);
2111 #[cfg(unix)]
2112 options.mode(0o777);
2113 match options.open(&default_bin_path) {
2114 Ok(out) => {
2115 #[cfg(target_os = "macos")]
2116 let os = "darwin";
2117 #[cfg(not(target_os = "macos"))]
2118 let os = "linux";
2119 #[cfg(target_arch = "aarch64")]
2120 let arch = "arm64";
2121 #[cfg(not(target_arch = "aarch64"))]
2122 let arch = "x86_64";
2123 let server_url = format!(
2124 "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2125 );
2126 println!(
2127 "Failed to validate PocketIC server binary `{}`: `{}`. Going to download PocketIC server {} from {} to the local path {}. To avoid downloads during test execution, please specify the path to the (ungzipped and executable) PocketIC server {} using the function `PocketIcBuilder::with_server_binary` or using the `POCKET_IC_BIN` environment variable.",
2128 bin_path.display(),
2129 e,
2130 LATEST_SERVER_VERSION,
2131 server_url,
2132 default_bin_path.display(),
2133 LATEST_SERVER_VERSION
2134 );
2135 if let Err(e) = download_pocketic_server(server_url, out).await {
2136 let _ = std::fs::remove_file(default_bin_path);
2137 panic!("{}", e);
2138 }
2139 }
2140 _ => {
2141 let start = std::time::Instant::now();
2143 loop {
2144 if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2145 break;
2146 }
2147 if start.elapsed() > std::time::Duration::from_secs(60) {
2148 let _ = std::fs::remove_file(&default_bin_path);
2149 panic!(
2150 "Timed out waiting for PocketIC server being available at the local path {}.",
2151 default_bin_path.display()
2152 );
2153 }
2154 std::thread::sleep(std::time::Duration::from_millis(100));
2155 }
2156 }
2157 }
2158 }
2159
2160 let port_file_path = if params.reuse {
2161 let test_driver_pid = std::process::id();
2164 std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2165 } else {
2166 NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
2167 };
2168 let mut cmd = pocket_ic_server_cmd(&bin_path);
2169 if let Some(ttl) = params.ttl {
2170 cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2171 }
2172 if let Some(hard_ttl) = params.hard_ttl {
2173 cmd.arg("--hard-ttl").arg(hard_ttl.as_secs().to_string());
2174 }
2175 cmd.arg("--port-file");
2176 #[cfg(windows)]
2177 cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2178 #[cfg(not(windows))]
2179 cmd.arg(port_file_path.clone());
2180 if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2181 && !mute_server.is_empty()
2182 {
2183 cmd.stdout(std::process::Stdio::null());
2184 cmd.stderr(std::process::Stdio::null());
2185 }
2186
2187 #[cfg(unix)]
2190 {
2191 use std::os::unix::process::CommandExt;
2192 cmd.process_group(0);
2193 }
2194
2195 #[allow(clippy::zombie_processes)]
2197 let child = cmd
2198 .spawn()
2199 .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
2200
2201 loop {
2202 if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2203 && port_string.contains("\n")
2204 {
2205 let port: u16 = port_string
2206 .trim_end()
2207 .parse()
2208 .expect("Failed to parse port to number");
2209 break (
2210 child,
2211 Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2212 );
2213 }
2214 std::thread::sleep(Duration::from_millis(20));
2215 }
2216}
2217
2218#[derive(Error, Debug)]
2219pub enum DefaultEffectiveCanisterIdError {
2220 ReqwestError(#[from] reqwest::Error),
2221 JsonError(#[from] serde_json::Error),
2222 Utf8Error(#[from] std::string::FromUtf8Error),
2223}
2224
2225impl std::fmt::Display for DefaultEffectiveCanisterIdError {
2226 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2227 match self {
2228 DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2229 write!(f, "ReqwestError({err})")
2230 }
2231 DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({err})"),
2232 DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({err})"),
2233 }
2234 }
2235}
2236
2237pub fn get_default_effective_canister_id(
2245 pocket_ic_url: String,
2246) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2247 let runtime = Runtime::new().expect("Unable to create a runtime");
2248 runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2249 pocket_ic_url,
2250 ))
2251}
2252
2253pub fn copy_dir(
2254 src: impl AsRef<std::path::Path>,
2255 dst: impl AsRef<std::path::Path>,
2256) -> std::io::Result<()> {
2257 std::fs::create_dir_all(&dst)?;
2258 for entry in std::fs::read_dir(src)? {
2259 let entry = entry?;
2260 let ty = entry.file_type()?;
2261 if ty.is_dir() {
2262 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2263 } else {
2264 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2265 }
2266 }
2267 Ok(())
2268}
2269
2270#[cfg(test)]
2271mod test {
2272 use crate::{ErrorCode, RejectCode, check_pocketic_server_version};
2273 use strum::IntoEnumIterator;
2274
2275 #[test]
2276 fn reject_code_round_trip() {
2277 for initial in RejectCode::iter() {
2278 let round_trip = RejectCode::try_from(initial as u64).unwrap();
2279
2280 assert_eq!(initial, round_trip);
2281 }
2282 }
2283
2284 #[test]
2285 fn error_code_round_trip() {
2286 for initial in ErrorCode::iter() {
2287 let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2288
2289 assert_eq!(initial, round_trip);
2290 }
2291 }
2292
2293 #[test]
2294 fn reject_code_matches_ic_error_code() {
2295 assert_eq!(
2296 RejectCode::iter().len(),
2297 ic_error_types::RejectCode::iter().len()
2298 );
2299 for ic_reject_code in ic_error_types::RejectCode::iter() {
2300 let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2301 assert_eq!(format!("{reject_code:?}"), format!("{:?}", ic_reject_code));
2302 }
2303 }
2304
2305 #[test]
2306 fn error_code_matches_ic_error_code() {
2307 assert_eq!(
2308 ErrorCode::iter().len(),
2309 ic_error_types::ErrorCode::iter().len()
2310 );
2311 for ic_error_code in ic_error_types::ErrorCode::iter() {
2312 let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2313 assert_eq!(format!("{error_code:?}"), format!("{:?}", ic_error_code));
2314 }
2315 }
2316
2317 #[test]
2318 fn test_check_pocketic_server_version() {
2319 assert!(
2320 check_pocketic_server_version("pocket-ic-server")
2321 .unwrap_err()
2322 .contains("Unexpected PocketIC server version")
2323 );
2324 assert!(
2325 check_pocketic_server_version("pocket-ic 13.0.0")
2326 .unwrap_err()
2327 .contains("Unexpected PocketIC server version")
2328 );
2329 assert!(
2330 check_pocketic_server_version("pocket-ic-server 13 0 0")
2331 .unwrap_err()
2332 .contains("Failed to parse PocketIC server version")
2333 );
2334 assert!(
2335 check_pocketic_server_version("pocket-ic-server 12.0.0")
2336 .unwrap_err()
2337 .contains("Incompatible PocketIC server version")
2338 );
2339 check_pocketic_server_version("pocket-ic-server 13.0.0").unwrap();
2340 check_pocketic_server_version("pocket-ic-server 13.0.1").unwrap();
2341 check_pocketic_server_version("pocket-ic-server 13.1.0").unwrap();
2342 assert!(
2343 check_pocketic_server_version("pocket-ic-server 14.0.0")
2344 .unwrap_err()
2345 .contains("Incompatible PocketIC server version")
2346 );
2347 }
2348}