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