-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* moved the unsafest code into its own module * removed a lot of the unecessary locking and ref counting
- Loading branch information
Showing
13 changed files
with
439 additions
and
317 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
pub mod stub; | ||
|
||
mod store; | ||
mod unchecked; | ||
|
||
use std::fmt::{self, Formatter}; | ||
|
||
pub use self::{store::Store, stub::Stub}; | ||
|
||
use parking_lot::Mutex; | ||
|
||
/// A function mock | ||
/// | ||
/// Stores information about a mock, such as its stubs, with its | ||
/// inputs and output typed. | ||
pub struct Mock<'stub, I, O> { | ||
pub(super) stubs: Vec<Mutex<Stub<'stub, I, O>>>, | ||
} | ||
|
||
impl<'stub, I, O> Mock<'stub, I, O> { | ||
/// Creates an empty mock | ||
pub fn new() -> Self { | ||
Self { stubs: vec![] } | ||
} | ||
|
||
/// Attempts to invoke the mock | ||
/// | ||
/// Checks the given input against the stored stubs, invoking the | ||
/// first stub whose invocation matcher suceeds for the | ||
/// inputs. The stubs are checked in reverse insertion order such | ||
/// that the last inserted stub is the first attempted | ||
/// one. Returns an error if no stub is found for the given input. | ||
pub fn call(&self, mut input: I) -> Result<O, Vec<String>> { | ||
let mut errors = vec![]; | ||
|
||
for stub in self.stubs.iter().rev() { | ||
match stub.lock().call(input) { | ||
Err((i, e)) => { | ||
errors.push(format!("✗ {}", e)); | ||
input = i | ||
} | ||
Ok(o) => return Ok(o), | ||
} | ||
} | ||
|
||
Err(errors) | ||
} | ||
|
||
/// Adds a new stub for the mocked function | ||
pub fn add_stub(&mut self, stub: Stub<'stub, I, O>) { | ||
self.stubs.push(Mutex::new(stub)) | ||
} | ||
} | ||
|
||
impl<I, O> Default for Mock<'_, I, O> { | ||
fn default() -> Self { | ||
Self::new() | ||
} | ||
} | ||
|
||
impl<I, O> fmt::Debug for Mock<'_, I, O> { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("Mock").field("stubs", &self.stubs).finish() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use std::collections::HashMap; | ||
|
||
use super::{unchecked::Unchecked, Mock}; | ||
|
||
#[derive(Debug, Default)] | ||
pub struct Store { | ||
stubs: HashMap<usize, Unchecked<'static>>, | ||
} | ||
|
||
impl Store { | ||
/// Returns a mutable reference to a [`Mock`] for a given function | ||
/// | ||
/// If the given function has not yet been mocked, an empty mock | ||
/// is created for the function. | ||
pub fn get_or_create<R, I, O>(&mut self, id: fn(R, I) -> O) -> &mut Mock<I, O> { | ||
let mock = self.stubs.entry(id as usize).or_insert_with(|| { | ||
let mock: Mock<I, O> = Mock::new(); | ||
mock.into() | ||
}); | ||
|
||
unsafe { mock.as_typed_mut() } | ||
} | ||
|
||
/// Returns a reference to a [`Mock`] for a given function | ||
/// | ||
/// `None` is returned if the function was never mocked | ||
pub unsafe fn get<R, I, O>(&self, id: fn(R, I) -> O) -> Option<&Mock<I, O>> { | ||
self.stubs.get(&(id as usize)).map(|m| m.as_typed()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use std::{ | ||
fmt::{self, Formatter}, | ||
num::NonZeroUsize, | ||
}; | ||
|
||
use crate::matcher::InvocationMatcher; | ||
|
||
pub struct Stub<'a, I, O> { | ||
matcher: Box<dyn InvocationMatcher<I> + Send>, | ||
answer: Answer<'a, I, O>, | ||
} | ||
|
||
pub enum Answer<'a, I, O> { | ||
Exhausted, | ||
Once(Box<dyn FnOnce(I) -> O + Send + 'a>), | ||
Many { | ||
stub: Box<dyn FnMut(I) -> O + Send + 'a>, | ||
times: Times, | ||
}, | ||
} | ||
|
||
#[derive(Debug, Clone, Copy)] | ||
pub enum Times { | ||
Always, | ||
Times(NonZeroUsize), | ||
} | ||
|
||
impl Times { | ||
pub fn decrement(self) -> Option<Self> { | ||
match self { | ||
Times::Always => Some(self), | ||
Times::Times(n) => NonZeroUsize::new(n.get() - 1).map(Times::Times), | ||
} | ||
} | ||
} | ||
|
||
impl<'a, I, O> Stub<'a, I, O> { | ||
pub fn new( | ||
stub: Answer<'a, I, O>, | ||
matcher: impl InvocationMatcher<I> + Send + 'static, | ||
) -> Self { | ||
Stub { | ||
matcher: Box::new(matcher), | ||
answer: stub, | ||
} | ||
} | ||
|
||
pub fn call(&mut self, input: I) -> Result<O, (I, String)> { | ||
// TODO: should the error message be different if the stub is also exhausted? | ||
if let Err(e) = self.matcher.matches(&input) { | ||
return Err((input, e)); | ||
} | ||
|
||
self.answer.call(input) | ||
} | ||
} | ||
|
||
impl<'a, I, O> Answer<'a, I, O> { | ||
fn call(&mut self, input: I) -> Result<O, (I, String)> { | ||
// no need to replace if we can keep decrementing | ||
if let Answer::Many { stub, times } = self { | ||
if let Some(decremented) = times.decrement() { | ||
*times = decremented; | ||
return Ok(stub(input)); | ||
} | ||
} | ||
|
||
// otherwise replace it with an exhaust | ||
match std::mem::replace(self, Answer::Exhausted) { | ||
Answer::Exhausted => Err((input, "this stub has been exhausted".to_string())), | ||
Answer::Once(stub) => Ok(stub(input)), | ||
Answer::Many { mut stub, .. } => Ok(stub(input)), | ||
} | ||
} | ||
} | ||
|
||
impl<'a, I, O> fmt::Debug for Stub<'a, I, O> { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("Stub") | ||
// TODO: Add debug information for InvocationMatcher | ||
// .field("matcher", &self.matcher) | ||
.field("answer", match &self.answer { | ||
Answer::Exhausted => &"Exhausted", | ||
Answer::Once(_) => &"Once", | ||
Answer::Many { .. } => &"Many", | ||
}) | ||
.finish() | ||
} | ||
} |
Oops, something went wrong.