/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Mocking utilities. //! //! Mock data is set on a per-thread basis. [`crate::std::thread`] handles this automatically for //! scoped threads, and warns about creating threads otherwise (which won't be able to //! automatically share mocked data, but it can be easily done with [`SharedMockData`] when //! appropriate). //! //! Mock data is stored using type erasure, with a [`MockKey`] indexing arbitrary values. Use //! [`mock_key!`] to define keys and the values to which they map. This approach was taken as a //! matter of covenience for programmers, and the resulting creation and consumption APIs are //! succinct yet extensible. //! //! Consumers should define keys (and expose them for mockers), and at runtime create a mock key //! instance and call [`MockKey::get`] or [`MockKey::try_get`] to retrieve mocked values to use. //! //! Mockers should call [`builder`] to create a builder, [`set`](Builder::set) key/value mappings, //! and call [`run`](Builder::run) to execute code with the mock data set.
use std::any::{Any, TypeId}; use std::cell::RefCell; use std::collections::{hash_map::DefaultHasher, HashMap}; use std::hash::{Hash, Hasher}; use std::sync::atomic::{AtomicPtr, Ordering::Relaxed}; use std::sync::Arc;
type MockDataMap = HashMap<Box<dyn MockKeyStored>, Box<dyn Any + Send + Sync>>;
/// A trait intended to be used as a trait object interface for mock keys. pubtrait MockKeyStored: Any + std::fmt::Debug + Send + Sync { fn eq(&self, other: &dyn MockKeyStored) -> bool; fn hash(&self, state: &mut DefaultHasher);
}
/// A type which can be used as a mock key. pubtrait MockKey: MockKeyStored + Sized { /// The value to which the key maps. type Value: Any + Send + Sync;
/// Get the value set for this key, returning `None` if no data is set. fn try_get<F, R>(&self, f: F) -> Option<R> where
F: FnOnce(&Self::Value) -> R,
{
MOCK_DATA.with_borrow(move |data| match data {
None => panic!("no mock data set"),
Some(d) => d
.get(selfas &dyn MockKeyStored)
.and_then(move |b| b.downcast_ref())
.map(f),
})
}
/// Get the value set for this key. /// /// Panics if no mock data is set for the key. fn get<F, R>(&self, f: F) -> R where
F: FnOnce(&Self::Value) -> R,
{ matchself.try_get(f) {
Some(v) => v,
None => panic!("mock data for {self:?} not set"),
}
}
}
/// Mock data which can be shared amongst threads. #[derive(Clone)] pubstruct SharedMockData(Option<Arc<MockDataMap>>);
impl SharedMockData { /// Create a `SharedMockData` which stores the mock data from the current thread. pubfn new() -> Self {
MOCK_DATA.with_borrow(|data| SharedMockData(data.clone()))
}
/// Set the mock data on the current thread. /// /// # Safety /// Callers must ensure that the mock data outlives the lifetime of the thread. pubunsafefn set(self) {
MOCK_DATA.with_borrow_mut(move |data| *data = self.0);
}
/// Call the given function with this mock data set. pubfn call<R>(self, f: impl FnOnce() -> R) -> R { let prev = MOCK_DATA.replace(self.0); let ret = f();
MOCK_DATA.set(prev);
ret
}
}
/// Create a mock builder, which allows adding mock data and running functions under that mock /// environment. pubfn builder() -> Builder {
Builder::new()
}
/// A mock data builder. #[derive(Default)] pubstruct Builder {
data: MockDataMap,
}
/// Set a mock data key/value mapping. pubfn set<K: MockKey>(&mutself, key: K, value: K::Value) -> &e='color:red'>mutSelf { self.data.insert(Box::new(key), Box::new(value)); self
}
/// Run the given function with mock data set. pubfn run<F, R>(&mutself, f: F) -> R where
F: FnOnce() -> R,
{
SharedMockData(Some(Arc::new(std::mem::take(&mutself.data)))).call(f)
}
}
/// A general-purpose [`MockKey`] keyed by an identifier string and the stored type. /// /// Use [`hook`] or [`try_hook`] in code accessing the values. pubstruct MockHook<T> {
name: &'static str,
_p: std::marker::PhantomData<fn() -> T>,
}
impl<T: Any + Send + Sync + 'static> MockKey for MockHook<T> { type Value = T;
}
impl<T> MockHook<T> { /// Create a new mock hook key with the given name. pubfn new(name: &'static str) -> Self {
MockHook {
name,
_p: Default::default(),
}
}
}
/// Create a mock hook with the given name. When mocking isn't enabled, the given value will be /// used instead. Panics if the hook isn't set. pubfn hook<T: Any + Send + Sync + Clone>(_normally: T, name: &'static str) -> T {
MockHook::new(name).get(|v: &T| v.clone())
}
/// Create a mock hook with the given name. When mocking isn't enabled or the hook hasn't been set, /// the given value will be used instead. pubfn try_hook<T: Any + Send + Sync + Clone>(fallback: T, name: &'static str) -> T {
MockHook::new(name)
.try_get(|v: &T| v.clone())
.unwrap_or(fallback)
}
/// A static which can be replaced with mocked values when mocking is enabled. /// /// This is especially useful to avoid statics with interior mutations affecting tests.
macro_rules! mocked_static {
( $(#[$m:meta])* $vis:vis static $name:ident: $T:ty = $init:expr ; $($item:item)*) => {
mock_key! { $(#[$m])* #[allow(non_camel_case_types)] pub(crate) struct $name => std::sync::Arc<$T> }
$($item)*
};
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.