diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9f404f1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,245 @@ +#[macro_export] +macro_rules! mock { + ( + $mock: ident; + + impl $mocked_trait: ident for $mocked_trait_target: ident { + $( + fn $func: ident$(< + $($type_param: tt$(: ($($type_param_bound: tt +)+))?),* + >)?( + self: ($($self_type: tt)+), + $($func_param: ident: $func_param_type: ty),* + )$( -> $return_type: ty)? + $(where ($($where: tt)*))?; + )* + } + ) => { + $crate::__private::paste! { + mod [<__ $mock>] { + use super::*; + + pub struct $mock { + $( + [<$func _expectations>]: std::collections::HashMap< + Vec<$crate::__private::type_id::TypeID>, + [<$mock Expectation _ $func>]$(<$($crate::__to_unit!($type_param)),*>)? + >, + )* + } + + impl $mock + { + pub fn new() -> Self + { + Self { + $( + [<$func _expectations>]: std::collections::HashMap::new() + ),* + } + } + + $( + #[allow(unused)] + pub(crate) fn [<expect_ $func>]$(< + $($type_param$(: $($type_param_bound +)*)?),* + >)?(&mut self) + -> &mut [<$mock Expectation _ $func>]$(<$($type_param),*>)? + $(where $($where)*)? + { + let ids = vec![ + $($($crate::__private::type_id::TypeID::of::<$type_param>()),*)? + ]; + + let expectation = + [<$mock Expectation _ $func>]$(::<$($type_param),*>)?::new() + .strip_type_params(); + + self.[<$func _expectations>].insert(ids.clone(), expectation); + + self.[<$func _expectations>] + .get_mut(&ids) + .unwrap() + .with_type_params_mut() + } + )* + } + + impl $mocked_trait for $mock { + $( + fn $func$(<$($type_param$(: $($type_param_bound +)+)?),*>)?( + self: $($self_type)+, + $($func_param: $func_param_type),* + )$( -> $return_type)? + $(where $($where)*)? + { + let ids = vec![ + $($($crate::__private::type_id::TypeID::of::<$type_param>()),*)? + ]; + + let expectation = self + .[<$func _expectations>] + .get(&ids) + .expect(concat!( + "No expectation found for function ", + stringify!($func) + )) + .with_type_params$(::<$($type_param),*>)?(); + + let Some(returning) = &expectation.returning else { + panic!(concat!( + "Expectation for function", + stringify!($func), + " is missing a function to call") + ); + }; + + returning(self, $($func_param),*) + } + )* + } + + $( + #[allow(non_camel_case_types, non_snake_case)] + pub struct [<$mock Expectation _ $func>]$(<$($type_param),*>)? { + returning: Option< + fn( + $crate::__replace_ref_type!($($self_type)*, $mock), + $($func_param_type),* + )$( -> $return_type)?>, + + $( + $([<$type_param _phantom>]: std::marker::PhantomData<$type_param>,)* + )? + } + + impl$(<$($type_param),*>)? [<$mock Expectation _ $func>]$(< + $($type_param),* + >)? { + #[allow(unused)] + fn new() -> Self { + Self { + returning: None, + $( + $([<$type_param _phantom>]: std::marker::PhantomData,)* + )? + } + } + + #[allow(unused)] + pub fn returning( + &mut self, + func: fn( + $crate::__replace_ref_type!($($self_type)*, $mock), + $($func_param_type),* + )$( -> $return_type)? + ) -> &mut Self + { + self.returning = Some(func); + + self + } + + #[allow(unused)] + fn strip_type_params( + self, + ) -> [<$mock Expectation _ $func>]$(<$($crate::__to_unit!($type_param)),*>)? + { + $crate::__if_empty_else!(($($($type_param)*)?); { + // No type parameters are present + self + }, { + // Type parameters are present + unsafe { std::mem::transmute(self) } + }) + + } + } + + impl [<$mock Expectation _ $func>]$(<$($crate::__to_unit!($type_param)),*>)? { + fn with_type_params$(<$($type_param),*>)?( + &self, + ) -> &[<$mock Expectation _ $func>]$(<$($type_param),*>)? + { + unsafe { &*(self as *const Self).cast() } + } + + #[allow(unused)] + fn with_type_params_mut$(<$($type_param),*>)?( + &mut self, + ) -> &mut [<$mock Expectation _ $func>]$(<$($type_param),*>)? + { + unsafe { &mut *(self as *mut Self).cast() } + } + } + )* + } + + use [<__ $mock>]::$mock; + } + }; +} + +#[doc(hidden)] +pub mod __private +{ + pub use paste::paste; + + pub mod type_id + { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct TypeID + { + id: usize, + } + + impl TypeID + { + #[inline] + pub fn of<T>() -> Self + { + Self { + id: Self::of::<T> as usize, + } + } + } + } + + #[macro_export] + #[doc(hidden)] + macro_rules! __to_unit { + ($anything: tt) => { + () + }; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! __replace_ref_type { + (&$old_type: tt, $new_type: tt) => { + &$new_type + }; + + (&mut $old_type: tt, $new_type: tt) => { + &mut $new_type + }; + } + + #[macro_export] + #[doc(hidden)] + macro_rules! __if_empty_else { + (($($input: tt)*); $if_empty: block, $else: block) => { + $crate::__if_empty_else!(@($($input)*); $if_empty, $else) + }; + + // Empty + (@(); $if_empty: block, $else: block) => { + $if_empty + }; + + // Not empty + (@($($input: tt)+); $if_empty: block, $else: block) => { + $else + }; + } +} |