ic_testkit/pic/
standalone_recipe.rs1use std::io;
2
3use ic_platform_types::principal::ExternalPrincipal as Principal;
4
5use super::{
6 CachedPocketIcBaseline, CanisterSnapshotTarget, FailureDisposition, PocketIcBaselineRecipe,
7 PreparedBaseline, RebuildReason, ResetPolicies, ResetReceipt, ResetRequirements,
8 SnapshotRestoreFunding, StandaloneCanisterFixture, is_dead_pocket_ic_transport_error,
9};
10
11pub struct StandaloneCanisterRecipe<B> {
28 requirements: ResetRequirements,
29 build: B,
30}
31
32impl<B> StandaloneCanisterRecipe<B> {
33 #[must_use]
37 pub fn new(funding: SnapshotRestoreFunding, build: B) -> Self {
38 Self {
39 requirements: ResetRequirements {
40 funding,
41 policies: ResetPolicies::default(),
42 },
43 build,
44 }
45 }
46}
47
48impl<B, Fut> PocketIcBaselineRecipe for StandaloneCanisterRecipe<B>
49where
50 B: Fn() -> Fut + Send + Sync + 'static,
51 Fut: std::future::Future<Output = io::Result<StandaloneCanisterFixture>> + Send,
52{
53 type Metadata = Principal;
54 type Error = io::Error;
56
57 fn reset_requirements(&self) -> &ResetRequirements {
58 &self.requirements
59 }
60
61 async fn build(&self) -> Result<CachedPocketIcBaseline<Principal>, io::Error> {
62 let (pocket_ic, canister_id) = (self.build)().await?.into_parts();
63 if let Err(source) = pocket_ic.instance().stop_canister(canister_id, None).await {
64 let error = io::Error::other(super::ControllerSnapshotError::CaptureFailed {
65 canister_id,
66 sender: None,
67 source: Box::new(source),
68 cleanup_failures: Vec::new(),
69 });
70 if let Err(cleanup) = pocket_ic.close().await {
71 tracing::warn!("PocketIC deletion after stop failure failed: {cleanup}");
72 }
73 return Err(error);
74 }
75 let baseline = CachedPocketIcBaseline::capture(
76 pocket_ic,
77 [CanisterSnapshotTarget {
78 canister_id,
79 sender: None,
80 }],
81 canister_id,
82 )
83 .await
84 .map_err(io::Error::other)?;
85 if let Err(error) = self.drive_to_readiness(&baseline).await {
86 if let Err(cleanup) = baseline.close().await {
87 tracing::warn!("PocketIC deletion after readiness failure failed: {cleanup}");
88 }
89 return Err(error);
90 }
91 Ok(baseline)
92 }
93
94 fn reset_non_snapshot_state(
95 &self,
96 _baseline: &CachedPocketIcBaseline<Principal>,
97 ) -> impl std::future::Future<Output = Result<ResetReceipt, io::Error>> + Send {
98 std::future::ready(Ok(ResetReceipt::default()))
99 }
100
101 async fn prepare_restore(
102 &self,
103 baseline: &CachedPocketIcBaseline<Principal>,
104 ) -> Result<(), io::Error> {
105 baseline
106 .pocket_ic()
107 .stop_canister(*baseline.metadata(), None)
108 .await
109 .map_err(io::Error::other)
110 }
111
112 async fn drive_to_readiness(
113 &self,
114 baseline: &CachedPocketIcBaseline<Principal>,
115 ) -> Result<(), io::Error> {
116 baseline
117 .pocket_ic()
118 .start_canister(*baseline.metadata(), None)
119 .await
120 .map_err(io::Error::other)
121 }
122
123 fn validate(
124 &self,
125 _baseline: &CachedPocketIcBaseline<Principal>,
126 _preparation: &PreparedBaseline,
127 ) -> impl std::future::Future<Output = Result<(), io::Error>> + Send {
128 std::future::ready(Ok(()))
129 }
130
131 fn classify_failure(
132 &self,
133 _stage: super::BaselinePreparationStage,
134 error: &(dyn std::error::Error + 'static),
135 ) -> FailureDisposition {
136 if is_dead_pocket_ic_transport_error(error) {
137 FailureDisposition::Rebuild(RebuildReason::DeadPocketIcTransport)
138 } else {
139 FailureDisposition::Fatal
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests;