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::{ControllerSnapshotError, ControllerSnapshots, PocketIcSnapshotExt, transport};
9
10pub struct CachedPocketIcBaseline<T> {
15 pocket_ic: PocketIc,
16 snapshots: ControllerSnapshots,
17 metadata: T,
18}
19
20pub struct CachedPocketIcBaselineGuard<'a, T> {
25 guard: MutexGuard<'a, Option<CachedPocketIcBaseline<T>>>,
26}
27
28enum CachedBaselineRestoreFailure {
29 DeadInstanceTransport,
30 Panic(Box<dyn std::any::Any + Send>),
31}
32
33fn acquire_cached_pocket_ic_baseline<T, F>(
35 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
36 build: F,
37) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
38where
39 F: FnOnce() -> CachedPocketIcBaseline<T>,
40{
41 let mut guard = slot
42 .lock()
43 .unwrap_or_else(std::sync::PoisonError::into_inner);
44 let cache_hit = guard.is_some();
45
46 if !cache_hit {
47 *guard = Some(build());
48 }
49
50 (CachedPocketIcBaselineGuard { guard }, cache_hit)
51}
52
53pub fn restore_or_rebuild_cached_pocket_ic_baseline<T, B, R>(
56 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
57 build: B,
58 restore: R,
59) -> (CachedPocketIcBaselineGuard<'static, T>, bool)
60where
61 B: Fn() -> CachedPocketIcBaseline<T>,
62 R: Fn(&CachedPocketIcBaseline<T>),
63{
64 let (baseline, cache_hit) = acquire_cached_pocket_ic_baseline(slot, &build);
65 if !cache_hit {
66 return (baseline, false);
67 }
68
69 match try_restore_cached_pocket_ic_baseline(
70 baseline
71 .guard
72 .as_ref()
73 .expect("cached PocketIC baseline must exist"),
74 restore,
75 ) {
76 Ok(()) => return (baseline, true),
77 Err(CachedBaselineRestoreFailure::DeadInstanceTransport) => {}
78 Err(CachedBaselineRestoreFailure::Panic(payload)) => {
79 resume_unwind(payload);
80 }
81 }
82
83 drop(baseline);
84 drop_stale_cached_pocket_ic_baseline(slot);
85
86 let (rebuilt, _cache_hit) = acquire_cached_pocket_ic_baseline(slot, build);
87 (rebuilt, false)
88}
89
90fn try_restore_cached_pocket_ic_baseline<T, R>(
93 baseline: &CachedPocketIcBaseline<T>,
94 restore: R,
95) -> Result<(), CachedBaselineRestoreFailure>
96where
97 R: Fn(&CachedPocketIcBaseline<T>),
98{
99 match catch_unwind(AssertUnwindSafe(|| restore(baseline))) {
100 Ok(()) => Ok(()),
101 Err(payload) => {
102 if transport::panic_is_dead_instance_transport(payload.as_ref()) {
103 Err(CachedBaselineRestoreFailure::DeadInstanceTransport)
104 } else {
105 Err(CachedBaselineRestoreFailure::Panic(payload))
106 }
107 }
108 }
109}
110
111fn drop_stale_cached_pocket_ic_baseline<T>(
114 slot: &'static Mutex<Option<CachedPocketIcBaseline<T>>>,
115) {
116 let stale = {
117 let mut slot = slot
118 .lock()
119 .unwrap_or_else(std::sync::PoisonError::into_inner);
120 slot.take()
121 };
122
123 if let Some(stale) = stale {
124 let _ = catch_unwind(AssertUnwindSafe(|| {
125 drop(stale);
126 }));
127 }
128}
129
130impl<T> CachedPocketIcBaselineGuard<'_, T> {
131 #[must_use]
133 pub fn pocket_ic(&self) -> &PocketIc {
134 self.guard
135 .as_ref()
136 .expect("cached PocketIC baseline must exist")
137 .pocket_ic()
138 }
139
140 #[must_use]
142 pub fn metadata(&self) -> &T {
143 self.guard
144 .as_ref()
145 .expect("cached PocketIC baseline must exist")
146 .metadata()
147 }
148
149 #[must_use]
151 pub fn metadata_mut(&mut self) -> &mut T {
152 self.guard
153 .as_mut()
154 .expect("cached PocketIC baseline must exist")
155 .metadata_mut()
156 }
157
158 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
160 self.guard
161 .as_ref()
162 .expect("cached PocketIC baseline must exist")
163 .restore(controller_id)
164 }
165}
166
167impl<T> CachedPocketIcBaseline<T> {
168 pub fn capture<I>(
170 pocket_ic: PocketIc,
171 controller_id: Principal,
172 canister_ids: I,
173 metadata: T,
174 ) -> Result<Self, ControllerSnapshotError>
175 where
176 I: IntoIterator<Item = Principal>,
177 {
178 let snapshots = pocket_ic.capture_controller_snapshots(controller_id, canister_ids)?;
179
180 Ok(Self {
181 pocket_ic,
182 snapshots,
183 metadata,
184 })
185 }
186
187 pub fn restore(&self, controller_id: Principal) -> Result<(), ControllerSnapshotError> {
189 self.pocket_ic
190 .restore_controller_snapshots(controller_id, &self.snapshots)
191 }
192
193 #[must_use]
195 pub const fn pocket_ic(&self) -> &PocketIc {
196 &self.pocket_ic
197 }
198
199 #[must_use]
201 pub const fn metadata(&self) -> &T {
202 &self.metadata
203 }
204
205 #[must_use]
207 pub const fn metadata_mut(&mut self) -> &mut T {
208 &mut self.metadata
209 }
210}