Skip to main content

pocket_ic/
lib.rs

1#![allow(clippy::test_attr_in_doctest)]
2#![doc = include_str!("../README.md")]
3/// # PocketIC: A Canister Testing Platform
4///
5/// PocketIC is the local canister smart contract testing platform for the [Internet Computer](https://internetcomputer.org/).
6///
7/// It consists of the PocketIC server, which can run many independent IC instances, and a client library (this crate), which provides an interface to your IC instances.
8///
9/// With PocketIC, testing canisters is as simple as calling rust functions. Here is a minimal example:
10///
11/// ```rust
12/// use candid::{Principal, encode_one};
13/// use pocket_ic::PocketIc;
14///
15/// // 2T cycles
16/// const INIT_CYCLES: u128 = 2_000_000_000_000;
17///
18/// // Create a counter canister and charge it with 2T cycles.
19/// fn deploy_counter_canister(pic: &PocketIc) -> Principal {
20///     let canister_id = pic.create_canister();
21///     pic.add_cycles(canister_id, INIT_CYCLES);
22///     let counter_wasm = todo!();
23///     pic.install_canister(canister_id, counter_wasm, vec![], None);
24///     canister_id
25/// }
26///
27/// // Call a method on the counter canister as the anonymous principal.
28/// fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
29///     pic.update_call(
30///         canister_id,
31///         Principal::anonymous(),
32///         method,
33///         encode_one(()).unwrap(),
34///     )
35///     .expect("Failed to call counter canister")
36/// }
37///
38/// #[test]
39/// fn test_counter_canister() {
40///     let pic = PocketIc::new();
41///     let canister_id = deploy_counter_canister(&pic);
42///
43///     // Make some calls to the counter canister.
44///     let reply = call_counter_canister(&pic, canister_id, "read");
45///     assert_eq!(reply, vec![0, 0, 0, 0]);
46///     let reply = call_counter_canister(&pic, canister_id, "write");
47///     assert_eq!(reply, vec![1, 0, 0, 0]);
48///     let reply = call_counter_canister(&pic, canister_id, "write");
49///     assert_eq!(reply, vec![2, 0, 0, 0]);
50///     let reply = call_counter_canister(&pic, canister_id, "read");
51///     assert_eq!(reply, vec![2, 0, 0, 0]);
52/// }
53/// ```
54/// For more information, see the [README](https://crates.io/crates/pocket-ic).
55///
56use crate::{
57    common::rest::{
58        AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet,
59        HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceHttpGatewayConfig, InstanceId,
60        MockCanisterHttpResponse, RawEffectivePrincipal, RawMessageId, RawSenderInfo,
61        RawSubnetBlockmakers, RawTickConfigs, RawTime, SubnetId, SubnetKind, SubnetSpec, Topology,
62    },
63    nonblocking::PocketIc as PocketIcAsync,
64};
65use candid::{
66    Principal, decode_args, encode_args,
67    utils::{ArgumentDecoder, ArgumentEncoder},
68};
69use flate2::read::GzDecoder;
70pub use ic_management_canister_types::{
71    CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
72    EnvironmentVariable, Snapshot,
73};
74pub use ic_transport_types::SubnetMetrics;
75use reqwest::Url;
76use schemars::JsonSchema;
77use semver::{Version, VersionReq};
78use serde::{Deserialize, Serialize};
79use slog::Level;
80#[cfg(unix)]
81use std::os::unix::fs::OpenOptionsExt;
82#[cfg(windows)]
83use std::sync::Once;
84use std::{
85    fs::OpenOptions,
86    net::{IpAddr, SocketAddr},
87    path::PathBuf,
88    process::{Child, Command},
89    sync::{Arc, mpsc::channel},
90    thread,
91    thread::JoinHandle,
92    time::{Duration, SystemTime, UNIX_EPOCH},
93};
94use strum_macros::EnumIter;
95use tempfile::{NamedTempFile, TempDir};
96use thiserror::Error;
97use tokio::runtime::Runtime;
98use tracing::{instrument, warn};
99#[cfg(windows)]
100use wslpath::windows_to_wsl;
101
102pub mod common;
103pub mod nonblocking;
104
105const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
106
107const MIN_SERVER_VERSION: &str = "15.0.0";
108const MAX_SERVER_VERSION: &str = "16";
109
110/// Public to facilitate downloading the PocketIC server.
111pub const LATEST_SERVER_VERSION: &str = "15.0.0";
112
113// the default timeout of a PocketIC operation
114const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
115
116const LOCALHOST: &str = "127.0.0.1";
117
118enum PocketIcStateKind {
119    /// A persistent state dir managed by the user.
120    StateDir(PathBuf),
121    /// A fresh temporary directory used if the user does not provide
122    /// a persistent state directory managed by the user.
123    /// The temporary directory is deleted when `PocketIcState` is dropped
124    /// unless `PocketIcState` is turned into a persistent state
125    /// at the path given by `PocketIcState::into_path`.
126    TempDir(TempDir),
127}
128
129pub struct PocketIcState {
130    state: PocketIcStateKind,
131}
132
133impl PocketIcState {
134    #[allow(clippy::new_without_default)]
135    pub fn new() -> Self {
136        let temp_dir = TempDir::new().unwrap();
137        Self {
138            state: PocketIcStateKind::TempDir(temp_dir),
139        }
140    }
141
142    pub fn new_from_path(state_dir: PathBuf) -> Self {
143        Self {
144            state: PocketIcStateKind::StateDir(state_dir),
145        }
146    }
147
148    pub fn into_path(self) -> PathBuf {
149        match self.state {
150            PocketIcStateKind::StateDir(state_dir) => state_dir,
151            PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
152        }
153    }
154
155    pub(crate) fn state_dir(&self) -> PathBuf {
156        match &self.state {
157            PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
158            PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
159        }
160    }
161}
162
163pub struct PocketIcBuilder {
164    config: Option<ExtendedSubnetConfigSet>,
165    http_gateway_config: Option<InstanceHttpGatewayConfig>,
166    server_binary: Option<PathBuf>,
167    server_url: Option<Url>,
168    max_request_time_ms: Option<u64>,
169    read_only_state_dir: Option<PathBuf>,
170    state_dir: Option<PocketIcState>,
171    icp_config: IcpConfig,
172    log_level: Option<Level>,
173    bitcoind_addr: Option<Vec<SocketAddr>>,
174    dogecoind_addr: Option<Vec<SocketAddr>>,
175    icp_features: IcpFeatures,
176    initial_time: Option<InitialTime>,
177    mainnet_nns_subnet_id: Option<bool>,
178    disable_ingress_validation: Option<bool>,
179}
180
181#[allow(clippy::new_without_default)]
182impl PocketIcBuilder {
183    pub fn new() -> Self {
184        Self {
185            config: None,
186            http_gateway_config: None,
187            server_binary: None,
188            server_url: None,
189            max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
190            read_only_state_dir: None,
191            state_dir: None,
192            icp_config: IcpConfig::default(),
193            log_level: None,
194            bitcoind_addr: None,
195            dogecoind_addr: None,
196            icp_features: IcpFeatures::default(),
197            initial_time: None,
198            mainnet_nns_subnet_id: None,
199            disable_ingress_validation: None,
200        }
201    }
202
203    pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
204        let mut builder = Self::new();
205        builder.config = Some(config.into());
206        builder
207    }
208
209    pub fn build(self) -> PocketIc {
210        PocketIc::from_components(
211            self.config.unwrap_or_default(),
212            self.server_url,
213            self.server_binary,
214            self.max_request_time_ms,
215            self.read_only_state_dir,
216            self.state_dir,
217            self.icp_config,
218            self.log_level,
219            self.bitcoind_addr,
220            self.dogecoind_addr,
221            self.icp_features,
222            self.initial_time,
223            self.http_gateway_config,
224            self.mainnet_nns_subnet_id,
225            self.disable_ingress_validation,
226        )
227    }
228
229    pub async fn build_async(self) -> PocketIcAsync {
230        PocketIcAsync::from_components(
231            self.config.unwrap_or_default(),
232            self.server_url,
233            self.server_binary,
234            self.max_request_time_ms,
235            self.read_only_state_dir,
236            self.state_dir,
237            self.icp_config,
238            self.log_level,
239            self.bitcoind_addr,
240            self.dogecoind_addr,
241            self.icp_features,
242            self.initial_time,
243            self.http_gateway_config,
244            self.mainnet_nns_subnet_id,
245            self.disable_ingress_validation,
246        )
247        .await
248    }
249
250    /// Provide the path to the PocketIC server binary used instead of the environment variable `POCKET_IC_BIN`.
251    pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
252        self.server_binary = Some(server_binary);
253        self
254    }
255
256    /// Use an already running PocketIC server.
257    pub fn with_server_url(mut self, server_url: Url) -> Self {
258        self.server_url = Some(server_url);
259        self
260    }
261
262    pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
263        self.max_request_time_ms = max_request_time_ms;
264        self
265    }
266
267    pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
268        self.state_dir = Some(PocketIcState::new_from_path(state_dir));
269        self
270    }
271
272    pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
273        self.state_dir = Some(state_dir);
274        self
275    }
276
277    pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
278        self.read_only_state_dir = Some(read_only_state_dir.state_dir());
279        self
280    }
281
282    pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
283        self.icp_config = icp_config;
284        self
285    }
286
287    pub fn with_log_level(mut self, log_level: Level) -> Self {
288        self.log_level = Some(log_level);
289        self
290    }
291
292    pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
293        self.with_bitcoind_addrs(vec![bitcoind_addr])
294    }
295
296    pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
297        Self {
298            bitcoind_addr: Some(bitcoind_addrs),
299            ..self
300        }
301    }
302
303    pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
304        Self {
305            dogecoind_addr: Some(dogecoind_addrs),
306            ..self
307        }
308    }
309
310    /// Add an empty NNS subnet unless an NNS subnet has already been added.
311    pub fn with_nns_subnet(mut self) -> Self {
312        let mut config = self.config.unwrap_or_default();
313        config.nns = Some(config.nns.unwrap_or_default());
314        self.config = Some(config);
315        self
316    }
317
318    /// Add an NNS subnet with state loaded from the given state directory.
319    /// Note that the provided path must be accessible for the PocketIC server process.
320    ///
321    /// `path_to_state` should lead to a directory which is expected to have
322    /// the following structure:
323    ///
324    /// path_to_state/
325    ///  |-- backups
326    ///  |-- checkpoints
327    ///  |-- diverged_checkpoints
328    ///  |-- diverged_state_markers
329    ///  |-- fs_tmp
330    ///  |-- page_deltas
331    ///  |-- states_metadata.pbuf
332    ///  |-- tip
333    ///  `-- tmp
334    pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
335        self.with_subnet_state(SubnetKind::NNS, path_to_state)
336    }
337
338    /// Add a subnet with state loaded from the given state directory.
339    /// Note that the provided path must be accessible for the PocketIC server process.
340    ///
341    /// `path_to_state` should point to a directory which is expected to have
342    /// the following structure:
343    ///
344    /// path_to_state/
345    ///  |-- backups
346    ///  |-- checkpoints
347    ///  |-- diverged_checkpoints
348    ///  |-- diverged_state_markers
349    ///  |-- fs_tmp
350    ///  |-- page_deltas
351    ///  |-- states_metadata.pbuf
352    ///  |-- tip
353    ///  `-- tmp
354    pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
355        let mut config = self.config.unwrap_or_default();
356        #[cfg(not(windows))]
357        let state_dir = path_to_state;
358        #[cfg(windows)]
359        let state_dir = wsl_path(&path_to_state, "subnet state").into();
360        let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
361        match subnet_kind {
362            SubnetKind::NNS => config.nns = Some(subnet_spec),
363            SubnetKind::SNS => config.sns = Some(subnet_spec),
364            SubnetKind::II => config.ii = Some(subnet_spec),
365            SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
366            SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
367            SubnetKind::TestThresholdKeys => config.test_threshold_keys = Some(subnet_spec),
368            SubnetKind::Application => config.application.push(subnet_spec),
369            SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
370            SubnetKind::System => config.system.push(subnet_spec),
371            SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
372        };
373        self.config = Some(config);
374        self
375    }
376
377    /// Add an empty sns subnet unless an SNS subnet has already been added.
378    pub fn with_sns_subnet(mut self) -> Self {
379        let mut config = self.config.unwrap_or_default();
380        config.sns = Some(config.sns.unwrap_or_default());
381        self.config = Some(config);
382        self
383    }
384
385    /// Add an empty II subnet unless an II subnet has already been added.
386    pub fn with_ii_subnet(mut self) -> Self {
387        let mut config = self.config.unwrap_or_default();
388        config.ii = Some(config.ii.unwrap_or_default());
389        self.config = Some(config);
390        self
391    }
392
393    /// Add an empty fiduciary subnet unless a fiduciary subnet has already been added.
394    pub fn with_fiduciary_subnet(mut self) -> Self {
395        let mut config = self.config.unwrap_or_default();
396        config.fiduciary = Some(config.fiduciary.unwrap_or_default());
397        self.config = Some(config);
398        self
399    }
400
401    /// Add an empty bitcoin subnet unless a bitcoin subnet has already been added.
402    pub fn with_bitcoin_subnet(mut self) -> Self {
403        let mut config = self.config.unwrap_or_default();
404        config.bitcoin = Some(config.bitcoin.unwrap_or_default());
405        self.config = Some(config);
406        self
407    }
408
409    /// Add an empty test threshold keys subnet unless a test threshold keys subnet has already been added.
410    pub fn with_test_threshold_keys_subnet(mut self) -> Self {
411        let mut config = self.config.unwrap_or_default();
412        config.test_threshold_keys = Some(config.test_threshold_keys.unwrap_or_default());
413        self.config = Some(config);
414        self
415    }
416
417    /// Add an empty generic system subnet.
418    pub fn with_system_subnet(mut self) -> Self {
419        let mut config = self.config.unwrap_or_default();
420        config.system.push(SubnetSpec::default());
421        self.config = Some(config);
422        self
423    }
424
425    /// Add an empty generic application subnet.
426    pub fn with_application_subnet(mut self) -> Self {
427        let mut config = self.config.unwrap_or_default();
428        config.application.push(SubnetSpec::default());
429        self.config = Some(config);
430        self
431    }
432
433    /// Add an empty generic verified application subnet.
434    pub fn with_verified_application_subnet(mut self) -> Self {
435        let mut config = self.config.unwrap_or_default();
436        config.verified_application.push(SubnetSpec::default());
437        self.config = Some(config);
438        self
439    }
440
441    /// Add an empty generic application subnet with benchmarking instruction configuration.
442    pub fn with_benchmarking_application_subnet(mut self) -> Self {
443        let mut config = self.config.unwrap_or_default();
444        config
445            .application
446            .push(SubnetSpec::default().with_benchmarking_instruction_config());
447        self.config = Some(config);
448        self
449    }
450
451    /// Add an empty generic system subnet with benchmarking instruction configuration.
452    pub fn with_benchmarking_system_subnet(mut self) -> Self {
453        let mut config = self.config.unwrap_or_default();
454        config
455            .system
456            .push(SubnetSpec::default().with_benchmarking_instruction_config());
457        self.config = Some(config);
458        self
459    }
460
461    /// Enables selected ICP features supported by PocketIC and implemented by system canisters
462    /// (deployed to the PocketIC instance automatically when creating a new PocketIC instance).
463    /// Subnets to which the system canisters are deployed are automatically declared as empty subnets,
464    /// e.g., `PocketIcBuilder::with_nns_subnet` is implicitly implied by specifying the `icp_token` feature.
465    pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
466        self.icp_features = icp_features;
467        self
468    }
469
470    /// Sets the initial timestamp of the new instance to the provided value which must be at least
471    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
472    /// - 06 May 2021 21:17:10 CEST otherwise.
473    #[deprecated(note = "Use `with_initial_time` instead")]
474    pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
475        self.initial_time = Some(InitialTime::Timestamp(RawTime {
476            nanos_since_epoch: initial_timestamp_nanos,
477        }));
478        self
479    }
480
481    /// Sets the initial time of the new instance to the provided value which must be at least
482    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
483    /// - 06 May 2021 21:17:10 CEST otherwise.
484    pub fn with_initial_time(mut self, initial_time: Time) -> Self {
485        self.initial_time = Some(InitialTime::Timestamp(RawTime {
486            nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
487        }));
488        self
489    }
490
491    /// Configures the new instance to make progress automatically,
492    /// i.e., periodically update the time of the IC instance
493    /// to the real time and execute rounds on the subnets.
494    pub fn with_auto_progress(mut self) -> Self {
495        let config = AutoProgressConfig {
496            artificial_delay_ms: None,
497        };
498        self.initial_time = Some(InitialTime::AutoProgress(config));
499        self
500    }
501
502    pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
503        self.http_gateway_config = Some(http_gateway_config);
504        self
505    }
506
507    pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
508        self.mainnet_nns_subnet_id = Some(true);
509        self
510    }
511
512    pub fn disable_ingress_validation(mut self) -> Self {
513        self.disable_ingress_validation = Some(true);
514        self
515    }
516}
517
518/// Representation of system time as duration since UNIX epoch
519/// with cross-platform nanosecond precision.
520#[derive(Copy, Clone, PartialEq, PartialOrd)]
521pub struct Time(Duration);
522
523impl Time {
524    /// Number of nanoseconds since UNIX EPOCH.
525    pub fn as_nanos_since_unix_epoch(&self) -> u64 {
526        self.0.as_nanos().try_into().unwrap()
527    }
528
529    pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
530        Time(Duration::from_nanos(nanos))
531    }
532}
533
534impl std::fmt::Debug for Time {
535    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536        let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
537        write!(f, "{nanos_since_unix_epoch}")
538    }
539}
540
541impl std::ops::Add<Duration> for Time {
542    type Output = Time;
543    fn add(self, dur: Duration) -> Time {
544        Time(self.0 + dur)
545    }
546}
547
548impl From<SystemTime> for Time {
549    fn from(time: SystemTime) -> Self {
550        Self::from_nanos_since_unix_epoch(
551            time.duration_since(UNIX_EPOCH)
552                .unwrap()
553                .as_nanos()
554                .try_into()
555                .unwrap(),
556        )
557    }
558}
559
560impl TryFrom<Time> for SystemTime {
561    type Error = String;
562
563    fn try_from(time: Time) -> Result<SystemTime, String> {
564        let nanos = time.as_nanos_since_unix_epoch();
565        let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
566        let roundtrip: Time = system_time.into();
567        if roundtrip.as_nanos_since_unix_epoch() == nanos {
568            Ok(system_time)
569        } else {
570            Err(format!(
571                "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
572            ))
573        }
574    }
575}
576
577/// Specifies where to place a newly created canister.
578#[derive(Clone, Debug)]
579pub enum CreateCanisterPlacement {
580    /// Place the canister on the given subnet.
581    SubnetId(SubnetId),
582    /// Create the canister with the given specific canister ID.
583    CanisterId(CanisterId),
584}
585
586/// Parameters for [`PocketIc::create_canister_with_params`].
587#[derive(Clone, Debug, Default)]
588pub struct CreateCanisterParams {
589    /// Initial cycles balance; defaults to 100T if `None`.
590    pub cycles: Option<u128>,
591    /// Canister settings; defaults to default canister settings if `None`.
592    pub settings: Option<CanisterSettings>,
593    /// Canister placement (subnet or specific canister ID); a random application subnet is chosen if `None`.
594    pub placement: Option<CreateCanisterPlacement>,
595}
596
597/// Main entry point for interacting with PocketIC.
598pub struct PocketIc {
599    pocket_ic: PocketIcAsync,
600    runtime: Arc<tokio::runtime::Runtime>,
601    thread: Option<JoinHandle<()>>,
602}
603
604impl PocketIc {
605    /// Creates a new PocketIC instance with a single application subnet on the server.
606    /// The server is started if it's not already running.
607    pub fn new() -> Self {
608        PocketIcBuilder::new().with_application_subnet().build()
609    }
610
611    /// Creates a PocketIC handle to an existing instance on a running server.
612    /// Note that this handle does not extend the lifetime of the existing instance,
613    /// i.e., the existing instance is deleted and this handle stops working
614    /// when the PocketIC handle that created the existing instance is dropped.
615    pub fn new_from_existing_instance(
616        server_url: Url,
617        instance_id: InstanceId,
618        max_request_time_ms: Option<u64>,
619    ) -> Self {
620        let (tx, rx) = channel();
621        let thread = thread::spawn(move || {
622            let rt = tokio::runtime::Builder::new_current_thread()
623                .enable_all()
624                .build()
625                .unwrap();
626            tx.send(rt).unwrap();
627        });
628        let runtime = rx.recv().unwrap();
629
630        let pocket_ic =
631            PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
632
633        Self {
634            pocket_ic,
635            runtime: Arc::new(runtime),
636            thread: Some(thread),
637        }
638    }
639
640    #[allow(clippy::too_many_arguments)]
641    pub(crate) fn from_components(
642        subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
643        server_url: Option<Url>,
644        server_binary: Option<PathBuf>,
645        max_request_time_ms: Option<u64>,
646        read_only_state_dir: Option<PathBuf>,
647        state_dir: Option<PocketIcState>,
648        icp_config: IcpConfig,
649        log_level: Option<Level>,
650        bitcoind_addr: Option<Vec<SocketAddr>>,
651        dogecoind_addr: Option<Vec<SocketAddr>>,
652        icp_features: IcpFeatures,
653        initial_time: Option<InitialTime>,
654        http_gateway_config: Option<InstanceHttpGatewayConfig>,
655        mainnet_nns_subnet_id: Option<bool>,
656        disable_ingress_validation: Option<bool>,
657    ) -> Self {
658        let (tx, rx) = channel();
659        let thread = thread::spawn(move || {
660            let rt = tokio::runtime::Builder::new_current_thread()
661                .enable_all()
662                .build()
663                .unwrap();
664            tx.send(rt).unwrap();
665        });
666        let runtime = rx.recv().unwrap();
667
668        let pocket_ic = runtime.block_on(async {
669            PocketIcAsync::from_components(
670                subnet_config_set,
671                server_url,
672                server_binary,
673                max_request_time_ms,
674                read_only_state_dir,
675                state_dir,
676                icp_config,
677                log_level,
678                bitcoind_addr,
679                dogecoind_addr,
680                icp_features,
681                initial_time,
682                http_gateway_config,
683                mainnet_nns_subnet_id,
684                disable_ingress_validation,
685            )
686            .await
687        });
688
689        Self {
690            pocket_ic,
691            runtime: Arc::new(runtime),
692            thread: Some(thread),
693        }
694    }
695
696    pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
697        self.pocket_ic.take_state_internal()
698    }
699
700    /// Returns the URL of the PocketIC server on which this PocketIC instance is running.
701    pub fn get_server_url(&self) -> Url {
702        self.pocket_ic.get_server_url()
703    }
704
705    /// Returns the instance ID.
706    pub fn instance_id(&self) -> InstanceId {
707        self.pocket_ic.instance_id
708    }
709
710    /// Returns the topology of the different subnets of this PocketIC instance.
711    pub fn topology(&self) -> Topology {
712        let runtime = self.runtime.clone();
713        runtime.block_on(async { self.pocket_ic.topology().await })
714    }
715
716    /// Upload and store a binary blob to the PocketIC server.
717    #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
718    pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
719        let runtime = self.runtime.clone();
720        runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
721    }
722
723    /// Set stable memory of a canister. Optional GZIP compression can be used for reduced
724    /// data traffic.
725    #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
726    pub fn set_stable_memory(
727        &self,
728        canister_id: CanisterId,
729        data: Vec<u8>,
730        compression: BlobCompression,
731    ) {
732        let runtime = self.runtime.clone();
733        runtime.block_on(async {
734            self.pocket_ic
735                .set_stable_memory(canister_id, data, compression)
736                .await
737        })
738    }
739
740    /// Get stable memory of a canister.
741    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
742    pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
743        let runtime = self.runtime.clone();
744        runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
745    }
746
747    /// List all instances and their status.
748    #[instrument(ret)]
749    pub fn list_instances() -> Vec<String> {
750        let runtime = tokio::runtime::Builder::new_current_thread()
751            .build()
752            .unwrap();
753        let url = runtime.block_on(async {
754            let (_, server_url) = start_server(StartServerParams {
755                reuse: true,
756                ..Default::default()
757            })
758            .await;
759            server_url.join("instances").unwrap()
760        });
761        let instances: Vec<String> = reqwest::blocking::Client::new()
762            .get(url)
763            .send()
764            .expect("Failed to get result")
765            .json()
766            .expect("Failed to get json");
767        instances
768    }
769
770    /// Verify a canister signature.
771    #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
772    pub fn verify_canister_signature(
773        &self,
774        msg: Vec<u8>,
775        sig: Vec<u8>,
776        pubkey: Vec<u8>,
777        root_pubkey: Vec<u8>,
778    ) -> Result<(), String> {
779        let runtime = self.runtime.clone();
780        runtime.block_on(async {
781            self.pocket_ic
782                .verify_canister_signature(msg, sig, pubkey, root_pubkey)
783                .await
784        })
785    }
786
787    /// Make the IC produce and progress by one block.
788    /// Note that multiple ticks might be necessary to observe
789    /// an expected effect, e.g., if the effect depends on
790    /// inter-canister calls or heartbeats.
791    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
792    pub fn tick(&self) {
793        let runtime = self.runtime.clone();
794        runtime.block_on(async { self.pocket_ic.tick().await })
795    }
796
797    /// Make the IC produce and progress by one block with custom
798    /// configs for the round.
799    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
800    pub fn tick_with_configs(&self, configs: TickConfigs) {
801        let runtime = self.runtime.clone();
802        runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
803    }
804
805    /// Configures the IC to make progress automatically,
806    /// i.e., periodically update the time of the IC
807    /// to the real time and execute rounds on the subnets.
808    /// Returns the URL at which `/api` requests
809    /// for this instance can be made.
810    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
811    pub fn auto_progress(&self) -> Url {
812        let runtime = self.runtime.clone();
813        runtime.block_on(async { self.pocket_ic.auto_progress().await })
814    }
815
816    /// Returns whether automatic progress is enabled on the PocketIC instance.
817    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
818    pub fn auto_progress_enabled(&self) -> bool {
819        let runtime = self.runtime.clone();
820        runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
821    }
822
823    /// Stops automatic progress (see `auto_progress`) on the IC.
824    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
825    pub fn stop_progress(&self) {
826        let runtime = self.runtime.clone();
827        runtime.block_on(async { self.pocket_ic.stop_progress().await })
828    }
829
830    /// Returns the URL at which `/api` requests
831    /// for this instance can be made if the HTTP
832    /// gateway has been started.
833    pub fn url(&self) -> Option<Url> {
834        self.pocket_ic.url()
835    }
836
837    /// Creates an HTTP gateway for this PocketIC instance binding to `127.0.0.1`
838    /// and an optionally specified port (defaults to choosing an arbitrary unassigned port);
839    /// listening on `localhost`;
840    /// and configures the PocketIC instance to make progress automatically, i.e.,
841    /// periodically update the time of the PocketIC instance to the real time
842    /// and process messages on the PocketIC instance.
843    /// Returns the URL at which `/api` requests
844    /// for this instance can be made.
845    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
846    pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
847        let runtime = self.runtime.clone();
848        runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
849    }
850
851    /// Creates an HTTP gateway for this PocketIC instance binding
852    /// to an optionally specified IP address (defaults to `127.0.0.1`)
853    /// and port (defaults to choosing an arbitrary unassigned port);
854    /// listening on optionally specified domains (default to `localhost`);
855    /// and using an optionally specified TLS certificate (if provided, an HTTPS gateway is created)
856    /// and configures the PocketIC instance to make progress automatically, i.e.,
857    /// periodically update the time of the PocketIC instance to the real time
858    /// and process messages on the PocketIC instance.
859    /// Returns the URL at which `/api` requests
860    /// for this instance can be made.
861    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
862    pub fn make_live_with_params(
863        &mut self,
864        ip_addr: Option<IpAddr>,
865        listen_at: Option<u16>,
866        domains: Option<Vec<String>>,
867        https_config: Option<HttpsConfig>,
868    ) -> Url {
869        let runtime = self.runtime.clone();
870        runtime.block_on(async {
871            self.pocket_ic
872                .make_live_with_params(ip_addr, listen_at, domains, https_config)
873                .await
874        })
875    }
876
877    /// Stops auto progress (automatic time updates and round executions)
878    /// and the HTTP gateway for this IC instance.
879    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
880    pub fn stop_live(&mut self) {
881        let runtime = self.runtime.clone();
882        runtime.block_on(async { self.pocket_ic.stop_live().await })
883    }
884
885    /// Get the root key of this IC instance. Returns `None` if the IC has no NNS subnet.
886    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
887    pub fn root_key(&self) -> Option<Vec<u8>> {
888        let runtime = self.runtime.clone();
889        runtime.block_on(async { self.pocket_ic.root_key().await })
890    }
891
892    /// Get the current time of the IC.
893    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
894    pub fn get_time(&self) -> Time {
895        let runtime = self.runtime.clone();
896        runtime.block_on(async { self.pocket_ic.get_time().await })
897    }
898
899    /// Set the current time of the IC, on all subnets.
900    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
901    pub fn set_time(&self, time: Time) {
902        let runtime = self.runtime.clone();
903        runtime.block_on(async { self.pocket_ic.set_time(time).await })
904    }
905
906    /// Set the current certified time of the IC, on all subnets.
907    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
908    pub fn set_certified_time(&self, time: Time) {
909        let runtime = self.runtime.clone();
910        runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
911    }
912
913    /// Advance the time on the IC on all subnets by some nanoseconds.
914    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
915    pub fn advance_time(&self, duration: Duration) {
916        let runtime = self.runtime.clone();
917        runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
918    }
919
920    /// Get the controllers of a canister.
921    /// Panics if the canister does not exist.
922    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
923    pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
924        let runtime = self.runtime.clone();
925        runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
926    }
927
928    /// Get the current cycles balance of a canister.
929    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
930    pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
931        let runtime = self.runtime.clone();
932        runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
933    }
934
935    /// Add cycles to a canister. Returns the new balance.
936    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
937    pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
938        let runtime = self.runtime.clone();
939        runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
940    }
941
942    /// Submit an update call (without executing it immediately).
943    pub fn submit_call(
944        &self,
945        canister_id: CanisterId,
946        sender: Principal,
947        method: &str,
948        payload: Vec<u8>,
949    ) -> Result<RawMessageId, RejectResponse> {
950        let runtime = self.runtime.clone();
951        runtime.block_on(async {
952            self.pocket_ic
953                .submit_call(canister_id, sender, method, payload)
954                .await
955        })
956    }
957
958    /// Submit an update call with a provided effective principal (without executing it immediately).
959    pub fn submit_call_with_effective_principal(
960        &self,
961        canister_id: CanisterId,
962        effective_principal: RawEffectivePrincipal,
963        sender: Principal,
964        method: &str,
965        payload: Vec<u8>,
966    ) -> Result<RawMessageId, RejectResponse> {
967        let runtime = self.runtime.clone();
968        runtime.block_on(async {
969            self.pocket_ic
970                .submit_call_with_effective_principal(
971                    canister_id,
972                    effective_principal,
973                    sender,
974                    method,
975                    payload,
976                )
977                .await
978        })
979    }
980
981    /// Submit an update call with a provided effective principal and sender info (without executing it immediately).
982    pub fn submit_call_with_effective_principal_and_sender_info(
983        &self,
984        canister_id: CanisterId,
985        effective_principal: RawEffectivePrincipal,
986        sender: Principal,
987        method: &str,
988        payload: Vec<u8>,
989        sender_info: RawSenderInfo,
990    ) -> Result<RawMessageId, RejectResponse> {
991        let runtime = self.runtime.clone();
992        runtime.block_on(async {
993            self.pocket_ic
994                .submit_call_with_effective_principal_and_sender_info(
995                    canister_id,
996                    effective_principal,
997                    sender,
998                    method,
999                    payload,
1000                    sender_info,
1001                )
1002                .await
1003        })
1004    }
1005
1006    /// Submit an update call with sender info (without executing it immediately).
1007    pub fn submit_call_with_sender_info(
1008        &self,
1009        canister_id: CanisterId,
1010        sender: Principal,
1011        method: &str,
1012        payload: Vec<u8>,
1013        sender_info: RawSenderInfo,
1014    ) -> Result<RawMessageId, RejectResponse> {
1015        let runtime = self.runtime.clone();
1016        runtime.block_on(async {
1017            self.pocket_ic
1018                .submit_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1019                .await
1020        })
1021    }
1022
1023    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1024    pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
1025        let runtime = self.runtime.clone();
1026        runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
1027    }
1028
1029    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1030    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
1031    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
1032    pub fn ingress_status(
1033        &self,
1034        message_id: RawMessageId,
1035    ) -> Option<Result<Vec<u8>, RejectResponse>> {
1036        let runtime = self.runtime.clone();
1037        runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
1038    }
1039
1040    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1041    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
1042    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
1043    /// If the status of the update call is known, but the update call was submitted by a different caller, then an error is returned.
1044    pub fn ingress_status_as(
1045        &self,
1046        message_id: RawMessageId,
1047        caller: Principal,
1048    ) -> IngressStatusResult {
1049        let runtime = self.runtime.clone();
1050        runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
1051    }
1052
1053    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
1054    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
1055    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
1056    pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
1057        let runtime = self.runtime.clone();
1058        runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
1059    }
1060
1061    /// Execute an update call on a canister.
1062    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1063    pub fn update_call(
1064        &self,
1065        canister_id: CanisterId,
1066        sender: Principal,
1067        method: &str,
1068        payload: Vec<u8>,
1069    ) -> Result<Vec<u8>, RejectResponse> {
1070        let runtime = self.runtime.clone();
1071        runtime.block_on(async {
1072            self.pocket_ic
1073                .update_call(canister_id, sender, method, payload)
1074                .await
1075        })
1076    }
1077
1078    /// Execute a query call on a canister.
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()))]
1080    pub fn query_call(
1081        &self,
1082        canister_id: CanisterId,
1083        sender: Principal,
1084        method: &str,
1085        payload: Vec<u8>,
1086    ) -> Result<Vec<u8>, RejectResponse> {
1087        let runtime = self.runtime.clone();
1088        runtime.block_on(async {
1089            self.pocket_ic
1090                .query_call(canister_id, sender, method, payload)
1091                .await
1092        })
1093    }
1094
1095    /// Fetch canister logs via a query call to the management canister.
1096    pub fn fetch_canister_logs(
1097        &self,
1098        canister_id: CanisterId,
1099        sender: Principal,
1100    ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
1101        let runtime = self.runtime.clone();
1102        runtime.block_on(async {
1103            self.pocket_ic
1104                .fetch_canister_logs(canister_id, sender)
1105                .await
1106        })
1107    }
1108
1109    /// Request a canister's status.
1110    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1111    pub fn canister_status(
1112        &self,
1113        canister_id: CanisterId,
1114        sender: Option<Principal>,
1115    ) -> Result<CanisterStatusResult, RejectResponse> {
1116        let runtime = self.runtime.clone();
1117        runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1118    }
1119
1120    /// Create a canister with default settings as the anonymous principal.
1121    /// The canister is created with 100T cycles.
1122    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1123    pub fn create_canister(&self) -> CanisterId {
1124        let runtime = self.runtime.clone();
1125        runtime.block_on(async { self.pocket_ic.create_canister().await })
1126    }
1127
1128    /// Create a canister with optional custom settings and a sender.
1129    /// The canister is created with 100T cycles.
1130    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1131    pub fn create_canister_with_settings(
1132        &self,
1133        sender: Option<Principal>,
1134        settings: Option<CanisterSettings>,
1135    ) -> CanisterId {
1136        let runtime = self.runtime.clone();
1137        runtime.block_on(async {
1138            self.pocket_ic
1139                .create_canister_with_settings(sender, settings)
1140                .await
1141        })
1142    }
1143
1144    /// Creates a canister with a specific canister ID and optional custom settings.
1145    /// The canister is created with 100T cycles.
1146    /// Returns an error if the canister ID is already in use.
1147    /// Creates a new subnet if the canister ID is not contained in any of the subnets.
1148    ///
1149    /// The canister ID must be an IC mainnet canister ID that does not belong to the NNS or II subnet,
1150    /// otherwise the function might panic (for NNS and II canister IDs,
1151    /// the PocketIC instance should already be created with those subnets).
1152    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, canister_id = %canister_id.to_string()))]
1153    pub fn create_canister_with_id(
1154        &self,
1155        sender: Option<Principal>,
1156        settings: Option<CanisterSettings>,
1157        canister_id: CanisterId,
1158    ) -> Result<CanisterId, String> {
1159        let runtime = self.runtime.clone();
1160        runtime.block_on(async {
1161            self.pocket_ic
1162                .create_canister_with_id(sender, settings, canister_id)
1163                .await
1164        })
1165    }
1166
1167    /// Create a canister on a specific subnet with optional custom settings.
1168    /// The canister is created with 100T cycles.
1169    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, subnet_id = %subnet_id.to_string()))]
1170    pub fn create_canister_on_subnet(
1171        &self,
1172        sender: Option<Principal>,
1173        settings: Option<CanisterSettings>,
1174        subnet_id: SubnetId,
1175    ) -> CanisterId {
1176        let runtime = self.runtime.clone();
1177        runtime.block_on(async {
1178            self.pocket_ic
1179                .create_canister_on_subnet(sender, settings, subnet_id)
1180                .await
1181        })
1182    }
1183
1184    /// Create a canister with optional cycles, settings, and placement.
1185    /// The placement specifies either a target subnet or a specific canister ID.
1186    /// Defaults to 100T cycles if `params.cycles` is `None`.
1187    /// Returns an error if the specified canister ID is already in use.
1188    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1189    pub fn create_canister_with_params(
1190        &self,
1191        sender: Option<Principal>,
1192        params: CreateCanisterParams,
1193    ) -> Result<CanisterId, String> {
1194        let runtime = self.runtime.clone();
1195        runtime.block_on(async {
1196            self.pocket_ic
1197                .create_canister_with_params(sender, params)
1198                .await
1199        })
1200    }
1201
1202    /// Upload a WASM chunk to the WASM chunk store of a canister.
1203    /// Returns the WASM chunk hash.
1204    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1205    pub fn upload_chunk(
1206        &self,
1207        canister_id: CanisterId,
1208        sender: Option<Principal>,
1209        chunk: Vec<u8>,
1210    ) -> Result<Vec<u8>, RejectResponse> {
1211        let runtime = self.runtime.clone();
1212        runtime.block_on(async {
1213            self.pocket_ic
1214                .upload_chunk(canister_id, sender, chunk)
1215                .await
1216        })
1217    }
1218
1219    /// List WASM chunk hashes in the WASM chunk store of a canister.
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()))]
1221    pub fn stored_chunks(
1222        &self,
1223        canister_id: CanisterId,
1224        sender: Option<Principal>,
1225    ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1226        let runtime = self.runtime.clone();
1227        runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1228    }
1229
1230    /// Clear the WASM chunk store of a canister.
1231    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1232    pub fn clear_chunk_store(
1233        &self,
1234        canister_id: CanisterId,
1235        sender: Option<Principal>,
1236    ) -> Result<(), RejectResponse> {
1237        let runtime = self.runtime.clone();
1238        runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1239    }
1240
1241    /// Install a WASM module assembled from chunks on an existing canister.
1242    #[instrument(skip(self, mode, chunk_hashes_list, wasm_module_hash, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string(), store_canister_id = %store_canister_id.to_string(), arg_len = %arg.len()))]
1243    pub fn install_chunked_canister(
1244        &self,
1245        canister_id: CanisterId,
1246        sender: Option<Principal>,
1247        mode: CanisterInstallMode,
1248        store_canister_id: CanisterId,
1249        chunk_hashes_list: Vec<Vec<u8>>,
1250        wasm_module_hash: Vec<u8>,
1251        arg: Vec<u8>,
1252    ) -> Result<(), RejectResponse> {
1253        let runtime = self.runtime.clone();
1254        runtime.block_on(async {
1255            self.pocket_ic
1256                .install_chunked_canister(
1257                    canister_id,
1258                    sender,
1259                    mode,
1260                    store_canister_id,
1261                    chunk_hashes_list,
1262                    wasm_module_hash,
1263                    arg,
1264                )
1265                .await
1266        })
1267    }
1268
1269    /// Install a WASM module on an existing canister.
1270    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1271    pub fn install_canister(
1272        &self,
1273        canister_id: CanisterId,
1274        wasm_module: Vec<u8>,
1275        arg: Vec<u8>,
1276        sender: Option<Principal>,
1277    ) {
1278        let runtime = self.runtime.clone();
1279        runtime.block_on(async {
1280            self.pocket_ic
1281                .install_canister(canister_id, wasm_module, arg, sender)
1282                .await
1283        })
1284    }
1285
1286    /// Upgrade a canister with a new WASM module.
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()))]
1288    pub fn upgrade_canister(
1289        &self,
1290        canister_id: CanisterId,
1291        wasm_module: Vec<u8>,
1292        arg: Vec<u8>,
1293        sender: Option<Principal>,
1294    ) -> Result<(), RejectResponse> {
1295        let runtime = self.runtime.clone();
1296        runtime.block_on(async {
1297            self.pocket_ic
1298                .upgrade_canister(canister_id, wasm_module, arg, sender)
1299                .await
1300        })
1301    }
1302
1303    /// Upgrade a Motoko EOP canister with a new WASM module.
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()))]
1305    pub fn upgrade_eop_canister(
1306        &self,
1307        canister_id: CanisterId,
1308        wasm_module: Vec<u8>,
1309        arg: Vec<u8>,
1310        sender: Option<Principal>,
1311    ) -> Result<(), RejectResponse> {
1312        let runtime = self.runtime.clone();
1313        runtime.block_on(async {
1314            self.pocket_ic
1315                .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1316                .await
1317        })
1318    }
1319
1320    /// Reinstall a canister WASM module.
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()))]
1322    pub fn reinstall_canister(
1323        &self,
1324        canister_id: CanisterId,
1325        wasm_module: Vec<u8>,
1326        arg: Vec<u8>,
1327        sender: Option<Principal>,
1328    ) -> Result<(), RejectResponse> {
1329        let runtime = self.runtime.clone();
1330        runtime.block_on(async {
1331            self.pocket_ic
1332                .reinstall_canister(canister_id, wasm_module, arg, sender)
1333                .await
1334        })
1335    }
1336
1337    /// Uninstall a canister.
1338    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1339    pub fn uninstall_canister(
1340        &self,
1341        canister_id: CanisterId,
1342        sender: Option<Principal>,
1343    ) -> Result<(), RejectResponse> {
1344        let runtime = self.runtime.clone();
1345        runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1346    }
1347
1348    /// Take canister snapshot.
1349    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1350    pub fn take_canister_snapshot(
1351        &self,
1352        canister_id: CanisterId,
1353        sender: Option<Principal>,
1354        replace_snapshot: Option<Vec<u8>>,
1355    ) -> Result<Snapshot, RejectResponse> {
1356        let runtime = self.runtime.clone();
1357        runtime.block_on(async {
1358            self.pocket_ic
1359                .take_canister_snapshot(canister_id, sender, replace_snapshot)
1360                .await
1361        })
1362    }
1363
1364    /// Load canister snapshot.
1365    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1366    pub fn load_canister_snapshot(
1367        &self,
1368        canister_id: CanisterId,
1369        sender: Option<Principal>,
1370        snapshot_id: Vec<u8>,
1371    ) -> Result<(), RejectResponse> {
1372        let runtime = self.runtime.clone();
1373        runtime.block_on(async {
1374            self.pocket_ic
1375                .load_canister_snapshot(canister_id, sender, snapshot_id)
1376                .await
1377        })
1378    }
1379
1380    /// List canister snapshots.
1381    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1382    pub fn list_canister_snapshots(
1383        &self,
1384        canister_id: CanisterId,
1385        sender: Option<Principal>,
1386    ) -> Result<Vec<Snapshot>, RejectResponse> {
1387        let runtime = self.runtime.clone();
1388        runtime.block_on(async {
1389            self.pocket_ic
1390                .list_canister_snapshots(canister_id, sender)
1391                .await
1392        })
1393    }
1394
1395    /// Delete canister snapshot.
1396    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1397    pub fn delete_canister_snapshot(
1398        &self,
1399        canister_id: CanisterId,
1400        sender: Option<Principal>,
1401        snapshot_id: Vec<u8>,
1402    ) -> Result<(), RejectResponse> {
1403        let runtime = self.runtime.clone();
1404        runtime.block_on(async {
1405            self.pocket_ic
1406                .delete_canister_snapshot(canister_id, sender, snapshot_id)
1407                .await
1408        })
1409    }
1410
1411    /// Update canister settings.
1412    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1413    pub fn update_canister_settings(
1414        &self,
1415        canister_id: CanisterId,
1416        sender: Option<Principal>,
1417        settings: CanisterSettings,
1418    ) -> Result<(), RejectResponse> {
1419        let runtime = self.runtime.clone();
1420        runtime.block_on(async {
1421            self.pocket_ic
1422                .update_canister_settings(canister_id, sender, settings)
1423                .await
1424        })
1425    }
1426
1427    /// Set canister's controllers.
1428    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1429    pub fn set_controllers(
1430        &self,
1431        canister_id: CanisterId,
1432        sender: Option<Principal>,
1433        new_controllers: Vec<Principal>,
1434    ) -> Result<(), RejectResponse> {
1435        let runtime = self.runtime.clone();
1436        runtime.block_on(async {
1437            self.pocket_ic
1438                .set_controllers(canister_id, sender, new_controllers)
1439                .await
1440        })
1441    }
1442
1443    /// Start a canister.
1444    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1445    pub fn start_canister(
1446        &self,
1447        canister_id: CanisterId,
1448        sender: Option<Principal>,
1449    ) -> Result<(), RejectResponse> {
1450        let runtime = self.runtime.clone();
1451        runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1452    }
1453
1454    /// Stop a canister.
1455    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1456    pub fn stop_canister(
1457        &self,
1458        canister_id: CanisterId,
1459        sender: Option<Principal>,
1460    ) -> Result<(), RejectResponse> {
1461        let runtime = self.runtime.clone();
1462        runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1463    }
1464
1465    /// Delete a canister.
1466    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1467    pub fn delete_canister(
1468        &self,
1469        canister_id: CanisterId,
1470        sender: Option<Principal>,
1471    ) -> Result<(), RejectResponse> {
1472        let runtime = self.runtime.clone();
1473        runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1474    }
1475
1476    /// Checks whether the provided canister exists.
1477    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1478    pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1479        let runtime = self.runtime.clone();
1480        runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1481    }
1482
1483    /// Deletes a subnet. Panics if the subnet does not exist or is a named subnet.
1484    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1485    pub fn delete_subnet(&self, subnet_id: SubnetId) {
1486        let runtime = self.runtime.clone();
1487        runtime.block_on(async { self.pocket_ic.delete_subnet(subnet_id).await })
1488    }
1489
1490    /// Returns the subnet ID of the canister if the canister exists.
1491    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1492    pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1493        let runtime = self.runtime.clone();
1494        runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1495    }
1496
1497    /// Returns subnet metrics for a given subnet.
1498    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1499    pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1500        let runtime = self.runtime.clone();
1501        runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1502    }
1503
1504    pub fn update_call_with_effective_principal(
1505        &self,
1506        canister_id: CanisterId,
1507        effective_principal: RawEffectivePrincipal,
1508        sender: Principal,
1509        method: &str,
1510        payload: Vec<u8>,
1511    ) -> Result<Vec<u8>, RejectResponse> {
1512        let runtime = self.runtime.clone();
1513        runtime.block_on(async {
1514            self.pocket_ic
1515                .update_call_with_effective_principal(
1516                    canister_id,
1517                    effective_principal,
1518                    sender,
1519                    method,
1520                    payload,
1521                )
1522                .await
1523        })
1524    }
1525
1526    /// Execute an update call with a provided effective principal and sender info on a canister.
1527    pub fn update_call_with_effective_principal_and_sender_info(
1528        &self,
1529        canister_id: CanisterId,
1530        effective_principal: RawEffectivePrincipal,
1531        sender: Principal,
1532        method: &str,
1533        payload: Vec<u8>,
1534        sender_info: RawSenderInfo,
1535    ) -> Result<Vec<u8>, RejectResponse> {
1536        let runtime = self.runtime.clone();
1537        runtime.block_on(async {
1538            self.pocket_ic
1539                .update_call_with_effective_principal_and_sender_info(
1540                    canister_id,
1541                    effective_principal,
1542                    sender,
1543                    method,
1544                    payload,
1545                    sender_info,
1546                )
1547                .await
1548        })
1549    }
1550
1551    /// Execute an update call with sender info on a canister.
1552    pub fn update_call_with_sender_info(
1553        &self,
1554        canister_id: CanisterId,
1555        sender: Principal,
1556        method: &str,
1557        payload: Vec<u8>,
1558        sender_info: RawSenderInfo,
1559    ) -> Result<Vec<u8>, RejectResponse> {
1560        let runtime = self.runtime.clone();
1561        runtime.block_on(async {
1562            self.pocket_ic
1563                .update_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1564                .await
1565        })
1566    }
1567
1568    /// Execute a query call on a canister explicitly specifying an effective principal to route the request:
1569    /// this API is useful for making generic query calls (including management canister query calls) without using dedicated functions from this library
1570    /// (e.g., making generic query calls in dfx to a PocketIC instance).
1571    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), effective_principal = %effective_principal.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1572    pub fn query_call_with_effective_principal(
1573        &self,
1574        canister_id: CanisterId,
1575        effective_principal: RawEffectivePrincipal,
1576        sender: Principal,
1577        method: &str,
1578        payload: Vec<u8>,
1579    ) -> Result<Vec<u8>, RejectResponse> {
1580        let runtime = self.runtime.clone();
1581        runtime.block_on(async {
1582            self.pocket_ic
1583                .query_call_with_effective_principal(
1584                    canister_id,
1585                    effective_principal,
1586                    sender,
1587                    method,
1588                    payload,
1589                )
1590                .await
1591        })
1592    }
1593
1594    /// Execute a query call with a provided effective principal and sender info on a canister.
1595    pub fn query_call_with_effective_principal_and_sender_info(
1596        &self,
1597        canister_id: CanisterId,
1598        effective_principal: RawEffectivePrincipal,
1599        sender: Principal,
1600        method: &str,
1601        payload: Vec<u8>,
1602        sender_info: RawSenderInfo,
1603    ) -> Result<Vec<u8>, RejectResponse> {
1604        let runtime = self.runtime.clone();
1605        runtime.block_on(async {
1606            self.pocket_ic
1607                .query_call_with_effective_principal_and_sender_info(
1608                    canister_id,
1609                    effective_principal,
1610                    sender,
1611                    method,
1612                    payload,
1613                    sender_info,
1614                )
1615                .await
1616        })
1617    }
1618
1619    /// Execute a query call with sender info on a canister.
1620    pub fn query_call_with_sender_info(
1621        &self,
1622        canister_id: CanisterId,
1623        sender: Principal,
1624        method: &str,
1625        payload: Vec<u8>,
1626        sender_info: RawSenderInfo,
1627    ) -> Result<Vec<u8>, RejectResponse> {
1628        let runtime = self.runtime.clone();
1629        runtime.block_on(async {
1630            self.pocket_ic
1631                .query_call_with_sender_info(canister_id, sender, method, payload, sender_info)
1632                .await
1633        })
1634    }
1635
1636    /// Get the pending canister HTTP outcalls.
1637    /// Note that an additional `PocketIc::tick` is necessary after a canister
1638    /// executes a message making a canister HTTP outcall for the HTTP outcall
1639    /// to be retrievable here.
1640    /// Note that, unless a PocketIC instance is in auto progress mode,
1641    /// a response to the pending canister HTTP outcalls
1642    /// must be produced by the test driver and passed on to the PocketIC instace
1643    /// using `PocketIc::mock_canister_http_response`.
1644    /// In auto progress mode, the PocketIC server produces a response for every
1645    /// pending canister HTTP outcall by actually making an HTTP request
1646    /// to the specified URL.
1647    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1648    pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1649        let runtime = self.runtime.clone();
1650        runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1651    }
1652
1653    /// Mock a response to a pending canister HTTP outcall.
1654    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1655    pub fn mock_canister_http_response(
1656        &self,
1657        mock_canister_http_response: MockCanisterHttpResponse,
1658    ) {
1659        let runtime = self.runtime.clone();
1660        runtime.block_on(async {
1661            self.pocket_ic
1662                .mock_canister_http_response(mock_canister_http_response)
1663                .await
1664        })
1665    }
1666
1667    /// Download a canister snapshot to a given snapshot directory.
1668    /// The sender must be a controller of the canister.
1669    /// The snapshot directory must be empty if it exists.
1670    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1671    pub fn canister_snapshot_download(
1672        &self,
1673        canister_id: CanisterId,
1674        sender: Principal,
1675        snapshot_id: Vec<u8>,
1676        snapshot_dir: PathBuf,
1677    ) {
1678        let runtime = self.runtime.clone();
1679        runtime.block_on(async {
1680            self.pocket_ic
1681                .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1682                .await
1683        })
1684    }
1685
1686    /// Upload a canister snapshot from a given snapshot directory.
1687    /// The sender must be a controller of the canister.
1688    /// Returns the snapshot ID of the uploaded snapshot.
1689    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1690    pub fn canister_snapshot_upload(
1691        &self,
1692        canister_id: CanisterId,
1693        sender: Principal,
1694        replace_snapshot: Option<Vec<u8>>,
1695        snapshot_dir: PathBuf,
1696    ) -> Vec<u8> {
1697        let runtime = self.runtime.clone();
1698        runtime.block_on(async {
1699            self.pocket_ic
1700                .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1701                .await
1702        })
1703    }
1704}
1705
1706impl Default for PocketIc {
1707    fn default() -> Self {
1708        Self::new()
1709    }
1710}
1711
1712impl Drop for PocketIc {
1713    fn drop(&mut self) {
1714        self.runtime.block_on(async {
1715            self.pocket_ic.do_drop().await;
1716        });
1717        if let Some(thread) = self.thread.take() {
1718            thread.join().unwrap();
1719        }
1720    }
1721}
1722
1723/// Call a canister candid method, authenticated. The sender can be impersonated (i.e., the
1724/// signature is not verified).
1725/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
1726pub fn call_candid_as<Input, Output>(
1727    env: &PocketIc,
1728    canister_id: CanisterId,
1729    effective_principal: RawEffectivePrincipal,
1730    sender: Principal,
1731    method: &str,
1732    input: Input,
1733) -> Result<Output, RejectResponse>
1734where
1735    Input: ArgumentEncoder,
1736    Output: for<'a> ArgumentDecoder<'a>,
1737{
1738    with_candid(input, |payload| {
1739        env.update_call_with_effective_principal(
1740            canister_id,
1741            effective_principal,
1742            sender,
1743            method,
1744            payload,
1745        )
1746    })
1747}
1748
1749/// Call a canister candid method, anonymous.
1750/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
1751pub fn call_candid<Input, Output>(
1752    env: &PocketIc,
1753    canister_id: CanisterId,
1754    effective_principal: RawEffectivePrincipal,
1755    method: &str,
1756    input: Input,
1757) -> Result<Output, RejectResponse>
1758where
1759    Input: ArgumentEncoder,
1760    Output: for<'a> ArgumentDecoder<'a>,
1761{
1762    call_candid_as(
1763        env,
1764        canister_id,
1765        effective_principal,
1766        Principal::anonymous(),
1767        method,
1768        input,
1769    )
1770}
1771
1772/// Call a canister candid query method, anonymous.
1773pub fn query_candid<Input, Output>(
1774    env: &PocketIc,
1775    canister_id: CanisterId,
1776    method: &str,
1777    input: Input,
1778) -> Result<Output, RejectResponse>
1779where
1780    Input: ArgumentEncoder,
1781    Output: for<'a> ArgumentDecoder<'a>,
1782{
1783    query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1784}
1785
1786/// Call a canister candid query method, authenticated. The sender can be impersonated (i.e., the
1787/// signature is not verified).
1788pub fn query_candid_as<Input, Output>(
1789    env: &PocketIc,
1790    canister_id: CanisterId,
1791    sender: Principal,
1792    method: &str,
1793    input: Input,
1794) -> Result<Output, RejectResponse>
1795where
1796    Input: ArgumentEncoder,
1797    Output: for<'a> ArgumentDecoder<'a>,
1798{
1799    with_candid(input, |bytes| {
1800        env.query_call(canister_id, sender, method, bytes)
1801    })
1802}
1803
1804/// Call a canister candid update method, anonymous.
1805pub fn update_candid<Input, Output>(
1806    env: &PocketIc,
1807    canister_id: CanisterId,
1808    method: &str,
1809    input: Input,
1810) -> Result<Output, RejectResponse>
1811where
1812    Input: ArgumentEncoder,
1813    Output: for<'a> ArgumentDecoder<'a>,
1814{
1815    update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1816}
1817
1818/// Call a canister candid update method, authenticated. The sender can be impersonated (i.e., the
1819/// signature is not verified).
1820pub fn update_candid_as<Input, Output>(
1821    env: &PocketIc,
1822    canister_id: CanisterId,
1823    sender: Principal,
1824    method: &str,
1825    input: Input,
1826) -> Result<Output, RejectResponse>
1827where
1828    Input: ArgumentEncoder,
1829    Output: for<'a> ArgumentDecoder<'a>,
1830{
1831    with_candid(input, |bytes| {
1832        env.update_call(canister_id, sender, method, bytes)
1833    })
1834}
1835
1836/// A helper function that we use to implement both [`call_candid`] and
1837/// [`query_candid`].
1838pub fn with_candid<Input, Output>(
1839    input: Input,
1840    f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1841) -> Result<Output, RejectResponse>
1842where
1843    Input: ArgumentEncoder,
1844    Output: for<'a> ArgumentDecoder<'a>,
1845{
1846    let in_bytes = encode_args(input).expect("failed to encode args");
1847    f(in_bytes).map(|out_bytes| {
1848        decode_args(&out_bytes).unwrap_or_else(|e| {
1849            panic!(
1850                "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1851                std::any::type_name::<Output>(),
1852                e,
1853                out_bytes,
1854                String::from_utf8_lossy(&out_bytes),
1855            )
1856        })
1857    })
1858}
1859
1860/// Error type for [`TryFrom<u64>`].
1861#[derive(Clone, Copy, Debug)]
1862pub enum TryFromError {
1863    ValueOutOfRange(u64),
1864}
1865
1866/// User-facing error codes.
1867///
1868/// The error codes are currently assigned using an HTTP-like
1869/// convention: the most significant digit is the corresponding reject
1870/// code and the rest is just a sequentially assigned two-digit
1871/// number.
1872#[derive(
1873    PartialOrd,
1874    Ord,
1875    Clone,
1876    Copy,
1877    Debug,
1878    PartialEq,
1879    Eq,
1880    Hash,
1881    Serialize,
1882    Deserialize,
1883    JsonSchema,
1884    EnumIter,
1885)]
1886pub enum ErrorCode {
1887    // 1xx -- `RejectCode::SysFatal`
1888    SubnetOversubscribed = 101,
1889    MaxNumberOfCanistersReached = 102,
1890    // 2xx -- `RejectCode::SysTransient`
1891    CanisterQueueFull = 201,
1892    IngressMessageTimeout = 202,
1893    CanisterQueueNotEmpty = 203,
1894    IngressHistoryFull = 204,
1895    CanisterIdAlreadyExists = 205,
1896    StopCanisterRequestTimeout = 206,
1897    CanisterOutOfCycles = 207,
1898    CertifiedStateUnavailable = 208,
1899    CanisterInstallCodeRateLimited = 209,
1900    CanisterHeapDeltaRateLimited = 210,
1901    // 3xx -- `RejectCode::DestinationInvalid`
1902    CanisterNotFound = 301,
1903    CanisterSnapshotNotFound = 305,
1904    // 4xx -- `RejectCode::CanisterReject`
1905    InsufficientMemoryAllocation = 402,
1906    InsufficientCyclesForCreateCanister = 403,
1907    SubnetNotFound = 404,
1908    CanisterNotHostedBySubnet = 405,
1909    CanisterRejectedMessage = 406,
1910    UnknownManagementMessage = 407,
1911    InvalidManagementPayload = 408,
1912    CanisterSnapshotImmutable = 409,
1913    InvalidSubnetAdmin = 410,
1914    // 5xx -- `RejectCode::CanisterError`
1915    CanisterTrapped = 502,
1916    CanisterCalledTrap = 503,
1917    CanisterContractViolation = 504,
1918    CanisterInvalidWasm = 505,
1919    CanisterDidNotReply = 506,
1920    CanisterOutOfMemory = 507,
1921    CanisterStopped = 508,
1922    CanisterStopping = 509,
1923    CanisterNotStopped = 510,
1924    CanisterStoppingCancelled = 511,
1925    CanisterInvalidController = 512,
1926    CanisterFunctionNotFound = 513,
1927    CanisterNonEmpty = 514,
1928    QueryCallGraphLoopDetected = 517,
1929    InsufficientCyclesInCall = 520,
1930    CanisterWasmEngineError = 521,
1931    CanisterInstructionLimitExceeded = 522,
1932    CanisterMemoryAccessLimitExceeded = 524,
1933    QueryCallGraphTooDeep = 525,
1934    QueryCallGraphTotalInstructionLimitExceeded = 526,
1935    CompositeQueryCalledInReplicatedMode = 527,
1936    QueryTimeLimitExceeded = 528,
1937    QueryCallGraphInternal = 529,
1938    InsufficientCyclesInComputeAllocation = 530,
1939    InsufficientCyclesInMemoryAllocation = 531,
1940    InsufficientCyclesInMemoryGrow = 532,
1941    ReservedCyclesLimitExceededInMemoryAllocation = 533,
1942    ReservedCyclesLimitExceededInMemoryGrow = 534,
1943    InsufficientCyclesInMessageMemoryGrow = 535,
1944    CanisterMethodNotFound = 536,
1945    CanisterWasmModuleNotFound = 537,
1946    CanisterAlreadyInstalled = 538,
1947    CanisterWasmMemoryLimitExceeded = 539,
1948    ReservedCyclesLimitIsTooLow = 540,
1949    CanisterInvalidControllerOrSubnetAdmin = 541,
1950    // 6xx -- `RejectCode::SysUnknown`
1951    DeadlineExpired = 601,
1952    ResponseDropped = 602,
1953}
1954
1955impl TryFrom<u64> for ErrorCode {
1956    type Error = TryFromError;
1957    fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1958        match err {
1959            // 1xx -- `RejectCode::SysFatal`
1960            101 => Ok(ErrorCode::SubnetOversubscribed),
1961            102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1962            // 2xx -- `RejectCode::SysTransient`
1963            201 => Ok(ErrorCode::CanisterQueueFull),
1964            202 => Ok(ErrorCode::IngressMessageTimeout),
1965            203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1966            204 => Ok(ErrorCode::IngressHistoryFull),
1967            205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1968            206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1969            207 => Ok(ErrorCode::CanisterOutOfCycles),
1970            208 => Ok(ErrorCode::CertifiedStateUnavailable),
1971            209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1972            210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1973            // 3xx -- `RejectCode::DestinationInvalid`
1974            301 => Ok(ErrorCode::CanisterNotFound),
1975            305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1976            // 4xx -- `RejectCode::CanisterReject`
1977            402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1978            403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1979            404 => Ok(ErrorCode::SubnetNotFound),
1980            405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1981            406 => Ok(ErrorCode::CanisterRejectedMessage),
1982            407 => Ok(ErrorCode::UnknownManagementMessage),
1983            408 => Ok(ErrorCode::InvalidManagementPayload),
1984            409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1985            410 => Ok(ErrorCode::InvalidSubnetAdmin),
1986            // 5xx -- `RejectCode::CanisterError`
1987            502 => Ok(ErrorCode::CanisterTrapped),
1988            503 => Ok(ErrorCode::CanisterCalledTrap),
1989            504 => Ok(ErrorCode::CanisterContractViolation),
1990            505 => Ok(ErrorCode::CanisterInvalidWasm),
1991            506 => Ok(ErrorCode::CanisterDidNotReply),
1992            507 => Ok(ErrorCode::CanisterOutOfMemory),
1993            508 => Ok(ErrorCode::CanisterStopped),
1994            509 => Ok(ErrorCode::CanisterStopping),
1995            510 => Ok(ErrorCode::CanisterNotStopped),
1996            511 => Ok(ErrorCode::CanisterStoppingCancelled),
1997            512 => Ok(ErrorCode::CanisterInvalidController),
1998            513 => Ok(ErrorCode::CanisterFunctionNotFound),
1999            514 => Ok(ErrorCode::CanisterNonEmpty),
2000            517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
2001            520 => Ok(ErrorCode::InsufficientCyclesInCall),
2002            521 => Ok(ErrorCode::CanisterWasmEngineError),
2003            522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
2004            524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
2005            525 => Ok(ErrorCode::QueryCallGraphTooDeep),
2006            526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
2007            527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
2008            528 => Ok(ErrorCode::QueryTimeLimitExceeded),
2009            529 => Ok(ErrorCode::QueryCallGraphInternal),
2010            530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
2011            531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
2012            532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
2013            533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
2014            534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
2015            535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
2016            536 => Ok(ErrorCode::CanisterMethodNotFound),
2017            537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
2018            538 => Ok(ErrorCode::CanisterAlreadyInstalled),
2019            539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
2020            540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
2021            541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
2022            // 6xx -- `RejectCode::SysUnknown`
2023            601 => Ok(ErrorCode::DeadlineExpired),
2024            602 => Ok(ErrorCode::ResponseDropped),
2025            _ => Err(TryFromError::ValueOutOfRange(err)),
2026        }
2027    }
2028}
2029
2030impl std::fmt::Display for ErrorCode {
2031    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2032        // E.g. "IC0301"
2033        write!(f, "IC{:04}", *self as i32)
2034    }
2035}
2036
2037/// User-facing reject codes.
2038///
2039/// They can be derived from the most significant digit of the
2040/// corresponding error code.
2041#[derive(
2042    PartialOrd,
2043    Ord,
2044    Clone,
2045    Copy,
2046    Debug,
2047    PartialEq,
2048    Eq,
2049    Hash,
2050    Serialize,
2051    Deserialize,
2052    JsonSchema,
2053    EnumIter,
2054)]
2055pub enum RejectCode {
2056    SysFatal = 1,
2057    SysTransient = 2,
2058    DestinationInvalid = 3,
2059    CanisterReject = 4,
2060    CanisterError = 5,
2061    SysUnknown = 6,
2062}
2063
2064impl TryFrom<u64> for RejectCode {
2065    type Error = TryFromError;
2066    fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
2067        match err {
2068            1 => Ok(RejectCode::SysFatal),
2069            2 => Ok(RejectCode::SysTransient),
2070            3 => Ok(RejectCode::DestinationInvalid),
2071            4 => Ok(RejectCode::CanisterReject),
2072            5 => Ok(RejectCode::CanisterError),
2073            6 => Ok(RejectCode::SysUnknown),
2074            _ => Err(TryFromError::ValueOutOfRange(err)),
2075        }
2076    }
2077}
2078
2079/// User-facing type describing an unsuccessful (also called reject) call response.
2080#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
2081pub struct RejectResponse {
2082    pub reject_code: RejectCode,
2083    pub reject_message: String,
2084    pub error_code: ErrorCode,
2085    pub certified: bool,
2086}
2087
2088impl std::fmt::Display for RejectResponse {
2089    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2090        // Follows [agent-rs](https://github.com/dfinity/agent-rs/blob/a651dbbe69e61d4e8508c144cd60cfa3118eeb3a/ic-agent/src/agent/agent_error.rs#L54)
2091        write!(
2092            f,
2093            "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
2094            self.reject_code, self.reject_message, self.error_code
2095        )
2096    }
2097}
2098
2099/// This enum describes the result of retrieving ingress status.
2100/// The `IngressStatusResult::Forbidden` variant is produced
2101/// if an optional caller is provided and a corresponding read state request
2102/// for the status of the same update call signed by that specified caller
2103/// was rejected because the update call was submitted by a different caller.
2104#[derive(Debug, Serialize, Deserialize)]
2105pub enum IngressStatusResult {
2106    NotAvailable,
2107    Forbidden(String),
2108    Success(Result<Vec<u8>, RejectResponse>),
2109}
2110
2111#[derive(Clone, Debug, Default)]
2112pub struct TickConfigs {
2113    pub blockmakers: Option<Vec<SubnetBlockmakers>>,
2114}
2115
2116impl From<TickConfigs> for RawTickConfigs {
2117    fn from(tick_configs: TickConfigs) -> Self {
2118        Self {
2119            blockmakers: tick_configs.blockmakers.map(|blockmakers| {
2120                blockmakers
2121                    .into_iter()
2122                    .map(|blockmaker| blockmaker.into())
2123                    .collect()
2124            }),
2125        }
2126    }
2127}
2128
2129#[derive(Clone, Debug)]
2130pub struct SubnetBlockmakers {
2131    pub subnet: Principal,
2132    pub blockmaker: Principal,
2133    pub failed_blockmakers: Vec<Principal>,
2134}
2135
2136impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
2137    fn from(blockmaker: SubnetBlockmakers) -> Self {
2138        Self {
2139            subnet: blockmaker.subnet.into(),
2140            blockmaker: blockmaker.blockmaker.into(),
2141            failed_blockmakers: blockmaker
2142                .failed_blockmakers
2143                .into_iter()
2144                .map(|p| p.into())
2145                .collect(),
2146        }
2147    }
2148}
2149
2150#[cfg(windows)]
2151fn wsl_path(path: &PathBuf, desc: &str) -> String {
2152    windows_to_wsl(
2153        path.as_os_str()
2154            .to_str()
2155            .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
2156    )
2157    .unwrap_or_else(|e| {
2158        panic!(
2159            "Could not convert {} path ({:?}) to WSL path: {:?}",
2160            desc, path, e
2161        )
2162    })
2163}
2164
2165#[cfg(windows)]
2166static WSL_WARM_UP: Once = Once::new();
2167
2168#[cfg(windows)]
2169fn warm_up_wsl() {
2170    WSL_WARM_UP.call_once(|| {
2171        let output = Command::new("wsl")
2172            .arg("bash")
2173            .arg("-c")
2174            .arg("true")
2175            .output()
2176            .expect("Failed to warm up WSL");
2177        if !output.status.success() {
2178            panic!(
2179                "Failed to warm up WSL.\nStatus: {}\nStdout: {}\nStderr: {}",
2180                output.status,
2181                String::from_utf8_lossy(&output.stdout),
2182                String::from_utf8_lossy(&output.stderr),
2183            );
2184        }
2185    });
2186}
2187
2188#[cfg(windows)]
2189fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2190    warm_up_wsl();
2191    let mut cmd = Command::new("wsl");
2192    cmd.arg(wsl_path(bin_path, "PocketIC binary"));
2193    cmd
2194}
2195
2196#[cfg(not(windows))]
2197fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2198    Command::new(bin_path)
2199}
2200
2201fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
2202    let unexpected_version = format!(
2203        "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
2204    );
2205    let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
2206        return Err(unexpected_version);
2207    };
2208    if pocket_ic_server != POCKET_IC_SERVER_NAME {
2209        return Err(unexpected_version);
2210    }
2211    let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
2212    let version = Version::parse(version)
2213        .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
2214    if !req.matches(&version) {
2215        return Err(format!(
2216            "Incompatible PocketIC server version: got {version}; expected {req}."
2217        ));
2218    }
2219
2220    Ok(())
2221}
2222
2223fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
2224    let mut cmd = pocket_ic_server_cmd(server_binary);
2225    cmd.arg("--version");
2226    let output = cmd.output().map_err(|e| e.to_string())?;
2227    if !output.status.success() {
2228        return Err(format!(
2229            "PocketIC server failed to print its version.\nStatus: {}\nStdout: {}\nStderr: {}",
2230            output.status,
2231            String::from_utf8_lossy(&output.stdout),
2232            String::from_utf8_lossy(&output.stderr),
2233        ));
2234    }
2235    let version_str = String::from_utf8(output.stdout)
2236        .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
2237    let version_line = version_str.trim_end_matches('\n');
2238    check_pocketic_server_version(version_line)
2239}
2240
2241async fn download_pocketic_server(
2242    server_url: String,
2243    mut out: std::fs::File,
2244) -> Result<(), String> {
2245    let binary = reqwest::get(server_url)
2246        .await
2247        .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2248        .bytes()
2249        .await
2250        .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2251        .to_vec();
2252    let mut gz = GzDecoder::new(&binary[..]);
2253    let _ = std::io::copy(&mut gz, &mut out)
2254        .map_err(|e| format!("Failed to write PocketIC server binary: {e}"));
2255    Ok(())
2256}
2257
2258#[derive(Default)]
2259pub struct StartServerParams {
2260    pub server_binary: Option<PathBuf>,
2261    /// Reuse an existing PocketIC server spawned by this process.
2262    pub reuse: bool,
2263    /// TTL for the PocketIC server.
2264    /// The server stops gracefully if no request has been received for the duration of its TTL
2265    /// after the last request finished and if there are no more pending requests.
2266    /// A default value of TTL is used if no `ttl` is specified here.
2267    /// Note: The TTL might not be overriden if the same test process sets `reuse` to `true`
2268    /// and passes different values of `ttl`.
2269    pub ttl: Option<Duration>,
2270    /// Hard TTL for the PocketIC server.
2271    /// The server stops with a hard exit after the duration of its hard TTL
2272    /// since its launch.
2273    /// If no `hard_ttl` is specified here, then the PocketIC server
2274    /// does not use any default hard TTL.
2275    /// Note: The hard TTL might not be overriden if the same test process sets `reuse` to `true`
2276    /// and passes different values of `hard_ttl`.
2277    pub hard_ttl: Option<Duration>,
2278}
2279
2280/// Attempt to start a new PocketIC server.
2281pub async fn start_server(params: StartServerParams) -> (Child, Url) {
2282    let default_bin_dir =
2283        std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
2284    let default_bin_path = default_bin_dir.join("pocket-ic");
2285    let bin_path_provided =
2286        params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
2287    let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
2288        std::env::var_os("POCKET_IC_BIN")
2289            .unwrap_or_else(|| default_bin_path.clone().into())
2290            .into()
2291    });
2292
2293    if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
2294        if bin_path_provided {
2295            panic!(
2296                "Failed to validate PocketIC server binary `{}`: `{}`.",
2297                bin_path.display(),
2298                e
2299            );
2300        }
2301        bin_path = default_bin_path.clone();
2302        std::fs::create_dir_all(&default_bin_dir)
2303            .expect("Failed to create PocketIC server directory");
2304        let mut options = OpenOptions::new();
2305        options.write(true).create_new(true);
2306        #[cfg(unix)]
2307        options.mode(0o777);
2308        match options.open(&default_bin_path) {
2309            Ok(out) => {
2310                #[cfg(target_os = "macos")]
2311                let os = "darwin";
2312                #[cfg(not(target_os = "macos"))]
2313                let os = "linux";
2314                #[cfg(target_arch = "aarch64")]
2315                let arch = "arm64";
2316                #[cfg(not(target_arch = "aarch64"))]
2317                let arch = "x86_64";
2318                let server_url = format!(
2319                    "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2320                );
2321                println!(
2322                    "Failed to validate PocketIC server binary `{}`: `{}`. Going to download PocketIC server {} from {} to the local path {}. To avoid downloads during test execution, please specify the path to the (ungzipped and executable) PocketIC server {} using the function `PocketIcBuilder::with_server_binary` or using the `POCKET_IC_BIN` environment variable.",
2323                    bin_path.display(),
2324                    e,
2325                    LATEST_SERVER_VERSION,
2326                    server_url,
2327                    default_bin_path.display(),
2328                    LATEST_SERVER_VERSION
2329                );
2330                if let Err(e) = download_pocketic_server(server_url, out).await {
2331                    let _ = std::fs::remove_file(default_bin_path);
2332                    panic!("{}", e);
2333                }
2334            }
2335            _ => {
2336                // PocketIC server has already been created by another test: wait until it's fully downloaded.
2337                let start = std::time::Instant::now();
2338                loop {
2339                    if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2340                        break;
2341                    }
2342                    if start.elapsed() > std::time::Duration::from_secs(60) {
2343                        let _ = std::fs::remove_file(&default_bin_path);
2344                        panic!(
2345                            "Timed out waiting for PocketIC server being available at the local path {}.",
2346                            default_bin_path.display()
2347                        );
2348                    }
2349                    std::thread::sleep(std::time::Duration::from_millis(100));
2350                }
2351            }
2352        }
2353    }
2354
2355    let port_file_path = if params.reuse {
2356        // We use the test driver's process ID to share the PocketIC server between multiple tests
2357        // launched by the same test driver.
2358        let test_driver_pid = std::process::id();
2359        std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2360    } else {
2361        NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
2362    };
2363    let mut cmd = pocket_ic_server_cmd(&bin_path);
2364    if let Some(ttl) = params.ttl {
2365        cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2366    }
2367    if let Some(hard_ttl) = params.hard_ttl {
2368        cmd.arg("--hard-ttl").arg(hard_ttl.as_secs().to_string());
2369    }
2370    cmd.arg("--port-file");
2371    #[cfg(windows)]
2372    cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2373    #[cfg(not(windows))]
2374    cmd.arg(port_file_path.clone());
2375    if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2376        && !mute_server.is_empty()
2377    {
2378        cmd.stdout(std::process::Stdio::null());
2379        cmd.stderr(std::process::Stdio::null());
2380    }
2381
2382    // Start the server in the background so that it doesn't receive signals such as CTRL^C
2383    // from the foreground terminal.
2384    #[cfg(unix)]
2385    {
2386        use std::os::unix::process::CommandExt;
2387        cmd.process_group(0);
2388    }
2389
2390    // TODO: SDK-1936
2391    #[allow(clippy::zombie_processes)]
2392    let child = cmd
2393        .spawn()
2394        .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
2395
2396    loop {
2397        if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2398            && port_string.contains("\n")
2399        {
2400            let port: u16 = port_string
2401                .trim_end()
2402                .parse()
2403                .expect("Failed to parse port to number");
2404            break (
2405                child,
2406                Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2407            );
2408        }
2409        std::thread::sleep(Duration::from_millis(20));
2410    }
2411}
2412
2413#[derive(Error, Debug)]
2414pub enum DefaultEffectiveCanisterIdError {
2415    ReqwestError(#[from] reqwest::Error),
2416    JsonError(#[from] serde_json::Error),
2417    Utf8Error(#[from] std::string::FromUtf8Error),
2418}
2419
2420impl std::fmt::Display for DefaultEffectiveCanisterIdError {
2421    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2422        match self {
2423            DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2424                write!(f, "ReqwestError({err})")
2425            }
2426            DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({err})"),
2427            DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({err})"),
2428        }
2429    }
2430}
2431
2432/// Retrieves a default effective canister id for canister creation on a PocketIC instance
2433/// characterized by:
2434///  - a PocketIC instance URL of the form http://<ip>:<port>/instances/<instance_id>;
2435///  - a PocketIC HTTP gateway URL of the form http://<ip>:port for a PocketIC instance.
2436///
2437/// Returns an error if the PocketIC instance topology could not be fetched or parsed, e.g.,
2438/// because the given URL points to a replica (i.e., does not meet any of the above two properties).
2439pub fn get_default_effective_canister_id(
2440    pocket_ic_url: String,
2441) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2442    let runtime = Runtime::new().expect("Unable to create a runtime");
2443    runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2444        pocket_ic_url,
2445    ))
2446}
2447
2448pub fn copy_dir(
2449    src: impl AsRef<std::path::Path>,
2450    dst: impl AsRef<std::path::Path>,
2451) -> std::io::Result<()> {
2452    std::fs::create_dir_all(&dst)?;
2453    for entry in std::fs::read_dir(src)? {
2454        let entry = entry?;
2455        let ty = entry.file_type()?;
2456        if ty.is_dir() {
2457            copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2458        } else {
2459            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2460        }
2461    }
2462    Ok(())
2463}
2464
2465#[cfg(test)]
2466mod test {
2467    use crate::{ErrorCode, RejectCode, check_pocketic_server_version};
2468    use strum::IntoEnumIterator;
2469
2470    #[test]
2471    fn reject_code_round_trip() {
2472        for initial in RejectCode::iter() {
2473            let round_trip = RejectCode::try_from(initial as u64).unwrap();
2474
2475            assert_eq!(initial, round_trip);
2476        }
2477    }
2478
2479    #[test]
2480    fn error_code_round_trip() {
2481        for initial in ErrorCode::iter() {
2482            let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2483
2484            assert_eq!(initial, round_trip);
2485        }
2486    }
2487
2488    #[test]
2489    fn reject_code_matches_ic_error_code() {
2490        assert_eq!(
2491            RejectCode::iter().len(),
2492            ic_error_types::RejectCode::iter().len()
2493        );
2494        for ic_reject_code in ic_error_types::RejectCode::iter() {
2495            let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2496            assert_eq!(format!("{reject_code:?}"), format!("{:?}", ic_reject_code));
2497        }
2498    }
2499
2500    #[test]
2501    fn error_code_matches_ic_error_code() {
2502        assert_eq!(
2503            ErrorCode::iter().len(),
2504            ic_error_types::ErrorCode::iter().len()
2505        );
2506        for ic_error_code in ic_error_types::ErrorCode::iter() {
2507            let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2508            assert_eq!(format!("{error_code:?}"), format!("{:?}", ic_error_code));
2509        }
2510    }
2511
2512    #[test]
2513    fn test_check_pocketic_server_version() {
2514        assert!(
2515            check_pocketic_server_version("pocket-ic-server")
2516                .unwrap_err()
2517                .contains("Unexpected PocketIC server version")
2518        );
2519        assert!(
2520            check_pocketic_server_version("pocket-ic 15.0.0")
2521                .unwrap_err()
2522                .contains("Unexpected PocketIC server version")
2523        );
2524        assert!(
2525            check_pocketic_server_version("pocket-ic-server 15 0 0")
2526                .unwrap_err()
2527                .contains("Failed to parse PocketIC server version")
2528        );
2529        assert!(
2530            check_pocketic_server_version("pocket-ic-server 14.0.0")
2531                .unwrap_err()
2532                .contains("Incompatible PocketIC server version")
2533        );
2534        check_pocketic_server_version("pocket-ic-server 15.0.0").unwrap();
2535        check_pocketic_server_version("pocket-ic-server 15.0.1").unwrap();
2536        check_pocketic_server_version("pocket-ic-server 15.1.0").unwrap();
2537        assert!(
2538            check_pocketic_server_version("pocket-ic-server 16.0.0")
2539                .unwrap_err()
2540                .contains("Incompatible PocketIC server version")
2541        );
2542    }
2543}