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, MockFlexibleCanisterHttpResponse, RawEffectivePrincipal,
61 RawMessageId, RawSenderInfo, RawSubnetBlockmakers, RawTickConfigs, RawTime, SubnetId,
62 SubnetKind, SubnetSpec, Topology,
63 },
64 nonblocking::PocketIc as PocketIcAsync,
65};
66use candid::{
67 Principal, decode_args, encode_args,
68 utils::{ArgumentDecoder, ArgumentEncoder},
69};
70use flate2::read::GzDecoder;
71pub use ic_management_canister_types::{
72 CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
73 EnvironmentVariable, Snapshot,
74};
75pub use ic_transport_types::SubnetMetrics;
76use reqwest::Url;
77use schemars::JsonSchema;
78use semver::{Version, VersionReq};
79use serde::{Deserialize, Serialize};
80use slog::Level;
81#[cfg(unix)]
82use std::os::unix::fs::OpenOptionsExt;
83#[cfg(windows)]
84use std::sync::Once;
85use std::{
86 fs::OpenOptions,
87 net::{IpAddr, SocketAddr},
88 path::PathBuf,
89 process::{Child, Command},
90 sync::{Arc, mpsc::channel},
91 thread,
92 thread::JoinHandle,
93 time::{Duration, SystemTime, UNIX_EPOCH},
94};
95use strum_macros::EnumIter;
96use tempfile::{NamedTempFile, TempDir};
97use thiserror::Error;
98use tokio::runtime::Runtime;
99use tracing::{instrument, warn};
100#[cfg(windows)]
101use wslpath::windows_to_wsl;
102
103pub mod common;
104pub mod nonblocking;
105
106const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
107
108const MIN_SERVER_VERSION: &str = "16.0.0";
109const MAX_SERVER_VERSION: &str = "17";
110
111pub const LATEST_SERVER_VERSION: &str = "16.0.0";
113
114const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
116
117const LOCALHOST: &str = "127.0.0.1";
118
119enum PocketIcStateKind {
120 StateDir(PathBuf),
122 TempDir(TempDir),
128}
129
130pub struct PocketIcState {
131 state: PocketIcStateKind,
132}
133
134impl PocketIcState {
135 #[allow(clippy::new_without_default)]
136 pub fn new() -> Self {
137 let temp_dir = TempDir::new().unwrap();
138 Self {
139 state: PocketIcStateKind::TempDir(temp_dir),
140 }
141 }
142
143 pub fn new_from_path(state_dir: PathBuf) -> Self {
144 Self {
145 state: PocketIcStateKind::StateDir(state_dir),
146 }
147 }
148
149 pub fn into_path(self) -> PathBuf {
150 match self.state {
151 PocketIcStateKind::StateDir(state_dir) => state_dir,
152 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
153 }
154 }
155
156 pub(crate) fn state_dir(&self) -> PathBuf {
157 match &self.state {
158 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
159 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
160 }
161 }
162}
163
164pub struct PocketIcBuilder {
165 config: Option<ExtendedSubnetConfigSet>,
166 http_gateway_config: Option<InstanceHttpGatewayConfig>,
167 server_binary: Option<PathBuf>,
168 server_url: Option<Url>,
169 max_request_time_ms: Option<u64>,
170 read_only_state_dir: Option<PathBuf>,
171 state_dir: Option<PocketIcState>,
172 icp_config: IcpConfig,
173 log_level: Option<Level>,
174 bitcoind_addr: Option<Vec<SocketAddr>>,
175 dogecoind_addr: Option<Vec<SocketAddr>>,
176 icp_features: IcpFeatures,
177 initial_time: Option<InitialTime>,
178 mainnet_nns_subnet_id: Option<bool>,
179 disable_ingress_validation: Option<bool>,
180}
181
182#[allow(clippy::new_without_default)]
183impl PocketIcBuilder {
184 pub fn new() -> Self {
185 Self {
186 config: None,
187 http_gateway_config: None,
188 server_binary: None,
189 server_url: None,
190 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
191 read_only_state_dir: None,
192 state_dir: None,
193 icp_config: IcpConfig::default(),
194 log_level: None,
195 bitcoind_addr: None,
196 dogecoind_addr: None,
197 icp_features: IcpFeatures::default(),
198 initial_time: None,
199 mainnet_nns_subnet_id: None,
200 disable_ingress_validation: None,
201 }
202 }
203
204 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
205 let mut builder = Self::new();
206 builder.config = Some(config.into());
207 builder
208 }
209
210 pub fn build(self) -> PocketIc {
211 PocketIc::from_components(
212 self.config.unwrap_or_default(),
213 self.server_url,
214 self.server_binary,
215 self.max_request_time_ms,
216 self.read_only_state_dir,
217 self.state_dir,
218 self.icp_config,
219 self.log_level,
220 self.bitcoind_addr,
221 self.dogecoind_addr,
222 self.icp_features,
223 self.initial_time,
224 self.http_gateway_config,
225 self.mainnet_nns_subnet_id,
226 self.disable_ingress_validation,
227 )
228 }
229
230 pub async fn build_async(self) -> PocketIcAsync {
231 PocketIcAsync::from_components(
232 self.config.unwrap_or_default(),
233 self.server_url,
234 self.server_binary,
235 self.max_request_time_ms,
236 self.read_only_state_dir,
237 self.state_dir,
238 self.icp_config,
239 self.log_level,
240 self.bitcoind_addr,
241 self.dogecoind_addr,
242 self.icp_features,
243 self.initial_time,
244 self.http_gateway_config,
245 self.mainnet_nns_subnet_id,
246 self.disable_ingress_validation,
247 )
248 .await
249 }
250
251 pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
253 self.server_binary = Some(server_binary);
254 self
255 }
256
257 pub fn with_server_url(mut self, server_url: Url) -> Self {
259 self.server_url = Some(server_url);
260 self
261 }
262
263 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
264 self.max_request_time_ms = max_request_time_ms;
265 self
266 }
267
268 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
269 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
270 self
271 }
272
273 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
274 self.state_dir = Some(state_dir);
275 self
276 }
277
278 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
279 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
280 self
281 }
282
283 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
284 self.icp_config = icp_config;
285 self
286 }
287
288 pub fn with_log_level(mut self, log_level: Level) -> Self {
289 self.log_level = Some(log_level);
290 self
291 }
292
293 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
294 self.with_bitcoind_addrs(vec![bitcoind_addr])
295 }
296
297 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
298 Self {
299 bitcoind_addr: Some(bitcoind_addrs),
300 ..self
301 }
302 }
303
304 pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
305 Self {
306 dogecoind_addr: Some(dogecoind_addrs),
307 ..self
308 }
309 }
310
311 pub fn with_nns_subnet(mut self) -> Self {
313 let mut config = self.config.unwrap_or_default();
314 config.nns = Some(config.nns.unwrap_or_default());
315 self.config = Some(config);
316 self
317 }
318
319 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
336 self.with_subnet_state(SubnetKind::NNS, path_to_state)
337 }
338
339 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
356 let mut config = self.config.unwrap_or_default();
357 #[cfg(not(windows))]
358 let state_dir = path_to_state;
359 #[cfg(windows)]
360 let state_dir = wsl_path(&path_to_state, "subnet state").into();
361 let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
362 match subnet_kind {
363 SubnetKind::NNS => config.nns = Some(subnet_spec),
364 SubnetKind::SNS => config.sns = Some(subnet_spec),
365 SubnetKind::II => config.ii = Some(subnet_spec),
366 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
367 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
368 SubnetKind::TestThresholdKeys => config.test_threshold_keys = Some(subnet_spec),
369 SubnetKind::Application => config.application.push(subnet_spec),
370 SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
371 SubnetKind::System => config.system.push(subnet_spec),
372 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
373 };
374 self.config = Some(config);
375 self
376 }
377
378 pub fn with_sns_subnet(mut self) -> Self {
380 let mut config = self.config.unwrap_or_default();
381 config.sns = Some(config.sns.unwrap_or_default());
382 self.config = Some(config);
383 self
384 }
385
386 pub fn with_ii_subnet(mut self) -> Self {
388 let mut config = self.config.unwrap_or_default();
389 config.ii = Some(config.ii.unwrap_or_default());
390 self.config = Some(config);
391 self
392 }
393
394 pub fn with_fiduciary_subnet(mut self) -> Self {
396 let mut config = self.config.unwrap_or_default();
397 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
398 self.config = Some(config);
399 self
400 }
401
402 pub fn with_bitcoin_subnet(mut self) -> Self {
404 let mut config = self.config.unwrap_or_default();
405 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
406 self.config = Some(config);
407 self
408 }
409
410 pub fn with_test_threshold_keys_subnet(mut self) -> Self {
412 let mut config = self.config.unwrap_or_default();
413 config.test_threshold_keys = Some(config.test_threshold_keys.unwrap_or_default());
414 self.config = Some(config);
415 self
416 }
417
418 pub fn with_system_subnet(mut self) -> Self {
420 let mut config = self.config.unwrap_or_default();
421 config.system.push(SubnetSpec::default());
422 self.config = Some(config);
423 self
424 }
425
426 pub fn with_application_subnet(mut self) -> Self {
428 let mut config = self.config.unwrap_or_default();
429 config.application.push(SubnetSpec::default());
430 self.config = Some(config);
431 self
432 }
433
434 pub fn with_verified_application_subnet(mut self) -> Self {
436 let mut config = self.config.unwrap_or_default();
437 config.verified_application.push(SubnetSpec::default());
438 self.config = Some(config);
439 self
440 }
441
442 pub fn with_benchmarking_application_subnet(mut self) -> Self {
444 let mut config = self.config.unwrap_or_default();
445 config
446 .application
447 .push(SubnetSpec::default().with_benchmarking_instruction_config());
448 self.config = Some(config);
449 self
450 }
451
452 pub fn with_benchmarking_system_subnet(mut self) -> Self {
454 let mut config = self.config.unwrap_or_default();
455 config
456 .system
457 .push(SubnetSpec::default().with_benchmarking_instruction_config());
458 self.config = Some(config);
459 self
460 }
461
462 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
467 self.icp_features = icp_features;
468 self
469 }
470
471 #[deprecated(note = "Use `with_initial_time` instead")]
475 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
476 self.initial_time = Some(InitialTime::Timestamp(RawTime {
477 nanos_since_epoch: initial_timestamp_nanos,
478 }));
479 self
480 }
481
482 pub fn with_initial_time(mut self, initial_time: Time) -> Self {
486 self.initial_time = Some(InitialTime::Timestamp(RawTime {
487 nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
488 }));
489 self
490 }
491
492 pub fn with_auto_progress(mut self) -> Self {
498 let config = AutoProgressConfig {
499 artificial_delay_ms: None,
500 };
501 self.initial_time = Some(InitialTime::AutoProgress(config));
502 self
503 }
504
505 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
506 self.http_gateway_config = Some(http_gateway_config);
507 self
508 }
509
510 pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
511 self.mainnet_nns_subnet_id = Some(true);
512 self
513 }
514
515 pub fn disable_ingress_validation(mut self) -> Self {
516 self.disable_ingress_validation = Some(true);
517 self
518 }
519}
520
521#[derive(Copy, Clone, PartialEq, PartialOrd)]
524pub struct Time(Duration);
525
526impl Time {
527 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
529 self.0.as_nanos().try_into().unwrap()
530 }
531
532 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
533 Time(Duration::from_nanos(nanos))
534 }
535}
536
537impl std::fmt::Debug for Time {
538 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
539 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
540 write!(f, "{nanos_since_unix_epoch}")
541 }
542}
543
544impl std::ops::Add<Duration> for Time {
545 type Output = Time;
546 fn add(self, dur: Duration) -> Time {
547 Time(self.0 + dur)
548 }
549}
550
551impl From<SystemTime> for Time {
552 fn from(time: SystemTime) -> Self {
553 Self::from_nanos_since_unix_epoch(
554 time.duration_since(UNIX_EPOCH)
555 .unwrap()
556 .as_nanos()
557 .try_into()
558 .unwrap(),
559 )
560 }
561}
562
563impl TryFrom<Time> for SystemTime {
564 type Error = String;
565
566 fn try_from(time: Time) -> Result<SystemTime, String> {
567 let nanos = time.as_nanos_since_unix_epoch();
568 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
569 let roundtrip: Time = system_time.into();
570 if roundtrip.as_nanos_since_unix_epoch() == nanos {
571 Ok(system_time)
572 } else {
573 Err(format!(
574 "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
575 ))
576 }
577 }
578}
579
580#[derive(Clone, Debug)]
582pub enum CreateCanisterPlacement {
583 SubnetId(SubnetId),
585 CanisterId(CanisterId),
587}
588
589#[derive(Clone, Debug, Default)]
591pub struct CreateCanisterParams {
592 pub cycles: Option<u128>,
594 pub settings: Option<CanisterSettings>,
596 pub placement: Option<CreateCanisterPlacement>,
598}
599
600pub struct PocketIc {
602 pocket_ic: PocketIcAsync,
603 runtime: Arc<tokio::runtime::Runtime>,
604 thread: Option<JoinHandle<()>>,
605}
606
607impl PocketIc {
608 pub fn new() -> Self {
611 PocketIcBuilder::new().with_application_subnet().build()
612 }
613
614 pub fn new_from_existing_instance(
619 server_url: Url,
620 instance_id: InstanceId,
621 max_request_time_ms: Option<u64>,
622 ) -> Self {
623 let (tx, rx) = channel();
624 let thread = thread::spawn(move || {
625 let rt = tokio::runtime::Builder::new_current_thread()
626 .enable_all()
627 .build()
628 .unwrap();
629 tx.send(rt).unwrap();
630 });
631 let runtime = rx.recv().unwrap();
632
633 let pocket_ic =
634 PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
635
636 Self {
637 pocket_ic,
638 runtime: Arc::new(runtime),
639 thread: Some(thread),
640 }
641 }
642
643 #[allow(clippy::too_many_arguments)]
644 pub(crate) fn from_components(
645 subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
646 server_url: Option<Url>,
647 server_binary: Option<PathBuf>,
648 max_request_time_ms: Option<u64>,
649 read_only_state_dir: Option<PathBuf>,
650 state_dir: Option<PocketIcState>,
651 icp_config: IcpConfig,
652 log_level: Option<Level>,
653 bitcoind_addr: Option<Vec<SocketAddr>>,
654 dogecoind_addr: Option<Vec<SocketAddr>>,
655 icp_features: IcpFeatures,
656 initial_time: Option<InitialTime>,
657 http_gateway_config: Option<InstanceHttpGatewayConfig>,
658 mainnet_nns_subnet_id: Option<bool>,
659 disable_ingress_validation: Option<bool>,
660 ) -> Self {
661 let (tx, rx) = channel();
662 let thread = thread::spawn(move || {
663 let rt = tokio::runtime::Builder::new_current_thread()
664 .enable_all()
665 .build()
666 .unwrap();
667 tx.send(rt).unwrap();
668 });
669 let runtime = rx.recv().unwrap();
670
671 let pocket_ic = runtime.block_on(async {
672 PocketIcAsync::from_components(
673 subnet_config_set,
674 server_url,
675 server_binary,
676 max_request_time_ms,
677 read_only_state_dir,
678 state_dir,
679 icp_config,
680 log_level,
681 bitcoind_addr,
682 dogecoind_addr,
683 icp_features,
684 initial_time,
685 http_gateway_config,
686 mainnet_nns_subnet_id,
687 disable_ingress_validation,
688 )
689 .await
690 });
691
692 Self {
693 pocket_ic,
694 runtime: Arc::new(runtime),
695 thread: Some(thread),
696 }
697 }
698
699 pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
700 self.pocket_ic.take_state_internal()
701 }
702
703 pub fn get_server_url(&self) -> Url {
705 self.pocket_ic.get_server_url()
706 }
707
708 pub fn instance_id(&self) -> InstanceId {
710 self.pocket_ic.instance_id
711 }
712
713 pub fn topology(&self) -> Topology {
715 let runtime = self.runtime.clone();
716 runtime.block_on(async { self.pocket_ic.topology().await })
717 }
718
719 #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
721 pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
722 let runtime = self.runtime.clone();
723 runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
724 }
725
726 #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
729 pub fn set_stable_memory(
730 &self,
731 canister_id: CanisterId,
732 data: Vec<u8>,
733 compression: BlobCompression,
734 ) {
735 let runtime = self.runtime.clone();
736 runtime.block_on(async {
737 self.pocket_ic
738 .set_stable_memory(canister_id, data, compression)
739 .await
740 })
741 }
742
743 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
745 pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
746 let runtime = self.runtime.clone();
747 runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
748 }
749
750 #[instrument(ret)]
752 pub fn list_instances() -> Vec<String> {
753 let runtime = tokio::runtime::Builder::new_current_thread()
754 .build()
755 .unwrap();
756 let url = runtime.block_on(async {
757 let (_, server_url) = start_server(StartServerParams {
758 reuse: true,
759 ..Default::default()
760 })
761 .await;
762 server_url.join("instances").unwrap()
763 });
764 let instances: Vec<String> = reqwest::blocking::Client::new()
765 .get(url)
766 .send()
767 .expect("Failed to get result")
768 .json()
769 .expect("Failed to get json");
770 instances
771 }
772
773 #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
775 pub fn verify_canister_signature(
776 &self,
777 msg: Vec<u8>,
778 sig: Vec<u8>,
779 pubkey: Vec<u8>,
780 root_pubkey: Vec<u8>,
781 ) -> Result<(), String> {
782 let runtime = self.runtime.clone();
783 runtime.block_on(async {
784 self.pocket_ic
785 .verify_canister_signature(msg, sig, pubkey, root_pubkey)
786 .await
787 })
788 }
789
790 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
795 pub fn tick(&self) {
796 let runtime = self.runtime.clone();
797 runtime.block_on(async { self.pocket_ic.tick().await })
798 }
799
800 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
803 pub fn tick_with_configs(&self, configs: TickConfigs) {
804 let runtime = self.runtime.clone();
805 runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
806 }
807
808 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
816 pub fn auto_progress(&self) -> Url {
817 let runtime = self.runtime.clone();
818 runtime.block_on(async { self.pocket_ic.auto_progress().await })
819 }
820
821 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
823 pub fn auto_progress_enabled(&self) -> bool {
824 let runtime = self.runtime.clone();
825 runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
826 }
827
828 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
830 pub fn stop_progress(&self) {
831 let runtime = self.runtime.clone();
832 runtime.block_on(async { self.pocket_ic.stop_progress().await })
833 }
834
835 pub fn url(&self) -> Option<Url> {
839 self.pocket_ic.url()
840 }
841
842 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
853 pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
854 let runtime = self.runtime.clone();
855 runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
856 }
857
858 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
871 pub fn make_live_with_params(
872 &mut self,
873 ip_addr: Option<IpAddr>,
874 listen_at: Option<u16>,
875 domains: Option<Vec<String>>,
876 https_config: Option<HttpsConfig>,
877 ) -> Url {
878 let runtime = self.runtime.clone();
879 runtime.block_on(async {
880 self.pocket_ic
881 .make_live_with_params(ip_addr, listen_at, domains, https_config)
882 .await
883 })
884 }
885
886 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
889 pub fn stop_live(&mut self) {
890 let runtime = self.runtime.clone();
891 runtime.block_on(async { self.pocket_ic.stop_live().await })
892 }
893
894 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
896 pub fn root_key(&self) -> Option<Vec<u8>> {
897 let runtime = self.runtime.clone();
898 runtime.block_on(async { self.pocket_ic.root_key().await })
899 }
900
901 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
903 pub fn get_time(&self) -> Time {
904 let runtime = self.runtime.clone();
905 runtime.block_on(async { self.pocket_ic.get_time().await })
906 }
907
908 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
910 pub fn set_time(&self, time: Time) {
911 let runtime = self.runtime.clone();
912 runtime.block_on(async { self.pocket_ic.set_time(time).await })
913 }
914
915 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
917 pub fn set_certified_time(&self, time: Time) {
918 let runtime = self.runtime.clone();
919 runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
920 }
921
922 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
924 pub fn advance_time(&self, duration: Duration) {
925 let runtime = self.runtime.clone();
926 runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
927 }
928
929 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
932 pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
933 let runtime = self.runtime.clone();
934 runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
935 }
936
937 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
939 pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
940 let runtime = self.runtime.clone();
941 runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
942 }
943
944 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
946 pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
947 let runtime = self.runtime.clone();
948 runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
949 }
950
951 pub fn submit_call(
953 &self,
954 canister_id: CanisterId,
955 sender: Principal,
956 method: &str,
957 payload: Vec<u8>,
958 ) -> Result<RawMessageId, RejectResponse> {
959 let runtime = self.runtime.clone();
960 runtime.block_on(async {
961 self.pocket_ic
962 .submit_call(canister_id, sender, method, payload)
963 .await
964 })
965 }
966
967 pub fn submit_call_with_effective_principal(
969 &self,
970 canister_id: CanisterId,
971 effective_principal: RawEffectivePrincipal,
972 sender: Principal,
973 method: &str,
974 payload: Vec<u8>,
975 ) -> Result<RawMessageId, RejectResponse> {
976 let runtime = self.runtime.clone();
977 runtime.block_on(async {
978 self.pocket_ic
979 .submit_call_with_effective_principal(
980 canister_id,
981 effective_principal,
982 sender,
983 method,
984 payload,
985 )
986 .await
987 })
988 }
989
990 pub fn submit_call_with_effective_principal_and_sender_info(
992 &self,
993 canister_id: CanisterId,
994 effective_principal: RawEffectivePrincipal,
995 sender: Principal,
996 method: &str,
997 payload: Vec<u8>,
998 sender_info: RawSenderInfo,
999 ) -> Result<RawMessageId, RejectResponse> {
1000 let runtime = self.runtime.clone();
1001 runtime.block_on(async {
1002 self.pocket_ic
1003 .submit_call_with_effective_principal_and_sender_info(
1004 canister_id,
1005 effective_principal,
1006 sender,
1007 method,
1008 payload,
1009 sender_info,
1010 )
1011 .await
1012 })
1013 }
1014
1015 pub fn submit_call_with_sender_info(
1017 &self,
1018 canister_id: CanisterId,
1019 sender: Principal,
1020 method: &str,
1021 payload: Vec<u8>,
1022 sender_info: RawSenderInfo,
1023 ) -> Result<RawMessageId, RejectResponse> {
1024 let runtime = self.runtime.clone();
1025 runtime.block_on(async {
1026 self.pocket_ic
1027 .submit_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1028 .await
1029 })
1030 }
1031
1032 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
1034 let runtime = self.runtime.clone();
1035 runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
1036 }
1037
1038 pub fn ingress_status(
1042 &self,
1043 message_id: RawMessageId,
1044 ) -> Option<Result<Vec<u8>, RejectResponse>> {
1045 let runtime = self.runtime.clone();
1046 runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
1047 }
1048
1049 pub fn ingress_status_as(
1054 &self,
1055 message_id: RawMessageId,
1056 caller: Principal,
1057 ) -> IngressStatusResult {
1058 let runtime = self.runtime.clone();
1059 runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
1060 }
1061
1062 pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
1066 let runtime = self.runtime.clone();
1067 runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
1068 }
1069
1070 #[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()))]
1072 pub fn update_call(
1073 &self,
1074 canister_id: CanisterId,
1075 sender: Principal,
1076 method: &str,
1077 payload: Vec<u8>,
1078 ) -> Result<Vec<u8>, RejectResponse> {
1079 let runtime = self.runtime.clone();
1080 runtime.block_on(async {
1081 self.pocket_ic
1082 .update_call(canister_id, sender, method, payload)
1083 .await
1084 })
1085 }
1086
1087 #[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()))]
1089 pub fn query_call(
1090 &self,
1091 canister_id: CanisterId,
1092 sender: Principal,
1093 method: &str,
1094 payload: Vec<u8>,
1095 ) -> Result<Vec<u8>, RejectResponse> {
1096 let runtime = self.runtime.clone();
1097 runtime.block_on(async {
1098 self.pocket_ic
1099 .query_call(canister_id, sender, method, payload)
1100 .await
1101 })
1102 }
1103
1104 pub fn fetch_canister_logs(
1106 &self,
1107 canister_id: CanisterId,
1108 sender: Principal,
1109 ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
1110 let runtime = self.runtime.clone();
1111 runtime.block_on(async {
1112 self.pocket_ic
1113 .fetch_canister_logs(canister_id, sender)
1114 .await
1115 })
1116 }
1117
1118 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1120 pub fn canister_status(
1121 &self,
1122 canister_id: CanisterId,
1123 sender: Option<Principal>,
1124 ) -> Result<CanisterStatusResult, RejectResponse> {
1125 let runtime = self.runtime.clone();
1126 runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1127 }
1128
1129 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1132 pub fn create_canister(&self) -> CanisterId {
1133 let runtime = self.runtime.clone();
1134 runtime.block_on(async { self.pocket_ic.create_canister().await })
1135 }
1136
1137 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1140 pub fn create_canister_with_settings(
1141 &self,
1142 sender: Option<Principal>,
1143 settings: Option<CanisterSettings>,
1144 ) -> CanisterId {
1145 let runtime = self.runtime.clone();
1146 runtime.block_on(async {
1147 self.pocket_ic
1148 .create_canister_with_settings(sender, settings)
1149 .await
1150 })
1151 }
1152
1153 #[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()))]
1162 pub fn create_canister_with_id(
1163 &self,
1164 sender: Option<Principal>,
1165 settings: Option<CanisterSettings>,
1166 canister_id: CanisterId,
1167 ) -> Result<CanisterId, String> {
1168 let runtime = self.runtime.clone();
1169 runtime.block_on(async {
1170 self.pocket_ic
1171 .create_canister_with_id(sender, settings, canister_id)
1172 .await
1173 })
1174 }
1175
1176 #[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()))]
1179 pub fn create_canister_on_subnet(
1180 &self,
1181 sender: Option<Principal>,
1182 settings: Option<CanisterSettings>,
1183 subnet_id: SubnetId,
1184 ) -> CanisterId {
1185 let runtime = self.runtime.clone();
1186 runtime.block_on(async {
1187 self.pocket_ic
1188 .create_canister_on_subnet(sender, settings, subnet_id)
1189 .await
1190 })
1191 }
1192
1193 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1198 pub fn create_canister_with_params(
1199 &self,
1200 sender: Option<Principal>,
1201 params: CreateCanisterParams,
1202 ) -> Result<CanisterId, String> {
1203 let runtime = self.runtime.clone();
1204 runtime.block_on(async {
1205 self.pocket_ic
1206 .create_canister_with_params(sender, params)
1207 .await
1208 })
1209 }
1210
1211 #[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()))]
1214 pub fn upload_chunk(
1215 &self,
1216 canister_id: CanisterId,
1217 sender: Option<Principal>,
1218 chunk: Vec<u8>,
1219 ) -> Result<Vec<u8>, RejectResponse> {
1220 let runtime = self.runtime.clone();
1221 runtime.block_on(async {
1222 self.pocket_ic
1223 .upload_chunk(canister_id, sender, chunk)
1224 .await
1225 })
1226 }
1227
1228 #[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()))]
1230 pub fn stored_chunks(
1231 &self,
1232 canister_id: CanisterId,
1233 sender: Option<Principal>,
1234 ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1235 let runtime = self.runtime.clone();
1236 runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1237 }
1238
1239 #[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()))]
1241 pub fn clear_chunk_store(
1242 &self,
1243 canister_id: CanisterId,
1244 sender: Option<Principal>,
1245 ) -> Result<(), RejectResponse> {
1246 let runtime = self.runtime.clone();
1247 runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1248 }
1249
1250 #[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()))]
1252 pub fn install_chunked_canister(
1253 &self,
1254 canister_id: CanisterId,
1255 sender: Option<Principal>,
1256 mode: CanisterInstallMode,
1257 store_canister_id: CanisterId,
1258 chunk_hashes_list: Vec<Vec<u8>>,
1259 wasm_module_hash: Vec<u8>,
1260 arg: Vec<u8>,
1261 ) -> Result<(), RejectResponse> {
1262 let runtime = self.runtime.clone();
1263 runtime.block_on(async {
1264 self.pocket_ic
1265 .install_chunked_canister(
1266 canister_id,
1267 sender,
1268 mode,
1269 store_canister_id,
1270 chunk_hashes_list,
1271 wasm_module_hash,
1272 arg,
1273 )
1274 .await
1275 })
1276 }
1277
1278 #[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()))]
1280 pub fn install_canister(
1281 &self,
1282 canister_id: CanisterId,
1283 wasm_module: Vec<u8>,
1284 arg: Vec<u8>,
1285 sender: Option<Principal>,
1286 ) {
1287 let runtime = self.runtime.clone();
1288 runtime.block_on(async {
1289 self.pocket_ic
1290 .install_canister(canister_id, wasm_module, arg, sender)
1291 .await
1292 })
1293 }
1294
1295 #[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()))]
1297 pub fn upgrade_canister(
1298 &self,
1299 canister_id: CanisterId,
1300 wasm_module: Vec<u8>,
1301 arg: Vec<u8>,
1302 sender: Option<Principal>,
1303 ) -> Result<(), RejectResponse> {
1304 let runtime = self.runtime.clone();
1305 runtime.block_on(async {
1306 self.pocket_ic
1307 .upgrade_canister(canister_id, wasm_module, arg, sender)
1308 .await
1309 })
1310 }
1311
1312 #[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()))]
1314 pub fn upgrade_eop_canister(
1315 &self,
1316 canister_id: CanisterId,
1317 wasm_module: Vec<u8>,
1318 arg: Vec<u8>,
1319 sender: Option<Principal>,
1320 ) -> Result<(), RejectResponse> {
1321 let runtime = self.runtime.clone();
1322 runtime.block_on(async {
1323 self.pocket_ic
1324 .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1325 .await
1326 })
1327 }
1328
1329 #[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()))]
1331 pub fn reinstall_canister(
1332 &self,
1333 canister_id: CanisterId,
1334 wasm_module: Vec<u8>,
1335 arg: Vec<u8>,
1336 sender: Option<Principal>,
1337 ) -> Result<(), RejectResponse> {
1338 let runtime = self.runtime.clone();
1339 runtime.block_on(async {
1340 self.pocket_ic
1341 .reinstall_canister(canister_id, wasm_module, arg, sender)
1342 .await
1343 })
1344 }
1345
1346 #[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()))]
1348 pub fn uninstall_canister(
1349 &self,
1350 canister_id: CanisterId,
1351 sender: Option<Principal>,
1352 ) -> Result<(), RejectResponse> {
1353 let runtime = self.runtime.clone();
1354 runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1355 }
1356
1357 #[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()))]
1359 pub fn take_canister_snapshot(
1360 &self,
1361 canister_id: CanisterId,
1362 sender: Option<Principal>,
1363 replace_snapshot: Option<Vec<u8>>,
1364 ) -> Result<Snapshot, RejectResponse> {
1365 let runtime = self.runtime.clone();
1366 runtime.block_on(async {
1367 self.pocket_ic
1368 .take_canister_snapshot(canister_id, sender, replace_snapshot)
1369 .await
1370 })
1371 }
1372
1373 #[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()))]
1375 pub fn load_canister_snapshot(
1376 &self,
1377 canister_id: CanisterId,
1378 sender: Option<Principal>,
1379 snapshot_id: Vec<u8>,
1380 ) -> Result<(), RejectResponse> {
1381 let runtime = self.runtime.clone();
1382 runtime.block_on(async {
1383 self.pocket_ic
1384 .load_canister_snapshot(canister_id, sender, snapshot_id)
1385 .await
1386 })
1387 }
1388
1389 #[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()))]
1391 pub fn list_canister_snapshots(
1392 &self,
1393 canister_id: CanisterId,
1394 sender: Option<Principal>,
1395 ) -> Result<Vec<Snapshot>, RejectResponse> {
1396 let runtime = self.runtime.clone();
1397 runtime.block_on(async {
1398 self.pocket_ic
1399 .list_canister_snapshots(canister_id, sender)
1400 .await
1401 })
1402 }
1403
1404 #[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()))]
1406 pub fn delete_canister_snapshot(
1407 &self,
1408 canister_id: CanisterId,
1409 sender: Option<Principal>,
1410 snapshot_id: Vec<u8>,
1411 ) -> Result<(), RejectResponse> {
1412 let runtime = self.runtime.clone();
1413 runtime.block_on(async {
1414 self.pocket_ic
1415 .delete_canister_snapshot(canister_id, sender, snapshot_id)
1416 .await
1417 })
1418 }
1419
1420 #[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()))]
1422 pub fn update_canister_settings(
1423 &self,
1424 canister_id: CanisterId,
1425 sender: Option<Principal>,
1426 settings: CanisterSettings,
1427 ) -> Result<(), RejectResponse> {
1428 let runtime = self.runtime.clone();
1429 runtime.block_on(async {
1430 self.pocket_ic
1431 .update_canister_settings(canister_id, sender, settings)
1432 .await
1433 })
1434 }
1435
1436 #[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()))]
1438 pub fn set_controllers(
1439 &self,
1440 canister_id: CanisterId,
1441 sender: Option<Principal>,
1442 new_controllers: Vec<Principal>,
1443 ) -> Result<(), RejectResponse> {
1444 let runtime = self.runtime.clone();
1445 runtime.block_on(async {
1446 self.pocket_ic
1447 .set_controllers(canister_id, sender, new_controllers)
1448 .await
1449 })
1450 }
1451
1452 #[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()))]
1454 pub fn start_canister(
1455 &self,
1456 canister_id: CanisterId,
1457 sender: Option<Principal>,
1458 ) -> Result<(), RejectResponse> {
1459 let runtime = self.runtime.clone();
1460 runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1461 }
1462
1463 #[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()))]
1465 pub fn stop_canister(
1466 &self,
1467 canister_id: CanisterId,
1468 sender: Option<Principal>,
1469 ) -> Result<(), RejectResponse> {
1470 let runtime = self.runtime.clone();
1471 runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1472 }
1473
1474 #[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()))]
1476 pub fn delete_canister(
1477 &self,
1478 canister_id: CanisterId,
1479 sender: Option<Principal>,
1480 ) -> Result<(), RejectResponse> {
1481 let runtime = self.runtime.clone();
1482 runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1483 }
1484
1485 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1487 pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1488 let runtime = self.runtime.clone();
1489 runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1490 }
1491
1492 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1494 pub fn delete_subnet(&self, subnet_id: SubnetId) {
1495 let runtime = self.runtime.clone();
1496 runtime.block_on(async { self.pocket_ic.delete_subnet(subnet_id).await })
1497 }
1498
1499 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1501 pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1502 let runtime = self.runtime.clone();
1503 runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1504 }
1505
1506 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1508 pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1509 let runtime = self.runtime.clone();
1510 runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1511 }
1512
1513 pub fn update_call_with_effective_principal(
1514 &self,
1515 canister_id: CanisterId,
1516 effective_principal: RawEffectivePrincipal,
1517 sender: Principal,
1518 method: &str,
1519 payload: Vec<u8>,
1520 ) -> Result<Vec<u8>, RejectResponse> {
1521 let runtime = self.runtime.clone();
1522 runtime.block_on(async {
1523 self.pocket_ic
1524 .update_call_with_effective_principal(
1525 canister_id,
1526 effective_principal,
1527 sender,
1528 method,
1529 payload,
1530 )
1531 .await
1532 })
1533 }
1534
1535 pub fn update_call_with_effective_principal_and_sender_info(
1537 &self,
1538 canister_id: CanisterId,
1539 effective_principal: RawEffectivePrincipal,
1540 sender: Principal,
1541 method: &str,
1542 payload: Vec<u8>,
1543 sender_info: RawSenderInfo,
1544 ) -> Result<Vec<u8>, RejectResponse> {
1545 let runtime = self.runtime.clone();
1546 runtime.block_on(async {
1547 self.pocket_ic
1548 .update_call_with_effective_principal_and_sender_info(
1549 canister_id,
1550 effective_principal,
1551 sender,
1552 method,
1553 payload,
1554 sender_info,
1555 )
1556 .await
1557 })
1558 }
1559
1560 pub fn update_call_with_sender_info(
1562 &self,
1563 canister_id: CanisterId,
1564 sender: Principal,
1565 method: &str,
1566 payload: Vec<u8>,
1567 sender_info: RawSenderInfo,
1568 ) -> Result<Vec<u8>, RejectResponse> {
1569 let runtime = self.runtime.clone();
1570 runtime.block_on(async {
1571 self.pocket_ic
1572 .update_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1573 .await
1574 })
1575 }
1576
1577 #[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()))]
1581 pub fn query_call_with_effective_principal(
1582 &self,
1583 canister_id: CanisterId,
1584 effective_principal: RawEffectivePrincipal,
1585 sender: Principal,
1586 method: &str,
1587 payload: Vec<u8>,
1588 ) -> Result<Vec<u8>, RejectResponse> {
1589 let runtime = self.runtime.clone();
1590 runtime.block_on(async {
1591 self.pocket_ic
1592 .query_call_with_effective_principal(
1593 canister_id,
1594 effective_principal,
1595 sender,
1596 method,
1597 payload,
1598 )
1599 .await
1600 })
1601 }
1602
1603 pub fn query_call_with_effective_principal_and_sender_info(
1605 &self,
1606 canister_id: CanisterId,
1607 effective_principal: RawEffectivePrincipal,
1608 sender: Principal,
1609 method: &str,
1610 payload: Vec<u8>,
1611 sender_info: RawSenderInfo,
1612 ) -> Result<Vec<u8>, RejectResponse> {
1613 let runtime = self.runtime.clone();
1614 runtime.block_on(async {
1615 self.pocket_ic
1616 .query_call_with_effective_principal_and_sender_info(
1617 canister_id,
1618 effective_principal,
1619 sender,
1620 method,
1621 payload,
1622 sender_info,
1623 )
1624 .await
1625 })
1626 }
1627
1628 pub fn query_call_with_sender_info(
1630 &self,
1631 canister_id: CanisterId,
1632 sender: Principal,
1633 method: &str,
1634 payload: Vec<u8>,
1635 sender_info: RawSenderInfo,
1636 ) -> Result<Vec<u8>, RejectResponse> {
1637 let runtime = self.runtime.clone();
1638 runtime.block_on(async {
1639 self.pocket_ic
1640 .query_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1641 .await
1642 })
1643 }
1644
1645 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1659 pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1660 let runtime = self.runtime.clone();
1661 runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1662 }
1663
1664 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1670 pub fn mock_canister_http_response(
1671 &self,
1672 mock_canister_http_response: MockCanisterHttpResponse,
1673 ) {
1674 let runtime = self.runtime.clone();
1675 runtime.block_on(async {
1676 self.pocket_ic
1677 .mock_canister_http_response(mock_canister_http_response)
1678 .await
1679 })
1680 }
1681
1682 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1698 pub fn mock_flexible_canister_http_response(
1699 &self,
1700 mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse,
1701 ) {
1702 let runtime = self.runtime.clone();
1703 runtime.block_on(async {
1704 self.pocket_ic
1705 .mock_flexible_canister_http_response(mock_flexible_canister_http_response)
1706 .await
1707 })
1708 }
1709
1710 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1714 pub fn canister_snapshot_download(
1715 &self,
1716 canister_id: CanisterId,
1717 sender: Principal,
1718 snapshot_id: Vec<u8>,
1719 snapshot_dir: PathBuf,
1720 ) {
1721 let runtime = self.runtime.clone();
1722 runtime.block_on(async {
1723 self.pocket_ic
1724 .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1725 .await
1726 })
1727 }
1728
1729 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1733 pub fn canister_snapshot_upload(
1734 &self,
1735 canister_id: CanisterId,
1736 sender: Principal,
1737 replace_snapshot: Option<Vec<u8>>,
1738 snapshot_dir: PathBuf,
1739 ) -> Vec<u8> {
1740 let runtime = self.runtime.clone();
1741 runtime.block_on(async {
1742 self.pocket_ic
1743 .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1744 .await
1745 })
1746 }
1747}
1748
1749impl Default for PocketIc {
1750 fn default() -> Self {
1751 Self::new()
1752 }
1753}
1754
1755impl Drop for PocketIc {
1756 fn drop(&mut self) {
1757 self.runtime.block_on(async {
1758 self.pocket_ic.do_drop().await;
1759 });
1760 if let Some(thread) = self.thread.take() {
1761 thread.join().unwrap();
1762 }
1763 }
1764}
1765
1766pub fn call_candid_as<Input, Output>(
1770 env: &PocketIc,
1771 canister_id: CanisterId,
1772 effective_principal: RawEffectivePrincipal,
1773 sender: Principal,
1774 method: &str,
1775 input: Input,
1776) -> Result<Output, RejectResponse>
1777where
1778 Input: ArgumentEncoder,
1779 Output: for<'a> ArgumentDecoder<'a>,
1780{
1781 with_candid(input, |payload| {
1782 env.update_call_with_effective_principal(
1783 canister_id,
1784 effective_principal,
1785 sender,
1786 method,
1787 payload,
1788 )
1789 })
1790}
1791
1792pub fn call_candid<Input, Output>(
1795 env: &PocketIc,
1796 canister_id: CanisterId,
1797 effective_principal: RawEffectivePrincipal,
1798 method: &str,
1799 input: Input,
1800) -> Result<Output, RejectResponse>
1801where
1802 Input: ArgumentEncoder,
1803 Output: for<'a> ArgumentDecoder<'a>,
1804{
1805 call_candid_as(
1806 env,
1807 canister_id,
1808 effective_principal,
1809 Principal::anonymous(),
1810 method,
1811 input,
1812 )
1813}
1814
1815pub fn query_candid<Input, Output>(
1817 env: &PocketIc,
1818 canister_id: CanisterId,
1819 method: &str,
1820 input: Input,
1821) -> Result<Output, RejectResponse>
1822where
1823 Input: ArgumentEncoder,
1824 Output: for<'a> ArgumentDecoder<'a>,
1825{
1826 query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1827}
1828
1829pub fn query_candid_as<Input, Output>(
1832 env: &PocketIc,
1833 canister_id: CanisterId,
1834 sender: Principal,
1835 method: &str,
1836 input: Input,
1837) -> Result<Output, RejectResponse>
1838where
1839 Input: ArgumentEncoder,
1840 Output: for<'a> ArgumentDecoder<'a>,
1841{
1842 with_candid(input, |bytes| {
1843 env.query_call(canister_id, sender, method, bytes)
1844 })
1845}
1846
1847pub fn update_candid<Input, Output>(
1849 env: &PocketIc,
1850 canister_id: CanisterId,
1851 method: &str,
1852 input: Input,
1853) -> Result<Output, RejectResponse>
1854where
1855 Input: ArgumentEncoder,
1856 Output: for<'a> ArgumentDecoder<'a>,
1857{
1858 update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1859}
1860
1861pub fn update_candid_as<Input, Output>(
1864 env: &PocketIc,
1865 canister_id: CanisterId,
1866 sender: Principal,
1867 method: &str,
1868 input: Input,
1869) -> Result<Output, RejectResponse>
1870where
1871 Input: ArgumentEncoder,
1872 Output: for<'a> ArgumentDecoder<'a>,
1873{
1874 with_candid(input, |bytes| {
1875 env.update_call(canister_id, sender, method, bytes)
1876 })
1877}
1878
1879pub fn with_candid<Input, Output>(
1882 input: Input,
1883 f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1884) -> Result<Output, RejectResponse>
1885where
1886 Input: ArgumentEncoder,
1887 Output: for<'a> ArgumentDecoder<'a>,
1888{
1889 let in_bytes = encode_args(input).expect("failed to encode args");
1890 f(in_bytes).map(|out_bytes| {
1891 decode_args(&out_bytes).unwrap_or_else(|e| {
1892 panic!(
1893 "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1894 std::any::type_name::<Output>(),
1895 e,
1896 out_bytes,
1897 String::from_utf8_lossy(&out_bytes),
1898 )
1899 })
1900 })
1901}
1902
1903#[derive(Clone, Copy, Debug)]
1905pub enum TryFromError {
1906 ValueOutOfRange(u64),
1907}
1908
1909#[derive(
1916 PartialOrd,
1917 Ord,
1918 Clone,
1919 Copy,
1920 Debug,
1921 PartialEq,
1922 Eq,
1923 Hash,
1924 Serialize,
1925 Deserialize,
1926 JsonSchema,
1927 EnumIter,
1928)]
1929pub enum ErrorCode {
1930 SubnetOversubscribed = 101,
1932 MaxNumberOfCanistersReached = 102,
1933 CanisterQueueFull = 201,
1935 IngressMessageTimeout = 202,
1936 CanisterQueueNotEmpty = 203,
1937 IngressHistoryFull = 204,
1938 CanisterIdAlreadyExists = 205,
1939 StopCanisterRequestTimeout = 206,
1940 CanisterOutOfCycles = 207,
1941 CertifiedStateUnavailable = 208,
1942 CanisterInstallCodeRateLimited = 209,
1943 CanisterHeapDeltaRateLimited = 210,
1944 SubnetCoolingDown = 211,
1945 CanisterNotFound = 301,
1947 CanisterSnapshotNotFound = 305,
1948 InsufficientMemoryAllocation = 402,
1950 InsufficientCyclesForCreateCanister = 403,
1951 SubnetNotFound = 404,
1952 CanisterNotHostedBySubnet = 405,
1953 CanisterRejectedMessage = 406,
1954 UnknownManagementMessage = 407,
1955 InvalidManagementPayload = 408,
1956 CanisterSnapshotImmutable = 409,
1957 InvalidSubnetAdmin = 410,
1958 CanisterTrapped = 502,
1960 CanisterCalledTrap = 503,
1961 CanisterContractViolation = 504,
1962 CanisterInvalidWasm = 505,
1963 CanisterDidNotReply = 506,
1964 CanisterOutOfMemory = 507,
1965 CanisterStopped = 508,
1966 CanisterStopping = 509,
1967 CanisterNotStopped = 510,
1968 CanisterStoppingCancelled = 511,
1969 CanisterInvalidController = 512,
1970 CanisterFunctionNotFound = 513,
1971 CanisterNonEmpty = 514,
1972 QueryCallGraphLoopDetected = 517,
1973 InsufficientCyclesInCall = 520,
1974 CanisterWasmEngineError = 521,
1975 CanisterInstructionLimitExceeded = 522,
1976 CanisterMemoryAccessLimitExceeded = 524,
1977 QueryCallGraphTooDeep = 525,
1978 QueryCallGraphTotalInstructionLimitExceeded = 526,
1979 CompositeQueryCalledInReplicatedMode = 527,
1980 QueryTimeLimitExceeded = 528,
1981 QueryCallGraphInternal = 529,
1982 InsufficientCyclesInComputeAllocation = 530,
1983 InsufficientCyclesInMemoryAllocation = 531,
1984 InsufficientCyclesInMemoryGrow = 532,
1985 ReservedCyclesLimitExceededInMemoryAllocation = 533,
1986 ReservedCyclesLimitExceededInMemoryGrow = 534,
1987 InsufficientCyclesInMessageMemoryGrow = 535,
1988 CanisterMethodNotFound = 536,
1989 CanisterWasmModuleNotFound = 537,
1990 CanisterAlreadyInstalled = 538,
1991 CanisterWasmMemoryLimitExceeded = 539,
1992 ReservedCyclesLimitIsTooLow = 540,
1993 CanisterInvalidControllerOrSubnetAdmin = 541,
1994 CanisterStatusAccessDenied = 542,
1995 DeadlineExpired = 601,
1997 ResponseDropped = 602,
1998}
1999
2000impl TryFrom<u64> for ErrorCode {
2001 type Error = TryFromError;
2002 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
2003 match err {
2004 101 => Ok(ErrorCode::SubnetOversubscribed),
2006 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
2007 201 => Ok(ErrorCode::CanisterQueueFull),
2009 202 => Ok(ErrorCode::IngressMessageTimeout),
2010 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
2011 204 => Ok(ErrorCode::IngressHistoryFull),
2012 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
2013 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
2014 207 => Ok(ErrorCode::CanisterOutOfCycles),
2015 208 => Ok(ErrorCode::CertifiedStateUnavailable),
2016 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
2017 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
2018 211 => Ok(ErrorCode::SubnetCoolingDown),
2019 301 => Ok(ErrorCode::CanisterNotFound),
2021 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
2022 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
2024 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
2025 404 => Ok(ErrorCode::SubnetNotFound),
2026 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
2027 406 => Ok(ErrorCode::CanisterRejectedMessage),
2028 407 => Ok(ErrorCode::UnknownManagementMessage),
2029 408 => Ok(ErrorCode::InvalidManagementPayload),
2030 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
2031 410 => Ok(ErrorCode::InvalidSubnetAdmin),
2032 502 => Ok(ErrorCode::CanisterTrapped),
2034 503 => Ok(ErrorCode::CanisterCalledTrap),
2035 504 => Ok(ErrorCode::CanisterContractViolation),
2036 505 => Ok(ErrorCode::CanisterInvalidWasm),
2037 506 => Ok(ErrorCode::CanisterDidNotReply),
2038 507 => Ok(ErrorCode::CanisterOutOfMemory),
2039 508 => Ok(ErrorCode::CanisterStopped),
2040 509 => Ok(ErrorCode::CanisterStopping),
2041 510 => Ok(ErrorCode::CanisterNotStopped),
2042 511 => Ok(ErrorCode::CanisterStoppingCancelled),
2043 512 => Ok(ErrorCode::CanisterInvalidController),
2044 513 => Ok(ErrorCode::CanisterFunctionNotFound),
2045 514 => Ok(ErrorCode::CanisterNonEmpty),
2046 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
2047 520 => Ok(ErrorCode::InsufficientCyclesInCall),
2048 521 => Ok(ErrorCode::CanisterWasmEngineError),
2049 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
2050 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
2051 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
2052 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
2053 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
2054 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
2055 529 => Ok(ErrorCode::QueryCallGraphInternal),
2056 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
2057 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
2058 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
2059 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
2060 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
2061 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
2062 536 => Ok(ErrorCode::CanisterMethodNotFound),
2063 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
2064 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
2065 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
2066 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
2067 541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
2068 542 => Ok(ErrorCode::CanisterStatusAccessDenied),
2069 601 => Ok(ErrorCode::DeadlineExpired),
2071 602 => Ok(ErrorCode::ResponseDropped),
2072 _ => Err(TryFromError::ValueOutOfRange(err)),
2073 }
2074 }
2075}
2076
2077impl std::fmt::Display for ErrorCode {
2078 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2079 write!(f, "IC{:04}", *self as i32)
2081 }
2082}
2083
2084#[derive(
2089 PartialOrd,
2090 Ord,
2091 Clone,
2092 Copy,
2093 Debug,
2094 PartialEq,
2095 Eq,
2096 Hash,
2097 Serialize,
2098 Deserialize,
2099 JsonSchema,
2100 EnumIter,
2101)]
2102pub enum RejectCode {
2103 SysFatal = 1,
2104 SysTransient = 2,
2105 DestinationInvalid = 3,
2106 CanisterReject = 4,
2107 CanisterError = 5,
2108 SysUnknown = 6,
2109}
2110
2111impl TryFrom<u64> for RejectCode {
2112 type Error = TryFromError;
2113 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
2114 match err {
2115 1 => Ok(RejectCode::SysFatal),
2116 2 => Ok(RejectCode::SysTransient),
2117 3 => Ok(RejectCode::DestinationInvalid),
2118 4 => Ok(RejectCode::CanisterReject),
2119 5 => Ok(RejectCode::CanisterError),
2120 6 => Ok(RejectCode::SysUnknown),
2121 _ => Err(TryFromError::ValueOutOfRange(err)),
2122 }
2123 }
2124}
2125
2126#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
2128pub struct RejectResponse {
2129 pub reject_code: RejectCode,
2130 pub reject_message: String,
2131 pub error_code: ErrorCode,
2132 pub certified: bool,
2133}
2134
2135impl std::fmt::Display for RejectResponse {
2136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2137 write!(
2139 f,
2140 "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
2141 self.reject_code, self.reject_message, self.error_code
2142 )
2143 }
2144}
2145
2146#[derive(Debug, Serialize, Deserialize)]
2152pub enum IngressStatusResult {
2153 NotAvailable,
2154 Forbidden(String),
2155 Success(Result<Vec<u8>, RejectResponse>),
2156}
2157
2158#[derive(Clone, Debug, Default)]
2159pub struct TickConfigs {
2160 pub blockmakers: Option<Vec<SubnetBlockmakers>>,
2161}
2162
2163impl From<TickConfigs> for RawTickConfigs {
2164 fn from(tick_configs: TickConfigs) -> Self {
2165 Self {
2166 blockmakers: tick_configs.blockmakers.map(|blockmakers| {
2167 blockmakers
2168 .into_iter()
2169 .map(|blockmaker| blockmaker.into())
2170 .collect()
2171 }),
2172 }
2173 }
2174}
2175
2176#[derive(Clone, Debug)]
2177pub struct SubnetBlockmakers {
2178 pub subnet: Principal,
2179 pub blockmaker: Principal,
2180 pub failed_blockmakers: Vec<Principal>,
2181}
2182
2183impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
2184 fn from(blockmaker: SubnetBlockmakers) -> Self {
2185 Self {
2186 subnet: blockmaker.subnet.into(),
2187 blockmaker: blockmaker.blockmaker.into(),
2188 failed_blockmakers: blockmaker
2189 .failed_blockmakers
2190 .into_iter()
2191 .map(|p| p.into())
2192 .collect(),
2193 }
2194 }
2195}
2196
2197#[cfg(windows)]
2198fn wsl_path(path: &PathBuf, desc: &str) -> String {
2199 windows_to_wsl(
2200 path.as_os_str()
2201 .to_str()
2202 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
2203 )
2204 .unwrap_or_else(|e| {
2205 panic!(
2206 "Could not convert {} path ({:?}) to WSL path: {:?}",
2207 desc, path, e
2208 )
2209 })
2210}
2211
2212#[cfg(windows)]
2213static WSL_WARM_UP: Once = Once::new();
2214
2215#[cfg(windows)]
2216fn warm_up_wsl() {
2217 WSL_WARM_UP.call_once(|| {
2218 let output = Command::new("wsl")
2219 .arg("bash")
2220 .arg("-c")
2221 .arg("true")
2222 .output()
2223 .expect("Failed to warm up WSL");
2224 if !output.status.success() {
2225 panic!(
2226 "Failed to warm up WSL.\nStatus: {}\nStdout: {}\nStderr: {}",
2227 output.status,
2228 String::from_utf8_lossy(&output.stdout),
2229 String::from_utf8_lossy(&output.stderr),
2230 );
2231 }
2232 });
2233}
2234
2235#[cfg(windows)]
2236fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2237 warm_up_wsl();
2238 let mut cmd = Command::new("wsl");
2239 cmd.arg(wsl_path(bin_path, "PocketIC binary"));
2240 cmd
2241}
2242
2243#[cfg(not(windows))]
2244fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2245 Command::new(bin_path)
2246}
2247
2248fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
2249 let unexpected_version = format!(
2250 "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
2251 );
2252 let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
2253 return Err(unexpected_version);
2254 };
2255 if pocket_ic_server != POCKET_IC_SERVER_NAME {
2256 return Err(unexpected_version);
2257 }
2258 let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
2259 let version = Version::parse(version)
2260 .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
2261 if !req.matches(&version) {
2262 return Err(format!(
2263 "Incompatible PocketIC server version: got {version}; expected {req}."
2264 ));
2265 }
2266
2267 Ok(())
2268}
2269
2270fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
2271 let mut cmd = pocket_ic_server_cmd(server_binary);
2272 cmd.arg("--version");
2273 let output = cmd.output().map_err(|e| e.to_string())?;
2274 if !output.status.success() {
2275 return Err(format!(
2276 "PocketIC server failed to print its version.\nStatus: {}\nStdout: {}\nStderr: {}",
2277 output.status,
2278 String::from_utf8_lossy(&output.stdout),
2279 String::from_utf8_lossy(&output.stderr),
2280 ));
2281 }
2282 let version_str = String::from_utf8(output.stdout)
2283 .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
2284 let version_line = version_str.trim_end_matches('\n');
2285 check_pocketic_server_version(version_line)
2286}
2287
2288async fn download_pocketic_server(
2289 server_url: String,
2290 mut out: std::fs::File,
2291) -> Result<(), String> {
2292 let binary = reqwest::get(server_url)
2293 .await
2294 .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2295 .bytes()
2296 .await
2297 .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2298 .to_vec();
2299 let mut gz = GzDecoder::new(&binary[..]);
2300 let _ = std::io::copy(&mut gz, &mut out)
2301 .map_err(|e| format!("Failed to write PocketIC server binary: {e}"));
2302 Ok(())
2303}
2304
2305#[derive(Default)]
2306pub struct StartServerParams {
2307 pub server_binary: Option<PathBuf>,
2308 pub reuse: bool,
2310 pub ttl: Option<Duration>,
2317 pub hard_ttl: Option<Duration>,
2325}
2326
2327pub async fn start_server(params: StartServerParams) -> (Child, Url) {
2329 let default_bin_dir =
2330 std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
2331 let default_bin_path = default_bin_dir.join("pocket-ic");
2332 let bin_path_provided =
2333 params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
2334 let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
2335 std::env::var_os("POCKET_IC_BIN")
2336 .unwrap_or_else(|| default_bin_path.clone().into())
2337 .into()
2338 });
2339
2340 if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
2341 if bin_path_provided {
2342 panic!(
2343 "Failed to validate PocketIC server binary `{}`: `{}`.",
2344 bin_path.display(),
2345 e
2346 );
2347 }
2348 bin_path = default_bin_path.clone();
2349 std::fs::create_dir_all(&default_bin_dir)
2350 .expect("Failed to create PocketIC server directory");
2351 let mut options = OpenOptions::new();
2352 options.write(true).create_new(true);
2353 #[cfg(unix)]
2354 options.mode(0o777);
2355 match options.open(&default_bin_path) {
2356 Ok(out) => {
2357 #[cfg(target_os = "macos")]
2358 let os = "darwin";
2359 #[cfg(not(target_os = "macos"))]
2360 let os = "linux";
2361 #[cfg(target_arch = "aarch64")]
2362 let arch = "arm64";
2363 #[cfg(not(target_arch = "aarch64"))]
2364 let arch = "x86_64";
2365 let server_url = format!(
2366 "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2367 );
2368 println!(
2369 "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.",
2370 bin_path.display(),
2371 e,
2372 LATEST_SERVER_VERSION,
2373 server_url,
2374 default_bin_path.display(),
2375 LATEST_SERVER_VERSION
2376 );
2377 if let Err(e) = download_pocketic_server(server_url, out).await {
2378 let _ = std::fs::remove_file(default_bin_path);
2379 panic!("{}", e);
2380 }
2381 }
2382 _ => {
2383 let start = std::time::Instant::now();
2385 loop {
2386 if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2387 break;
2388 }
2389 if start.elapsed() > std::time::Duration::from_secs(60) {
2390 let _ = std::fs::remove_file(&default_bin_path);
2391 panic!(
2392 "Timed out waiting for PocketIC server being available at the local path {}.",
2393 default_bin_path.display()
2394 );
2395 }
2396 std::thread::sleep(std::time::Duration::from_millis(100));
2397 }
2398 }
2399 }
2400 }
2401
2402 let port_file_path = if params.reuse {
2403 let test_driver_pid = std::process::id();
2406 std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2407 } else {
2408 NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
2409 };
2410 let mut cmd = pocket_ic_server_cmd(&bin_path);
2411 if let Some(ttl) = params.ttl {
2412 cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2413 }
2414 if let Some(hard_ttl) = params.hard_ttl {
2415 cmd.arg("--hard-ttl").arg(hard_ttl.as_secs().to_string());
2416 }
2417 cmd.arg("--port-file");
2418 #[cfg(windows)]
2419 cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2420 #[cfg(not(windows))]
2421 cmd.arg(port_file_path.clone());
2422 if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2423 && !mute_server.is_empty()
2424 {
2425 cmd.stdout(std::process::Stdio::null());
2426 cmd.stderr(std::process::Stdio::null());
2427 }
2428
2429 #[cfg(unix)]
2432 {
2433 use std::os::unix::process::CommandExt;
2434 cmd.process_group(0);
2435 }
2436
2437 #[allow(clippy::zombie_processes)]
2439 let child = cmd
2440 .spawn()
2441 .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
2442
2443 loop {
2444 if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2445 && port_string.contains("\n")
2446 {
2447 let port: u16 = port_string
2448 .trim_end()
2449 .parse()
2450 .expect("Failed to parse port to number");
2451 break (
2452 child,
2453 Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2454 );
2455 }
2456 std::thread::sleep(Duration::from_millis(20));
2457 }
2458}
2459
2460#[derive(Error, Debug)]
2461pub enum DefaultEffectiveCanisterIdError {
2462 ReqwestError(#[from] reqwest::Error),
2463 JsonError(#[from] serde_json::Error),
2464 Utf8Error(#[from] std::string::FromUtf8Error),
2465}
2466
2467impl std::fmt::Display for DefaultEffectiveCanisterIdError {
2468 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2469 match self {
2470 DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2471 write!(f, "ReqwestError({err})")
2472 }
2473 DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({err})"),
2474 DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({err})"),
2475 }
2476 }
2477}
2478
2479pub fn get_default_effective_canister_id(
2487 pocket_ic_url: String,
2488) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2489 let runtime = Runtime::new().expect("Unable to create a runtime");
2490 runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2491 pocket_ic_url,
2492 ))
2493}
2494
2495pub fn copy_dir(
2496 src: impl AsRef<std::path::Path>,
2497 dst: impl AsRef<std::path::Path>,
2498) -> std::io::Result<()> {
2499 std::fs::create_dir_all(&dst)?;
2500 for entry in std::fs::read_dir(src)? {
2501 let entry = entry?;
2502 let ty = entry.file_type()?;
2503 if ty.is_dir() {
2504 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2505 } else {
2506 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2507 }
2508 }
2509 Ok(())
2510}
2511
2512#[cfg(test)]
2513mod test {
2514 use crate::{ErrorCode, RejectCode, check_pocketic_server_version};
2515 use strum::IntoEnumIterator;
2516
2517 #[test]
2518 fn reject_code_round_trip() {
2519 for initial in RejectCode::iter() {
2520 let round_trip = RejectCode::try_from(initial as u64).unwrap();
2521
2522 assert_eq!(initial, round_trip);
2523 }
2524 }
2525
2526 #[test]
2527 fn error_code_round_trip() {
2528 for initial in ErrorCode::iter() {
2529 let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2530
2531 assert_eq!(initial, round_trip);
2532 }
2533 }
2534
2535 #[test]
2536 fn reject_code_matches_ic_error_code() {
2537 assert_eq!(
2538 RejectCode::iter().len(),
2539 ic_error_types::RejectCode::iter().len()
2540 );
2541 for ic_reject_code in ic_error_types::RejectCode::iter() {
2542 let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2543 assert_eq!(format!("{reject_code:?}"), format!("{:?}", ic_reject_code));
2544 }
2545 }
2546
2547 #[test]
2548 fn error_code_matches_ic_error_code() {
2549 assert_eq!(
2550 ErrorCode::iter().len(),
2551 ic_error_types::ErrorCode::iter().len()
2552 );
2553 for ic_error_code in ic_error_types::ErrorCode::iter() {
2554 let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2555 assert_eq!(format!("{error_code:?}"), format!("{:?}", ic_error_code));
2556 }
2557 }
2558
2559 #[test]
2560 fn test_check_pocketic_server_version() {
2561 assert!(
2562 check_pocketic_server_version("pocket-ic-server")
2563 .unwrap_err()
2564 .contains("Unexpected PocketIC server version")
2565 );
2566 assert!(
2567 check_pocketic_server_version("pocket-ic 16.0.0")
2568 .unwrap_err()
2569 .contains("Unexpected PocketIC server version")
2570 );
2571 assert!(
2572 check_pocketic_server_version("pocket-ic-server 16 0 0")
2573 .unwrap_err()
2574 .contains("Failed to parse PocketIC server version")
2575 );
2576 assert!(
2577 check_pocketic_server_version("pocket-ic-server 15.0.0")
2578 .unwrap_err()
2579 .contains("Incompatible PocketIC server version")
2580 );
2581 check_pocketic_server_version("pocket-ic-server 16.0.0").unwrap();
2582 check_pocketic_server_version("pocket-ic-server 16.0.1").unwrap();
2583 check_pocketic_server_version("pocket-ic-server 16.1.0").unwrap();
2584 assert!(
2585 check_pocketic_server_version("pocket-ic-server 17.0.0")
2586 .unwrap_err()
2587 .contains("Incompatible PocketIC server version")
2588 );
2589 }
2590}