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