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>(
59 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
60 build: B,
61 restore: R,
62) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
63where
64 B: Fn() -> CachedPocketIcBaseline<T>,
65 R: Fn(&CachedPocketIcBaseline<T>),
66{
67 let (baseline, cache_hit) = acquire_cached_pocket_ic_baseline(slot, &build);
68 if !cache_hit {
69 return (baseline, false);
70 }
71
72 match try_restore_cached_pocket_ic_baseline(
73 baseline
74 .guard
75 .as_ref()
76 .expect("cached PocketIC baseline must exist"),
77 restore,
78 ) {
79 Ok(()) => return (baseline, true),
80 Err(CachedBaselineRestoreFailure::DeadInstanceTransport) => {}
81 Err(CachedBaselineRestoreFailure::Panic(payload)) => {
82 resume_unwind(payload);
83 }
84 }
85
86 drop(baseline);
87 drop_stale_cached_pocket_ic_baseline(slot);
88
89 let (rebuilt, _cache_hit) = acquire_cached_pocket_ic_baseline(slot, build);
90 (rebuilt, false)
91}
92
93fn try_restore_cached_pocket_ic_baseline<T, R>(
96 baseline: &CachedPocketIcBaseline<T>,
97 restore: R,
98) -> Result<(), CachedBaselineRestoreFailure>
99where
100 R: Fn(&CachedPocketIcBaseline<T>),
101{
102 match catch_unwind(AssertUnwindSafe(|| restore(baseline))) {
103 Ok(()) => Ok(()),
104 Err(payload) => {
105 if transport::panic_is_dead_instance_transport(payload.as_ref()) {
106 Err(CachedBaselineRestoreFailure::DeadInstanceTransport)
107 } else {
108 Err(CachedBaselineRestoreFailure::Panic(payload))
109 }
110 }
111 }
112}
113
114fn drop_stale_cached_pocket_ic_baseline<T>(
117 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
118) {
119 let stale = {
120 let mut slot = slot
121 .lock()
122 .unwrap_or_else(std::sync::PoisonError::into_inner);
123 slot.take()
124 };
125
126 if let Some(stale) = stale {
127 let _ = catch_unwind(AssertUnwindSafe(|| {
128 drop(stale);
129 }));
130 }
131}
132
133impl<T> CachedPocketIcBaselineGuard<'_, T> {
134 #[must_use]
136 pub fn pocket_ic(&self) -> &PocketIc {
137 self.guard
138 .as_ref()
139 .expect("cached PocketIC baseline must exist")
140 .pocket_ic()
141 }
142
143 #[must_use]
145 pub fn metadata(&self) -> &T {
146 self.guard
147 .as_ref()
148 .expect("cached PocketIC baseline must exist")
149 .metadata()
150 }
151
152 #[must_use]
154 pub fn metadata_mut(&mut self) -> &mut T {
155 self.guard
156 .as_mut()
157 .expect("cached PocketIC baseline must exist")
158 .metadata_mut()
159 }
160
161 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
163 self.guard
164 .as_ref()
165 .expect("cached PocketIC baseline must exist")
166 .restore(controller_id)
167 }
168
169 pub fn restore_with_funding(
171 &self,
172 controller_id: Principal,
173 funding: SnapshotRestoreFunding,
174 ) -> Result<(), ControllerSnapshotError> {
175 self.guard
176 .as_ref()
177 .expect("cached PocketIC baseline must exist")
178 .restore_with_funding(controller_id, funding)
179 }
180}
181
182impl<T> CachedPocketIcBaseline<T> {
183 pub fn capture<I>(
185 pocket_ic: PocketIc,
186 controller_id: Principal,
187 canister_ids: I,
188 metadata: T,
189 ) -> Result<Self, ControllerSnapshotError>
190 where
191 I: IntoIterator<Item = Principal>,
192 {
193 let snapshots = pocket_ic.capture_controller_snapshots(controller_id, canister_ids)?;
194
195 Ok(Self {
196 pocket_ic,
197 snapshots,
198 metadata,
199 })
200 }
201
202 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
204 self.pocket_ic
205 .restore_controller_snapshots(controller_id, &self.snapshots)
206 }
207
208 pub fn restore_with_funding(
210 &self,
211 controller_id: Principal,
212 funding: SnapshotRestoreFunding,
213 ) -> Result<(), ControllerSnapshotError> {
214 self.pocket_ic.restore_controller_snapshots_with_funding(
215 controller_id,
216 &self.snapshots,
217 funding,
218 )
219 }
220
221 #[must_use]
223 pub const fn pocket_ic(&self) -> &PocketIc {
224 &self.pocket_ic
225 }
226
227 #[must_use]
229 pub const fn metadata(&self) -> &T {
230 &self.metadata
231 }
232
233 #[must_use]
235 pub const fn metadata_mut(&mut self) -> &mut T {
236 &mut self.metadata
237 }
238}