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 Principal, decode_args, encode_args,
67 utils::{ArgumentDecoder, ArgumentEncoder},
68};
69use flate2::read::GzDecoder;
70use ic_management_canister_types::{
71 CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
72 Snapshot,
73};
74use ic_transport_types::SubnetMetrics;
75use reqwest::Url;
76use schemars::JsonSchema;
77use semver::{Version, VersionReq};
78use serde::{Deserialize, Serialize};
79use slog::Level;
80#[cfg(unix)]
81use std::os::unix::fs::OpenOptionsExt;
82use std::{
83 fs::OpenOptions,
84 net::{IpAddr, SocketAddr},
85 path::PathBuf,
86 process::{Child, Command},
87 sync::{Arc, mpsc::channel},
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
103const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
104
105const MIN_SERVER_VERSION: &str = "11.0.0";
106const MAX_SERVER_VERSION: &str = "12";
107
108pub const LATEST_SERVER_VERSION: &str = "11.0.0";
110
111const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
113
114const LOCALHOST: &str = "127.0.0.1";
115
116enum PocketIcStateKind {
117 StateDir(PathBuf),
119 TempDir(TempDir),
125}
126
127pub struct PocketIcState {
128 state: PocketIcStateKind,
129}
130
131impl PocketIcState {
132 #[allow(clippy::new_without_default)]
133 pub fn new() -> Self {
134 let temp_dir = TempDir::new().unwrap();
135 Self {
136 state: PocketIcStateKind::TempDir(temp_dir),
137 }
138 }
139
140 pub fn new_from_path(state_dir: PathBuf) -> Self {
141 Self {
142 state: PocketIcStateKind::StateDir(state_dir),
143 }
144 }
145
146 pub fn into_path(self) -> PathBuf {
147 match self.state {
148 PocketIcStateKind::StateDir(state_dir) => state_dir,
149 PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
150 }
151 }
152
153 pub(crate) fn state_dir(&self) -> PathBuf {
154 match &self.state {
155 PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
156 PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
157 }
158 }
159}
160
161pub struct PocketIcBuilder {
162 config: Option<ExtendedSubnetConfigSet>,
163 http_gateway_config: Option<InstanceHttpGatewayConfig>,
164 server_binary: Option<PathBuf>,
165 server_url: Option<Url>,
166 max_request_time_ms: Option<u64>,
167 read_only_state_dir: Option<PathBuf>,
168 state_dir: Option<PocketIcState>,
169 icp_config: IcpConfig,
170 log_level: Option<Level>,
171 bitcoind_addr: Option<Vec<SocketAddr>>,
172 dogecoind_addr: Option<Vec<SocketAddr>>,
173 icp_features: IcpFeatures,
174 initial_time: Option<InitialTime>,
175}
176
177#[allow(clippy::new_without_default)]
178impl PocketIcBuilder {
179 pub fn new() -> Self {
180 Self {
181 config: None,
182 http_gateway_config: None,
183 server_binary: None,
184 server_url: None,
185 max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
186 read_only_state_dir: None,
187 state_dir: None,
188 icp_config: IcpConfig::default(),
189 log_level: None,
190 bitcoind_addr: None,
191 dogecoind_addr: None,
192 icp_features: IcpFeatures::default(),
193 initial_time: None,
194 }
195 }
196
197 pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
198 let mut builder = Self::new();
199 builder.config = Some(config.into());
200 builder
201 }
202
203 pub fn build(self) -> PocketIc {
204 PocketIc::from_components(
205 self.config.unwrap_or_default(),
206 self.server_url,
207 self.server_binary,
208 self.max_request_time_ms,
209 self.read_only_state_dir,
210 self.state_dir,
211 self.icp_config,
212 self.log_level,
213 self.bitcoind_addr,
214 self.dogecoind_addr,
215 self.icp_features,
216 self.initial_time,
217 self.http_gateway_config,
218 )
219 }
220
221 pub async fn build_async(self) -> PocketIcAsync {
222 PocketIcAsync::from_components(
223 self.config.unwrap_or_default(),
224 self.server_url,
225 self.server_binary,
226 self.max_request_time_ms,
227 self.read_only_state_dir,
228 self.state_dir,
229 self.icp_config,
230 self.log_level,
231 self.bitcoind_addr,
232 self.dogecoind_addr,
233 self.icp_features,
234 self.initial_time,
235 self.http_gateway_config,
236 )
237 .await
238 }
239
240 pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
242 self.server_binary = Some(server_binary);
243 self
244 }
245
246 pub fn with_server_url(mut self, server_url: Url) -> Self {
248 self.server_url = Some(server_url);
249 self
250 }
251
252 pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
253 self.max_request_time_ms = max_request_time_ms;
254 self
255 }
256
257 pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
258 self.state_dir = Some(PocketIcState::new_from_path(state_dir));
259 self
260 }
261
262 pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
263 self.state_dir = Some(state_dir);
264 self
265 }
266
267 pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
268 self.read_only_state_dir = Some(read_only_state_dir.state_dir());
269 self
270 }
271
272 pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
273 self.icp_config = icp_config;
274 self
275 }
276
277 pub fn with_log_level(mut self, log_level: Level) -> Self {
278 self.log_level = Some(log_level);
279 self
280 }
281
282 pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
283 self.with_bitcoind_addrs(vec![bitcoind_addr])
284 }
285
286 pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
287 Self {
288 bitcoind_addr: Some(bitcoind_addrs),
289 ..self
290 }
291 }
292
293 pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
294 Self {
295 dogecoind_addr: Some(dogecoind_addrs),
296 ..self
297 }
298 }
299
300 pub fn with_nns_subnet(mut self) -> Self {
302 let mut config = self.config.unwrap_or_default();
303 config.nns = Some(config.nns.unwrap_or_default());
304 self.config = Some(config);
305 self
306 }
307
308 pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
325 self.with_subnet_state(SubnetKind::NNS, path_to_state)
326 }
327
328 pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
345 let mut config = self.config.unwrap_or_default();
346 let subnet_spec = SubnetSpec::default().with_state_dir(path_to_state);
347 match subnet_kind {
348 SubnetKind::NNS => config.nns = Some(subnet_spec),
349 SubnetKind::SNS => config.sns = Some(subnet_spec),
350 SubnetKind::II => config.ii = Some(subnet_spec),
351 SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
352 SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
353 SubnetKind::Application => config.application.push(subnet_spec),
354 SubnetKind::System => config.system.push(subnet_spec),
355 SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
356 };
357 self.config = Some(config);
358 self
359 }
360
361 pub fn with_sns_subnet(mut self) -> Self {
363 let mut config = self.config.unwrap_or_default();
364 config.sns = Some(config.sns.unwrap_or_default());
365 self.config = Some(config);
366 self
367 }
368
369 pub fn with_ii_subnet(mut self) -> Self {
371 let mut config = self.config.unwrap_or_default();
372 config.ii = Some(config.ii.unwrap_or_default());
373 self.config = Some(config);
374 self
375 }
376
377 pub fn with_fiduciary_subnet(mut self) -> Self {
379 let mut config = self.config.unwrap_or_default();
380 config.fiduciary = Some(config.fiduciary.unwrap_or_default());
381 self.config = Some(config);
382 self
383 }
384
385 pub fn with_bitcoin_subnet(mut self) -> Self {
387 let mut config = self.config.unwrap_or_default();
388 config.bitcoin = Some(config.bitcoin.unwrap_or_default());
389 self.config = Some(config);
390 self
391 }
392
393 pub fn with_system_subnet(mut self) -> Self {
395 let mut config = self.config.unwrap_or_default();
396 config.system.push(SubnetSpec::default());
397 self.config = Some(config);
398 self
399 }
400
401 pub fn with_application_subnet(mut self) -> Self {
403 let mut config = self.config.unwrap_or_default();
404 config.application.push(SubnetSpec::default());
405 self.config = Some(config);
406 self
407 }
408
409 pub fn with_verified_application_subnet(mut self) -> Self {
411 let mut config = self.config.unwrap_or_default();
412 config.verified_application.push(SubnetSpec::default());
413 self.config = Some(config);
414 self
415 }
416
417 pub fn with_benchmarking_application_subnet(mut self) -> Self {
419 let mut config = self.config.unwrap_or_default();
420 config
421 .application
422 .push(SubnetSpec::default().with_benchmarking_instruction_config());
423 self.config = Some(config);
424 self
425 }
426
427 pub fn with_benchmarking_system_subnet(mut self) -> Self {
429 let mut config = self.config.unwrap_or_default();
430 config
431 .system
432 .push(SubnetSpec::default().with_benchmarking_instruction_config());
433 self.config = Some(config);
434 self
435 }
436
437 pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
442 self.icp_features = icp_features;
443 self
444 }
445
446 #[deprecated(note = "Use `with_initial_time` instead")]
450 pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
451 self.initial_time = Some(InitialTime::Timestamp(RawTime {
452 nanos_since_epoch: initial_timestamp_nanos,
453 }));
454 self
455 }
456
457 pub fn with_initial_time(mut self, initial_time: Time) -> Self {
461 self.initial_time = Some(InitialTime::Timestamp(RawTime {
462 nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
463 }));
464 self
465 }
466
467 pub fn with_auto_progress(mut self) -> Self {
471 let config = AutoProgressConfig {
472 artificial_delay_ms: None,
473 };
474 self.initial_time = Some(InitialTime::AutoProgress(config));
475 self
476 }
477
478 pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
479 self.http_gateway_config = Some(http_gateway_config);
480 self
481 }
482}
483
484#[derive(Copy, Clone, PartialEq, PartialOrd)]
487pub struct Time(Duration);
488
489impl Time {
490 pub fn as_nanos_since_unix_epoch(&self) -> u64 {
492 self.0.as_nanos().try_into().unwrap()
493 }
494
495 pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
496 Time(Duration::from_nanos(nanos))
497 }
498}
499
500impl std::fmt::Debug for Time {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
503 write!(f, "{nanos_since_unix_epoch}")
504 }
505}
506
507impl std::ops::Add<Duration> for Time {
508 type Output = Time;
509 fn add(self, dur: Duration) -> Time {
510 Time(self.0 + dur)
511 }
512}
513
514impl From<SystemTime> for Time {
515 fn from(time: SystemTime) -> Self {
516 Self::from_nanos_since_unix_epoch(
517 time.duration_since(UNIX_EPOCH)
518 .unwrap()
519 .as_nanos()
520 .try_into()
521 .unwrap(),
522 )
523 }
524}
525
526impl TryFrom<Time> for SystemTime {
527 type Error = String;
528
529 fn try_from(time: Time) -> Result<SystemTime, String> {
530 let nanos = time.as_nanos_since_unix_epoch();
531 let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
532 let roundtrip: Time = system_time.into();
533 if roundtrip.as_nanos_since_unix_epoch() == nanos {
534 Ok(system_time)
535 } else {
536 Err(format!(
537 "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
538 ))
539 }
540 }
541}
542
543pub struct PocketIc {
545 pocket_ic: PocketIcAsync,
546 runtime: Arc<tokio::runtime::Runtime>,
547 thread: Option<JoinHandle<()>>,
548}
549
550impl PocketIc {
551 pub fn new() -> Self {
554 PocketIcBuilder::new().with_application_subnet().build()
555 }
556
557 pub fn new_from_existing_instance(
562 server_url: Url,
563 instance_id: InstanceId,
564 max_request_time_ms: Option<u64>,
565 ) -> Self {
566 let (tx, rx) = channel();
567 let thread = thread::spawn(move || {
568 let rt = tokio::runtime::Builder::new_current_thread()
569 .enable_all()
570 .build()
571 .unwrap();
572 tx.send(rt).unwrap();
573 });
574 let runtime = rx.recv().unwrap();
575
576 let pocket_ic =
577 PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
578
579 Self {
580 pocket_ic,
581 runtime: Arc::new(runtime),
582 thread: Some(thread),
583 }
584 }
585
586 #[allow(clippy::too_many_arguments)]
587 pub(crate) fn from_components(
588 subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
589 server_url: Option<Url>,
590 server_binary: Option<PathBuf>,
591 max_request_time_ms: Option<u64>,
592 read_only_state_dir: Option<PathBuf>,
593 state_dir: Option<PocketIcState>,
594 icp_config: IcpConfig,
595 log_level: Option<Level>,
596 bitcoind_addr: Option<Vec<SocketAddr>>,
597 dogecoind_addr: Option<Vec<SocketAddr>>,
598 icp_features: IcpFeatures,
599 initial_time: Option<InitialTime>,
600 http_gateway_config: Option<InstanceHttpGatewayConfig>,
601 ) -> Self {
602 let (tx, rx) = channel();
603 let thread = thread::spawn(move || {
604 let rt = tokio::runtime::Builder::new_current_thread()
605 .enable_all()
606 .build()
607 .unwrap();
608 tx.send(rt).unwrap();
609 });
610 let runtime = rx.recv().unwrap();
611
612 let pocket_ic = runtime.block_on(async {
613 PocketIcAsync::from_components(
614 subnet_config_set,
615 server_url,
616 server_binary,
617 max_request_time_ms,
618 read_only_state_dir,
619 state_dir,
620 icp_config,
621 log_level,
622 bitcoind_addr,
623 dogecoind_addr,
624 icp_features,
625 initial_time,
626 http_gateway_config,
627 )
628 .await
629 });
630
631 Self {
632 pocket_ic,
633 runtime: Arc::new(runtime),
634 thread: Some(thread),
635 }
636 }
637
638 pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
639 self.pocket_ic.take_state_internal()
640 }
641
642 pub fn get_server_url(&self) -> Url {
644 self.pocket_ic.get_server_url()
645 }
646
647 pub fn instance_id(&self) -> InstanceId {
649 self.pocket_ic.instance_id
650 }
651
652 pub fn topology(&self) -> Topology {
654 let runtime = self.runtime.clone();
655 runtime.block_on(async { self.pocket_ic.topology().await })
656 }
657
658 #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
660 pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
661 let runtime = self.runtime.clone();
662 runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
663 }
664
665 #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
668 pub fn set_stable_memory(
669 &self,
670 canister_id: CanisterId,
671 data: Vec<u8>,
672 compression: BlobCompression,
673 ) {
674 let runtime = self.runtime.clone();
675 runtime.block_on(async {
676 self.pocket_ic
677 .set_stable_memory(canister_id, data, compression)
678 .await
679 })
680 }
681
682 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
684 pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
685 let runtime = self.runtime.clone();
686 runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
687 }
688
689 #[instrument(ret)]
691 pub fn list_instances() -> Vec<String> {
692 let runtime = tokio::runtime::Builder::new_current_thread()
693 .build()
694 .unwrap();
695 let url = runtime.block_on(async {
696 let (_, server_url) = start_server(StartServerParams {
697 reuse: true,
698 ..Default::default()
699 })
700 .await;
701 server_url.join("instances").unwrap()
702 });
703 let instances: Vec<String> = reqwest::blocking::Client::new()
704 .get(url)
705 .send()
706 .expect("Failed to get result")
707 .json()
708 .expect("Failed to get json");
709 instances
710 }
711
712 #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
714 pub fn verify_canister_signature(
715 &self,
716 msg: Vec<u8>,
717 sig: Vec<u8>,
718 pubkey: Vec<u8>,
719 root_pubkey: Vec<u8>,
720 ) -> Result<(), String> {
721 let runtime = self.runtime.clone();
722 runtime.block_on(async {
723 self.pocket_ic
724 .verify_canister_signature(msg, sig, pubkey, root_pubkey)
725 .await
726 })
727 }
728
729 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
734 pub fn tick(&self) {
735 let runtime = self.runtime.clone();
736 runtime.block_on(async { self.pocket_ic.tick().await })
737 }
738
739 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
742 pub fn tick_with_configs(&self, configs: crate::common::rest::TickConfigs) {
743 let runtime = self.runtime.clone();
744 runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
745 }
746
747 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
753 pub fn auto_progress(&self) -> Url {
754 let runtime = self.runtime.clone();
755 runtime.block_on(async { self.pocket_ic.auto_progress().await })
756 }
757
758 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
760 pub fn auto_progress_enabled(&self) -> bool {
761 let runtime = self.runtime.clone();
762 runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
763 }
764
765 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
767 pub fn stop_progress(&self) {
768 let runtime = self.runtime.clone();
769 runtime.block_on(async { self.pocket_ic.stop_progress().await })
770 }
771
772 pub fn url(&self) -> Option<Url> {
776 self.pocket_ic.url()
777 }
778
779 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
788 pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
789 let runtime = self.runtime.clone();
790 runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
791 }
792
793 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
804 pub fn make_live_with_params(
805 &mut self,
806 ip_addr: Option<IpAddr>,
807 listen_at: Option<u16>,
808 domains: Option<Vec<String>>,
809 https_config: Option<HttpsConfig>,
810 ) -> Url {
811 let runtime = self.runtime.clone();
812 runtime.block_on(async {
813 self.pocket_ic
814 .make_live_with_params(ip_addr, listen_at, domains, https_config)
815 .await
816 })
817 }
818
819 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
822 pub fn stop_live(&mut self) {
823 let runtime = self.runtime.clone();
824 runtime.block_on(async { self.pocket_ic.stop_live().await })
825 }
826
827 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
829 pub fn root_key(&self) -> Option<Vec<u8>> {
830 let runtime = self.runtime.clone();
831 runtime.block_on(async { self.pocket_ic.root_key().await })
832 }
833
834 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
836 pub fn get_time(&self) -> Time {
837 let runtime = self.runtime.clone();
838 runtime.block_on(async { self.pocket_ic.get_time().await })
839 }
840
841 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
843 pub fn set_time(&self, time: Time) {
844 let runtime = self.runtime.clone();
845 runtime.block_on(async { self.pocket_ic.set_time(time).await })
846 }
847
848 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
850 pub fn set_certified_time(&self, time: Time) {
851 let runtime = self.runtime.clone();
852 runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
853 }
854
855 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
857 pub fn advance_time(&self, duration: Duration) {
858 let runtime = self.runtime.clone();
859 runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
860 }
861
862 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
865 pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
866 let runtime = self.runtime.clone();
867 runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
868 }
869
870 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
872 pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
873 let runtime = self.runtime.clone();
874 runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
875 }
876
877 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
879 pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
880 let runtime = self.runtime.clone();
881 runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
882 }
883
884 pub fn submit_call(
886 &self,
887 canister_id: CanisterId,
888 sender: Principal,
889 method: &str,
890 payload: Vec<u8>,
891 ) -> Result<RawMessageId, RejectResponse> {
892 let runtime = self.runtime.clone();
893 runtime.block_on(async {
894 self.pocket_ic
895 .submit_call(canister_id, sender, method, payload)
896 .await
897 })
898 }
899
900 pub fn submit_call_with_effective_principal(
902 &self,
903 canister_id: CanisterId,
904 effective_principal: RawEffectivePrincipal,
905 sender: Principal,
906 method: &str,
907 payload: Vec<u8>,
908 ) -> Result<RawMessageId, RejectResponse> {
909 let runtime = self.runtime.clone();
910 runtime.block_on(async {
911 self.pocket_ic
912 .submit_call_with_effective_principal(
913 canister_id,
914 effective_principal,
915 sender,
916 method,
917 payload,
918 )
919 .await
920 })
921 }
922
923 pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
925 let runtime = self.runtime.clone();
926 runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
927 }
928
929 pub fn ingress_status(
933 &self,
934 message_id: RawMessageId,
935 ) -> Option<Result<Vec<u8>, RejectResponse>> {
936 let runtime = self.runtime.clone();
937 runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
938 }
939
940 pub fn ingress_status_as(
945 &self,
946 message_id: RawMessageId,
947 caller: Principal,
948 ) -> IngressStatusResult {
949 let runtime = self.runtime.clone();
950 runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
951 }
952
953 pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
957 let runtime = self.runtime.clone();
958 runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
959 }
960
961 #[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()))]
963 pub fn update_call(
964 &self,
965 canister_id: CanisterId,
966 sender: Principal,
967 method: &str,
968 payload: Vec<u8>,
969 ) -> Result<Vec<u8>, RejectResponse> {
970 let runtime = self.runtime.clone();
971 runtime.block_on(async {
972 self.pocket_ic
973 .update_call(canister_id, sender, method, payload)
974 .await
975 })
976 }
977
978 #[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()))]
980 pub fn query_call(
981 &self,
982 canister_id: CanisterId,
983 sender: Principal,
984 method: &str,
985 payload: Vec<u8>,
986 ) -> Result<Vec<u8>, RejectResponse> {
987 let runtime = self.runtime.clone();
988 runtime.block_on(async {
989 self.pocket_ic
990 .query_call(canister_id, sender, method, payload)
991 .await
992 })
993 }
994
995 pub fn fetch_canister_logs(
997 &self,
998 canister_id: CanisterId,
999 sender: Principal,
1000 ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
1001 let runtime = self.runtime.clone();
1002 runtime.block_on(async {
1003 self.pocket_ic
1004 .fetch_canister_logs(canister_id, sender)
1005 .await
1006 })
1007 }
1008
1009 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1011 pub fn canister_status(
1012 &self,
1013 canister_id: CanisterId,
1014 sender: Option<Principal>,
1015 ) -> Result<CanisterStatusResult, RejectResponse> {
1016 let runtime = self.runtime.clone();
1017 runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1018 }
1019
1020 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1022 pub fn create_canister(&self) -> CanisterId {
1023 let runtime = self.runtime.clone();
1024 runtime.block_on(async { self.pocket_ic.create_canister().await })
1025 }
1026
1027 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1029 pub fn create_canister_with_settings(
1030 &self,
1031 sender: Option<Principal>,
1032 settings: Option<CanisterSettings>,
1033 ) -> CanisterId {
1034 let runtime = self.runtime.clone();
1035 runtime.block_on(async {
1036 self.pocket_ic
1037 .create_canister_with_settings(sender, settings)
1038 .await
1039 })
1040 }
1041
1042 #[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()))]
1050 pub fn create_canister_with_id(
1051 &self,
1052 sender: Option<Principal>,
1053 settings: Option<CanisterSettings>,
1054 canister_id: CanisterId,
1055 ) -> Result<CanisterId, String> {
1056 let runtime = self.runtime.clone();
1057 runtime.block_on(async {
1058 self.pocket_ic
1059 .create_canister_with_id(sender, settings, canister_id)
1060 .await
1061 })
1062 }
1063
1064 #[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()))]
1066 pub fn create_canister_on_subnet(
1067 &self,
1068 sender: Option<Principal>,
1069 settings: Option<CanisterSettings>,
1070 subnet_id: SubnetId,
1071 ) -> CanisterId {
1072 let runtime = self.runtime.clone();
1073 runtime.block_on(async {
1074 self.pocket_ic
1075 .create_canister_on_subnet(sender, settings, subnet_id)
1076 .await
1077 })
1078 }
1079
1080 #[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()))]
1083 pub fn upload_chunk(
1084 &self,
1085 canister_id: CanisterId,
1086 sender: Option<Principal>,
1087 chunk: Vec<u8>,
1088 ) -> Result<Vec<u8>, RejectResponse> {
1089 let runtime = self.runtime.clone();
1090 runtime.block_on(async {
1091 self.pocket_ic
1092 .upload_chunk(canister_id, sender, chunk)
1093 .await
1094 })
1095 }
1096
1097 #[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()))]
1099 pub fn stored_chunks(
1100 &self,
1101 canister_id: CanisterId,
1102 sender: Option<Principal>,
1103 ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1104 let runtime = self.runtime.clone();
1105 runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1106 }
1107
1108 #[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()))]
1110 pub fn clear_chunk_store(
1111 &self,
1112 canister_id: CanisterId,
1113 sender: Option<Principal>,
1114 ) -> Result<(), RejectResponse> {
1115 let runtime = self.runtime.clone();
1116 runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1117 }
1118
1119 #[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()))]
1121 pub fn install_chunked_canister(
1122 &self,
1123 canister_id: CanisterId,
1124 sender: Option<Principal>,
1125 mode: CanisterInstallMode,
1126 store_canister_id: CanisterId,
1127 chunk_hashes_list: Vec<Vec<u8>>,
1128 wasm_module_hash: Vec<u8>,
1129 arg: Vec<u8>,
1130 ) -> Result<(), RejectResponse> {
1131 let runtime = self.runtime.clone();
1132 runtime.block_on(async {
1133 self.pocket_ic
1134 .install_chunked_canister(
1135 canister_id,
1136 sender,
1137 mode,
1138 store_canister_id,
1139 chunk_hashes_list,
1140 wasm_module_hash,
1141 arg,
1142 )
1143 .await
1144 })
1145 }
1146
1147 #[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()))]
1149 pub fn install_canister(
1150 &self,
1151 canister_id: CanisterId,
1152 wasm_module: Vec<u8>,
1153 arg: Vec<u8>,
1154 sender: Option<Principal>,
1155 ) {
1156 let runtime = self.runtime.clone();
1157 runtime.block_on(async {
1158 self.pocket_ic
1159 .install_canister(canister_id, wasm_module, arg, sender)
1160 .await
1161 })
1162 }
1163
1164 #[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()))]
1166 pub fn upgrade_canister(
1167 &self,
1168 canister_id: CanisterId,
1169 wasm_module: Vec<u8>,
1170 arg: Vec<u8>,
1171 sender: Option<Principal>,
1172 ) -> Result<(), RejectResponse> {
1173 let runtime = self.runtime.clone();
1174 runtime.block_on(async {
1175 self.pocket_ic
1176 .upgrade_canister(canister_id, wasm_module, arg, sender)
1177 .await
1178 })
1179 }
1180
1181 #[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()))]
1183 pub fn upgrade_eop_canister(
1184 &self,
1185 canister_id: CanisterId,
1186 wasm_module: Vec<u8>,
1187 arg: Vec<u8>,
1188 sender: Option<Principal>,
1189 ) -> Result<(), RejectResponse> {
1190 let runtime = self.runtime.clone();
1191 runtime.block_on(async {
1192 self.pocket_ic
1193 .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1194 .await
1195 })
1196 }
1197
1198 #[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()))]
1200 pub fn reinstall_canister(
1201 &self,
1202 canister_id: CanisterId,
1203 wasm_module: Vec<u8>,
1204 arg: Vec<u8>,
1205 sender: Option<Principal>,
1206 ) -> Result<(), RejectResponse> {
1207 let runtime = self.runtime.clone();
1208 runtime.block_on(async {
1209 self.pocket_ic
1210 .reinstall_canister(canister_id, wasm_module, arg, sender)
1211 .await
1212 })
1213 }
1214
1215 #[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()))]
1217 pub fn uninstall_canister(
1218 &self,
1219 canister_id: CanisterId,
1220 sender: Option<Principal>,
1221 ) -> Result<(), RejectResponse> {
1222 let runtime = self.runtime.clone();
1223 runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1224 }
1225
1226 #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1228 pub fn take_canister_snapshot(
1229 &self,
1230 canister_id: CanisterId,
1231 sender: Option<Principal>,
1232 replace_snapshot: Option<Vec<u8>>,
1233 ) -> Result<Snapshot, RejectResponse> {
1234 let runtime = self.runtime.clone();
1235 runtime.block_on(async {
1236 self.pocket_ic
1237 .take_canister_snapshot(canister_id, sender, replace_snapshot)
1238 .await
1239 })
1240 }
1241
1242 #[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()))]
1244 pub fn load_canister_snapshot(
1245 &self,
1246 canister_id: CanisterId,
1247 sender: Option<Principal>,
1248 snapshot_id: Vec<u8>,
1249 ) -> Result<(), RejectResponse> {
1250 let runtime = self.runtime.clone();
1251 runtime.block_on(async {
1252 self.pocket_ic
1253 .load_canister_snapshot(canister_id, sender, snapshot_id)
1254 .await
1255 })
1256 }
1257
1258 #[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()))]
1260 pub fn list_canister_snapshots(
1261 &self,
1262 canister_id: CanisterId,
1263 sender: Option<Principal>,
1264 ) -> Result<Vec<Snapshot>, RejectResponse> {
1265 let runtime = self.runtime.clone();
1266 runtime.block_on(async {
1267 self.pocket_ic
1268 .list_canister_snapshots(canister_id, sender)
1269 .await
1270 })
1271 }
1272
1273 #[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()))]
1275 pub fn delete_canister_snapshot(
1276 &self,
1277 canister_id: CanisterId,
1278 sender: Option<Principal>,
1279 snapshot_id: Vec<u8>,
1280 ) -> Result<(), RejectResponse> {
1281 let runtime = self.runtime.clone();
1282 runtime.block_on(async {
1283 self.pocket_ic
1284 .delete_canister_snapshot(canister_id, sender, snapshot_id)
1285 .await
1286 })
1287 }
1288
1289 #[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()))]
1291 pub fn update_canister_settings(
1292 &self,
1293 canister_id: CanisterId,
1294 sender: Option<Principal>,
1295 settings: CanisterSettings,
1296 ) -> Result<(), RejectResponse> {
1297 let runtime = self.runtime.clone();
1298 runtime.block_on(async {
1299 self.pocket_ic
1300 .update_canister_settings(canister_id, sender, settings)
1301 .await
1302 })
1303 }
1304
1305 #[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()))]
1307 pub fn set_controllers(
1308 &self,
1309 canister_id: CanisterId,
1310 sender: Option<Principal>,
1311 new_controllers: Vec<Principal>,
1312 ) -> Result<(), RejectResponse> {
1313 let runtime = self.runtime.clone();
1314 runtime.block_on(async {
1315 self.pocket_ic
1316 .set_controllers(canister_id, sender, new_controllers)
1317 .await
1318 })
1319 }
1320
1321 #[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()))]
1323 pub fn start_canister(
1324 &self,
1325 canister_id: CanisterId,
1326 sender: Option<Principal>,
1327 ) -> Result<(), RejectResponse> {
1328 let runtime = self.runtime.clone();
1329 runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1330 }
1331
1332 #[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()))]
1334 pub fn stop_canister(
1335 &self,
1336 canister_id: CanisterId,
1337 sender: Option<Principal>,
1338 ) -> Result<(), RejectResponse> {
1339 let runtime = self.runtime.clone();
1340 runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1341 }
1342
1343 #[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()))]
1345 pub fn delete_canister(
1346 &self,
1347 canister_id: CanisterId,
1348 sender: Option<Principal>,
1349 ) -> Result<(), RejectResponse> {
1350 let runtime = self.runtime.clone();
1351 runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1352 }
1353
1354 #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1356 pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1357 let runtime = self.runtime.clone();
1358 runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1359 }
1360
1361 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1363 pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1364 let runtime = self.runtime.clone();
1365 runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1366 }
1367
1368 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1370 pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1371 let runtime = self.runtime.clone();
1372 runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1373 }
1374
1375 pub fn update_call_with_effective_principal(
1376 &self,
1377 canister_id: CanisterId,
1378 effective_principal: RawEffectivePrincipal,
1379 sender: Principal,
1380 method: &str,
1381 payload: Vec<u8>,
1382 ) -> Result<Vec<u8>, RejectResponse> {
1383 let runtime = self.runtime.clone();
1384 runtime.block_on(async {
1385 self.pocket_ic
1386 .update_call_with_effective_principal(
1387 canister_id,
1388 effective_principal,
1389 sender,
1390 method,
1391 payload,
1392 )
1393 .await
1394 })
1395 }
1396
1397 #[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()))]
1401 pub fn query_call_with_effective_principal(
1402 &self,
1403 canister_id: CanisterId,
1404 effective_principal: RawEffectivePrincipal,
1405 sender: Principal,
1406 method: &str,
1407 payload: Vec<u8>,
1408 ) -> Result<Vec<u8>, RejectResponse> {
1409 let runtime = self.runtime.clone();
1410 runtime.block_on(async {
1411 self.pocket_ic
1412 .query_call_with_effective_principal(
1413 canister_id,
1414 effective_principal,
1415 sender,
1416 method,
1417 payload,
1418 )
1419 .await
1420 })
1421 }
1422
1423 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1435 pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1436 let runtime = self.runtime.clone();
1437 runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1438 }
1439
1440 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1442 pub fn mock_canister_http_response(
1443 &self,
1444 mock_canister_http_response: MockCanisterHttpResponse,
1445 ) {
1446 let runtime = self.runtime.clone();
1447 runtime.block_on(async {
1448 self.pocket_ic
1449 .mock_canister_http_response(mock_canister_http_response)
1450 .await
1451 })
1452 }
1453
1454 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1458 pub fn canister_snapshot_download(
1459 &self,
1460 canister_id: CanisterId,
1461 sender: Principal,
1462 snapshot_id: Vec<u8>,
1463 snapshot_dir: PathBuf,
1464 ) {
1465 let runtime = self.runtime.clone();
1466 runtime.block_on(async {
1467 self.pocket_ic
1468 .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1469 .await
1470 })
1471 }
1472
1473 #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1477 pub fn canister_snapshot_upload(
1478 &self,
1479 canister_id: CanisterId,
1480 sender: Principal,
1481 replace_snapshot: Option<Vec<u8>>,
1482 snapshot_dir: PathBuf,
1483 ) -> Vec<u8> {
1484 let runtime = self.runtime.clone();
1485 runtime.block_on(async {
1486 self.pocket_ic
1487 .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1488 .await
1489 })
1490 }
1491}
1492
1493impl Default for PocketIc {
1494 fn default() -> Self {
1495 Self::new()
1496 }
1497}
1498
1499impl Drop for PocketIc {
1500 fn drop(&mut self) {
1501 self.runtime.block_on(async {
1502 self.pocket_ic.do_drop().await;
1503 });
1504 if let Some(thread) = self.thread.take() {
1505 thread.join().unwrap();
1506 }
1507 }
1508}
1509
1510pub fn call_candid_as<Input, Output>(
1514 env: &PocketIc,
1515 canister_id: CanisterId,
1516 effective_principal: RawEffectivePrincipal,
1517 sender: Principal,
1518 method: &str,
1519 input: Input,
1520) -> Result<Output, RejectResponse>
1521where
1522 Input: ArgumentEncoder,
1523 Output: for<'a> ArgumentDecoder<'a>,
1524{
1525 with_candid(input, |payload| {
1526 env.update_call_with_effective_principal(
1527 canister_id,
1528 effective_principal,
1529 sender,
1530 method,
1531 payload,
1532 )
1533 })
1534}
1535
1536pub fn call_candid<Input, Output>(
1539 env: &PocketIc,
1540 canister_id: CanisterId,
1541 effective_principal: RawEffectivePrincipal,
1542 method: &str,
1543 input: Input,
1544) -> Result<Output, RejectResponse>
1545where
1546 Input: ArgumentEncoder,
1547 Output: for<'a> ArgumentDecoder<'a>,
1548{
1549 call_candid_as(
1550 env,
1551 canister_id,
1552 effective_principal,
1553 Principal::anonymous(),
1554 method,
1555 input,
1556 )
1557}
1558
1559pub fn query_candid<Input, Output>(
1561 env: &PocketIc,
1562 canister_id: CanisterId,
1563 method: &str,
1564 input: Input,
1565) -> Result<Output, RejectResponse>
1566where
1567 Input: ArgumentEncoder,
1568 Output: for<'a> ArgumentDecoder<'a>,
1569{
1570 query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1571}
1572
1573pub fn query_candid_as<Input, Output>(
1576 env: &PocketIc,
1577 canister_id: CanisterId,
1578 sender: Principal,
1579 method: &str,
1580 input: Input,
1581) -> Result<Output, RejectResponse>
1582where
1583 Input: ArgumentEncoder,
1584 Output: for<'a> ArgumentDecoder<'a>,
1585{
1586 with_candid(input, |bytes| {
1587 env.query_call(canister_id, sender, method, bytes)
1588 })
1589}
1590
1591pub fn update_candid<Input, Output>(
1593 env: &PocketIc,
1594 canister_id: CanisterId,
1595 method: &str,
1596 input: Input,
1597) -> Result<Output, RejectResponse>
1598where
1599 Input: ArgumentEncoder,
1600 Output: for<'a> ArgumentDecoder<'a>,
1601{
1602 update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1603}
1604
1605pub fn update_candid_as<Input, Output>(
1608 env: &PocketIc,
1609 canister_id: CanisterId,
1610 sender: Principal,
1611 method: &str,
1612 input: Input,
1613) -> Result<Output, RejectResponse>
1614where
1615 Input: ArgumentEncoder,
1616 Output: for<'a> ArgumentDecoder<'a>,
1617{
1618 with_candid(input, |bytes| {
1619 env.update_call(canister_id, sender, method, bytes)
1620 })
1621}
1622
1623pub fn with_candid<Input, Output>(
1626 input: Input,
1627 f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1628) -> Result<Output, RejectResponse>
1629where
1630 Input: ArgumentEncoder,
1631 Output: for<'a> ArgumentDecoder<'a>,
1632{
1633 let in_bytes = encode_args(input).expect("failed to encode args");
1634 f(in_bytes).map(|out_bytes| {
1635 decode_args(&out_bytes).unwrap_or_else(|e| {
1636 panic!(
1637 "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1638 std::any::type_name::<Output>(),
1639 e,
1640 out_bytes,
1641 String::from_utf8_lossy(&out_bytes),
1642 )
1643 })
1644 })
1645}
1646
1647#[derive(Clone, Copy, Debug)]
1649pub enum TryFromError {
1650 ValueOutOfRange(u64),
1651}
1652
1653#[derive(
1660 PartialOrd,
1661 Ord,
1662 Clone,
1663 Copy,
1664 Debug,
1665 PartialEq,
1666 Eq,
1667 Hash,
1668 Serialize,
1669 Deserialize,
1670 JsonSchema,
1671 EnumIter,
1672)]
1673pub enum ErrorCode {
1674 SubnetOversubscribed = 101,
1676 MaxNumberOfCanistersReached = 102,
1677 CanisterQueueFull = 201,
1679 IngressMessageTimeout = 202,
1680 CanisterQueueNotEmpty = 203,
1681 IngressHistoryFull = 204,
1682 CanisterIdAlreadyExists = 205,
1683 StopCanisterRequestTimeout = 206,
1684 CanisterOutOfCycles = 207,
1685 CertifiedStateUnavailable = 208,
1686 CanisterInstallCodeRateLimited = 209,
1687 CanisterHeapDeltaRateLimited = 210,
1688 CanisterNotFound = 301,
1690 CanisterSnapshotNotFound = 305,
1691 InsufficientMemoryAllocation = 402,
1693 InsufficientCyclesForCreateCanister = 403,
1694 SubnetNotFound = 404,
1695 CanisterNotHostedBySubnet = 405,
1696 CanisterRejectedMessage = 406,
1697 UnknownManagementMessage = 407,
1698 InvalidManagementPayload = 408,
1699 CanisterSnapshotImmutable = 409,
1700 CanisterTrapped = 502,
1702 CanisterCalledTrap = 503,
1703 CanisterContractViolation = 504,
1704 CanisterInvalidWasm = 505,
1705 CanisterDidNotReply = 506,
1706 CanisterOutOfMemory = 507,
1707 CanisterStopped = 508,
1708 CanisterStopping = 509,
1709 CanisterNotStopped = 510,
1710 CanisterStoppingCancelled = 511,
1711 CanisterInvalidController = 512,
1712 CanisterFunctionNotFound = 513,
1713 CanisterNonEmpty = 514,
1714 QueryCallGraphLoopDetected = 517,
1715 InsufficientCyclesInCall = 520,
1716 CanisterWasmEngineError = 521,
1717 CanisterInstructionLimitExceeded = 522,
1718 CanisterMemoryAccessLimitExceeded = 524,
1719 QueryCallGraphTooDeep = 525,
1720 QueryCallGraphTotalInstructionLimitExceeded = 526,
1721 CompositeQueryCalledInReplicatedMode = 527,
1722 QueryTimeLimitExceeded = 528,
1723 QueryCallGraphInternal = 529,
1724 InsufficientCyclesInComputeAllocation = 530,
1725 InsufficientCyclesInMemoryAllocation = 531,
1726 InsufficientCyclesInMemoryGrow = 532,
1727 ReservedCyclesLimitExceededInMemoryAllocation = 533,
1728 ReservedCyclesLimitExceededInMemoryGrow = 534,
1729 InsufficientCyclesInMessageMemoryGrow = 535,
1730 CanisterMethodNotFound = 536,
1731 CanisterWasmModuleNotFound = 537,
1732 CanisterAlreadyInstalled = 538,
1733 CanisterWasmMemoryLimitExceeded = 539,
1734 ReservedCyclesLimitIsTooLow = 540,
1735 DeadlineExpired = 601,
1737 ResponseDropped = 602,
1738}
1739
1740impl TryFrom<u64> for ErrorCode {
1741 type Error = TryFromError;
1742 fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1743 match err {
1744 101 => Ok(ErrorCode::SubnetOversubscribed),
1746 102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1747 201 => Ok(ErrorCode::CanisterQueueFull),
1749 202 => Ok(ErrorCode::IngressMessageTimeout),
1750 203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1751 204 => Ok(ErrorCode::IngressHistoryFull),
1752 205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1753 206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1754 207 => Ok(ErrorCode::CanisterOutOfCycles),
1755 208 => Ok(ErrorCode::CertifiedStateUnavailable),
1756 209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1757 210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1758 301 => Ok(ErrorCode::CanisterNotFound),
1760 305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1761 402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1763 403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1764 404 => Ok(ErrorCode::SubnetNotFound),
1765 405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1766 406 => Ok(ErrorCode::CanisterRejectedMessage),
1767 407 => Ok(ErrorCode::UnknownManagementMessage),
1768 408 => Ok(ErrorCode::InvalidManagementPayload),
1769 409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1770 502 => Ok(ErrorCode::CanisterTrapped),
1772 503 => Ok(ErrorCode::CanisterCalledTrap),
1773 504 => Ok(ErrorCode::CanisterContractViolation),
1774 505 => Ok(ErrorCode::CanisterInvalidWasm),
1775 506 => Ok(ErrorCode::CanisterDidNotReply),
1776 507 => Ok(ErrorCode::CanisterOutOfMemory),
1777 508 => Ok(ErrorCode::CanisterStopped),
1778 509 => Ok(ErrorCode::CanisterStopping),
1779 510 => Ok(ErrorCode::CanisterNotStopped),
1780 511 => Ok(ErrorCode::CanisterStoppingCancelled),
1781 512 => Ok(ErrorCode::CanisterInvalidController),
1782 513 => Ok(ErrorCode::CanisterFunctionNotFound),
1783 514 => Ok(ErrorCode::CanisterNonEmpty),
1784 517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
1785 520 => Ok(ErrorCode::InsufficientCyclesInCall),
1786 521 => Ok(ErrorCode::CanisterWasmEngineError),
1787 522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
1788 524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
1789 525 => Ok(ErrorCode::QueryCallGraphTooDeep),
1790 526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
1791 527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
1792 528 => Ok(ErrorCode::QueryTimeLimitExceeded),
1793 529 => Ok(ErrorCode::QueryCallGraphInternal),
1794 530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
1795 531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
1796 532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
1797 533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
1798 534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
1799 535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
1800 536 => Ok(ErrorCode::CanisterMethodNotFound),
1801 537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
1802 538 => Ok(ErrorCode::CanisterAlreadyInstalled),
1803 539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
1804 540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
1805 601 => Ok(ErrorCode::DeadlineExpired),
1807 602 => Ok(ErrorCode::ResponseDropped),
1808 _ => Err(TryFromError::ValueOutOfRange(err)),
1809 }
1810 }
1811}
1812
1813impl std::fmt::Display for ErrorCode {
1814 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1815 write!(f, "IC{:04}", *self as i32)
1817 }
1818}
1819
1820#[derive(
1825 PartialOrd,
1826 Ord,
1827 Clone,
1828 Copy,
1829 Debug,
1830 PartialEq,
1831 Eq,
1832 Hash,
1833 Serialize,
1834 Deserialize,
1835 JsonSchema,
1836 EnumIter,
1837)]
1838pub enum RejectCode {
1839 SysFatal = 1,
1840 SysTransient = 2,
1841 DestinationInvalid = 3,
1842 CanisterReject = 4,
1843 CanisterError = 5,
1844 SysUnknown = 6,
1845}
1846
1847impl TryFrom<u64> for RejectCode {
1848 type Error = TryFromError;
1849 fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
1850 match err {
1851 1 => Ok(RejectCode::SysFatal),
1852 2 => Ok(RejectCode::SysTransient),
1853 3 => Ok(RejectCode::DestinationInvalid),
1854 4 => Ok(RejectCode::CanisterReject),
1855 5 => Ok(RejectCode::CanisterError),
1856 6 => Ok(RejectCode::SysUnknown),
1857 _ => Err(TryFromError::ValueOutOfRange(err)),
1858 }
1859 }
1860}
1861
1862#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1864pub struct RejectResponse {
1865 pub reject_code: RejectCode,
1866 pub reject_message: String,
1867 pub error_code: ErrorCode,
1868 pub certified: bool,
1869}
1870
1871impl std::fmt::Display for RejectResponse {
1872 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1873 write!(
1875 f,
1876 "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
1877 self.reject_code, self.reject_message, self.error_code
1878 )
1879 }
1880}
1881
1882#[derive(Debug, Serialize, Deserialize)]
1888pub enum IngressStatusResult {
1889 NotAvailable,
1890 Forbidden(String),
1891 Success(Result<Vec<u8>, RejectResponse>),
1892}
1893
1894#[cfg(windows)]
1895fn wsl_path(path: &PathBuf, desc: &str) -> String {
1896 windows_to_wsl(
1897 path.as_os_str()
1898 .to_str()
1899 .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
1900 )
1901 .unwrap_or_else(|e| {
1902 panic!(
1903 "Could not convert {} path ({:?}) to WSL path: {:?}",
1904 desc, path, e
1905 )
1906 })
1907}
1908
1909#[cfg(windows)]
1910fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1911 let mut cmd = Command::new("wsl");
1912 cmd.arg(wsl_path(bin_path, "PocketIC binary"));
1913 cmd
1914}
1915
1916#[cfg(not(windows))]
1917fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1918 Command::new(bin_path)
1919}
1920
1921fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
1922 let unexpected_version = format!(
1923 "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
1924 );
1925 let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
1926 return Err(unexpected_version);
1927 };
1928 if pocket_ic_server != POCKET_IC_SERVER_NAME {
1929 return Err(unexpected_version);
1930 }
1931 let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
1932 let version = Version::parse(version)
1933 .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
1934 if !req.matches(&version) {
1935 return Err(format!(
1936 "Incompatible PocketIC server version: got {version}; expected {req}."
1937 ));
1938 }
1939
1940 Ok(())
1941}
1942
1943fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
1944 let mut cmd = pocket_ic_server_cmd(server_binary);
1945 cmd.arg("--version");
1946 let version = cmd.output().map_err(|e| e.to_string())?.stdout;
1947 let version_str = String::from_utf8(version)
1948 .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
1949 let version_line = version_str.trim_end_matches('\n');
1950 check_pocketic_server_version(version_line)
1951}
1952
1953async fn download_pocketic_server(
1954 server_url: String,
1955 mut out: std::fs::File,
1956) -> Result<(), String> {
1957 let binary = reqwest::get(server_url)
1958 .await
1959 .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
1960 .bytes()
1961 .await
1962 .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
1963 .to_vec();
1964 let mut gz = GzDecoder::new(&binary[..]);
1965 let _ = std::io::copy(&mut gz, &mut out)
1966 .map_err(|e| format!("Failed to write PocketIC server binary: {e}"));
1967 Ok(())
1968}
1969
1970#[derive(Default)]
1971pub struct StartServerParams {
1972 pub server_binary: Option<PathBuf>,
1973 pub reuse: bool,
1975 pub ttl: Option<Duration>,
1982}
1983
1984pub async fn start_server(params: StartServerParams) -> (Child, Url) {
1986 let default_bin_dir =
1987 std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
1988 let default_bin_path = default_bin_dir.join("pocket-ic");
1989 let bin_path_provided =
1990 params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
1991 let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
1992 std::env::var_os("POCKET_IC_BIN")
1993 .unwrap_or_else(|| default_bin_path.clone().into())
1994 .into()
1995 });
1996
1997 if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
1998 if bin_path_provided {
1999 panic!(
2000 "Failed to validate PocketIC server binary `{}`: `{}`.",
2001 bin_path.display(),
2002 e
2003 );
2004 }
2005 bin_path = default_bin_path.clone();
2006 std::fs::create_dir_all(&default_bin_dir)
2007 .expect("Failed to create PocketIC server directory");
2008 let mut options = OpenOptions::new();
2009 options.write(true).create_new(true);
2010 #[cfg(unix)]
2011 options.mode(0o777);
2012 match options.open(&default_bin_path) {
2013 Ok(out) => {
2014 #[cfg(target_os = "macos")]
2015 let os = "darwin";
2016 #[cfg(not(target_os = "macos"))]
2017 let os = "linux";
2018 #[cfg(target_arch = "aarch64")]
2019 let arch = "arm64";
2020 #[cfg(not(target_arch = "aarch64"))]
2021 let arch = "x86_64";
2022 let server_url = format!(
2023 "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2024 );
2025 println!(
2026 "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.",
2027 bin_path.display(),
2028 e,
2029 LATEST_SERVER_VERSION,
2030 server_url,
2031 default_bin_path.display(),
2032 LATEST_SERVER_VERSION
2033 );
2034 if let Err(e) = download_pocketic_server(server_url, out).await {
2035 let _ = std::fs::remove_file(default_bin_path);
2036 panic!("{}", e);
2037 }
2038 }
2039 _ => {
2040 let start = std::time::Instant::now();
2042 loop {
2043 if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2044 break;
2045 }
2046 if start.elapsed() > std::time::Duration::from_secs(60) {
2047 let _ = std::fs::remove_file(&default_bin_path);
2048 panic!(
2049 "Timed out waiting for PocketIC server being available at the local path {}.",
2050 default_bin_path.display()
2051 );
2052 }
2053 std::thread::sleep(std::time::Duration::from_millis(100));
2054 }
2055 }
2056 }
2057 }
2058
2059 let port_file_path = if params.reuse {
2060 let test_driver_pid = std::process::id();
2063 std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2064 } else {
2065 NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
2066 };
2067 let mut cmd = pocket_ic_server_cmd(&bin_path);
2068 if let Some(ttl) = params.ttl {
2069 cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2070 }
2071 cmd.arg("--port-file");
2072 #[cfg(windows)]
2073 cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2074 #[cfg(not(windows))]
2075 cmd.arg(port_file_path.clone());
2076 if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2077 && !mute_server.is_empty()
2078 {
2079 cmd.stdout(std::process::Stdio::null());
2080 cmd.stderr(std::process::Stdio::null());
2081 }
2082
2083 #[cfg(unix)]
2086 {
2087 use std::os::unix::process::CommandExt;
2088 cmd.process_group(0);
2089 }
2090
2091 #[allow(clippy::zombie_processes)]
2093 let child = cmd
2094 .spawn()
2095 .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
2096
2097 loop {
2098 if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2099 && port_string.contains("\n")
2100 {
2101 let port: u16 = port_string
2102 .trim_end()
2103 .parse()
2104 .expect("Failed to parse port to number");
2105 break (
2106 child,
2107 Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2108 );
2109 }
2110 std::thread::sleep(Duration::from_millis(20));
2111 }
2112}
2113
2114#[derive(Error, Debug)]
2115pub enum DefaultEffectiveCanisterIdError {
2116 ReqwestError(#[from] reqwest::Error),
2117 JsonError(#[from] serde_json::Error),
2118 Utf8Error(#[from] std::string::FromUtf8Error),
2119}
2120
2121impl std::fmt::Display for DefaultEffectiveCanisterIdError {
2122 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2123 match self {
2124 DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2125 write!(f, "ReqwestError({err})")
2126 }
2127 DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({err})"),
2128 DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({err})"),
2129 }
2130 }
2131}
2132
2133pub fn get_default_effective_canister_id(
2141 pocket_ic_url: String,
2142) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2143 let runtime = Runtime::new().expect("Unable to create a runtime");
2144 runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2145 pocket_ic_url,
2146 ))
2147}
2148
2149pub fn copy_dir(
2150 src: impl AsRef<std::path::Path>,
2151 dst: impl AsRef<std::path::Path>,
2152) -> std::io::Result<()> {
2153 std::fs::create_dir_all(&dst)?;
2154 for entry in std::fs::read_dir(src)? {
2155 let entry = entry?;
2156 let ty = entry.file_type()?;
2157 if ty.is_dir() {
2158 copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2159 } else {
2160 std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2161 }
2162 }
2163 Ok(())
2164}
2165
2166#[cfg(test)]
2167mod test {
2168 use crate::{ErrorCode, RejectCode, check_pocketic_server_version};
2169 use strum::IntoEnumIterator;
2170
2171 #[test]
2172 fn reject_code_round_trip() {
2173 for initial in RejectCode::iter() {
2174 let round_trip = RejectCode::try_from(initial as u64).unwrap();
2175
2176 assert_eq!(initial, round_trip);
2177 }
2178 }
2179
2180 #[test]
2181 fn error_code_round_trip() {
2182 for initial in ErrorCode::iter() {
2183 let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2184
2185 assert_eq!(initial, round_trip);
2186 }
2187 }
2188
2189 #[test]
2190 fn reject_code_matches_ic_error_code() {
2191 assert_eq!(
2192 RejectCode::iter().len(),
2193 ic_error_types::RejectCode::iter().len()
2194 );
2195 for ic_reject_code in ic_error_types::RejectCode::iter() {
2196 let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2197 assert_eq!(format!("{reject_code:?}"), format!("{:?}", ic_reject_code));
2198 }
2199 }
2200
2201 #[test]
2202 fn error_code_matches_ic_error_code() {
2203 assert_eq!(
2204 ErrorCode::iter().len(),
2205 ic_error_types::ErrorCode::iter().len()
2206 );
2207 for ic_error_code in ic_error_types::ErrorCode::iter() {
2208 let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2209 assert_eq!(format!("{error_code:?}"), format!("{:?}", ic_error_code));
2210 }
2211 }
2212
2213 #[test]
2214 fn test_check_pocketic_server_version() {
2215 assert!(
2216 check_pocketic_server_version("pocket-ic-server")
2217 .unwrap_err()
2218 .contains("Unexpected PocketIC server version")
2219 );
2220 assert!(
2221 check_pocketic_server_version("pocket-ic 11.0.0")
2222 .unwrap_err()
2223 .contains("Unexpected PocketIC server version")
2224 );
2225 assert!(
2226 check_pocketic_server_version("pocket-ic-server 11 0 0")
2227 .unwrap_err()
2228 .contains("Failed to parse PocketIC server version")
2229 );
2230 assert!(
2231 check_pocketic_server_version("pocket-ic-server 10.0.0")
2232 .unwrap_err()
2233 .contains("Incompatible PocketIC server version")
2234 );
2235 check_pocketic_server_version("pocket-ic-server 11.0.0").unwrap();
2236 check_pocketic_server_version("pocket-ic-server 11.0.1").unwrap();
2237 check_pocketic_server_version("pocket-ic-server 11.1.0").unwrap();
2238 assert!(
2239 check_pocketic_server_version("pocket-ic-server 12.0.0")
2240 .unwrap_err()
2241 .contains("Incompatible PocketIC server version")
2242 );
2243 }
2244}