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, RawTime, SubnetId,
61 SubnetKind, SubnetSpec, Topology,
62 },
63 nonblocking::PocketIc as PocketIcAsync,
64};
65use candid::{
66 decode_args, encode_args,
67 utils::{ArgumentDecoder, ArgumentEncoder},
68 Principal,
69};
70use flate2::read::GzDecoder;
71use ic_management_canister_types::{
72 CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
73 Snapshot,
74};
75use ic_transport_types::SubnetMetrics;
76use reqwest::Url;
77use schemars::JsonSchema;
78use serde::{Deserialize, Serialize};
79use slog::Level;
80#[cfg(unix)]
81use std::os::unix::fs::OpenOptionsExt;
82use std::{
83 fs::OpenOptions,
84 net::{IpAddr, SocketAddr},
85 path::PathBuf,
86 process::{Child, Command},
87 sync::{mpsc::channel, Arc},
88 thread,
89 thread::JoinHandle,
90 time::{Duration, SystemTime, UNIX_EPOCH},
91};
92use strum_macros::EnumIter;
93use tempfile::{NamedTempFile, TempDir};
94use thiserror::Error;
95use tokio::runtime::Runtime;
96use tracing::{instrument, warn};
97#[cfg(windows)]
98use wslpath::windows_to_wsl;
99
100pub mod common;
101pub mod nonblocking;
102
103pub const EXPECTED_SERVER_VERSION: &str = "10.0.0";
104
105const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
107
108const LOCALHOST: &str = "127.0.0.1";
109
110enum PocketIcStateKind {
111 StateDir(PathBuf),
113 TempDir(TempDir),
119}
120
121pub struct PocketIcState {
122 state: PocketIcStateKind,
123}
124
125impl PocketIcState {
126 #[allow(clippy::new_without_default)]
127 pub fn new() -> Self {
128 let temp_dir = TempDir::new().unwrap();
129 Self {
130 state: PocketIcStateKind::TempDir(temp_dir),
131 }
132 }
133
134 pub fn new_from_path(state_dir: PathBuf) -> Self {
135 Self {
136 state: PocketIcStateKind::StateDir(state_dir),
137 }
138 }
139
140 pub fn into_path(self) -> PathBuf {
141 match self.state {
142 PocketIcStateKind::StateDir(state_dir) => state_dir,
143 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
144 }
145 }
146
147 pub(crate) fn state_dir(&self) -> PathBuf {
148 match &self.state {
149 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
150 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
151 }
152 }
153}
154
155pub struct PocketIcBuilder {
156 config: Option<ExtendedSubnetConfigSet>,
157 http_gateway_config: Option<InstanceHttpGatewayConfig>,
158 server_binary: Option<PathBuf>,
159 server_url: Option<Url>,
160 max_request_time_ms: Option<u64>,
161 read_only_state_dir: Option<PathBuf>,
162 state_dir: Option<PocketIcState>,
163 icp_config: IcpConfig,
164 log_level: Option<Level>,
165 bitcoind_addr: Option<Vec<SocketAddr>>,
166 icp_features: IcpFeatures,
167 initial_time: Option<InitialTime>,
168}
169
170#[allow(clippy::new_without_default)]
171impl PocketIcBuilder {
172 pub fn new() -> Self {
173 Self {
174 config: None,
175 http_gateway_config: None,
176 server_binary: None,
177 server_url: None,
178 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
179 read_only_state_dir: None,
180 state_dir: None,
181 icp_config: IcpConfig::default(),
182 log_level: None,
183 bitcoind_addr: None,
184 icp_features: IcpFeatures::default(),
185 initial_time: None,
186 }
187 }
188
189 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
190 let mut builder = Self::new();
191 builder.config = Some(config.into());
192 builder
193 }
194
195 pub fn build(self) -> PocketIc {
196 PocketIc::from_components(
197 self.config.unwrap_or_default(),
198 self.server_url,
199 self.server_binary,
200 self.max_request_time_ms,
201 self.read_only_state_dir,
202 self.state_dir,
203 self.icp_config,
204 self.log_level,
205 self.bitcoind_addr,
206 self.icp_features,
207 self.initial_time,
208 self.http_gateway_config,
209 )
210 }
211
212 pub async fn build_async(self) -> PocketIcAsync {
213 PocketIcAsync::from_components(
214 self.config.unwrap_or_default(),
215 self.server_url,
216 self.server_binary,
217 self.max_request_time_ms,
218 self.read_only_state_dir,
219 self.state_dir,
220 self.icp_config,
221 self.log_level,
222 self.bitcoind_addr,
223 self.icp_features,
224 self.initial_time,
225 self.http_gateway_config,
226 )
227 .await
228 }
229
230 pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
232 self.server_binary = Some(server_binary);
233 self
234 }
235
236 pub fn with_server_url(mut self, server_url: Url) -> Self {
238 self.server_url = Some(server_url);
239 self
240 }
241
242 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
243 self.max_request_time_ms = max_request_time_ms;
244 self
245 }
246
247 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
248 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
249 self
250 }
251
252 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
253 self.state_dir = Some(state_dir);
254 self
255 }
256
257 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
258 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
259 self
260 }
261
262 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
263 self.icp_config = icp_config;
264 self
265 }
266
267 pub fn with_log_level(mut self, log_level: Level) -> Self {
268 self.log_level = Some(log_level);
269 self
270 }
271
272 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
273 self.with_bitcoind_addrs(vec![bitcoind_addr])
274 }
275
276 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
277 Self {
278 bitcoind_addr: Some(bitcoind_addrs),
279 ..self
280 }
281 }
282
283 pub fn with_nns_subnet(mut self) -> Self {
285 let mut config = self.config.unwrap_or_default();
286 config.nns = Some(config.nns.unwrap_or_default());
287 self.config = Some(config);
288 self
289 }
290
291 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
308 self.with_subnet_state(SubnetKind::NNS, path_to_state)
309 }
310
311 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
328 let mut config = self.config.unwrap_or_default();
329 let subnet_spec = SubnetSpec::default().with_state_dir(path_to_state);
330 match subnet_kind {
331 SubnetKind::NNS => config.nns = Some(subnet_spec),
332 SubnetKind::SNS => config.sns = Some(subnet_spec),
333 SubnetKind::II => config.ii = Some(subnet_spec),
334 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
335 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
336 SubnetKind::Application => config.application.push(subnet_spec),
337 SubnetKind::System => config.system.push(subnet_spec),
338 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
339 };
340 self.config = Some(config);
341 self
342 }
343
344 pub fn with_sns_subnet(mut self) -> Self {
346 let mut config = self.config.unwrap_or_default();
347 config.sns = Some(config.sns.unwrap_or_default());
348 self.config = Some(config);
349 self
350 }
351
352 pub fn with_ii_subnet(mut self) -> Self {
354 let mut config = self.config.unwrap_or_default();
355 config.ii = Some(config.ii.unwrap_or_default());
356 self.config = Some(config);
357 self
358 }
359
360 pub fn with_fiduciary_subnet(mut self) -> Self {
362 let mut config = self.config.unwrap_or_default();
363 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
364 self.config = Some(config);
365 self
366 }
367
368 pub fn with_bitcoin_subnet(mut self) -> Self {
370 let mut config = self.config.unwrap_or_default();
371 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
372 self.config = Some(config);
373 self
374 }
375
376 pub fn with_system_subnet(mut self) -> Self {
378 let mut config = self.config.unwrap_or_default();
379 config.system.push(SubnetSpec::default());
380 self.config = Some(config);
381 self
382 }
383
384 pub fn with_application_subnet(mut self) -> Self {
386 let mut config = self.config.unwrap_or_default();
387 config.application.push(SubnetSpec::default());
388 self.config = Some(config);
389 self
390 }
391
392 pub fn with_verified_application_subnet(mut self) -> Self {
394 let mut config = self.config.unwrap_or_default();
395 config.verified_application.push(SubnetSpec::default());
396 self.config = Some(config);
397 self
398 }
399
400 pub fn with_benchmarking_application_subnet(mut self) -> Self {
402 let mut config = self.config.unwrap_or_default();
403 config
404 .application
405 .push(SubnetSpec::default().with_benchmarking_instruction_config());
406 self.config = Some(config);
407 self
408 }
409
410 pub fn with_benchmarking_system_subnet(mut self) -> Self {
412 let mut config = self.config.unwrap_or_default();
413 config
414 .system
415 .push(SubnetSpec::default().with_benchmarking_instruction_config());
416 self.config = Some(config);
417 self
418 }
419
420 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
423 self.icp_features = icp_features;
424 self
425 }
426
427 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
431 self.initial_time = Some(InitialTime::Timestamp(RawTime {
432 nanos_since_epoch: initial_timestamp_nanos,
433 }));
434 self
435 }
436
437 pub fn with_auto_progress(mut self) -> Self {
441 let config = AutoProgressConfig {
442 artificial_delay_ms: None,
443 };
444 self.initial_time = Some(InitialTime::AutoProgress(config));
445 self
446 }
447
448 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
449 self.http_gateway_config = Some(http_gateway_config);
450 self
451 }
452}
453
454#[derive(Copy, Clone, PartialEq, PartialOrd)]
457pub struct Time(Duration);
458
459impl Time {
460 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
462 self.0.as_nanos().try_into().unwrap()
463 }
464
465 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
466 Time(Duration::from_nanos(nanos))
467 }
468}
469
470impl std::fmt::Debug for Time {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
473 write!(f, "{}", nanos_since_unix_epoch)
474 }
475}
476
477impl std::ops::Add<Duration> for Time {
478 type Output = Time;
479 fn add(self, dur: Duration) -> Time {
480 Time(self.0 + dur)
481 }
482}
483
484impl From<SystemTime> for Time {
485 fn from(time: SystemTime) -> Self {
486 Self::from_nanos_since_unix_epoch(
487 time.duration_since(UNIX_EPOCH)
488 .unwrap()
489 .as_nanos()
490 .try_into()
491 .unwrap(),
492 )
493 }
494}
495
496impl TryFrom<Time> for SystemTime {
497 type Error = String;
498
499 fn try_from(time: Time) -> Result<SystemTime, String> {
500 let nanos = time.as_nanos_since_unix_epoch();
501 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
502 let roundtrip: Time = system_time.into();
503 if roundtrip.as_nanos_since_unix_epoch() == nanos {
504 Ok(system_time)
505 } else {
506 Err(format!("Converting UNIX timestamp {} in nanoseconds to SystemTime failed due to losing precision", nanos))
507 }
508 }
509}
510
511pub struct PocketIc {
513 pocket_ic: PocketIcAsync,
514 runtime: Arc<tokio::runtime::Runtime>,
515 thread: Option<JoinHandle<()>>,
516}
517
518impl PocketIc {
519 pub fn new() -> Self {
522 PocketIcBuilder::new().with_application_subnet().build()
523 }
524
525 pub fn new_from_existing_instance(
530 server_url: Url,
531 instance_id: InstanceId,
532 max_request_time_ms: Option<u64>,
533 ) -> Self {
534 let (tx, rx) = channel();
535 let thread = thread::spawn(move || {
536 let rt = tokio::runtime::Builder::new_current_thread()
537 .enable_all()
538 .build()
539 .unwrap();
540 tx.send(rt).unwrap();
541 });
542 let runtime = rx.recv().unwrap();
543
544 let pocket_ic =
545 PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
546
547 Self {
548 pocket_ic,
549 runtime: Arc::new(runtime),
550 thread: Some(thread),
551 }
552 }
553
554 pub(crate) fn from_components(
555 subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
556 server_url: Option<Url>,
557 server_binary: Option<PathBuf>,
558 max_request_time_ms: Option<u64>,
559 read_only_state_dir: Option<PathBuf>,
560 state_dir: Option<PocketIcState>,
561 icp_config: IcpConfig,
562 log_level: Option<Level>,
563 bitcoind_addr: Option<Vec<SocketAddr>>,
564 icp_features: IcpFeatures,
565 initial_time: Option<InitialTime>,
566 http_gateway_config: Option<InstanceHttpGatewayConfig>,
567 ) -> Self {
568 let (tx, rx) = channel();
569 let thread = thread::spawn(move || {
570 let rt = tokio::runtime::Builder::new_current_thread()
571 .enable_all()
572 .build()
573 .unwrap();
574 tx.send(rt).unwrap();
575 });
576 let runtime = rx.recv().unwrap();
577
578 let pocket_ic = runtime.block_on(async {
579 PocketIcAsync::from_components(
580 subnet_config_set,
581 server_url,
582 server_binary,
583 max_request_time_ms,
584 read_only_state_dir,
585 state_dir,
586 icp_config,
587 log_level,
588 bitcoind_addr,
589 icp_features,
590 initial_time,
591 http_gateway_config,
592 )
593 .await
594 });
595
596 Self {
597 pocket_ic,
598 runtime: Arc::new(runtime),
599 thread: Some(thread),
600 }
601 }
602
603 pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
604 self.pocket_ic.take_state_internal()
605 }
606
607 pub fn get_server_url(&self) -> Url {
609 self.pocket_ic.get_server_url()
610 }
611
612 pub fn instance_id(&self) -> InstanceId {
614 self.pocket_ic.instance_id
615 }
616
617 pub fn topology(&self) -> Topology {
619 let runtime = self.runtime.clone();
620 runtime.block_on(async { self.pocket_ic.topology().await })
621 }
622
623 #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
625 pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
626 let runtime = self.runtime.clone();
627 runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
628 }
629
630 #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
633 pub fn set_stable_memory(
634 &self,
635 canister_id: CanisterId,
636 data: Vec<u8>,
637 compression: BlobCompression,
638 ) {
639 let runtime = self.runtime.clone();
640 runtime.block_on(async {
641 self.pocket_ic
642 .set_stable_memory(canister_id, data, compression)
643 .await
644 })
645 }
646
647 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
649 pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
650 let runtime = self.runtime.clone();
651 runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
652 }
653
654 #[instrument(ret)]
656 pub fn list_instances() -> Vec<String> {
657 let runtime = tokio::runtime::Builder::new_current_thread()
658 .build()
659 .unwrap();
660 let url = runtime.block_on(async {
661 let (_, server_url) = start_server(StartServerParams {
662 reuse: true,
663 ..Default::default()
664 })
665 .await;
666 server_url.join("instances").unwrap()
667 });
668 let instances: Vec<String> = reqwest::blocking::Client::new()
669 .get(url)
670 .send()
671 .expect("Failed to get result")
672 .json()
673 .expect("Failed to get json");
674 instances
675 }
676
677 #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
679 pub fn verify_canister_signature(
680 &self,
681 msg: Vec<u8>,
682 sig: Vec<u8>,
683 pubkey: Vec<u8>,
684 root_pubkey: Vec<u8>,
685 ) -> Result<(), String> {
686 let runtime = self.runtime.clone();
687 runtime.block_on(async {
688 self.pocket_ic
689 .verify_canister_signature(msg, sig, pubkey, root_pubkey)
690 .await
691 })
692 }
693
694 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
699 pub fn tick(&self) {
700 let runtime = self.runtime.clone();
701 runtime.block_on(async { self.pocket_ic.tick().await })
702 }
703
704 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
707 pub fn tick_with_configs(&self, configs: crate::common::rest::TickConfigs) {
708 let runtime = self.runtime.clone();
709 runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
710 }
711
712 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
718 pub fn auto_progress(&self) -> Url {
719 let runtime = self.runtime.clone();
720 runtime.block_on(async { self.pocket_ic.auto_progress().await })
721 }
722
723 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
725 pub fn auto_progress_enabled(&self) -> bool {
726 let runtime = self.runtime.clone();
727 runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
728 }
729
730 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
732 pub fn stop_progress(&self) {
733 let runtime = self.runtime.clone();
734 runtime.block_on(async { self.pocket_ic.stop_progress().await })
735 }
736
737 pub fn url(&self) -> Option<Url> {
741 self.pocket_ic.url()
742 }
743
744 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
753 pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
754 let runtime = self.runtime.clone();
755 runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
756 }
757
758 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
769 pub async fn make_live_with_params(
770 &mut self,
771 ip_addr: Option<IpAddr>,
772 listen_at: Option<u16>,
773 domains: Option<Vec<String>>,
774 https_config: Option<HttpsConfig>,
775 ) -> Url {
776 let runtime = self.runtime.clone();
777 runtime.block_on(async {
778 self.pocket_ic
779 .make_live_with_params(ip_addr, listen_at, domains, https_config)
780 .await
781 })
782 }
783
784 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
787 pub fn stop_live(&mut self) {
788 let runtime = self.runtime.clone();
789 runtime.block_on(async { self.pocket_ic.stop_live().await })
790 }
791
792 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
794 pub fn root_key(&self) -> Option<Vec<u8>> {
795 let runtime = self.runtime.clone();
796 runtime.block_on(async { self.pocket_ic.root_key().await })
797 }
798
799 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
801 pub fn get_time(&self) -> Time {
802 let runtime = self.runtime.clone();
803 runtime.block_on(async { self.pocket_ic.get_time().await })
804 }
805
806 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
808 pub fn set_time(&self, time: Time) {
809 let runtime = self.runtime.clone();
810 runtime.block_on(async { self.pocket_ic.set_time(time).await })
811 }
812
813 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
815 pub fn set_certified_time(&self, time: Time) {
816 let runtime = self.runtime.clone();
817 runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
818 }
819
820 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
822 pub fn advance_time(&self, duration: Duration) {
823 let runtime = self.runtime.clone();
824 runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
825 }
826
827 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
830 pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
831 let runtime = self.runtime.clone();
832 runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
833 }
834
835 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
837 pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
838 let runtime = self.runtime.clone();
839 runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
840 }
841
842 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
844 pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
845 let runtime = self.runtime.clone();
846 runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
847 }
848
849 pub fn submit_call(
851 &self,
852 canister_id: CanisterId,
853 sender: Principal,
854 method: &str,
855 payload: Vec<u8>,
856 ) -> Result<RawMessageId, RejectResponse> {
857 let runtime = self.runtime.clone();
858 runtime.block_on(async {
859 self.pocket_ic
860 .submit_call(canister_id, sender, method, payload)
861 .await
862 })
863 }
864
865 pub fn submit_call_with_effective_principal(
867 &self,
868 canister_id: CanisterId,
869 effective_principal: RawEffectivePrincipal,
870 sender: Principal,
871 method: &str,
872 payload: Vec<u8>,
873 ) -> Result<RawMessageId, RejectResponse> {
874 let runtime = self.runtime.clone();
875 runtime.block_on(async {
876 self.pocket_ic
877 .submit_call_with_effective_principal(
878 canister_id,
879 effective_principal,
880 sender,
881 method,
882 payload,
883 )
884 .await
885 })
886 }
887
888 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
890 let runtime = self.runtime.clone();
891 runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
892 }
893
894 pub fn ingress_status(
898 &self,
899 message_id: RawMessageId,
900 ) -> Option<Result<Vec<u8>, RejectResponse>> {
901 let runtime = self.runtime.clone();
902 runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
903 }
904
905 pub fn ingress_status_as(
910 &self,
911 message_id: RawMessageId,
912 caller: Principal,
913 ) -> IngressStatusResult {
914 let runtime = self.runtime.clone();
915 runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
916 }
917
918 pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
922 let runtime = self.runtime.clone();
923 runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
924 }
925
926 #[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()))]
928 pub fn update_call(
929 &self,
930 canister_id: CanisterId,
931 sender: Principal,
932 method: &str,
933 payload: Vec<u8>,
934 ) -> Result<Vec<u8>, RejectResponse> {
935 let runtime = self.runtime.clone();
936 runtime.block_on(async {
937 self.pocket_ic
938 .update_call(canister_id, sender, method, payload)
939 .await
940 })
941 }
942
943 #[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()))]
945 pub fn query_call(
946 &self,
947 canister_id: CanisterId,
948 sender: Principal,
949 method: &str,
950 payload: Vec<u8>,
951 ) -> Result<Vec<u8>, RejectResponse> {
952 let runtime = self.runtime.clone();
953 runtime.block_on(async {
954 self.pocket_ic
955 .query_call(canister_id, sender, method, payload)
956 .await
957 })
958 }
959
960 pub fn fetch_canister_logs(
962 &self,
963 canister_id: CanisterId,
964 sender: Principal,
965 ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
966 let runtime = self.runtime.clone();
967 runtime.block_on(async {
968 self.pocket_ic
969 .fetch_canister_logs(canister_id, sender)
970 .await
971 })
972 }
973
974 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
976 pub fn canister_status(
977 &self,
978 canister_id: CanisterId,
979 sender: Option<Principal>,
980 ) -> Result<CanisterStatusResult, RejectResponse> {
981 let runtime = self.runtime.clone();
982 runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
983 }
984
985 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
987 pub fn create_canister(&self) -> CanisterId {
988 let runtime = self.runtime.clone();
989 runtime.block_on(async { self.pocket_ic.create_canister().await })
990 }
991
992 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
994 pub fn create_canister_with_settings(
995 &self,
996 sender: Option<Principal>,
997 settings: Option<CanisterSettings>,
998 ) -> CanisterId {
999 let runtime = self.runtime.clone();
1000 runtime.block_on(async {
1001 self.pocket_ic
1002 .create_canister_with_settings(sender, settings)
1003 .await
1004 })
1005 }
1006
1007 #[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()))]
1015 pub fn create_canister_with_id(
1016 &self,
1017 sender: Option<Principal>,
1018 settings: Option<CanisterSettings>,
1019 canister_id: CanisterId,
1020 ) -> Result<CanisterId, String> {
1021 let runtime = self.runtime.clone();
1022 runtime.block_on(async {
1023 self.pocket_ic
1024 .create_canister_with_id(sender, settings, canister_id)
1025 .await
1026 })
1027 }
1028
1029 #[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()))]
1031 pub fn create_canister_on_subnet(
1032 &self,
1033 sender: Option<Principal>,
1034 settings: Option<CanisterSettings>,
1035 subnet_id: SubnetId,
1036 ) -> CanisterId {
1037 let runtime = self.runtime.clone();
1038 runtime.block_on(async {
1039 self.pocket_ic
1040 .create_canister_on_subnet(sender, settings, subnet_id)
1041 .await
1042 })
1043 }
1044
1045 #[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()))]
1048 pub fn upload_chunk(
1049 &self,
1050 canister_id: CanisterId,
1051 sender: Option<Principal>,
1052 chunk: Vec<u8>,
1053 ) -> Result<Vec<u8>, RejectResponse> {
1054 let runtime = self.runtime.clone();
1055 runtime.block_on(async {
1056 self.pocket_ic
1057 .upload_chunk(canister_id, sender, chunk)
1058 .await
1059 })
1060 }
1061
1062 #[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()))]
1064 pub fn stored_chunks(
1065 &self,
1066 canister_id: CanisterId,
1067 sender: Option<Principal>,
1068 ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1069 let runtime = self.runtime.clone();
1070 runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1071 }
1072
1073 #[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()))]
1075 pub fn clear_chunk_store(
1076 &self,
1077 canister_id: CanisterId,
1078 sender: Option<Principal>,
1079 ) -> Result<(), RejectResponse> {
1080 let runtime = self.runtime.clone();
1081 runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1082 }
1083
1084 #[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()))]
1086 pub fn install_chunked_canister(
1087 &self,
1088 canister_id: CanisterId,
1089 sender: Option<Principal>,
1090 mode: CanisterInstallMode,
1091 store_canister_id: CanisterId,
1092 chunk_hashes_list: Vec<Vec<u8>>,
1093 wasm_module_hash: Vec<u8>,
1094 arg: Vec<u8>,
1095 ) -> Result<(), RejectResponse> {
1096 let runtime = self.runtime.clone();
1097 runtime.block_on(async {
1098 self.pocket_ic
1099 .install_chunked_canister(
1100 canister_id,
1101 sender,
1102 mode,
1103 store_canister_id,
1104 chunk_hashes_list,
1105 wasm_module_hash,
1106 arg,
1107 )
1108 .await
1109 })
1110 }
1111
1112 #[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()))]
1114 pub fn install_canister(
1115 &self,
1116 canister_id: CanisterId,
1117 wasm_module: Vec<u8>,
1118 arg: Vec<u8>,
1119 sender: Option<Principal>,
1120 ) {
1121 let runtime = self.runtime.clone();
1122 runtime.block_on(async {
1123 self.pocket_ic
1124 .install_canister(canister_id, wasm_module, arg, sender)
1125 .await
1126 })
1127 }
1128
1129 #[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()))]
1131 pub fn upgrade_canister(
1132 &self,
1133 canister_id: CanisterId,
1134 wasm_module: Vec<u8>,
1135 arg: Vec<u8>,
1136 sender: Option<Principal>,
1137 ) -> Result<(), RejectResponse> {
1138 let runtime = self.runtime.clone();
1139 runtime.block_on(async {
1140 self.pocket_ic
1141 .upgrade_canister(canister_id, wasm_module, arg, sender)
1142 .await
1143 })
1144 }
1145
1146 #[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()))]
1148 pub fn upgrade_eop_canister(
1149 &self,
1150 canister_id: CanisterId,
1151 wasm_module: Vec<u8>,
1152 arg: Vec<u8>,
1153 sender: Option<Principal>,
1154 ) -> Result<(), RejectResponse> {
1155 let runtime = self.runtime.clone();
1156 runtime.block_on(async {
1157 self.pocket_ic
1158 .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1159 .await
1160 })
1161 }
1162
1163 #[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()))]
1165 pub fn reinstall_canister(
1166 &self,
1167 canister_id: CanisterId,
1168 wasm_module: Vec<u8>,
1169 arg: Vec<u8>,
1170 sender: Option<Principal>,
1171 ) -> Result<(), RejectResponse> {
1172 let runtime = self.runtime.clone();
1173 runtime.block_on(async {
1174 self.pocket_ic
1175 .reinstall_canister(canister_id, wasm_module, arg, sender)
1176 .await
1177 })
1178 }
1179
1180 #[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()))]
1182 pub fn uninstall_canister(
1183 &self,
1184 canister_id: CanisterId,
1185 sender: Option<Principal>,
1186 ) -> Result<(), RejectResponse> {
1187 let runtime = self.runtime.clone();
1188 runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1189 }
1190
1191 #[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()))]
1193 pub fn take_canister_snapshot(
1194 &self,
1195 canister_id: CanisterId,
1196 sender: Option<Principal>,
1197 replace_snapshot: Option<Vec<u8>>,
1198 ) -> Result<Snapshot, RejectResponse> {
1199 let runtime = self.runtime.clone();
1200 runtime.block_on(async {
1201 self.pocket_ic
1202 .take_canister_snapshot(canister_id, sender, replace_snapshot)
1203 .await
1204 })
1205 }
1206
1207 #[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()))]
1209 pub fn load_canister_snapshot(
1210 &self,
1211 canister_id: CanisterId,
1212 sender: Option<Principal>,
1213 snapshot_id: Vec<u8>,
1214 ) -> Result<(), RejectResponse> {
1215 let runtime = self.runtime.clone();
1216 runtime.block_on(async {
1217 self.pocket_ic
1218 .load_canister_snapshot(canister_id, sender, snapshot_id)
1219 .await
1220 })
1221 }
1222
1223 #[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()))]
1225 pub fn list_canister_snapshots(
1226 &self,
1227 canister_id: CanisterId,
1228 sender: Option<Principal>,
1229 ) -> Result<Vec<Snapshot>, RejectResponse> {
1230 let runtime = self.runtime.clone();
1231 runtime.block_on(async {
1232 self.pocket_ic
1233 .list_canister_snapshots(canister_id, sender)
1234 .await
1235 })
1236 }
1237
1238 #[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()))]
1240 pub fn delete_canister_snapshot(
1241 &self,
1242 canister_id: CanisterId,
1243 sender: Option<Principal>,
1244 snapshot_id: Vec<u8>,
1245 ) -> Result<(), RejectResponse> {
1246 let runtime = self.runtime.clone();
1247 runtime.block_on(async {
1248 self.pocket_ic
1249 .delete_canister_snapshot(canister_id, sender, snapshot_id)
1250 .await
1251 })
1252 }
1253
1254 #[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()))]
1256 pub fn update_canister_settings(
1257 &self,
1258 canister_id: CanisterId,
1259 sender: Option<Principal>,
1260 settings: CanisterSettings,
1261 ) -> Result<(), RejectResponse> {
1262 let runtime = self.runtime.clone();
1263 runtime.block_on(async {
1264 self.pocket_ic
1265 .update_canister_settings(canister_id, sender, settings)
1266 .await
1267 })
1268 }
1269
1270 #[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()))]
1272 pub fn set_controllers(
1273 &self,
1274 canister_id: CanisterId,
1275 sender: Option<Principal>,
1276 new_controllers: Vec<Principal>,
1277 ) -> Result<(), RejectResponse> {
1278 let runtime = self.runtime.clone();
1279 runtime.block_on(async {
1280 self.pocket_ic
1281 .set_controllers(canister_id, sender, new_controllers)
1282 .await
1283 })
1284 }
1285
1286 #[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()))]
1288 pub fn start_canister(
1289 &self,
1290 canister_id: CanisterId,
1291 sender: Option<Principal>,
1292 ) -> Result<(), RejectResponse> {
1293 let runtime = self.runtime.clone();
1294 runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1295 }
1296
1297 #[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()))]
1299 pub fn stop_canister(
1300 &self,
1301 canister_id: CanisterId,
1302 sender: Option<Principal>,
1303 ) -> Result<(), RejectResponse> {
1304 let runtime = self.runtime.clone();
1305 runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1306 }
1307
1308 #[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()))]
1310 pub fn delete_canister(
1311 &self,
1312 canister_id: CanisterId,
1313 sender: Option<Principal>,
1314 ) -> Result<(), RejectResponse> {
1315 let runtime = self.runtime.clone();
1316 runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1317 }
1318
1319 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1321 pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1322 let runtime = self.runtime.clone();
1323 runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1324 }
1325
1326 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1328 pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1329 let runtime = self.runtime.clone();
1330 runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1331 }
1332
1333 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1335 pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1336 let runtime = self.runtime.clone();
1337 runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1338 }
1339
1340 pub fn update_call_with_effective_principal(
1341 &self,
1342 canister_id: CanisterId,
1343 effective_principal: RawEffectivePrincipal,
1344 sender: Principal,
1345 method: &str,
1346 payload: Vec<u8>,
1347 ) -> Result<Vec<u8>, RejectResponse> {
1348 let runtime = self.runtime.clone();
1349 runtime.block_on(async {
1350 self.pocket_ic
1351 .update_call_with_effective_principal(
1352 canister_id,
1353 effective_principal,
1354 sender,
1355 method,
1356 payload,
1357 )
1358 .await
1359 })
1360 }
1361
1362 #[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()))]
1366 pub fn query_call_with_effective_principal(
1367 &self,
1368 canister_id: CanisterId,
1369 effective_principal: RawEffectivePrincipal,
1370 sender: Principal,
1371 method: &str,
1372 payload: Vec<u8>,
1373 ) -> Result<Vec<u8>, RejectResponse> {
1374 let runtime = self.runtime.clone();
1375 runtime.block_on(async {
1376 self.pocket_ic
1377 .query_call_with_effective_principal(
1378 canister_id,
1379 effective_principal,
1380 sender,
1381 method,
1382 payload,
1383 )
1384 .await
1385 })
1386 }
1387
1388 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1400 pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1401 let runtime = self.runtime.clone();
1402 runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1403 }
1404
1405 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1407 pub fn mock_canister_http_response(
1408 &self,
1409 mock_canister_http_response: MockCanisterHttpResponse,
1410 ) {
1411 let runtime = self.runtime.clone();
1412 runtime.block_on(async {
1413 self.pocket_ic
1414 .mock_canister_http_response(mock_canister_http_response)
1415 .await
1416 })
1417 }
1418}
1419
1420impl Default for PocketIc {
1421 fn default() -> Self {
1422 Self::new()
1423 }
1424}
1425
1426impl Drop for PocketIc {
1427 fn drop(&mut self) {
1428 self.runtime.block_on(async {
1429 self.pocket_ic.do_drop().await;
1430 });
1431 if let Some(thread) = self.thread.take() {
1432 thread.join().unwrap();
1433 }
1434 }
1435}
1436
1437pub fn call_candid_as<Input, Output>(
1441 env: &PocketIc,
1442 canister_id: CanisterId,
1443 effective_principal: RawEffectivePrincipal,
1444 sender: Principal,
1445 method: &str,
1446 input: Input,
1447) -> Result<Output, RejectResponse>
1448where
1449 Input: ArgumentEncoder,
1450 Output: for<'a> ArgumentDecoder<'a>,
1451{
1452 with_candid(input, |payload| {
1453 env.update_call_with_effective_principal(
1454 canister_id,
1455 effective_principal,
1456 sender,
1457 method,
1458 payload,
1459 )
1460 })
1461}
1462
1463pub fn call_candid<Input, Output>(
1466 env: &PocketIc,
1467 canister_id: CanisterId,
1468 effective_principal: RawEffectivePrincipal,
1469 method: &str,
1470 input: Input,
1471) -> Result<Output, RejectResponse>
1472where
1473 Input: ArgumentEncoder,
1474 Output: for<'a> ArgumentDecoder<'a>,
1475{
1476 call_candid_as(
1477 env,
1478 canister_id,
1479 effective_principal,
1480 Principal::anonymous(),
1481 method,
1482 input,
1483 )
1484}
1485
1486pub fn query_candid<Input, Output>(
1488 env: &PocketIc,
1489 canister_id: CanisterId,
1490 method: &str,
1491 input: Input,
1492) -> Result<Output, RejectResponse>
1493where
1494 Input: ArgumentEncoder,
1495 Output: for<'a> ArgumentDecoder<'a>,
1496{
1497 query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1498}
1499
1500pub fn query_candid_as<Input, Output>(
1503 env: &PocketIc,
1504 canister_id: CanisterId,
1505 sender: Principal,
1506 method: &str,
1507 input: Input,
1508) -> Result<Output, RejectResponse>
1509where
1510 Input: ArgumentEncoder,
1511 Output: for<'a> ArgumentDecoder<'a>,
1512{
1513 with_candid(input, |bytes| {
1514 env.query_call(canister_id, sender, method, bytes)
1515 })
1516}
1517
1518pub fn update_candid<Input, Output>(
1520 env: &PocketIc,
1521 canister_id: CanisterId,
1522 method: &str,
1523 input: Input,
1524) -> Result<Output, RejectResponse>
1525where
1526 Input: ArgumentEncoder,
1527 Output: for<'a> ArgumentDecoder<'a>,
1528{
1529 update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1530}
1531
1532pub fn update_candid_as<Input, Output>(
1535 env: &PocketIc,
1536 canister_id: CanisterId,
1537 sender: Principal,
1538 method: &str,
1539 input: Input,
1540) -> Result<Output, RejectResponse>
1541where
1542 Input: ArgumentEncoder,
1543 Output: for<'a> ArgumentDecoder<'a>,
1544{
1545 with_candid(input, |bytes| {
1546 env.update_call(canister_id, sender, method, bytes)
1547 })
1548}
1549
1550pub fn with_candid<Input, Output>(
1553 input: Input,
1554 f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1555) -> Result<Output, RejectResponse>
1556where
1557 Input: ArgumentEncoder,
1558 Output: for<'a> ArgumentDecoder<'a>,
1559{
1560 let in_bytes = encode_args(input).expect("failed to encode args");
1561 f(in_bytes).map(|out_bytes| {
1562 decode_args(&out_bytes).unwrap_or_else(|e| {
1563 panic!(
1564 "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1565 std::any::type_name::<Output>(),
1566 e,
1567 out_bytes,
1568 String::from_utf8_lossy(&out_bytes),
1569 )
1570 })
1571 })
1572}
1573
1574#[derive(Clone, Copy, Debug)]
1576pub enum TryFromError {
1577 ValueOutOfRange(u64),
1578}
1579
1580#[derive(
1587 PartialOrd,
1588 Ord,
1589 Clone,
1590 Copy,
1591 Debug,
1592 PartialEq,
1593 Eq,
1594 Hash,
1595 Serialize,
1596 Deserialize,
1597 JsonSchema,
1598 EnumIter,
1599)]
1600pub enum ErrorCode {
1601 SubnetOversubscribed = 101,
1603 MaxNumberOfCanistersReached = 102,
1604 CanisterQueueFull = 201,
1606 IngressMessageTimeout = 202,
1607 CanisterQueueNotEmpty = 203,
1608 IngressHistoryFull = 204,
1609 CanisterIdAlreadyExists = 205,
1610 StopCanisterRequestTimeout = 206,
1611 CanisterOutOfCycles = 207,
1612 CertifiedStateUnavailable = 208,
1613 CanisterInstallCodeRateLimited = 209,
1614 CanisterHeapDeltaRateLimited = 210,
1615 CanisterNotFound = 301,
1617 CanisterSnapshotNotFound = 305,
1618 InsufficientMemoryAllocation = 402,
1620 InsufficientCyclesForCreateCanister = 403,
1621 SubnetNotFound = 404,
1622 CanisterNotHostedBySubnet = 405,
1623 CanisterRejectedMessage = 406,
1624 UnknownManagementMessage = 407,
1625 InvalidManagementPayload = 408,
1626 CanisterSnapshotImmutable = 409,
1627 CanisterTrapped = 502,
1629 CanisterCalledTrap = 503,
1630 CanisterContractViolation = 504,
1631 CanisterInvalidWasm = 505,
1632 CanisterDidNotReply = 506,
1633 CanisterOutOfMemory = 507,
1634 CanisterStopped = 508,
1635 CanisterStopping = 509,
1636 CanisterNotStopped = 510,
1637 CanisterStoppingCancelled = 511,
1638 CanisterInvalidController = 512,
1639 CanisterFunctionNotFound = 513,
1640 CanisterNonEmpty = 514,
1641 QueryCallGraphLoopDetected = 517,
1642 InsufficientCyclesInCall = 520,
1643 CanisterWasmEngineError = 521,
1644 CanisterInstructionLimitExceeded = 522,
1645 CanisterMemoryAccessLimitExceeded = 524,
1646 QueryCallGraphTooDeep = 525,
1647 QueryCallGraphTotalInstructionLimitExceeded = 526,
1648 CompositeQueryCalledInReplicatedMode = 527,
1649 QueryTimeLimitExceeded = 528,
1650 QueryCallGraphInternal = 529,
1651 InsufficientCyclesInComputeAllocation = 530,
1652 InsufficientCyclesInMemoryAllocation = 531,
1653 InsufficientCyclesInMemoryGrow = 532,
1654 ReservedCyclesLimitExceededInMemoryAllocation = 533,
1655 ReservedCyclesLimitExceededInMemoryGrow = 534,
1656 InsufficientCyclesInMessageMemoryGrow = 535,
1657 CanisterMethodNotFound = 536,
1658 CanisterWasmModuleNotFound = 537,
1659 CanisterAlreadyInstalled = 538,
1660 CanisterWasmMemoryLimitExceeded = 539,
1661 ReservedCyclesLimitIsTooLow = 540,
1662 DeadlineExpired = 601,
1664 ResponseDropped = 602,
1665}
1666
1667impl TryFrom<u64> for ErrorCode {
1668 type Error = TryFromError;
1669 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1670 match err {
1671 101 => Ok(ErrorCode::SubnetOversubscribed),
1673 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1674 201 => Ok(ErrorCode::CanisterQueueFull),
1676 202 => Ok(ErrorCode::IngressMessageTimeout),
1677 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1678 204 => Ok(ErrorCode::IngressHistoryFull),
1679 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1680 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1681 207 => Ok(ErrorCode::CanisterOutOfCycles),
1682 208 => Ok(ErrorCode::CertifiedStateUnavailable),
1683 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1684 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1685 301 => Ok(ErrorCode::CanisterNotFound),
1687 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1688 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1690 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1691 404 => Ok(ErrorCode::SubnetNotFound),
1692 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1693 406 => Ok(ErrorCode::CanisterRejectedMessage),
1694 407 => Ok(ErrorCode::UnknownManagementMessage),
1695 408 => Ok(ErrorCode::InvalidManagementPayload),
1696 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1697 502 => Ok(ErrorCode::CanisterTrapped),
1699 503 => Ok(ErrorCode::CanisterCalledTrap),
1700 504 => Ok(ErrorCode::CanisterContractViolation),
1701 505 => Ok(ErrorCode::CanisterInvalidWasm),
1702 506 => Ok(ErrorCode::CanisterDidNotReply),
1703 507 => Ok(ErrorCode::CanisterOutOfMemory),
1704 508 => Ok(ErrorCode::CanisterStopped),
1705 509 => Ok(ErrorCode::CanisterStopping),
1706 510 => Ok(ErrorCode::CanisterNotStopped),
1707 511 => Ok(ErrorCode::CanisterStoppingCancelled),
1708 512 => Ok(ErrorCode::CanisterInvalidController),
1709 513 => Ok(ErrorCode::CanisterFunctionNotFound),
1710 514 => Ok(ErrorCode::CanisterNonEmpty),
1711 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
1712 520 => Ok(ErrorCode::InsufficientCyclesInCall),
1713 521 => Ok(ErrorCode::CanisterWasmEngineError),
1714 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
1715 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
1716 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
1717 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
1718 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
1719 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
1720 529 => Ok(ErrorCode::QueryCallGraphInternal),
1721 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
1722 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
1723 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
1724 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
1725 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
1726 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
1727 536 => Ok(ErrorCode::CanisterMethodNotFound),
1728 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
1729 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
1730 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
1731 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
1732 601 => Ok(ErrorCode::DeadlineExpired),
1734 602 => Ok(ErrorCode::ResponseDropped),
1735 _ => Err(TryFromError::ValueOutOfRange(err)),
1736 }
1737 }
1738}
1739
1740impl std::fmt::Display for ErrorCode {
1741 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1742 write!(f, "IC{:04}", *self as i32)
1744 }
1745}
1746
1747#[derive(
1752 PartialOrd,
1753 Ord,
1754 Clone,
1755 Copy,
1756 Debug,
1757 PartialEq,
1758 Eq,
1759 Hash,
1760 Serialize,
1761 Deserialize,
1762 JsonSchema,
1763 EnumIter,
1764)]
1765pub enum RejectCode {
1766 SysFatal = 1,
1767 SysTransient = 2,
1768 DestinationInvalid = 3,
1769 CanisterReject = 4,
1770 CanisterError = 5,
1771 SysUnknown = 6,
1772}
1773
1774impl TryFrom<u64> for RejectCode {
1775 type Error = TryFromError;
1776 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
1777 match err {
1778 1 => Ok(RejectCode::SysFatal),
1779 2 => Ok(RejectCode::SysTransient),
1780 3 => Ok(RejectCode::DestinationInvalid),
1781 4 => Ok(RejectCode::CanisterReject),
1782 5 => Ok(RejectCode::CanisterError),
1783 6 => Ok(RejectCode::SysUnknown),
1784 _ => Err(TryFromError::ValueOutOfRange(err)),
1785 }
1786 }
1787}
1788
1789#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1791pub struct RejectResponse {
1792 pub reject_code: RejectCode,
1793 pub reject_message: String,
1794 pub error_code: ErrorCode,
1795 pub certified: bool,
1796}
1797
1798impl std::fmt::Display for RejectResponse {
1799 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1800 write!(f, "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}", self.reject_code, self.reject_message, self.error_code)
1802 }
1803}
1804
1805#[derive(Debug, Serialize, Deserialize)]
1811pub enum IngressStatusResult {
1812 NotAvailable,
1813 Forbidden(String),
1814 Success(Result<Vec<u8>, RejectResponse>),
1815}
1816
1817#[cfg(windows)]
1818fn wsl_path(path: &PathBuf, desc: &str) -> String {
1819 windows_to_wsl(
1820 path.as_os_str()
1821 .to_str()
1822 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
1823 )
1824 .unwrap_or_else(|e| {
1825 panic!(
1826 "Could not convert {} path ({:?}) to WSL path: {:?}",
1827 desc, path, e
1828 )
1829 })
1830}
1831
1832#[cfg(windows)]
1833fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1834 let mut cmd = Command::new("wsl");
1835 cmd.arg(wsl_path(bin_path, "PocketIC binary"));
1836 cmd
1837}
1838
1839#[cfg(not(windows))]
1840fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1841 Command::new(bin_path)
1842}
1843
1844fn check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
1845 let mut cmd = pocket_ic_server_cmd(server_binary);
1846 cmd.arg("--version");
1847 let version = cmd.output().map_err(|e| e.to_string())?.stdout;
1848 let version_str = String::from_utf8(version)
1849 .map_err(|e| format!("Failed to parse PocketIC server version: {}.", e))?;
1850 let version_line = version_str.trim_end_matches('\n');
1851 let expected_version_line = format!("pocket-ic-server {}", EXPECTED_SERVER_VERSION);
1852 if version_line != expected_version_line {
1853 return Err(format!(
1854 "Incompatible PocketIC server version: got {}; expected {}.",
1855 version_line, expected_version_line
1856 ));
1857 }
1858 Ok(())
1859}
1860
1861async fn download_pocketic_server(
1862 server_url: String,
1863 mut out: std::fs::File,
1864) -> Result<(), String> {
1865 let binary = reqwest::get(server_url)
1866 .await
1867 .map_err(|e| format!("Failed to download PocketIC server: {}", e))?
1868 .bytes()
1869 .await
1870 .map_err(|e| format!("Failed to download PocketIC server: {}", e))?
1871 .to_vec();
1872 let mut gz = GzDecoder::new(&binary[..]);
1873 let _ = std::io::copy(&mut gz, &mut out)
1874 .map_err(|e| format!("Failed to write PocketIC server binary: {}", e));
1875 Ok(())
1876}
1877
1878#[derive(Default)]
1879pub struct StartServerParams {
1880 pub server_binary: Option<PathBuf>,
1881 pub reuse: bool,
1883}
1884
1885pub async fn start_server(params: StartServerParams) -> (Child, Url) {
1887 let default_bin_dir =
1888 std::env::temp_dir().join(format!("pocket-ic-server-{}", EXPECTED_SERVER_VERSION));
1889 let default_bin_path = default_bin_dir.join("pocket-ic");
1890 let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
1891 std::env::var_os("POCKET_IC_BIN")
1892 .unwrap_or_else(|| default_bin_path.clone().into())
1893 .into()
1894 });
1895
1896 if let Err(e) = check_pocketic_server_version(&bin_path) {
1897 bin_path = default_bin_path.clone();
1898 std::fs::create_dir_all(&default_bin_dir)
1899 .expect("Failed to create PocketIC server directory");
1900 let mut options = OpenOptions::new();
1901 options.write(true).create_new(true);
1902 #[cfg(unix)]
1903 options.mode(0o777);
1904 if let Ok(out) = options.open(&default_bin_path) {
1905 #[cfg(target_os = "macos")]
1906 let os = "darwin";
1907 #[cfg(not(target_os = "macos"))]
1908 let os = "linux";
1909 #[cfg(target_arch = "aarch64")]
1910 let arch = "arm64";
1911 #[cfg(not(target_arch = "aarch64"))]
1912 let arch = "x86_64";
1913 let server_url = format!(
1914 "https://github.com/dfinity/pocketic/releases/download/{}/pocket-ic-{}-{}.gz",
1915 EXPECTED_SERVER_VERSION, arch, os
1916 );
1917 println!("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.", e, EXPECTED_SERVER_VERSION, server_url, default_bin_path.display(), EXPECTED_SERVER_VERSION);
1918 if let Err(e) = download_pocketic_server(server_url, out).await {
1919 let _ = std::fs::remove_file(default_bin_path);
1920 panic!("{}", e);
1921 }
1922 } else {
1923 let start = std::time::Instant::now();
1925 loop {
1926 if check_pocketic_server_version(&default_bin_path).is_ok() {
1927 break;
1928 }
1929 if start.elapsed() > std::time::Duration::from_secs(60) {
1930 let _ = std::fs::remove_file(&default_bin_path);
1931 panic!("Timed out waiting for PocketIC server being available at the local path {}.", default_bin_path.display());
1932 }
1933 std::thread::sleep(std::time::Duration::from_millis(100));
1934 }
1935 }
1936 }
1937
1938 let port_file_path = if params.reuse {
1939 let test_driver_pid = std::process::id();
1942 std::env::temp_dir().join(format!("pocket_ic_{}.port", test_driver_pid))
1943 } else {
1944 NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
1945 };
1946 let mut cmd = pocket_ic_server_cmd(&bin_path);
1947 cmd.arg("--port-file");
1948 #[cfg(windows)]
1949 cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
1950 #[cfg(not(windows))]
1951 cmd.arg(port_file_path.clone());
1952 if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER") {
1953 if !mute_server.is_empty() {
1954 cmd.stdout(std::process::Stdio::null());
1955 cmd.stderr(std::process::Stdio::null());
1956 }
1957 }
1958
1959 #[cfg(unix)]
1962 {
1963 use std::os::unix::process::CommandExt;
1964 cmd.process_group(0);
1965 }
1966
1967 #[allow(clippy::zombie_processes)]
1969 let child = cmd
1970 .spawn()
1971 .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
1972
1973 loop {
1974 if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone()) {
1975 if port_string.contains("\n") {
1976 let port: u16 = port_string
1977 .trim_end()
1978 .parse()
1979 .expect("Failed to parse port to number");
1980 break (
1981 child,
1982 Url::parse(&format!("http://{}:{}/", LOCALHOST, port)).unwrap(),
1983 );
1984 }
1985 }
1986 std::thread::sleep(Duration::from_millis(20));
1987 }
1988}
1989
1990#[derive(Error, Debug)]
1991pub enum DefaultEffectiveCanisterIdError {
1992 ReqwestError(#[from] reqwest::Error),
1993 JsonError(#[from] serde_json::Error),
1994 Utf8Error(#[from] std::string::FromUtf8Error),
1995}
1996
1997impl std::fmt::Display for DefaultEffectiveCanisterIdError {
1998 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1999 match self {
2000 DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2001 write!(f, "ReqwestError({})", err)
2002 }
2003 DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({})", err),
2004 DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({})", err),
2005 }
2006 }
2007}
2008
2009pub fn get_default_effective_canister_id(
2017 pocket_ic_url: String,
2018) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2019 let runtime = Runtime::new().expect("Unable to create a runtime");
2020 runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2021 pocket_ic_url,
2022 ))
2023}
2024
2025pub fn copy_dir(
2026 src: impl AsRef<std::path::Path>,
2027 dst: impl AsRef<std::path::Path>,
2028) -> std::io::Result<()> {
2029 std::fs::create_dir_all(&dst)?;
2030 for entry in std::fs::read_dir(src)? {
2031 let entry = entry?;
2032 let ty = entry.file_type()?;
2033 if ty.is_dir() {
2034 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2035 } else {
2036 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2037 }
2038 }
2039 Ok(())
2040}
2041
2042#[cfg(test)]
2043mod test {
2044 use crate::{ErrorCode, RejectCode};
2045 use strum::IntoEnumIterator;
2046
2047 #[test]
2048 fn reject_code_round_trip() {
2049 for initial in RejectCode::iter() {
2050 let round_trip = RejectCode::try_from(initial as u64).unwrap();
2051
2052 assert_eq!(initial, round_trip);
2053 }
2054 }
2055
2056 #[test]
2057 fn error_code_round_trip() {
2058 for initial in ErrorCode::iter() {
2059 let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2060
2061 assert_eq!(initial, round_trip);
2062 }
2063 }
2064
2065 #[test]
2066 fn reject_code_matches_ic_error_code() {
2067 assert_eq!(
2068 RejectCode::iter().len(),
2069 ic_error_types::RejectCode::iter().len()
2070 );
2071 for ic_reject_code in ic_error_types::RejectCode::iter() {
2072 let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2073 assert_eq!(
2074 format!("{:?}", reject_code),
2075 format!("{:?}", ic_reject_code)
2076 );
2077 }
2078 }
2079
2080 #[test]
2081 fn error_code_matches_ic_error_code() {
2082 assert_eq!(
2083 ErrorCode::iter().len(),
2084 ic_error_types::ErrorCode::iter().len()
2085 );
2086 for ic_error_code in ic_error_types::ErrorCode::iter() {
2087 let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2088 assert_eq!(format!("{:?}", error_code), format!("{:?}", ic_error_code));
2089 }
2090 }
2091}