tor_persist/
testing.rs

1//! Testing-only StateMgr that stores values in a hash table.
2
3use crate::err::{Action, ErrorSource, Resource};
4use crate::{Error, LockStatus, Result, StateMgr};
5use serde::{de::DeserializeOwned, Serialize};
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9/// A state manager for testing support, that allows simulating persistence
10/// without having to store anything to disk.
11///
12/// Only available when this crate is built with the `testing` feature.
13#[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
14#[derive(Clone, Debug)]
15pub struct TestingStateMgr {
16    /// Inner reference-counted storage.
17    inner: Arc<Mutex<TestingStateMgrInner>>,
18}
19
20/// The inner state of a TestingStateMgr.
21#[derive(Debug)]
22struct TestingStateMgrInner {
23    /// True if this manager, and all references to it, hold the lock on
24    /// the storage.
25    lock_held: bool,
26    /// The underlying shared storage object.
27    storage: Arc<Mutex<TestingStateMgrStorage>>,
28}
29
30impl TestingStateMgrInner {
31    /// Release the lock, if we hold it. Otherwise, do nothing.
32    fn unlock(&mut self) {
33        if self.lock_held {
34            self.lock_held = false;
35            let mut storage = self.storage.lock().expect("Lock poisoned");
36            storage.lock_available = true;
37        }
38    }
39}
40
41/// Implementation type for [`TestingStateMgr`]: represents an underlying
42/// storage system that can be shared by multiple TestingStateMgr instances
43/// at a time, only one of which can hold the lock.
44#[derive(Debug)]
45struct TestingStateMgrStorage {
46    /// True if nobody currently holds the lock for this storage.
47    lock_available: bool,
48    /// Map from key to JSON-encoded values.
49    ///
50    /// We serialize our values here for convenience (so that we don't
51    /// have to use `Any`) and to try to detect any
52    /// serialization-related bugs.
53    entries: HashMap<String, String>,
54}
55
56impl Default for TestingStateMgr {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl TestingStateMgr {
63    /// Create a new empty unlocked [`TestingStateMgr`].
64    pub fn new() -> Self {
65        let storage = TestingStateMgrStorage {
66            lock_available: true,
67            entries: HashMap::new(),
68        };
69        let inner = TestingStateMgrInner {
70            lock_held: false,
71            storage: Arc::new(Mutex::new(storage)),
72        };
73        TestingStateMgr {
74            inner: Arc::new(Mutex::new(inner)),
75        }
76    }
77
78    /// Create a new unlocked [`TestingStateMgr`] that shares the same
79    /// underlying storage with this one.
80    #[must_use]
81    pub fn new_manager(&self) -> Self {
82        let inner = self.inner.lock().expect("Lock poisoned.");
83        let new_inner = TestingStateMgrInner {
84            lock_held: false,
85            storage: Arc::clone(&inner.storage),
86        };
87        TestingStateMgr {
88            inner: Arc::new(Mutex::new(new_inner)),
89        }
90    }
91
92    /// Return an error Resource corresponding to a given `key`.
93    fn err_resource(&self, key: &str) -> Resource {
94        Resource::Temporary {
95            key: key.to_string(),
96        }
97    }
98}
99
100impl StateMgr for TestingStateMgr {
101    fn load<D>(&self, key: &str) -> Result<Option<D>>
102    where
103        D: DeserializeOwned,
104    {
105        let inner = self.inner.lock().expect("Lock poisoned.");
106        let storage = inner.storage.lock().expect("Lock poisoned.");
107        let content = storage.entries.get(key);
108        match content {
109            Some(value) => {
110                Ok(Some(serde_json::from_str(value).map_err(|e| {
111                    Error::new(e, Action::Loading, self.err_resource(key))
112                })?))
113            }
114            None => Ok(None),
115        }
116    }
117
118    fn store<S>(&self, key: &str, val: &S) -> Result<()>
119    where
120        S: Serialize,
121    {
122        let inner = self.inner.lock().expect("Lock poisoned.");
123        if !inner.lock_held {
124            return Err(Error::new(
125                ErrorSource::NoLock,
126                Action::Storing,
127                Resource::Manager,
128            ));
129        }
130        let mut storage = inner.storage.lock().expect("Lock poisoned.");
131
132        let val = serde_json::to_string_pretty(val)
133            .map_err(|e| Error::new(e, Action::Storing, self.err_resource(key)))?;
134
135        storage.entries.insert(key.to_string(), val);
136        Ok(())
137    }
138
139    fn can_store(&self) -> bool {
140        let inner = self.inner.lock().expect("Lock poisoned.");
141
142        inner.lock_held
143    }
144
145    fn try_lock(&self) -> Result<LockStatus> {
146        let mut inner = self.inner.lock().expect("Lock poisoned.");
147        if inner.lock_held {
148            return Ok(LockStatus::AlreadyHeld);
149        }
150
151        let mut storage = inner.storage.lock().expect("Lock poisoned");
152        if storage.lock_available {
153            storage.lock_available = false;
154            drop(storage); // release borrow
155            inner.lock_held = true;
156            Ok(LockStatus::NewlyAcquired)
157        } else {
158            Ok(LockStatus::NoLock)
159        }
160    }
161
162    fn unlock(&self) -> Result<()> {
163        let mut inner = self.inner.lock().expect("Lock poisoned.");
164        inner.unlock();
165        Ok(())
166    }
167}
168
169impl Drop for TestingStateMgrInner {
170    fn drop(&mut self) {
171        self.unlock();
172    }
173}
174
175#[cfg(test)]
176mod test {
177    // @@ begin test lint list maintained by maint/add_warning @@
178    #![allow(clippy::bool_assert_comparison)]
179    #![allow(clippy::clone_on_copy)]
180    #![allow(clippy::dbg_macro)]
181    #![allow(clippy::mixed_attributes_style)]
182    #![allow(clippy::print_stderr)]
183    #![allow(clippy::print_stdout)]
184    #![allow(clippy::single_char_pattern)]
185    #![allow(clippy::unwrap_used)]
186    #![allow(clippy::unchecked_duration_subtraction)]
187    #![allow(clippy::useless_vec)]
188    #![allow(clippy::needless_pass_by_value)]
189    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
190    use super::*;
191    use serde::{Deserialize, Serialize};
192
193    #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
194    struct Ex1 {
195        v1: u32,
196        v2: u64,
197    }
198    #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
199    struct Ex2 {
200        s1: String,
201        s2: String,
202    }
203    #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
204    enum OldEnum {
205        Variant1,
206    }
207    #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
208    enum NewEnum {
209        Variant1,
210        Variant2,
211    }
212
213    #[test]
214    fn basic_tests() {
215        let mgr = TestingStateMgr::new();
216        let v1 = Ex1 { v1: 8, v2: 99 };
217        let s1 = Ex2 {
218            s1: "Hello".into(),
219            s2: "World".into(),
220        };
221
222        assert_eq!(mgr.load::<Ex1>("item1").unwrap(), None);
223        assert!(matches!(
224            mgr.store("item1", &v1).unwrap_err().source(),
225            ErrorSource::NoLock
226        ));
227
228        assert!(!mgr.can_store());
229        assert_eq!(mgr.try_lock().unwrap(), LockStatus::NewlyAcquired);
230        assert!(mgr.can_store());
231
232        assert!(mgr.store("item1", &v1).is_ok());
233        assert_eq!(mgr.load::<Ex1>("item1").unwrap(), Some(v1));
234        assert!(mgr.load::<Ex2>("item1").is_err());
235
236        assert!(mgr.store("item2", &s1).is_ok());
237        assert_eq!(mgr.load::<Ex2>("item2").unwrap(), Some(s1));
238        assert!(mgr.load::<Ex1>("item2").is_err());
239
240        let v2 = Ex1 { v1: 10, v2: 12 };
241        assert!(mgr.store("item1", &v2).is_ok());
242        assert_eq!(mgr.load::<Ex1>("item1").unwrap(), Some(v2));
243    }
244
245    #[test]
246    fn lock_blocking() {
247        let mgr = TestingStateMgr::new();
248
249        assert!(!mgr.can_store());
250
251        let mgr2 = mgr.new_manager();
252
253        assert_eq!(mgr.try_lock().unwrap(), LockStatus::NewlyAcquired);
254        assert_eq!(mgr.try_lock().unwrap(), LockStatus::AlreadyHeld);
255        assert!(mgr.can_store());
256
257        assert!(!mgr2.can_store());
258        assert_eq!(mgr2.try_lock().unwrap(), LockStatus::NoLock);
259        assert!(!mgr2.can_store());
260
261        drop(mgr);
262        assert_eq!(mgr2.try_lock().unwrap(), LockStatus::NewlyAcquired);
263        assert!(mgr2.can_store());
264    }
265
266    #[test]
267    fn typesafe_handles() {
268        use crate::DynStorageHandle;
269        let mgr = TestingStateMgr::new();
270
271        let h1: DynStorageHandle<Ex1> = mgr.clone().create_handle("foo");
272        let h2: DynStorageHandle<Ex2> = mgr.clone().create_handle("bar");
273        let h3: DynStorageHandle<Ex2> = mgr.clone().create_handle("baz");
274
275        let v1 = Ex1 { v1: 1, v2: 2 };
276        let s1 = Ex2 {
277            s1: "aaa".into(),
278            s2: "bbb".into(),
279        };
280        let s2 = Ex2 {
281            s1: "jj".into(),
282            s2: "yrfmstbyes".into(),
283        };
284
285        assert!(matches!(
286            h1.store(&v1).unwrap_err().source(),
287            ErrorSource::NoLock
288        ));
289        assert!(mgr.try_lock().unwrap().held());
290        assert!(h1.can_store());
291        assert!(h1.store(&v1).is_ok());
292
293        assert!(h2.can_store());
294        assert!(h2.store(&s1).is_ok());
295        assert!(h3.load().unwrap().is_none());
296        assert!(h3.store(&s2).is_ok());
297
298        assert_eq!(h1.load().unwrap(), Some(v1));
299        assert_eq!(h2.load().unwrap(), Some(s1));
300        assert_eq!(h3.load().unwrap(), Some(s2));
301    }
302
303    #[test]
304    fn futureproof() {
305        use crate::Futureproof;
306
307        let v1 = Ex1 { v1: 8, v2: 99 };
308
309        let v1_ser = serde_json::to_string(&v1).unwrap();
310
311        let v1_as_ex1: Futureproof<Ex1> = serde_json::from_str(&v1_ser).unwrap();
312        let v1_as_ex2: Futureproof<Ex2> = serde_json::from_str(&v1_ser).unwrap();
313        assert!(v1_as_ex1.clone().into_option().is_some());
314        assert!(v1_as_ex2.into_option().is_none());
315
316        assert_eq!(serde_json::to_string(&v1_as_ex1).unwrap(), v1_ser);
317    }
318
319    #[test]
320    fn futureproof_enums() {
321        use crate::Futureproof;
322
323        let new1 = NewEnum::Variant1;
324        let new2 = NewEnum::Variant2;
325
326        let new1_ser = serde_json::to_string(&new1).unwrap();
327        let new2_ser = serde_json::to_string(&new2).unwrap();
328
329        let old1: Futureproof<OldEnum> = serde_json::from_str(&new1_ser).unwrap();
330        let old2: Futureproof<OldEnum> = serde_json::from_str(&new2_ser).unwrap();
331
332        assert!(old1.into_option().is_some());
333        assert!(old2.into_option().is_none());
334    }
335}