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