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