use core::{
cell::{Ref, RefCell, RefMut},
marker::PhantomData,
ops::{Deref, DerefMut},
};
use alloc::{borrow::Cow, boxed::Box};
use crate::{
boot_services::StandardBootServices,
component::{
metadata::MetaData,
service::IntoService,
storage::{Deferred, Storage, UnsafeStorageCell},
},
runtime_services::StandardRuntimeServices,
};
use super::storage::ConfigRaw;
type ParamItem<'w, 'state, P> = <P as Param>::Item<'w, 'state>;
pub unsafe trait Param {
type State: Send + Sync + 'static;
type Item<'storage, 'state>;
unsafe fn get_param<'storage, 'state>(
_state: &'state Self::State,
_storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state>;
fn validate(_state: &Self::State, _storage: UnsafeStorageCell) -> bool;
fn try_validate(state: &Self::State, storage: UnsafeStorageCell) -> Result<(), Cow<'static, str>> {
if Self::validate(state, storage) {
Ok(())
} else {
Err(Cow::from(alloc::format!("{} not available.", super::type_name::normalized::<Self>())))
}
}
fn init_state(storage: &mut Storage, meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>>;
}
#[doc(hidden)]
pub struct RunOnce;
#[doc(hidden)]
pub struct RunMany;
#[doc(hidden)]
pub trait ComponentInput: Sized {}
#[doc(hidden)]
impl ComponentInput for () {}
#[diagnostic::on_unimplemented(
message = "The function signature does not meet the requirements.\n\n{Self}\n",
note = "1. The first parameter must be Self, &Self, or &mut Self.",
note = "2. The remaining parameters must implement patina::component::params::Param",
note = "3. Only a function with up to 5 parameters, excluding self, is supported.",
note = "4. The return type must be patina::error::Result<()>"
)]
pub trait ParamFunction<Marker>: Send + Sync + 'static {
type Param: Param;
type In: ComponentInput;
type Out;
fn run(&mut self, input: &mut Option<Self::In>, param_value: ParamItem<Self::Param>) -> Self::Out;
}
macro_rules! impl_param_function {
($($param:ident),*) => {
#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<Out, Func, $($param : Param),*> ParamFunction<fn($($param,)*)->Out> for Func
where
Func: Send + Sync + 'static,
for<'a, 'b> &'a mut Func:
FnMut($($param), *) -> Out +
FnMut($(ParamItem<$param>),*) -> Out,
Out: 'static,
{
type Param = ($($param,)*);
type In = ();
type Out = Out;
fn run(&mut self, _input: &mut Option<()>, param_value: ParamItem<($($param,)*)>) -> Out {
fn call_inner<Out, $($param),*>(
mut f: impl FnMut($($param),*) -> Out,
$($param: $param,)*
) -> Out {
f($($param),*)
}
let ($($param,)*) = param_value;
call_inner(self, $($param),*)
}
}
#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<In, Out, Func, $($param: Param),*> ParamFunction<(RunOnce, fn(In, $($param,)*)->Out)> for Func
where
Func: Send + Sync + 'static,
for <'a> &'a mut Func:
FnMut(In, $($param),*) -> Out +
FnMut(In, $(ParamItem<$param>),*) -> Out,
In: ComponentInput + 'static,
Out: 'static,
{
type Param = ($($param,)*);
type In = In;
type Out = Out;
fn run(&mut self, input: &mut Option<In>, param_value: ParamItem<($($param,)*)>) -> Out {
#[allow(clippy::too_many_arguments)]
fn call_inner<In, Out, $($param,)*>(
mut f: impl FnMut(In, $($param),*) -> Out,
input: In,
$($param: $param,)*
) -> Out {
f(input, $($param),*)
}
let ($($param,)*) = param_value;
call_inner(self, input.take().unwrap(), $($param),*)
}
}
#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<In, Out, Func, $($param: Param),*> ParamFunction<(RunMany, fn(&mut In, $($param,)*)->Out)> for Func
where
Func: Send + Sync + 'static,
for <'a, 'b> &'a mut Func:
FnMut(&'b mut In, $($param),*) -> Out +
FnMut(&'b mut In, $(ParamItem<$param>),*) -> Out,
In: ComponentInput + 'static,
Out: 'static,
{
type Param = ($($param,)*);
type In = In;
type Out = Out;
fn run(&mut self, input: &mut Option<In>, param_value: ParamItem<($($param,)*)>) -> Out {
#[allow(clippy::too_many_arguments)]
fn call_inner<In, Out, $($param,)*>(
mut f: impl FnMut(&mut In, $($param),*) -> Out,
input: &mut In,
$($param: $param,)*
) -> Out {
f(input, $($param),*)
}
let ($($param,)*) = param_value;
call_inner(self, input.as_mut().unwrap(), $($param),*)
}
}
#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<In, Out, Func, $($param: Param),*> ParamFunction<(RunMany, fn(&In, $($param,)*)->Out)> for Func
where
Func: Send + Sync + 'static,
for <'a, 'b> &'a mut Func:
FnMut(&'b In, $($param),*) -> Out +
FnMut(&'b In, $(ParamItem<$param>),*) -> Out,
In: ComponentInput + 'static,
Out: 'static,
{
type Param = ($($param,)*);
type In = In;
type Out = Out;
fn run(&mut self, input: &mut Option<In>, param_value: ParamItem<($($param,)*)>) -> Out {
#[allow(clippy::too_many_arguments)]
fn call_inner<In, Out, $($param,)*>(
mut f: impl FnMut(&In, $($param),*) -> Out,
input: &In,
$($param: $param,)*
) -> Out {
f(input, $($param),*)
}
let ($($param,)*) = param_value;
call_inner(self, input.as_ref().unwrap(), $($param),*)
}
}
}
}
impl_param_function!();
impl_param_function!(T1);
impl_param_function!(T1, T2);
impl_param_function!(T1, T2, T3);
impl_param_function!(T1, T2, T3, T4);
impl_param_function!(T1, T2, T3, T4, T5);
impl_param_function!(T1, T2, T3, T4, T5, T6);
unsafe impl<P: Param> Param for Option<P> {
type State = P::State;
type Item<'storage, 'state> = Option<P::Item<'storage, 'state>>;
unsafe fn get_param<'storage, 'state>(
state: &'state Self::State,
storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state> {
match P::validate(state, storage) {
true => Some(unsafe { P::get_param(state, storage) }),
false => None,
}
}
fn validate(_state: &Self::State, _storage: UnsafeStorageCell) -> bool {
true
}
fn init_state(storage: &mut Storage, meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
P::init_state(storage, meta)
}
}
#[derive(Debug)]
pub struct Config<'c, T: Default + 'static> {
value: Ref<'c, ConfigRaw>,
_marker: PhantomData<T>,
}
impl<T: Default + 'static> Config<'_, T> {
#[allow(clippy::test_attr_in_doctest)]
pub fn mock(value: T) -> Self {
let refcell: RefCell<ConfigRaw> = RefCell::new(ConfigRaw::new(true, Box::new(value)));
let leaked = Box::leak(Box::new(refcell));
Config { value: leaked.borrow(), _marker: PhantomData }
}
}
impl<T: Default + 'static> Deref for Config<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
.downcast_ref()
.unwrap_or_else(|| panic!("Config should be of type {}", super::type_name::normalized::<T>()))
}
}
impl<'c, T: Default + 'static> From<Ref<'c, ConfigRaw>> for Config<'c, T> {
fn from(value: Ref<'c, ConfigRaw>) -> Self {
Self { value, _marker: PhantomData }
}
}
unsafe impl<T: Default + 'static> Param for Config<'_, T> {
type State = usize;
type Item<'storage, 'state> = Config<'storage, T>;
unsafe fn get_param<'storage, 'state>(
lookup_id: &'state Self::State,
storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state> {
Config::from(unsafe { storage.storage().get_raw_config(*lookup_id) })
}
fn validate(state: &Self::State, storage: UnsafeStorageCell) -> bool {
unsafe { storage.storage() }.get_raw_config(*state).is_locked()
}
fn init_state(storage: &mut Storage, meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
let id = storage.add_config_default_if_not_present::<T>();
if meta.access().has_writes_all_configs() {
return Err(Cow::from(alloc::format!(
"Config<{}> conflicts with a previous &mut Storage access.",
super::type_name::normalized::<T>()
)));
}
if meta.access().has_config_write(id) {
return Err(Cow::from(alloc::format!(
"Config<{0}> conflicts with a previous ConfigMut<{0}> access.",
super::type_name::normalized::<T>()
)));
}
meta.access_mut().add_config_read(id);
Ok(id)
}
}
#[derive(Debug)]
pub struct ConfigMut<'c, T: Default + 'static> {
value: RefMut<'c, ConfigRaw>,
_marker: PhantomData<T>,
}
impl<T: Default + 'static> ConfigMut<'_, T> {
#[allow(clippy::test_attr_in_doctest)]
pub fn mock(value: T) -> Self {
let refcell: RefCell<ConfigRaw> = RefCell::new(ConfigRaw::new(false, Box::new(value)));
let leaked = Box::leak(Box::new(refcell));
ConfigMut { value: leaked.borrow_mut(), _marker: PhantomData }
}
pub fn lock(&mut self) {
self.value.lock();
}
}
impl<T: Default + 'static> Deref for ConfigMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
.downcast_ref()
.unwrap_or_else(|| panic!("Config should be of type {}", super::type_name::normalized::<T>()))
}
}
impl<T: Default + 'static> DerefMut for ConfigMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.value
.downcast_mut()
.unwrap_or_else(|| panic!("Config should be of type {}", super::type_name::normalized::<T>()))
}
}
impl<'c, T: Default + 'static> From<RefMut<'c, ConfigRaw>> for ConfigMut<'c, T> {
fn from(value: RefMut<'c, ConfigRaw>) -> Self {
Self { value, _marker: PhantomData }
}
}
unsafe impl<T: Default + 'static> Param for ConfigMut<'_, T> {
type State = usize;
type Item<'storage, 'state> = ConfigMut<'storage, T>;
unsafe fn get_param<'storage, 'state>(
lookup_id: &'state Self::State,
storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state> {
ConfigMut::from(unsafe { storage.storage().get_raw_config_mut(*lookup_id) })
}
fn validate(state: &Self::State, storage: UnsafeStorageCell) -> bool {
!unsafe { storage.storage() }.get_raw_config(*state).is_locked()
}
fn init_state(storage: &mut Storage, meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
let id = storage.add_config_default_if_not_present::<T>();
storage.unlock_config(id);
if meta.access().has_writes_all_configs() {
return Err(Cow::from(alloc::format!(
"ConfigMut<{}> conflicts with a previous &mut Storage access.",
super::type_name::normalized::<T>()
)));
}
if meta.access().has_reads_all_configs() {
return Err(Cow::from(alloc::format!(
"ConfigMut<{}> conflicts with a previous &Storage access.",
super::type_name::normalized::<T>()
)));
}
if meta.access().has_config_write(id) {
return Err(Cow::from(alloc::format!(
"ConfigMut<{0}> conflicts with a previous ConfigMut<{0}> access.",
super::type_name::normalized::<T>()
)));
}
if meta.access().has_config_read(id) {
return Err(Cow::from(alloc::format!(
"ConfigMut<{0}> conflicts with a previous Config<{0}> access.",
super::type_name::normalized::<T>()
)));
}
meta.access_mut().add_config_write(id);
Ok(id)
}
}
pub struct Commands<'storage> {
queue: &'storage mut Deferred,
}
impl Commands<'_> {
pub fn add_config<C: Default + 'static>(&mut self, config: C) {
self.queue.add_command(move |storage| {
storage.add_config(config);
});
}
pub fn add_service<S: IntoService + 'static>(&mut self, service: S) {
self.queue.add_command(move |storage| {
storage.add_service(service);
});
}
#[allow(clippy::test_attr_in_doctest)]
pub fn mock() -> Self {
Commands { queue: Box::leak(Box::new(Deferred::default())) }
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}
unsafe impl Param for Commands<'_> {
type State = ();
type Item<'storage, 'state> = Commands<'storage>;
unsafe fn get_param<'storage, 'state>(
_state: &'state Self::State,
storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state> {
Commands { queue: unsafe { storage.storage_mut().deferred() } }
}
fn validate(_state: &Self::State, _storage: UnsafeStorageCell) -> bool {
true
}
fn init_state(_storage: &mut Storage, meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
if meta.access().has_deferred() {
return Err(Cow::from("Commands conflicts with a previous Commands access."));
}
meta.access_mut().deferred();
Ok(())
}
}
unsafe impl Param for StandardBootServices {
type State = ();
type Item<'storage, 'state> = Self;
unsafe fn get_param<'state>(
_state: &'state Self::State,
storage: UnsafeStorageCell<'_>,
) -> Self::Item<'static, 'state> {
StandardBootServices::clone(unsafe { storage.storage().boot_services() })
}
fn validate(_state: &Self::State, storage: UnsafeStorageCell) -> bool {
unsafe { storage.storage() }.boot_services().is_init()
}
fn init_state(_storage: &mut Storage, _meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
Ok(())
}
}
unsafe impl Param for StandardRuntimeServices {
type State = ();
type Item<'storage, 'state> = Self;
unsafe fn get_param<'state>(
_state: &'state Self::State,
storage: UnsafeStorageCell<'_>,
) -> Self::Item<'static, 'state> {
StandardRuntimeServices::clone(unsafe { storage.storage().runtime_services() })
}
fn validate(_state: &Self::State, storage: UnsafeStorageCell) -> bool {
unsafe { storage.storage() }.runtime_services().is_init()
}
fn init_state(_storage: &mut Storage, _meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct Handle {
handle: r_efi::efi::Handle,
}
impl Handle {
#[cfg(any(test, feature = "mockall"))]
pub fn mock(handle: r_efi::efi::Handle) -> Self {
Self { handle }
}
}
impl core::ops::Deref for Handle {
type Target = r_efi::efi::Handle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
unsafe impl Param for Handle {
type State = ();
type Item<'storage, 'state> = Self;
unsafe fn get_param<'state>(
_state: &'state Self::State,
storage: UnsafeStorageCell<'_>,
) -> Self::Item<'static, 'state> {
let handle = unsafe { storage.storage() }.image_handle().expect("image_handle validated as Some in validate()");
Handle { handle }
}
fn validate(_state: &Self::State, storage: UnsafeStorageCell) -> bool {
unsafe { storage.storage() }.image_handle().is_some()
}
fn init_state(_storage: &mut Storage, _meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
Ok(())
}
}
macro_rules! impl_component_param_tuple {
($($param: ident), *) => {
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
unsafe impl<$($param: Param),*> Param for ($($param,)*) {
type State = ($($param::State,)*);
type Item<'storage, 'state> = ($($param::Item::<'storage, 'state>,)*);
unsafe fn get_param<'storage, 'state>(state: &'state Self::State, _storage: UnsafeStorageCell<'storage>) -> Self::Item<'storage, 'state> {
let ($($param,)*) = state;
#[allow(unused_unsafe)]
($(
unsafe { $param::get_param($param, _storage) },
)*)
}
fn try_validate(state: &Self::State, _storage: UnsafeStorageCell) -> Result<(), Cow<'static, str>> {
let ($($param,)*) = state;
$(
if !$param::validate($param, _storage) {
return Err(Cow::from(super::type_name::normalized::<$param>()));
}
)*
Ok(())
}
fn validate(_state: &Self::State, _storage: UnsafeStorageCell) -> bool {
true
}
fn init_state(_storage: &mut Storage, _meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
Ok(($($param::init_state(_storage, _meta)?,)*))
}
}
}
}
impl_component_param_tuple!();
impl_component_param_tuple!(T1);
impl_component_param_tuple!(T1, T2);
impl_component_param_tuple!(T1, T2, T3);
impl_component_param_tuple!(T1, T2, T3, T4);
impl_component_param_tuple!(T1, T2, T3, T4, T5);
impl_component_param_tuple!(T1, T2, T3, T4, T5, T6);
#[cfg(test)]
#[coverage(off)]
mod tests {
use core::sync::atomic::AtomicBool;
use crate::{
component::{IntoComponent, component, storage::Storage},
error::Result,
};
use crate as patina;
use super::*;
#[test]
fn test_config_mut_deref_sticks_outside_fn() {
fn my_fn(mut cfg: ConfigMut<i32>) {
*cfg += 1;
}
let inner_data: RefCell<ConfigRaw> = RefCell::new(ConfigRaw::new(false, Box::new(42)));
let config = ConfigMut::from(inner_data.borrow_mut());
my_fn(config);
let config = ConfigMut { value: inner_data.borrow_mut(), _marker: PhantomData::<i32> };
assert_eq!(*config, 43);
}
#[test]
fn test_config_mut_deref_sticks_inside_fn() {
fn my_fn(mut cfg: ConfigMut<i32>) {
*cfg += 1;
assert_eq!(43, *cfg);
}
let cfg = ConfigMut::mock(42);
my_fn(cfg);
}
#[test]
fn test_config_deref() {
fn my_fn(cfg: Config<i32>) {
assert_eq!(*cfg, 42);
}
let config = Config::mock(42);
my_fn(config);
}
#[test]
fn test_config_can_be_accessed_when_locked() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
let id = Config::<i32>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(Config::<i32>::try_validate(&id, (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
assert_eq!(0_i32, unsafe { *Config::<i32>::get_param(&id, cell_storage) });
}
#[test]
fn test_config_cannot_be_accessed_while_unlocked() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
let id = ConfigMut::<i32>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(
Config::<i32>::try_validate(&id, (&storage).into())
.is_err_and(|err| err == "patina::component::params::Config<i32> not available.")
);
}
#[test]
fn test_config_mut_cannot_be_accessed_while_locked() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
let id = Config::<i32>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(
ConfigMut::<i32>::try_validate(&id, (&storage).into())
.is_err_and(|err| err == "patina::component::params::ConfigMut<i32> not available.")
);
}
#[test]
fn test_config_mut_can_always_be_retrieved_while_unlocked() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
let id = ConfigMut::<i32>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(ConfigMut::<i32>::try_validate(&id, (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
assert_eq!(0_i32, unsafe { *ConfigMut::<i32>::get_param(&id, cell_storage) });
}
#[test]
fn test_config_mut_lock_fn_prevents_future_config_mut_access() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
let id = ConfigMut::<i32>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(ConfigMut::<i32>::try_validate(&id, (&storage).into()).is_ok());
storage.get_config_mut::<i32>().unwrap().lock();
assert!(ConfigMut::<i32>::try_validate(&id, (&storage).into()).is_err());
}
#[test]
fn test_storage_can_always_be_retrieved() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
<&Storage as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<&Storage as Param>::try_validate(&(), (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let _ = unsafe { <&Storage as Param>::get_param(&(), cell_storage) };
}
#[test]
fn test_storage_mut_can_always_be_retrieved() {
let mut storage = Storage::new();
let mut mock_metadata = MetaData::new::<i32>();
<&mut Storage as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<&mut Storage as Param>::try_validate(&(), (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let _ = unsafe { <&mut Storage as Param>::get_param(&(), cell_storage) };
}
#[test]
fn test_boot_services_fails_to_validate_when_null() {
let mut storage = Storage::default(); let mut mock_metadata = MetaData::new::<i32>();
<StandardBootServices as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert_eq!(
Err(Cow::from("patina::boot_services::StandardBootServices not available.")),
<StandardBootServices as Param>::try_validate(&(), (&storage).into())
);
}
#[test]
fn test_boot_services_can_be_retrieved() {
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
let mut mock_bs = core::mem::MaybeUninit::<r_efi::efi::BootServices>::zeroed();
storage.set_boot_services(StandardBootServices::new(mock_bs.as_mut_ptr()));
<StandardBootServices as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<StandardBootServices as Param>::try_validate(&(), (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let _ = unsafe { <StandardBootServices as Param>::get_param(&(), cell_storage) };
}
#[test]
fn test_runtime_services_fails_to_validate_when_null() {
let mut storage = Storage::default(); let mut mock_metadata = MetaData::new::<i32>();
<StandardRuntimeServices as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert_eq!(
Err(Cow::from("patina::runtime_services::StandardRuntimeServices not available.")),
<StandardRuntimeServices as Param>::try_validate(&(), (&storage).into())
);
}
#[test]
fn test_runtime_services_can_be_retrieved() {
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
let mut mock_rt = core::mem::MaybeUninit::<r_efi::efi::RuntimeServices>::zeroed();
storage.set_runtime_services(StandardRuntimeServices::new(mock_rt.as_mut_ptr()));
<StandardRuntimeServices as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<StandardRuntimeServices as Param>::try_validate(&(), (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let _ = unsafe { <StandardRuntimeServices as Param>::get_param(&(), cell_storage) };
}
#[test]
fn test_handle_fails_to_validate_when_not_set() {
let mut storage = Storage::default(); let mut mock_metadata = MetaData::new::<i32>();
<Handle as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert_eq!(
Err(Cow::from("patina::component::params::Handle not available.")),
<Handle as Param>::try_validate(&(), (&storage).into())
);
}
#[test]
fn test_handle_can_be_retrieved() {
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
let mock_handle = 0x1234usize as r_efi::efi::Handle;
storage.set_image_handle(mock_handle);
<Handle as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<Handle as Param>::try_validate(&(), (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let handle = unsafe { <Handle as Param>::get_param(&(), cell_storage) };
assert_eq!(*handle, mock_handle);
}
#[test]
fn test_option_returns_none_when_underlying_param_is_unavailable() {
let mut storage = Storage::default();
let mut mock_meadata = MetaData::new::<i32>();
<Option<StandardBootServices> as Param>::init_state(&mut storage, &mut mock_meadata).unwrap();
assert!(<Option<StandardBootServices> as Param>::try_validate(&(), (&storage).into()).is_ok());
assert!(unsafe { <Option<StandardBootServices> as Param>::get_param(&(), (&storage).into()).is_none() });
}
#[test]
fn test_option_returns_underlying_param() {
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
storage.add_config(42u32);
let state = <Option<Config<u32>> as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<Option<Config<u32>> as Param>::try_validate(&state, (&storage).into()).is_ok());
assert!(unsafe {
<Option<Config<u32>> as Param>::get_param(&state, (&storage).into()).is_some_and(|v| *v == 42)
});
}
#[test]
fn test_try_validate_on_tuple_returns_underlying_param_type_not_full_tuple_name() {
let mut storage = Storage::default();
let mut mock_meadata = MetaData::new::<i32>();
<(StandardBootServices, Config<i32>) as Param>::init_state(&mut storage, &mut mock_meadata).unwrap();
assert!(<(StandardBootServices, Config<i32>) as Param>::validate(&((), 0), (&storage).into()));
assert_eq!(
Err(Cow::from("patina::boot_services::StandardBootServices")),
<(StandardBootServices, Config<i32>) as Param>::try_validate(&((), 1), (&storage).into())
);
}
#[test]
fn test_get_commands() {
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
{
<Commands as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<Commands as Param>::try_validate(&(), (&storage).into()).is_ok());
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let mut commands = unsafe { <Commands as Param>::get_param(&(), cell_storage) };
assert!(commands.is_empty());
commands.add_config(42i32);
}
let cell_storage = UnsafeStorageCell::new_mutable(&mut storage);
let commands = unsafe { <Commands as Param>::get_param(&(), cell_storage) };
assert!(!commands.is_empty());
}
#[test]
fn test_deferred_commands_are_applied() {
trait TestService {
#[allow(dead_code)]
fn test(self);
}
#[derive(IntoService)]
#[service(dyn TestService)]
struct TestServiceImpl;
impl TestService for TestServiceImpl {
#[allow(dead_code)]
fn test(self) {
}
}
struct TestComponent;
#[component]
impl TestComponent {
fn entry_point(self, mut cmds: Commands) -> Result<()> {
cmds.add_config(42i32);
cmds.add_service(TestServiceImpl);
Ok(())
}
}
let mut storage = Storage::new();
let mut component = TestComponent.into_component();
component.initialize(&mut storage);
assert!(storage.get_config::<i32>().is_none());
assert_eq!(component.run(&mut storage), Ok(true));
assert!(storage.get_config::<i32>().is_none());
assert!(storage.get_service::<dyn TestService>().is_none());
storage.apply_deferred();
assert_eq!(*(storage.get_config::<i32>().unwrap()), 42);
assert!(storage.get_service::<dyn TestService>().is_some());
}
#[test]
fn test_deferred_and_config_compatability() {
struct TestComponent;
#[component]
impl TestComponent {
fn entry_point(self, _cmds: Commands, _config: Config<i32>, _config2: ConfigMut<u32>) -> Result<()> {
Ok(())
}
}
let mut storage = Storage::new();
let mut component = TestComponent.into_component();
component.initialize(&mut storage);
}
#[test]
fn test_param_function_consume_self_runs_successfully() {
static DID_RUN: AtomicBool = AtomicBool::new(false);
struct TestComponent;
#[component]
impl TestComponent {
fn entry_point(self) -> Result<()> {
DID_RUN.store(true, core::sync::atomic::Ordering::SeqCst);
Ok(())
}
}
let mut storage = Storage::new();
let mut component = TestComponent.into_component();
component.initialize(&mut storage);
assert_eq!(component.run(&mut storage), Ok(true));
assert!(DID_RUN.load(core::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_param_function_consume_ref_self_runs_successfully() {
static DID_RUN: AtomicBool = AtomicBool::new(false);
struct TestComponent;
#[component]
impl TestComponent {
fn entry_point(&self) -> Result<()> {
DID_RUN.store(true, core::sync::atomic::Ordering::SeqCst);
Ok(())
}
}
let mut storage = Storage::new();
let mut component = TestComponent.into_component();
component.initialize(&mut storage);
assert_eq!(component.run(&mut storage), Ok(true));
assert!(DID_RUN.load(core::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_param_function_consume_mut_ref_self_runs_successfully() {
static DID_RUN: AtomicBool = AtomicBool::new(false);
struct TestComponent;
#[component]
impl TestComponent {
fn entry_point(&mut self) -> Result<()> {
DID_RUN.store(true, core::sync::atomic::Ordering::SeqCst);
Ok(())
}
}
let mut storage = Storage::new();
let mut component = TestComponent.into_component();
component.initialize(&mut storage);
assert_eq!(component.run(&mut storage), Ok(true));
assert!(DID_RUN.load(core::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_config_param_conflict_scenarios() {
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<&mut Storage as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<Config<i32> as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"Config<i32> conflicts with a previous &mut Storage access."
);
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<ConfigMut<i32> as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<Config<i32> as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"Config<i32> conflicts with a previous ConfigMut<i32> access."
);
}
#[test]
fn test_config_mut_param_conflict_scenarios() {
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<&mut Storage as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<ConfigMut<i32> as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"ConfigMut<i32> conflicts with a previous &mut Storage access."
);
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<&Storage as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<ConfigMut<i32> as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"ConfigMut<i32> conflicts with a previous &Storage access."
);
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<Config<i32> as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<ConfigMut<i32> as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"ConfigMut<i32> conflicts with a previous Config<i32> access."
);
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<ConfigMut<i32> as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<ConfigMut<i32> as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"ConfigMut<i32> conflicts with a previous ConfigMut<i32> access."
);
}
#[test]
fn test_commands_conflict_scenarios() {
let mut storage = Storage::new();
let mut metadata = MetaData::new::<i32>();
assert!(<Commands as Param>::init_state(&mut storage, &mut metadata).is_ok());
assert_eq!(
<Commands as Param>::init_state(&mut storage, &mut metadata).unwrap_err(),
"Commands conflicts with a previous Commands access."
);
}
}