1#![cfg(not(target_family = "wasm"))]
2#![expect(
3 missing_docs,
4 clippy::too_many_arguments,
5 clippy::pedantic,
6 clippy::nursery,
7 reason = "initial upstream source transfer preserves public signatures and protocol names; see upstream/NOTICE.md"
8)]
9use crate::pocket_ic::{
65 common::rest::{
66 AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet,
67 HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceHttpGatewayConfig, InstanceId,
68 MockCanisterHttpResponse, MockFlexibleCanisterHttpResponse, RawEffectivePrincipal,
69 RawMessageId, RawSenderInfo, RawSubnetBlockmakers, RawTickConfigs, RawTime, SubnetId,
70 SubnetKind, SubnetSpec, Topology,
71 },
72 nonblocking::PocketIc as PocketIcAsync,
73};
74use candid::{
75 Principal, decode_args, encode_args,
76 utils::{ArgumentDecoder, ArgumentEncoder},
77};
78use flate2::read::GzDecoder;
79pub use ic_management_canister_types::{
80 CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
81 EnvironmentVariable, Snapshot,
82};
83pub use ic_transport_types::SubnetMetrics;
84use reqwest::Url;
85use schemars::JsonSchema;
86use semver::{Version, VersionReq};
87use serde::{Deserialize, Serialize};
88use slog::Level;
89#[cfg(unix)]
90use std::os::unix::fs::OpenOptionsExt;
91#[cfg(windows)]
92use std::sync::Once;
93use std::{
94 fs::OpenOptions,
95 net::{IpAddr, SocketAddr},
96 path::PathBuf,
97 process::{Child, Command},
98 sync::{Arc, mpsc::channel},
99 thread,
100 thread::JoinHandle,
101 time::{Duration, SystemTime, UNIX_EPOCH},
102};
103use strum_macros::EnumIter;
104use tempfile::{NamedTempFile, TempDir};
105use tokio::runtime::Runtime;
106use tracing::{instrument, warn};
107
108mod error;
109mod http;
110pub use error::{ClientError, Operation, RequestContext};
111pub mod common;
112pub mod nonblocking;
113pub mod snapshot;
114
115const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
116
117const MIN_SERVER_VERSION: &str = "16.0.0";
118const MAX_SERVER_VERSION: &str = "17";
119
120pub const LATEST_SERVER_VERSION: &str = "16.1.0";
122
123const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
125
126const LOCALHOST: &str = "127.0.0.1";
127
128enum PocketIcStateKind {
129 StateDir(PathBuf),
131 TempDir(TempDir),
137}
138
139pub struct PocketIcState {
140 state: PocketIcStateKind,
141}
142
143impl PocketIcState {
144 #[expect(
145 clippy::new_without_default,
146 reason = "preserve the imported PocketIC contract without an unrelated API change"
147 )]
148 pub fn new() -> Self {
149 let temp_dir = TempDir::new().unwrap();
150 Self {
151 state: PocketIcStateKind::TempDir(temp_dir),
152 }
153 }
154
155 pub fn new_from_path(state_dir: PathBuf) -> Self {
156 Self {
157 state: PocketIcStateKind::StateDir(state_dir),
158 }
159 }
160
161 pub fn into_path(self) -> PathBuf {
162 match self.state {
163 PocketIcStateKind::StateDir(state_dir) => state_dir,
164 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
165 }
166 }
167
168 pub(crate) fn state_dir(&self) -> PathBuf {
169 match &self.state {
170 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
171 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
172 }
173 }
174}
175
176pub struct PocketIcBuilder {
177 config: Option<ExtendedSubnetConfigSet>,
178 http_gateway_config: Option<InstanceHttpGatewayConfig>,
179 server_binary: Option<PathBuf>,
180 server_url: Option<Url>,
181 max_request_time_ms: Option<u64>,
182 read_only_state_dir: Option<PathBuf>,
183 state_dir: Option<PocketIcState>,
184 icp_config: IcpConfig,
185 log_level: Option<Level>,
186 bitcoind_addr: Option<Vec<SocketAddr>>,
187 dogecoind_addr: Option<Vec<SocketAddr>>,
188 icp_features: IcpFeatures,
189 initial_time: Option<InitialTime>,
190 mainnet_nns_subnet_id: Option<bool>,
191 disable_ingress_validation: Option<bool>,
192}
193
194#[expect(
195 clippy::new_without_default,
196 reason = "preserve the imported PocketIC contract without an unrelated API change"
197)]
198impl PocketIcBuilder {
199 pub fn new() -> Self {
200 Self {
201 config: None,
202 http_gateway_config: None,
203 server_binary: None,
204 server_url: None,
205 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
206 read_only_state_dir: None,
207 state_dir: None,
208 icp_config: IcpConfig::default(),
209 log_level: None,
210 bitcoind_addr: None,
211 dogecoind_addr: None,
212 icp_features: IcpFeatures::default(),
213 initial_time: None,
214 mainnet_nns_subnet_id: None,
215 disable_ingress_validation: None,
216 }
217 }
218
219 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
220 let mut builder = Self::new();
221 builder.config = Some(config.into());
222 builder
223 }
224
225 pub fn build(self) -> Result<PocketIc, ClientError> {
226 PocketIc::from_components(
227 self.config.unwrap_or_default(),
228 self.server_url,
229 self.server_binary,
230 self.max_request_time_ms,
231 self.read_only_state_dir,
232 self.state_dir,
233 self.icp_config,
234 self.log_level,
235 self.bitcoind_addr,
236 self.dogecoind_addr,
237 self.icp_features,
238 self.initial_time,
239 self.http_gateway_config,
240 self.mainnet_nns_subnet_id,
241 self.disable_ingress_validation,
242 )
243 }
244
245 pub async fn build_async(self) -> Result<PocketIcAsync, ClientError> {
246 PocketIcAsync::from_components(
247 self.config.unwrap_or_default(),
248 self.server_url,
249 self.server_binary,
250 self.max_request_time_ms,
251 self.read_only_state_dir,
252 self.state_dir,
253 self.icp_config,
254 self.log_level,
255 self.bitcoind_addr,
256 self.dogecoind_addr,
257 self.icp_features,
258 self.initial_time,
259 self.http_gateway_config,
260 self.mainnet_nns_subnet_id,
261 self.disable_ingress_validation,
262 )
263 .await
264 }
265
266 pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
268 self.server_binary = Some(server_binary);
269 self
270 }
271
272 pub fn with_server_url(mut self, server_url: Url) -> Self {
274 self.server_url = Some(server_url);
275 self
276 }
277
278 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
279 self.max_request_time_ms = max_request_time_ms;
280 self
281 }
282
283 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
284 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
285 self
286 }
287
288 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
289 self.state_dir = Some(state_dir);
290 self
291 }
292
293 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
294 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
295 self
296 }
297
298 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
299 self.icp_config = icp_config;
300 self
301 }
302
303 pub fn with_log_level(mut self, log_level: Level) -> Self {
304 self.log_level = Some(log_level);
305 self
306 }
307
308 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
309 self.with_bitcoind_addrs(vec![bitcoind_addr])
310 }
311
312 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
313 Self {
314 bitcoind_addr: Some(bitcoind_addrs),
315 ..self
316 }
317 }
318
319 pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
320 Self {
321 dogecoind_addr: Some(dogecoind_addrs),
322 ..self
323 }
324 }
325
326 pub fn with_nns_subnet(mut self) -> Self {
328 let mut config = self.config.unwrap_or_default();
329 config.nns = Some(config.nns.unwrap_or_default());
330 self.config = Some(config);
331 self
332 }
333
334 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
351 self.with_subnet_state(SubnetKind::NNS, path_to_state)
352 }
353
354 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
371 let mut config = self.config.unwrap_or_default();
372 #[cfg(not(windows))]
373 let state_dir = path_to_state;
374 #[cfg(windows)]
375 let state_dir = wsl_path(&path_to_state, "subnet state").into();
376 let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
377 match subnet_kind {
378 SubnetKind::NNS => config.nns = Some(subnet_spec),
379 SubnetKind::SNS => config.sns = Some(subnet_spec),
380 SubnetKind::II => config.ii = Some(subnet_spec),
381 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
382 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
383 SubnetKind::TestThresholdKeys => config.test_threshold_keys = Some(subnet_spec),
384 SubnetKind::Application => config.application.push(subnet_spec),
385 SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
386 SubnetKind::System => config.system.push(subnet_spec),
387 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
388 };
389 self.config = Some(config);
390 self
391 }
392
393 pub fn with_sns_subnet(mut self) -> Self {
395 let mut config = self.config.unwrap_or_default();
396 config.sns = Some(config.sns.unwrap_or_default());
397 self.config = Some(config);
398 self
399 }
400
401 pub fn with_ii_subnet(mut self) -> Self {
403 let mut config = self.config.unwrap_or_default();
404 config.ii = Some(config.ii.unwrap_or_default());
405 self.config = Some(config);
406 self
407 }
408
409 pub fn with_fiduciary_subnet(mut self) -> Self {
411 let mut config = self.config.unwrap_or_default();
412 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
413 self.config = Some(config);
414 self
415 }
416
417 pub fn with_bitcoin_subnet(mut self) -> Self {
419 let mut config = self.config.unwrap_or_default();
420 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
421 self.config = Some(config);
422 self
423 }
424
425 pub fn with_test_threshold_keys_subnet(mut self) -> Self {
427 let mut config = self.config.unwrap_or_default();
428 config.test_threshold_keys = Some(config.test_threshold_keys.unwrap_or_default());
429 self.config = Some(config);
430 self
431 }
432
433 pub fn with_system_subnet(mut self) -> Self {
435 let mut config = self.config.unwrap_or_default();
436 config.system.push(SubnetSpec::default());
437 self.config = Some(config);
438 self
439 }
440
441 pub fn with_application_subnet(mut self) -> Self {
443 let mut config = self.config.unwrap_or_default();
444 config.application.push(SubnetSpec::default());
445 self.config = Some(config);
446 self
447 }
448
449 pub fn with_verified_application_subnet(mut self) -> Self {
451 let mut config = self.config.unwrap_or_default();
452 config.verified_application.push(SubnetSpec::default());
453 self.config = Some(config);
454 self
455 }
456
457 pub fn with_benchmarking_application_subnet(mut self) -> Self {
459 let mut config = self.config.unwrap_or_default();
460 config
461 .application
462 .push(SubnetSpec::default().with_benchmarking_instruction_config());
463 self.config = Some(config);
464 self
465 }
466
467 pub fn with_benchmarking_system_subnet(mut self) -> Self {
469 let mut config = self.config.unwrap_or_default();
470 config
471 .system
472 .push(SubnetSpec::default().with_benchmarking_instruction_config());
473 self.config = Some(config);
474 self
475 }
476
477 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
482 self.icp_features = icp_features;
483 self
484 }
485
486 #[deprecated(note = "Use `with_initial_time` instead")]
490 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
491 self.initial_time = Some(InitialTime::Timestamp(RawTime {
492 nanos_since_epoch: initial_timestamp_nanos,
493 }));
494 self
495 }
496
497 pub fn with_initial_time(mut self, initial_time: Time) -> Self {
501 self.initial_time = Some(InitialTime::Timestamp(RawTime {
502 nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
503 }));
504 self
505 }
506
507 pub fn with_auto_progress(mut self) -> Self {
513 let config = AutoProgressConfig {
514 artificial_delay_ms: None,
515 };
516 self.initial_time = Some(InitialTime::AutoProgress(config));
517 self
518 }
519
520 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
521 self.http_gateway_config = Some(http_gateway_config);
522 self
523 }
524
525 pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
526 self.mainnet_nns_subnet_id = Some(true);
527 self
528 }
529
530 pub fn disable_ingress_validation(mut self) -> Self {
531 self.disable_ingress_validation = Some(true);
532 self
533 }
534}
535
536#[derive(Copy, Clone, PartialEq, PartialOrd)]
539pub struct Time(Duration);
540
541impl Time {
542 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
544 self.0.as_nanos().try_into().unwrap()
545 }
546
547 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
548 Time(Duration::from_nanos(nanos))
549 }
550}
551
552impl std::fmt::Debug for Time {
553 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
555 write!(f, "{nanos_since_unix_epoch}")
556 }
557}
558
559impl std::ops::Add<Duration> for Time {
560 type Output = Time;
561 fn add(self, dur: Duration) -> Time {
562 Time(self.0 + dur)
563 }
564}
565
566impl From<SystemTime> for Time {
567 fn from(time: SystemTime) -> Self {
568 Self::from_nanos_since_unix_epoch(
569 time.duration_since(UNIX_EPOCH)
570 .unwrap()
571 .as_nanos()
572 .try_into()
573 .unwrap(),
574 )
575 }
576}
577
578impl TryFrom<Time> for SystemTime {
579 type Error = String;
580
581 fn try_from(time: Time) -> Result<SystemTime, String> {
582 let nanos = time.as_nanos_since_unix_epoch();
583 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
584 let roundtrip: Time = system_time.into();
585 if roundtrip.as_nanos_since_unix_epoch() == nanos {
586 Ok(system_time)
587 } else {
588 Err(format!(
589 "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
590 ))
591 }
592 }
593}
594
595#[derive(Clone, Debug)]
597pub enum CreateCanisterPlacement {
598 SubnetId(SubnetId),
600 CanisterId(CanisterId),
602}
603
604#[derive(Clone, Debug, Default)]
606pub struct CreateCanisterParams {
607 pub cycles: Option<u128>,
609 pub settings: Option<CanisterSettings>,
611 pub placement: Option<CreateCanisterPlacement>,
613}
614
615pub struct PocketIc {
617 pocket_ic: PocketIcAsync,
618 runtime: Arc<tokio::runtime::Runtime>,
619 thread: Option<JoinHandle<()>>,
620}
621
622impl PocketIc {
623 pub fn new() -> Result<Self, ClientError> {
626 PocketIcBuilder::new().with_application_subnet().build()
627 }
628
629 pub fn new_from_existing_instance(
634 server_url: Url,
635 instance_id: InstanceId,
636 max_request_time_ms: Option<u64>,
637 ) -> Self {
638 let (tx, rx) = channel();
639 let thread = thread::spawn(move || {
640 let rt = tokio::runtime::Builder::new_current_thread()
641 .enable_all()
642 .build()
643 .unwrap();
644 tx.send(rt).unwrap();
645 });
646 let runtime = rx.recv().unwrap();
647
648 let pocket_ic =
649 PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
650
651 Self {
652 pocket_ic,
653 runtime: Arc::new(runtime),
654 thread: Some(thread),
655 }
656 }
657
658 #[expect(
659 clippy::too_many_arguments,
660 reason = "retained upstream client method signature"
661 )]
662 pub(crate) fn from_components(
663 subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
664 server_url: Option<Url>,
665 server_binary: Option<PathBuf>,
666 max_request_time_ms: Option<u64>,
667 read_only_state_dir: Option<PathBuf>,
668 state_dir: Option<PocketIcState>,
669 icp_config: IcpConfig,
670 log_level: Option<Level>,
671 bitcoind_addr: Option<Vec<SocketAddr>>,
672 dogecoind_addr: Option<Vec<SocketAddr>>,
673 icp_features: IcpFeatures,
674 initial_time: Option<InitialTime>,
675 http_gateway_config: Option<InstanceHttpGatewayConfig>,
676 mainnet_nns_subnet_id: Option<bool>,
677 disable_ingress_validation: Option<bool>,
678 ) -> Result<Self, ClientError> {
679 let (tx, rx) = channel();
680 let thread = thread::spawn(move || {
681 let rt = tokio::runtime::Builder::new_current_thread()
682 .enable_all()
683 .build()
684 .unwrap();
685 tx.send(rt).unwrap();
686 });
687 let runtime = rx.recv().unwrap();
688
689 let pocket_ic = runtime.block_on(async {
690 PocketIcAsync::from_components(
691 subnet_config_set,
692 server_url,
693 server_binary,
694 max_request_time_ms,
695 read_only_state_dir,
696 state_dir,
697 icp_config,
698 log_level,
699 bitcoind_addr,
700 dogecoind_addr,
701 icp_features,
702 initial_time,
703 http_gateway_config,
704 mainnet_nns_subnet_id,
705 disable_ingress_validation,
706 )
707 .await
708 })?;
709
710 Ok(Self {
711 pocket_ic,
712 runtime: Arc::new(runtime),
713 thread: Some(thread),
714 })
715 }
716
717 pub fn drop_and_take_state(&mut self) -> Result<Option<PocketIcState>, ClientError> {
718 self.runtime.block_on(self.pocket_ic.do_drop())?;
719 Ok(self.pocket_ic.take_state_internal())
720 }
721
722 pub fn get_server_url(&self) -> Url {
724 self.pocket_ic.get_server_url()
725 }
726
727 pub fn instance_id(&self) -> InstanceId {
729 self.pocket_ic.instance_id
730 }
731
732 pub fn topology(&self) -> Result<Topology, ClientError> {
734 let runtime = self.runtime.clone();
735 runtime.block_on(async { self.pocket_ic.topology().await })
736 }
737
738 #[instrument(ret, skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
740 pub fn upload_blob(
741 &self,
742 blob: Vec<u8>,
743 compression: BlobCompression,
744 ) -> Result<BlobId, ClientError> {
745 let runtime = self.runtime.clone();
746 runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
747 }
748
749 #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
752 pub fn set_stable_memory(
753 &self,
754 canister_id: CanisterId,
755 data: Vec<u8>,
756 compression: BlobCompression,
757 ) -> Result<(), ClientError> {
758 let runtime = self.runtime.clone();
759 runtime.block_on(async {
760 self.pocket_ic
761 .set_stable_memory(canister_id, data, compression)
762 .await
763 })
764 }
765
766 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
768 pub fn get_stable_memory(&self, canister_id: CanisterId) -> Result<Vec<u8>, ClientError> {
769 let runtime = self.runtime.clone();
770 runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
771 }
772
773 #[instrument(ret)]
775 pub fn list_instances() -> Result<Vec<String>, ClientError> {
776 let runtime = tokio::runtime::Builder::new_current_thread()
777 .enable_all()
778 .build()?;
779 runtime.block_on(PocketIcAsync::list_instances())
780 }
781
782 #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
784 pub fn verify_canister_signature(
785 &self,
786 msg: Vec<u8>,
787 sig: Vec<u8>,
788 pubkey: Vec<u8>,
789 root_pubkey: Vec<u8>,
790 ) -> Result<(), ClientError> {
791 let runtime = self.runtime.clone();
792 runtime.block_on(async {
793 self.pocket_ic
794 .verify_canister_signature(msg, sig, pubkey, root_pubkey)
795 .await
796 })
797 }
798
799 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
804 pub fn tick(&self) -> Result<(), ClientError> {
805 let runtime = self.runtime.clone();
806 runtime.block_on(async { self.pocket_ic.tick().await })
807 }
808
809 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
812 pub fn tick_with_configs(&self, configs: TickConfigs) -> Result<(), ClientError> {
813 let runtime = self.runtime.clone();
814 runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
815 }
816
817 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
825 pub fn auto_progress(&self) -> Result<Url, ClientError> {
826 let runtime = self.runtime.clone();
827 runtime.block_on(async { self.pocket_ic.auto_progress().await })
828 }
829
830 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
832 pub fn auto_progress_enabled(&self) -> Result<bool, ClientError> {
833 let runtime = self.runtime.clone();
834 runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
835 }
836
837 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
839 pub fn stop_progress(&self) -> Result<(), ClientError> {
840 let runtime = self.runtime.clone();
841 runtime.block_on(async { self.pocket_ic.stop_progress().await })
842 }
843
844 pub fn url(&self) -> Option<Url> {
848 self.pocket_ic.url()
849 }
850
851 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
862 pub fn make_live(&mut self, listen_at: Option<u16>) -> Result<Url, ClientError> {
863 let runtime = self.runtime.clone();
864 runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
865 }
866
867 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
880 pub fn make_live_with_params(
881 &mut self,
882 ip_addr: Option<IpAddr>,
883 listen_at: Option<u16>,
884 domains: Option<Vec<String>>,
885 https_config: Option<HttpsConfig>,
886 ) -> Result<Url, ClientError> {
887 let runtime = self.runtime.clone();
888 runtime.block_on(async {
889 self.pocket_ic
890 .make_live_with_params(ip_addr, listen_at, domains, https_config)
891 .await
892 })
893 }
894
895 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
898 pub fn stop_live(&mut self) -> Result<(), ClientError> {
899 let runtime = self.runtime.clone();
900 runtime.block_on(async { self.pocket_ic.stop_live().await })
901 }
902
903 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
905 pub fn root_key(&self) -> Result<Option<Vec<u8>>, ClientError> {
906 let runtime = self.runtime.clone();
907 runtime.block_on(async { self.pocket_ic.root_key().await })
908 }
909
910 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
912 pub fn get_time(&self) -> Result<Time, ClientError> {
913 let runtime = self.runtime.clone();
914 runtime.block_on(async { self.pocket_ic.get_time().await })
915 }
916
917 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
919 pub fn set_time(&self, time: Time) -> Result<(), ClientError> {
920 let runtime = self.runtime.clone();
921 runtime.block_on(async { self.pocket_ic.set_time(time).await })
922 }
923
924 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
926 pub fn set_certified_time(&self, time: Time) -> Result<(), ClientError> {
927 let runtime = self.runtime.clone();
928 runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
929 }
930
931 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
933 pub fn advance_time(&self, duration: Duration) -> Result<(), ClientError> {
934 let runtime = self.runtime.clone();
935 runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
936 }
937
938 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
941 pub fn get_controllers(&self, canister_id: CanisterId) -> Result<Vec<Principal>, ClientError> {
942 let runtime = self.runtime.clone();
943 runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
944 }
945
946 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
948 pub fn cycle_balance(&self, canister_id: CanisterId) -> Result<u128, ClientError> {
949 let runtime = self.runtime.clone();
950 runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
951 }
952
953 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
955 pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> Result<u128, ClientError> {
956 let runtime = self.runtime.clone();
957 runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
958 }
959
960 pub fn submit_call(
962 &self,
963 canister_id: CanisterId,
964 sender: Principal,
965 method: &str,
966 payload: Vec<u8>,
967 ) -> Result<RawMessageId, ClientError> {
968 let runtime = self.runtime.clone();
969 runtime.block_on(async {
970 self.pocket_ic
971 .submit_call(canister_id, sender, method, payload)
972 .await
973 })
974 }
975
976 pub fn submit_call_with_effective_principal(
978 &self,
979 canister_id: CanisterId,
980 effective_principal: RawEffectivePrincipal,
981 sender: Principal,
982 method: &str,
983 payload: Vec<u8>,
984 ) -> Result<RawMessageId, ClientError> {
985 let runtime = self.runtime.clone();
986 runtime.block_on(async {
987 self.pocket_ic
988 .submit_call_with_effective_principal(
989 canister_id,
990 effective_principal,
991 sender,
992 method,
993 payload,
994 )
995 .await
996 })
997 }
998
999 pub fn submit_call_with_effective_principal_and_sender_info(
1001 &self,
1002 canister_id: CanisterId,
1003 effective_principal: RawEffectivePrincipal,
1004 sender: Principal,
1005 method: &str,
1006 payload: Vec<u8>,
1007 sender_info: RawSenderInfo,
1008 ) -> Result<RawMessageId, ClientError> {
1009 let runtime = self.runtime.clone();
1010 runtime.block_on(async {
1011 self.pocket_ic
1012 .submit_call_with_effective_principal_and_sender_info(
1013 canister_id,
1014 effective_principal,
1015 sender,
1016 method,
1017 payload,
1018 sender_info,
1019 )
1020 .await
1021 })
1022 }
1023
1024 pub fn submit_call_with_sender_info(
1026 &self,
1027 canister_id: CanisterId,
1028 sender: Principal,
1029 method: &str,
1030 payload: Vec<u8>,
1031 sender_info: RawSenderInfo,
1032 ) -> Result<RawMessageId, ClientError> {
1033 let runtime = self.runtime.clone();
1034 runtime.block_on(async {
1035 self.pocket_ic
1036 .submit_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1037 .await
1038 })
1039 }
1040
1041 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
1043 let runtime = self.runtime.clone();
1044 runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
1045 }
1046
1047 pub fn ingress_status(
1051 &self,
1052 message_id: RawMessageId,
1053 ) -> Result<Option<Result<Vec<u8>, RejectResponse>>, ClientError> {
1054 let runtime = self.runtime.clone();
1055 runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
1056 }
1057
1058 pub fn ingress_status_as(
1063 &self,
1064 message_id: RawMessageId,
1065 caller: Principal,
1066 ) -> Result<IngressStatusResult, ClientError> {
1067 let runtime = self.runtime.clone();
1068 runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
1069 }
1070
1071 pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, ClientError> {
1075 let runtime = self.runtime.clone();
1076 runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
1077 }
1078
1079 #[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()))]
1081 pub fn update_call(
1082 &self,
1083 canister_id: CanisterId,
1084 sender: Principal,
1085 method: &str,
1086 payload: Vec<u8>,
1087 ) -> Result<Vec<u8>, ClientError> {
1088 let runtime = self.runtime.clone();
1089 runtime.block_on(async {
1090 self.pocket_ic
1091 .update_call(canister_id, sender, method, payload)
1092 .await
1093 })
1094 }
1095
1096 #[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()))]
1098 pub fn query_call(
1099 &self,
1100 canister_id: CanisterId,
1101 sender: Principal,
1102 method: &str,
1103 payload: Vec<u8>,
1104 ) -> Result<Vec<u8>, ClientError> {
1105 let runtime = self.runtime.clone();
1106 runtime.block_on(async {
1107 self.pocket_ic
1108 .query_call(canister_id, sender, method, payload)
1109 .await
1110 })
1111 }
1112
1113 pub fn fetch_canister_logs(
1115 &self,
1116 canister_id: CanisterId,
1117 sender: Principal,
1118 ) -> Result<Vec<CanisterLogRecord>, ClientError> {
1119 let runtime = self.runtime.clone();
1120 runtime.block_on(async {
1121 self.pocket_ic
1122 .fetch_canister_logs(canister_id, sender)
1123 .await
1124 })
1125 }
1126
1127 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1129 pub fn canister_status(
1130 &self,
1131 canister_id: CanisterId,
1132 sender: Option<Principal>,
1133 ) -> Result<CanisterStatusResult, ClientError> {
1134 let runtime = self.runtime.clone();
1135 runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1136 }
1137
1138 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1141 pub fn create_canister(&self) -> Result<CanisterId, ClientError> {
1142 let runtime = self.runtime.clone();
1143 runtime.block_on(async { self.pocket_ic.create_canister().await })
1144 }
1145
1146 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1149 pub fn create_canister_with_settings(
1150 &self,
1151 sender: Option<Principal>,
1152 settings: Option<CanisterSettings>,
1153 ) -> Result<CanisterId, ClientError> {
1154 let runtime = self.runtime.clone();
1155 runtime.block_on(async {
1156 self.pocket_ic
1157 .create_canister_with_settings(sender, settings)
1158 .await
1159 })
1160 }
1161
1162 #[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()))]
1171 pub fn create_canister_with_id(
1172 &self,
1173 sender: Option<Principal>,
1174 settings: Option<CanisterSettings>,
1175 canister_id: CanisterId,
1176 ) -> Result<CanisterId, ClientError> {
1177 let runtime = self.runtime.clone();
1178 runtime.block_on(async {
1179 self.pocket_ic
1180 .create_canister_with_id(sender, settings, canister_id)
1181 .await
1182 })
1183 }
1184
1185 #[instrument(ret, 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()))]
1188 pub fn create_canister_on_subnet(
1189 &self,
1190 sender: Option<Principal>,
1191 settings: Option<CanisterSettings>,
1192 subnet_id: SubnetId,
1193 ) -> Result<CanisterId, ClientError> {
1194 let runtime = self.runtime.clone();
1195 runtime.block_on(async {
1196 self.pocket_ic
1197 .create_canister_on_subnet(sender, settings, subnet_id)
1198 .await
1199 })
1200 }
1201
1202 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1207 pub fn create_canister_with_params(
1208 &self,
1209 sender: Option<Principal>,
1210 params: CreateCanisterParams,
1211 ) -> Result<CanisterId, ClientError> {
1212 let runtime = self.runtime.clone();
1213 runtime.block_on(async {
1214 self.pocket_ic
1215 .create_canister_with_params(sender, params)
1216 .await
1217 })
1218 }
1219
1220 #[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()))]
1223 pub fn upload_chunk(
1224 &self,
1225 canister_id: CanisterId,
1226 sender: Option<Principal>,
1227 chunk: Vec<u8>,
1228 ) -> Result<Vec<u8>, ClientError> {
1229 let runtime = self.runtime.clone();
1230 runtime.block_on(async {
1231 self.pocket_ic
1232 .upload_chunk(canister_id, sender, chunk)
1233 .await
1234 })
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 stored_chunks(
1240 &self,
1241 canister_id: CanisterId,
1242 sender: Option<Principal>,
1243 ) -> Result<Vec<Vec<u8>>, ClientError> {
1244 let runtime = self.runtime.clone();
1245 runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1246 }
1247
1248 #[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()))]
1250 pub fn clear_chunk_store(
1251 &self,
1252 canister_id: CanisterId,
1253 sender: Option<Principal>,
1254 ) -> Result<(), ClientError> {
1255 let runtime = self.runtime.clone();
1256 runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1257 }
1258
1259 #[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()))]
1261 pub fn install_chunked_canister(
1262 &self,
1263 canister_id: CanisterId,
1264 sender: Option<Principal>,
1265 mode: CanisterInstallMode,
1266 store_canister_id: CanisterId,
1267 chunk_hashes_list: Vec<Vec<u8>>,
1268 wasm_module_hash: Vec<u8>,
1269 arg: Vec<u8>,
1270 ) -> Result<(), ClientError> {
1271 let runtime = self.runtime.clone();
1272 runtime.block_on(async {
1273 self.pocket_ic
1274 .install_chunked_canister(
1275 canister_id,
1276 sender,
1277 mode,
1278 store_canister_id,
1279 chunk_hashes_list,
1280 wasm_module_hash,
1281 arg,
1282 )
1283 .await
1284 })
1285 }
1286
1287 #[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()))]
1289 pub fn install_canister(
1290 &self,
1291 canister_id: CanisterId,
1292 wasm_module: Vec<u8>,
1293 arg: Vec<u8>,
1294 sender: Option<Principal>,
1295 ) -> Result<(), ClientError> {
1296 let runtime = self.runtime.clone();
1297 runtime.block_on(async {
1298 self.pocket_ic
1299 .install_canister(canister_id, wasm_module, arg, sender)
1300 .await
1301 })
1302 }
1303
1304 #[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()))]
1306 pub fn upgrade_canister(
1307 &self,
1308 canister_id: CanisterId,
1309 wasm_module: Vec<u8>,
1310 arg: Vec<u8>,
1311 sender: Option<Principal>,
1312 ) -> Result<(), ClientError> {
1313 let runtime = self.runtime.clone();
1314 runtime.block_on(async {
1315 self.pocket_ic
1316 .upgrade_canister(canister_id, wasm_module, arg, sender)
1317 .await
1318 })
1319 }
1320
1321 #[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()))]
1323 pub fn upgrade_eop_canister(
1324 &self,
1325 canister_id: CanisterId,
1326 wasm_module: Vec<u8>,
1327 arg: Vec<u8>,
1328 sender: Option<Principal>,
1329 ) -> Result<(), ClientError> {
1330 let runtime = self.runtime.clone();
1331 runtime.block_on(async {
1332 self.pocket_ic
1333 .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1334 .await
1335 })
1336 }
1337
1338 #[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()))]
1340 pub fn reinstall_canister(
1341 &self,
1342 canister_id: CanisterId,
1343 wasm_module: Vec<u8>,
1344 arg: Vec<u8>,
1345 sender: Option<Principal>,
1346 ) -> Result<(), ClientError> {
1347 let runtime = self.runtime.clone();
1348 runtime.block_on(async {
1349 self.pocket_ic
1350 .reinstall_canister(canister_id, wasm_module, arg, sender)
1351 .await
1352 })
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 uninstall_canister(
1358 &self,
1359 canister_id: CanisterId,
1360 sender: Option<Principal>,
1361 ) -> Result<(), ClientError> {
1362 let runtime = self.runtime.clone();
1363 runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1364 }
1365
1366 #[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()))]
1368 pub fn take_canister_snapshot(
1369 &self,
1370 canister_id: CanisterId,
1371 sender: Option<Principal>,
1372 replace_snapshot: Option<Vec<u8>>,
1373 ) -> Result<Snapshot, ClientError> {
1374 let runtime = self.runtime.clone();
1375 runtime.block_on(async {
1376 self.pocket_ic
1377 .take_canister_snapshot(canister_id, sender, replace_snapshot)
1378 .await
1379 })
1380 }
1381
1382 #[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()))]
1384 pub fn load_canister_snapshot(
1385 &self,
1386 canister_id: CanisterId,
1387 sender: Option<Principal>,
1388 snapshot_id: Vec<u8>,
1389 ) -> Result<(), ClientError> {
1390 let runtime = self.runtime.clone();
1391 runtime.block_on(async {
1392 self.pocket_ic
1393 .load_canister_snapshot(canister_id, sender, snapshot_id)
1394 .await
1395 })
1396 }
1397
1398 #[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()))]
1400 pub fn list_canister_snapshots(
1401 &self,
1402 canister_id: CanisterId,
1403 sender: Option<Principal>,
1404 ) -> Result<Vec<Snapshot>, ClientError> {
1405 let runtime = self.runtime.clone();
1406 runtime.block_on(async {
1407 self.pocket_ic
1408 .list_canister_snapshots(canister_id, sender)
1409 .await
1410 })
1411 }
1412
1413 #[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()))]
1415 pub fn delete_canister_snapshot(
1416 &self,
1417 canister_id: CanisterId,
1418 sender: Option<Principal>,
1419 snapshot_id: Vec<u8>,
1420 ) -> Result<(), ClientError> {
1421 let runtime = self.runtime.clone();
1422 runtime.block_on(async {
1423 self.pocket_ic
1424 .delete_canister_snapshot(canister_id, sender, snapshot_id)
1425 .await
1426 })
1427 }
1428
1429 #[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()))]
1431 pub fn update_canister_settings(
1432 &self,
1433 canister_id: CanisterId,
1434 sender: Option<Principal>,
1435 settings: CanisterSettings,
1436 ) -> Result<(), ClientError> {
1437 let runtime = self.runtime.clone();
1438 runtime.block_on(async {
1439 self.pocket_ic
1440 .update_canister_settings(canister_id, sender, settings)
1441 .await
1442 })
1443 }
1444
1445 #[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()))]
1447 pub fn set_controllers(
1448 &self,
1449 canister_id: CanisterId,
1450 sender: Option<Principal>,
1451 new_controllers: Vec<Principal>,
1452 ) -> Result<(), ClientError> {
1453 let runtime = self.runtime.clone();
1454 runtime.block_on(async {
1455 self.pocket_ic
1456 .set_controllers(canister_id, sender, new_controllers)
1457 .await
1458 })
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 start_canister(
1464 &self,
1465 canister_id: CanisterId,
1466 sender: Option<Principal>,
1467 ) -> Result<(), ClientError> {
1468 let runtime = self.runtime.clone();
1469 runtime.block_on(async { self.pocket_ic.start_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 stop_canister(
1475 &self,
1476 canister_id: CanisterId,
1477 sender: Option<Principal>,
1478 ) -> Result<(), ClientError> {
1479 let runtime = self.runtime.clone();
1480 runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1481 }
1482
1483 #[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()))]
1485 pub fn delete_canister(
1486 &self,
1487 canister_id: CanisterId,
1488 sender: Option<Principal>,
1489 ) -> Result<(), ClientError> {
1490 let runtime = self.runtime.clone();
1491 runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1492 }
1493
1494 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1496 pub fn canister_exists(&self, canister_id: CanisterId) -> Result<bool, ClientError> {
1497 let runtime = self.runtime.clone();
1498 runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1499 }
1500
1501 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1503 pub fn delete_subnet(&self, subnet_id: SubnetId) -> Result<(), ClientError> {
1504 let runtime = self.runtime.clone();
1505 runtime.block_on(async { self.pocket_ic.delete_subnet(subnet_id).await })
1506 }
1507
1508 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1510 pub fn get_subnet(&self, canister_id: CanisterId) -> Result<Option<SubnetId>, ClientError> {
1511 let runtime = self.runtime.clone();
1512 runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1513 }
1514
1515 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1517 pub fn get_subnet_metrics(
1518 &self,
1519 subnet_id: Principal,
1520 ) -> Result<Option<SubnetMetrics>, ClientError> {
1521 let runtime = self.runtime.clone();
1522 runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1523 }
1524
1525 pub fn update_call_with_effective_principal(
1526 &self,
1527 canister_id: CanisterId,
1528 effective_principal: RawEffectivePrincipal,
1529 sender: Principal,
1530 method: &str,
1531 payload: Vec<u8>,
1532 ) -> Result<Vec<u8>, ClientError> {
1533 let runtime = self.runtime.clone();
1534 runtime.block_on(async {
1535 self.pocket_ic
1536 .update_call_with_effective_principal(
1537 canister_id,
1538 effective_principal,
1539 sender,
1540 method,
1541 payload,
1542 )
1543 .await
1544 })
1545 }
1546
1547 pub fn update_call_with_effective_principal_and_sender_info(
1549 &self,
1550 canister_id: CanisterId,
1551 effective_principal: RawEffectivePrincipal,
1552 sender: Principal,
1553 method: &str,
1554 payload: Vec<u8>,
1555 sender_info: RawSenderInfo,
1556 ) -> Result<Vec<u8>, ClientError> {
1557 let runtime = self.runtime.clone();
1558 runtime.block_on(async {
1559 self.pocket_ic
1560 .update_call_with_effective_principal_and_sender_info(
1561 canister_id,
1562 effective_principal,
1563 sender,
1564 method,
1565 payload,
1566 sender_info,
1567 )
1568 .await
1569 })
1570 }
1571
1572 pub fn update_call_with_sender_info(
1574 &self,
1575 canister_id: CanisterId,
1576 sender: Principal,
1577 method: &str,
1578 payload: Vec<u8>,
1579 sender_info: RawSenderInfo,
1580 ) -> Result<Vec<u8>, ClientError> {
1581 let runtime = self.runtime.clone();
1582 runtime.block_on(async {
1583 self.pocket_ic
1584 .update_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1585 .await
1586 })
1587 }
1588
1589 #[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()))]
1593 pub fn query_call_with_effective_principal(
1594 &self,
1595 canister_id: CanisterId,
1596 effective_principal: RawEffectivePrincipal,
1597 sender: Principal,
1598 method: &str,
1599 payload: Vec<u8>,
1600 ) -> Result<Vec<u8>, ClientError> {
1601 let runtime = self.runtime.clone();
1602 runtime.block_on(async {
1603 self.pocket_ic
1604 .query_call_with_effective_principal(
1605 canister_id,
1606 effective_principal,
1607 sender,
1608 method,
1609 payload,
1610 )
1611 .await
1612 })
1613 }
1614
1615 pub fn query_call_with_effective_principal_and_sender_info(
1617 &self,
1618 canister_id: CanisterId,
1619 effective_principal: RawEffectivePrincipal,
1620 sender: Principal,
1621 method: &str,
1622 payload: Vec<u8>,
1623 sender_info: RawSenderInfo,
1624 ) -> Result<Vec<u8>, ClientError> {
1625 let runtime = self.runtime.clone();
1626 runtime.block_on(async {
1627 self.pocket_ic
1628 .query_call_with_effective_principal_and_sender_info(
1629 canister_id,
1630 effective_principal,
1631 sender,
1632 method,
1633 payload,
1634 sender_info,
1635 )
1636 .await
1637 })
1638 }
1639
1640 pub fn query_call_with_sender_info(
1642 &self,
1643 canister_id: CanisterId,
1644 sender: Principal,
1645 method: &str,
1646 payload: Vec<u8>,
1647 sender_info: RawSenderInfo,
1648 ) -> Result<Vec<u8>, ClientError> {
1649 let runtime = self.runtime.clone();
1650 runtime.block_on(async {
1651 self.pocket_ic
1652 .query_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1653 .await
1654 })
1655 }
1656
1657 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1671 pub fn get_canister_http(&self) -> Result<Vec<CanisterHttpRequest>, ClientError> {
1672 let runtime = self.runtime.clone();
1673 runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1674 }
1675
1676 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1682 pub fn mock_canister_http_response(
1683 &self,
1684 mock_canister_http_response: MockCanisterHttpResponse,
1685 ) -> Result<(), ClientError> {
1686 let runtime = self.runtime.clone();
1687 runtime.block_on(async {
1688 self.pocket_ic
1689 .mock_canister_http_response(mock_canister_http_response)
1690 .await
1691 })
1692 }
1693
1694 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1710 pub fn mock_flexible_canister_http_response(
1711 &self,
1712 mock_flexible_canister_http_response: MockFlexibleCanisterHttpResponse,
1713 ) -> Result<(), ClientError> {
1714 let runtime = self.runtime.clone();
1715 runtime.block_on(async {
1716 self.pocket_ic
1717 .mock_flexible_canister_http_response(mock_flexible_canister_http_response)
1718 .await
1719 })
1720 }
1721
1722 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1726 pub fn canister_snapshot_download(
1727 &self,
1728 canister_id: CanisterId,
1729 sender: Principal,
1730 snapshot_id: Vec<u8>,
1731 snapshot_dir: PathBuf,
1732 ) -> Result<(), ClientError> {
1733 let runtime = self.runtime.clone();
1734 runtime.block_on(async {
1735 self.pocket_ic
1736 .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1737 .await
1738 })
1739 }
1740
1741 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1745 pub fn canister_snapshot_upload(
1746 &self,
1747 canister_id: CanisterId,
1748 sender: Principal,
1749 replace_snapshot: Option<Vec<u8>>,
1750 snapshot_dir: PathBuf,
1751 ) -> Result<Vec<u8>, ClientError> {
1752 let runtime = self.runtime.clone();
1753 runtime.block_on(async {
1754 self.pocket_ic
1755 .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1756 .await
1757 })
1758 }
1759}
1760
1761impl Drop for PocketIc {
1762 fn drop(&mut self) {
1763 if !self.pocket_ic.cleanup_attempted {
1764 self.runtime.block_on(async {
1765 if let Err(error) = self.pocket_ic.do_drop().await {
1766 tracing::warn!("PocketIC drop failed: {error}");
1767 }
1768 });
1769 }
1770 if let Some(thread) = self.thread.take() {
1771 let _ = thread.join();
1772 }
1773 }
1774}
1775
1776pub fn call_candid_as<Input, Output>(
1780 env: &PocketIc,
1781 canister_id: CanisterId,
1782 effective_principal: RawEffectivePrincipal,
1783 sender: Principal,
1784 method: &str,
1785 input: Input,
1786) -> Result<Output, ClientError>
1787where
1788 Input: ArgumentEncoder,
1789 Output: for<'a> ArgumentDecoder<'a>,
1790{
1791 with_candid(input, |payload| {
1792 env.update_call_with_effective_principal(
1793 canister_id,
1794 effective_principal,
1795 sender,
1796 method,
1797 payload,
1798 )
1799 })
1800}
1801
1802pub fn call_candid<Input, Output>(
1805 env: &PocketIc,
1806 canister_id: CanisterId,
1807 effective_principal: RawEffectivePrincipal,
1808 method: &str,
1809 input: Input,
1810) -> Result<Output, ClientError>
1811where
1812 Input: ArgumentEncoder,
1813 Output: for<'a> ArgumentDecoder<'a>,
1814{
1815 call_candid_as(
1816 env,
1817 canister_id,
1818 effective_principal,
1819 Principal::anonymous(),
1820 method,
1821 input,
1822 )
1823}
1824
1825pub fn query_candid<Input, Output>(
1827 env: &PocketIc,
1828 canister_id: CanisterId,
1829 method: &str,
1830 input: Input,
1831) -> Result<Output, ClientError>
1832where
1833 Input: ArgumentEncoder,
1834 Output: for<'a> ArgumentDecoder<'a>,
1835{
1836 query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1837}
1838
1839pub fn query_candid_as<Input, Output>(
1842 env: &PocketIc,
1843 canister_id: CanisterId,
1844 sender: Principal,
1845 method: &str,
1846 input: Input,
1847) -> Result<Output, ClientError>
1848where
1849 Input: ArgumentEncoder,
1850 Output: for<'a> ArgumentDecoder<'a>,
1851{
1852 with_candid(input, |bytes| {
1853 env.query_call(canister_id, sender, method, bytes)
1854 })
1855}
1856
1857pub fn update_candid<Input, Output>(
1859 env: &PocketIc,
1860 canister_id: CanisterId,
1861 method: &str,
1862 input: Input,
1863) -> Result<Output, ClientError>
1864where
1865 Input: ArgumentEncoder,
1866 Output: for<'a> ArgumentDecoder<'a>,
1867{
1868 update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1869}
1870
1871pub fn update_candid_as<Input, Output>(
1874 env: &PocketIc,
1875 canister_id: CanisterId,
1876 sender: Principal,
1877 method: &str,
1878 input: Input,
1879) -> Result<Output, ClientError>
1880where
1881 Input: ArgumentEncoder,
1882 Output: for<'a> ArgumentDecoder<'a>,
1883{
1884 with_candid(input, |bytes| {
1885 env.update_call(canister_id, sender, method, bytes)
1886 })
1887}
1888
1889pub fn with_candid<Input, Output>(
1892 input: Input,
1893 f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, ClientError>,
1894) -> Result<Output, ClientError>
1895where
1896 Input: ArgumentEncoder,
1897 Output: for<'a> ArgumentDecoder<'a>,
1898{
1899 let bytes = encode_args(input)?;
1900 let output = f(bytes)?;
1901 Ok(decode_args(&output)?)
1902}
1903
1904#[derive(Clone, Copy, Debug)]
1906pub enum TryFromError {
1907 ValueOutOfRange(u64),
1908}
1909
1910#[derive(
1917 PartialOrd,
1918 Ord,
1919 Clone,
1920 Copy,
1921 Debug,
1922 PartialEq,
1923 Eq,
1924 Hash,
1925 Serialize,
1926 Deserialize,
1927 JsonSchema,
1928 EnumIter,
1929)]
1930pub enum ErrorCode {
1931 SubnetOversubscribed = 101,
1933 MaxNumberOfCanistersReached = 102,
1934 CanisterQueueFull = 201,
1936 IngressMessageTimeout = 202,
1937 CanisterQueueNotEmpty = 203,
1938 IngressHistoryFull = 204,
1939 CanisterIdAlreadyExists = 205,
1940 StopCanisterRequestTimeout = 206,
1941 CanisterOutOfCycles = 207,
1942 CertifiedStateUnavailable = 208,
1943 CanisterInstallCodeRateLimited = 209,
1944 CanisterHeapDeltaRateLimited = 210,
1945 SubnetCoolingDown = 211,
1946 CanisterNotFound = 301,
1948 CanisterSnapshotNotFound = 305,
1949 InsufficientMemoryAllocation = 402,
1951 InsufficientCyclesForCreateCanister = 403,
1952 SubnetNotFound = 404,
1953 CanisterNotHostedBySubnet = 405,
1954 CanisterRejectedMessage = 406,
1955 UnknownManagementMessage = 407,
1956 InvalidManagementPayload = 408,
1957 CanisterSnapshotImmutable = 409,
1958 InvalidSubnetAdmin = 410,
1959 CanisterTrapped = 502,
1961 CanisterCalledTrap = 503,
1962 CanisterContractViolation = 504,
1963 CanisterInvalidWasm = 505,
1964 CanisterDidNotReply = 506,
1965 CanisterOutOfMemory = 507,
1966 CanisterStopped = 508,
1967 CanisterStopping = 509,
1968 CanisterNotStopped = 510,
1969 CanisterStoppingCancelled = 511,
1970 CanisterInvalidController = 512,
1971 CanisterFunctionNotFound = 513,
1972 CanisterNonEmpty = 514,
1973 QueryCallGraphLoopDetected = 517,
1974 InsufficientCyclesInCall = 520,
1975 CanisterWasmEngineError = 521,
1976 CanisterInstructionLimitExceeded = 522,
1977 CanisterMemoryAccessLimitExceeded = 524,
1978 QueryCallGraphTooDeep = 525,
1979 QueryCallGraphTotalInstructionLimitExceeded = 526,
1980 CompositeQueryCalledInReplicatedMode = 527,
1981 QueryTimeLimitExceeded = 528,
1982 QueryCallGraphInternal = 529,
1983 InsufficientCyclesInComputeAllocation = 530,
1984 InsufficientCyclesInMemoryAllocation = 531,
1985 InsufficientCyclesInMemoryGrow = 532,
1986 ReservedCyclesLimitExceededInMemoryAllocation = 533,
1987 ReservedCyclesLimitExceededInMemoryGrow = 534,
1988 InsufficientCyclesInMessageMemoryGrow = 535,
1989 CanisterMethodNotFound = 536,
1990 CanisterWasmModuleNotFound = 537,
1991 CanisterAlreadyInstalled = 538,
1992 CanisterWasmMemoryLimitExceeded = 539,
1993 ReservedCyclesLimitIsTooLow = 540,
1994 CanisterInvalidControllerOrSubnetAdmin = 541,
1995 CanisterStatusAccessDenied = 542,
1996 DeadlineExpired = 601,
1998 ResponseDropped = 602,
1999}
2000
2001impl TryFrom<u64> for ErrorCode {
2002 type Error = TryFromError;
2003 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
2004 match err {
2005 101 => Ok(ErrorCode::SubnetOversubscribed),
2007 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
2008 201 => Ok(ErrorCode::CanisterQueueFull),
2010 202 => Ok(ErrorCode::IngressMessageTimeout),
2011 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
2012 204 => Ok(ErrorCode::IngressHistoryFull),
2013 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
2014 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
2015 207 => Ok(ErrorCode::CanisterOutOfCycles),
2016 208 => Ok(ErrorCode::CertifiedStateUnavailable),
2017 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
2018 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
2019 211 => Ok(ErrorCode::SubnetCoolingDown),
2020 301 => Ok(ErrorCode::CanisterNotFound),
2022 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
2023 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
2025 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
2026 404 => Ok(ErrorCode::SubnetNotFound),
2027 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
2028 406 => Ok(ErrorCode::CanisterRejectedMessage),
2029 407 => Ok(ErrorCode::UnknownManagementMessage),
2030 408 => Ok(ErrorCode::InvalidManagementPayload),
2031 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
2032 410 => Ok(ErrorCode::InvalidSubnetAdmin),
2033 502 => Ok(ErrorCode::CanisterTrapped),
2035 503 => Ok(ErrorCode::CanisterCalledTrap),
2036 504 => Ok(ErrorCode::CanisterContractViolation),
2037 505 => Ok(ErrorCode::CanisterInvalidWasm),
2038 506 => Ok(ErrorCode::CanisterDidNotReply),
2039 507 => Ok(ErrorCode::CanisterOutOfMemory),
2040 508 => Ok(ErrorCode::CanisterStopped),
2041 509 => Ok(ErrorCode::CanisterStopping),
2042 510 => Ok(ErrorCode::CanisterNotStopped),
2043 511 => Ok(ErrorCode::CanisterStoppingCancelled),
2044 512 => Ok(ErrorCode::CanisterInvalidController),
2045 513 => Ok(ErrorCode::CanisterFunctionNotFound),
2046 514 => Ok(ErrorCode::CanisterNonEmpty),
2047 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
2048 520 => Ok(ErrorCode::InsufficientCyclesInCall),
2049 521 => Ok(ErrorCode::CanisterWasmEngineError),
2050 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
2051 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
2052 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
2053 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
2054 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
2055 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
2056 529 => Ok(ErrorCode::QueryCallGraphInternal),
2057 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
2058 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
2059 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
2060 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
2061 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
2062 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
2063 536 => Ok(ErrorCode::CanisterMethodNotFound),
2064 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
2065 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
2066 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
2067 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
2068 541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
2069 542 => Ok(ErrorCode::CanisterStatusAccessDenied),
2070 601 => Ok(ErrorCode::DeadlineExpired),
2072 602 => Ok(ErrorCode::ResponseDropped),
2073 _ => Err(TryFromError::ValueOutOfRange(err)),
2074 }
2075 }
2076}
2077
2078impl std::fmt::Display for ErrorCode {
2079 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2080 write!(f, "IC{:04}", *self as i32)
2082 }
2083}
2084
2085#[derive(
2090 PartialOrd,
2091 Ord,
2092 Clone,
2093 Copy,
2094 Debug,
2095 PartialEq,
2096 Eq,
2097 Hash,
2098 Serialize,
2099 Deserialize,
2100 JsonSchema,
2101 EnumIter,
2102)]
2103pub enum RejectCode {
2104 SysFatal = 1,
2105 SysTransient = 2,
2106 DestinationInvalid = 3,
2107 CanisterReject = 4,
2108 CanisterError = 5,
2109 SysUnknown = 6,
2110}
2111
2112impl TryFrom<u64> for RejectCode {
2113 type Error = TryFromError;
2114 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
2115 match err {
2116 1 => Ok(RejectCode::SysFatal),
2117 2 => Ok(RejectCode::SysTransient),
2118 3 => Ok(RejectCode::DestinationInvalid),
2119 4 => Ok(RejectCode::CanisterReject),
2120 5 => Ok(RejectCode::CanisterError),
2121 6 => Ok(RejectCode::SysUnknown),
2122 _ => Err(TryFromError::ValueOutOfRange(err)),
2123 }
2124 }
2125}
2126
2127#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
2129pub struct RejectResponse {
2130 pub reject_code: RejectCode,
2131 pub reject_message: String,
2132 pub error_code: ErrorCode,
2133 pub certified: bool,
2134}
2135
2136impl std::fmt::Display for RejectResponse {
2137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2138 write!(
2140 f,
2141 "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
2142 self.reject_code, self.reject_message, self.error_code
2143 )
2144 }
2145}
2146
2147#[derive(Debug, Serialize, Deserialize)]
2153pub enum IngressStatusResult {
2154 NotAvailable,
2155 Forbidden(String),
2156 Success(Result<Vec<u8>, RejectResponse>),
2157}
2158
2159#[derive(Clone, Debug, Default)]
2160pub struct TickConfigs {
2161 pub blockmakers: Option<Vec<SubnetBlockmakers>>,
2162}
2163
2164impl From<TickConfigs> for RawTickConfigs {
2165 fn from(tick_configs: TickConfigs) -> Self {
2166 Self {
2167 blockmakers: tick_configs.blockmakers.map(|blockmakers| {
2168 blockmakers
2169 .into_iter()
2170 .map(|blockmaker| blockmaker.into())
2171 .collect()
2172 }),
2173 }
2174 }
2175}
2176
2177#[derive(Clone, Debug)]
2178pub struct SubnetBlockmakers {
2179 pub subnet: Principal,
2180 pub blockmaker: Principal,
2181 pub failed_blockmakers: Vec<Principal>,
2182}
2183
2184impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
2185 fn from(blockmaker: SubnetBlockmakers) -> Self {
2186 Self {
2187 subnet: blockmaker.subnet.into(),
2188 blockmaker: blockmaker.blockmaker.into(),
2189 failed_blockmakers: blockmaker
2190 .failed_blockmakers
2191 .into_iter()
2192 .map(|p| p.into())
2193 .collect(),
2194 }
2195 }
2196}
2197
2198#[cfg_attr(
2206 all(not(windows), not(test)),
2207 expect(
2208 dead_code,
2209 reason = "the retained WSL converter is used by Windows implementations"
2210 )
2211)]
2212fn windows_to_wsl(path: &str) -> Result<String, String> {
2213 let path = path.strip_prefix(r"\\?\").unwrap_or(path);
2214 let mut chars = path.chars();
2215 let drive = match (chars.next(), chars.next()) {
2216 (Some(drive), Some(':')) if drive.is_ascii_alphabetic() => drive.to_ascii_lowercase(),
2217 _ => {
2218 return Err(format!(
2219 "`{path}` does not start with a drive prefix such as `C:`"
2220 ));
2221 }
2222 };
2223 let rest = chars.as_str();
2224 if !rest.is_empty() && !rest.starts_with(['\\', '/']) {
2225 return Err(format!("`{path}` is a drive-relative path"));
2226 }
2227 Ok(format!("/mnt/{drive}{}", rest.replace('\\', "/")))
2228}
2229
2230#[cfg(windows)]
2231fn wsl_path(path: &PathBuf, desc: &str) -> String {
2232 windows_to_wsl(
2233 path.as_os_str()
2234 .to_str()
2235 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
2236 )
2237 .unwrap_or_else(|e| {
2238 panic!(
2239 "Could not convert {} path ({:?}) to WSL path: {:?}",
2240 desc, path, e
2241 )
2242 })
2243}
2244
2245#[cfg(windows)]
2246static WSL_WARM_UP: Once = Once::new();
2247
2248#[cfg(windows)]
2249fn warm_up_wsl() {
2250 WSL_WARM_UP.call_once(|| {
2251 let output = Command::new("wsl")
2252 .arg("bash")
2253 .arg("-c")
2254 .arg("true")
2255 .output()
2256 .expect("Failed to warm up WSL");
2257 if !output.status.success() {
2258 panic!(
2259 "Failed to warm up WSL.\nStatus: {}\nStdout: {}\nStderr: {}",
2260 output.status,
2261 String::from_utf8_lossy(&output.stdout),
2262 String::from_utf8_lossy(&output.stderr),
2263 );
2264 }
2265 });
2266}
2267
2268#[cfg(windows)]
2269fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2270 warm_up_wsl();
2271 let mut cmd = Command::new("wsl");
2272 cmd.arg(wsl_path(bin_path, "PocketIC binary"));
2273 cmd
2274}
2275
2276#[cfg(not(windows))]
2277fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2278 Command::new(bin_path)
2279}
2280
2281fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
2282 let unexpected_version = format!(
2283 "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
2284 );
2285 let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
2286 return Err(unexpected_version);
2287 };
2288 if pocket_ic_server != POCKET_IC_SERVER_NAME {
2289 return Err(unexpected_version);
2290 }
2291 let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
2292 let version = Version::parse(version)
2293 .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
2294 if !req.matches(&version) {
2295 return Err(format!(
2296 "Incompatible PocketIC server version: got {version}; expected {req}."
2297 ));
2298 }
2299
2300 Ok(())
2301}
2302
2303fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
2304 let mut cmd = pocket_ic_server_cmd(server_binary);
2305 cmd.arg("--version");
2306 let output = cmd.output().map_err(|e| e.to_string())?;
2307 if !output.status.success() {
2308 return Err(format!(
2309 "PocketIC server failed to print its version.\nStatus: {}\nStdout: {}\nStderr: {}",
2310 output.status,
2311 String::from_utf8_lossy(&output.stdout),
2312 String::from_utf8_lossy(&output.stderr),
2313 ));
2314 }
2315 let version_str = String::from_utf8(output.stdout)
2316 .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
2317 let version_line = version_str.trim_end_matches('\n');
2318 check_pocketic_server_version(version_line)
2319}
2320
2321async fn download_pocketic_server(
2322 server_url: String,
2323 mut out: std::fs::File,
2324) -> Result<(), ClientError> {
2325 let url = Url::parse(&server_url).map_err(|e| ClientError::Protocol(e.to_string()))?;
2326 let client = reqwest::Client::new();
2327 let mut exchange = http::Exchange::new(&url, Some(DEFAULT_MAX_REQUEST_TIME_MS));
2328 let (status, bytes) = exchange.bytes(&client, client.get(url.clone())).await?;
2329 if !status.is_success() {
2330 return Err(exchange.server_error(status, &bytes));
2331 }
2332 let mut gz = GzDecoder::new(&bytes[..]);
2333 std::io::copy(&mut gz, &mut out)?;
2334 Ok(())
2335}
2336
2337#[derive(Default)]
2338pub struct StartServerParams {
2339 pub server_binary: Option<PathBuf>,
2340 pub reuse: bool,
2342 pub ttl: Option<Duration>,
2349 pub hard_ttl: Option<Duration>,
2357}
2358
2359pub async fn start_server(params: StartServerParams) -> Result<(Child, Url), ClientError> {
2361 let default_bin_dir =
2362 std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
2363 let default_bin_path = default_bin_dir.join("pocket-ic");
2364 let bin_path_provided =
2365 params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
2366 let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
2367 std::env::var_os("POCKET_IC_BIN")
2368 .unwrap_or_else(|| default_bin_path.clone().into())
2369 .into()
2370 });
2371
2372 if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
2373 if bin_path_provided {
2374 return Err(ClientError::Protocol(format!(
2375 "Failed to validate PocketIC server binary {}: {e}",
2376 bin_path.display()
2377 )));
2378 }
2379 bin_path = default_bin_path.clone();
2380 std::fs::create_dir_all(&default_bin_dir)?;
2381 let mut options = OpenOptions::new();
2382 options.write(true).create_new(true);
2383 #[cfg(unix)]
2384 options.mode(0o777);
2385 match options.open(&default_bin_path) {
2386 Ok(out) => {
2387 #[cfg(target_os = "macos")]
2388 let os = "darwin";
2389 #[cfg(not(target_os = "macos"))]
2390 let os = "linux";
2391 #[cfg(target_arch = "aarch64")]
2392 let arch = "arm64";
2393 #[cfg(not(target_arch = "aarch64"))]
2394 let arch = "x86_64";
2395 let server_url = format!(
2396 "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2397 );
2398 println!(
2399 "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.",
2400 bin_path.display(),
2401 e,
2402 LATEST_SERVER_VERSION,
2403 server_url,
2404 default_bin_path.display(),
2405 LATEST_SERVER_VERSION
2406 );
2407 if let Err(e) = download_pocketic_server(server_url, out).await {
2408 let _ = std::fs::remove_file(default_bin_path);
2409 return Err(e);
2410 }
2411 }
2412 _ => {
2413 let start = std::time::Instant::now();
2415 loop {
2416 if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2417 break;
2418 }
2419 if start.elapsed() > std::time::Duration::from_secs(60) {
2420 let _ = std::fs::remove_file(&default_bin_path);
2421 return Err(ClientError::Protocol(
2422 "Timed out waiting for PocketIC server download".into(),
2423 ));
2424 }
2425 tokio::time::sleep(Duration::from_millis(100)).await;
2426 }
2427 }
2428 }
2429 }
2430
2431 let port_file_path = if params.reuse {
2432 let test_driver_pid = std::process::id();
2435 std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2436 } else {
2437 NamedTempFile::new()?.into_temp_path().to_path_buf()
2438 };
2439 let mut cmd = pocket_ic_server_cmd(&bin_path);
2440 if let Some(ttl) = params.ttl {
2441 cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2442 }
2443 if let Some(hard_ttl) = params.hard_ttl {
2444 cmd.arg("--hard-ttl").arg(hard_ttl.as_secs().to_string());
2445 }
2446 cmd.arg("--port-file");
2447 #[cfg(windows)]
2448 cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2449 #[cfg(not(windows))]
2450 cmd.arg(port_file_path.clone());
2451 if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2452 && !mute_server.is_empty()
2453 {
2454 cmd.stdout(std::process::Stdio::null());
2455 cmd.stderr(std::process::Stdio::null());
2456 }
2457
2458 #[cfg(unix)]
2461 {
2462 use std::os::unix::process::CommandExt;
2463 cmd.process_group(0);
2464 }
2465
2466 let mut child = cmd.spawn()?;
2468 let startup_deadline = std::time::Instant::now() + Duration::from_secs(60);
2469
2470 loop {
2471 if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2472 && port_string.contains("\n")
2473 {
2474 let port: u16 = port_string
2475 .trim_end()
2476 .parse()
2477 .expect("Failed to parse port to number");
2478 return Ok((
2479 child,
2480 Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2481 ));
2482 }
2483 if std::time::Instant::now() >= startup_deadline {
2484 let _ = child.kill();
2485 let _ = child.wait();
2486 return Err(ClientError::Protocol(
2487 "Timed out waiting for PocketIC server port".into(),
2488 ));
2489 }
2490 tokio::time::sleep(Duration::from_millis(20)).await;
2491 }
2492}
2493
2494pub fn get_default_effective_canister_id(pocket_ic_url: String) -> Result<Principal, ClientError> {
2502 let runtime = Runtime::new()?;
2503 runtime
2504 .block_on(crate::pocket_ic::nonblocking::get_default_effective_canister_id(pocket_ic_url))
2505}
2506
2507pub fn copy_dir(
2508 src: impl AsRef<std::path::Path>,
2509 dst: impl AsRef<std::path::Path>,
2510) -> std::io::Result<()> {
2511 std::fs::create_dir_all(&dst)?;
2512 for entry in std::fs::read_dir(src)? {
2513 let entry = entry?;
2514 let ty = entry.file_type()?;
2515 if ty.is_dir() {
2516 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2517 } else {
2518 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2519 }
2520 }
2521 Ok(())
2522}
2523
2524#[cfg(test)]
2525mod tests;