ic_testkit/pic/
baseline.rs1use candid::Principal;
2use pocket_ic::PocketIc;
3use std::{
4 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
5 sync::{Mutex, MutexGuard},
6};
7
8use super::{
9 ControllerSnapshotError, ControllerSnapshots, PocketIcSnapshotExt, SnapshotRestoreFunding,
10 transport,
11};
12
13pub struct CachedPocketIcBaseline<T> {
18 pocket_ic: PocketIc,
19 snapshots: ControllerSnapshots,
20 metadata: T,
21}
22
23pub struct CachedPocketIcBaselineGuard<'a, T> {
28 guard: MutexGuard<'a, Option<CachedPocketIcBaseline<T>>>,
29}
30
31enum CachedBaselineRestoreFailure {
32 DeadInstanceTransport,
33 Panic(Box<dyn std::any::Any + Send>),
34}
35
36fn acquire_cached_pocket_ic_baseline<T, F>(
38 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
39 build: F,
40) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
41where
42 F: FnOnce() -> CachedPocketIcBaseline<T>,
43{
44 let mut guard = slot
45 .lock()
46 .unwrap_or_else(std::sync::PoisonError::into_inner);
47 let cache_hit = guard.is_some();
48
49 if !cache_hit {
50 *guard = Some(build());
51 }
52
53 (CachedPocketIcBaselineGuard { guard }, cache_hit)
54}
55
56pub fn restore_or_rebuild_cached_pocket_ic_baseline<T, B, R>(
67 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
68 build: B,
69 restore: R,
70) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
71where
72 B: Fn() -> CachedPocketIcBaseline<T>,
73 R: Fn(&CachedPocketIcBaseline<T>),
74{
75 let (baseline, cache_hit) = acquire_cached_pocket_ic_baseline(slot, &build);
76 if !cache_hit {
77 return (baseline, false);
78 }
79
80 match try_restore_cached_pocket_ic_baseline(
81 baseline
82 .guard
83 .as_ref()
84 .expect("cached PocketIC baseline must exist"),
85 restore,
86 ) {
87 Ok(()) => return (baseline, true),
88 Err(CachedBaselineRestoreFailure::DeadInstanceTransport) => {}
89 Err(CachedBaselineRestoreFailure::Panic(payload)) => {
90 resume_unwind(payload);
91 }
92 }
93
94 drop(baseline);
95 drop_stale_cached_pocket_ic_baseline(slot);
96
97 let (rebuilt, _cache_hit) = acquire_cached_pocket_ic_baseline(slot, build);
98 (rebuilt, false)
99}
100
101fn try_restore_cached_pocket_ic_baseline<T, R>(
104 baseline: &CachedPocketIcBaseline<T>,
105 restore: R,
106) -> Result<(), CachedBaselineRestoreFailure>
107where
108 R: Fn(&CachedPocketIcBaseline<T>),
109{
110 match catch_unwind(AssertUnwindSafe(|| restore(baseline))) {
111 Ok(()) => Ok(()),
112 Err(payload) => {
113 if transport::panic_is_dead_instance_transport(payload.as_ref()) {
114 Err(CachedBaselineRestoreFailure::DeadInstanceTransport)
115 } else {
116 Err(CachedBaselineRestoreFailure::Panic(payload))
117 }
118 }
119 }
120}
121
122fn drop_stale_cached_pocket_ic_baseline<T>(
125 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
126) {
127 let stale = {
128 let mut slot = slot
129 .lock()
130 .unwrap_or_else(std::sync::PoisonError::into_inner);
131 slot.take()
132 };
133
134 if let Some(stale) = stale {
135 let _ = catch_unwind(AssertUnwindSafe(|| {
136 drop(stale);
137 }));
138 }
139}
140
141impl<T> CachedPocketIcBaselineGuard<'_, T> {
142 #[must_use]
144 pub fn pocket_ic(&self) -> &PocketIc {
145 self.guard
146 .as_ref()
147 .expect("cached PocketIC baseline must exist")
148 .pocket_ic()
149 }
150
151 #[must_use]
153 pub fn metadata(&self) -> &T {
154 self.guard
155 .as_ref()
156 .expect("cached PocketIC baseline must exist")
157 .metadata()
158 }
159
160 #[must_use]
162 pub fn metadata_mut(&mut self) -> &mut T {
163 self.guard
164 .as_mut()
165 .expect("cached PocketIC baseline must exist")
166 .metadata_mut()
167 }
168
169 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
171 self.guard
172 .as_ref()
173 .expect("cached PocketIC baseline must exist")
174 .restore(controller_id)
175 }
176
177 pub fn restore_with_funding(
179 &self,
180 controller_id: Principal,
181 funding: SnapshotRestoreFunding,
182 ) -> Result<(), ControllerSnapshotError> {
183 self.guard
184 .as_ref()
185 .expect("cached PocketIC baseline must exist")
186 .restore_with_funding(controller_id, funding)
187 }
188}
189
190impl<T> CachedPocketIcBaseline<T> {
191 pub fn capture<I>(
196 pocket_ic: PocketIc,
197 controller_id: Principal,
198 canister_ids: I,
199 metadata: T,
200 ) -> Result<Self, ControllerSnapshotError>
201 where
202 I: IntoIterator<Item = Principal>,
203 {
204 let snapshots = pocket_ic.capture_controller_snapshots(controller_id, canister_ids)?;
205
206 Ok(Self {
207 pocket_ic,
208 snapshots,
209 metadata,
210 })
211 }
212
213 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
215 self.pocket_ic
216 .restore_controller_snapshots(controller_id, &self.snapshots)
217 }
218
219 pub fn restore_with_funding(
221 &self,
222 controller_id: Principal,
223 funding: SnapshotRestoreFunding,
224 ) -> Result<(), ControllerSnapshotError> {
225 self.pocket_ic.restore_controller_snapshots_with_funding(
226 controller_id,
227 &self.snapshots,
228 funding,
229 )
230 }
231
232 #[must_use]
234 pub const fn pocket_ic(&self) -> &PocketIc {
235 &self.pocket_ic
236 }
237
238 #[must_use]
240 pub const fn metadata(&self) -> &T {
241 &self.metadata
242 }
243
244 #[must_use]
246 pub const fn metadata_mut(&mut self) -> &mut T {
247 &mut self.metadata
248 }
249}