Skip to content

Commit

Permalink
feat: A new experiment with AnswerFn that hopefully supports paramete…
Browse files Browse the repository at this point in the history
…r borrows
  • Loading branch information
audunhalland committed Mar 22, 2024
1 parent 4f57154 commit 78dca61
Show file tree
Hide file tree
Showing 32 changed files with 557 additions and 685 deletions.
72 changes: 58 additions & 14 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use crate::alloc::{String, ToString, Vec};
use crate::call_pattern::*;
use crate::fn_mocker::PatternMatchMode;
use crate::output::{IntoReturn, IntoReturnOnce, Return, ReturnDefault};
use crate::private::{AnswerClosure, AnswerClosureInner};
use crate::property::*;
use crate::responder::{Applier, BoxedApplier, DynResponder, IntoReturner};
use crate::responder::{Answerer, DynResponder, IntoReturner};
use crate::*;
use dyn_builder::*;

Expand Down Expand Up @@ -327,10 +328,11 @@ assert_eq!(None, u.get());
self.quantify()
}


/// Specify the response of the call pattern by applying the given function that can then compute it based on input parameters.
///
/// The applied function can also respond with types that don't implement [Send] and [Sync].
/// The applied function can respond with types that don't implement [Send] and [Sync].
///
/// The function signature has the same signature as the trait function that it mocks, including `self`.
///
/// # Example
#[doc = concat!("\
Expand All @@ -346,7 +348,7 @@ trait Trait {
let u = Unimock::new(
TraitMock::get_cell
.next_call(matching!(84))
.applies(&|input| respond(Cell::new(input / 2)))
.answers(&|_, input| Cell::new(input / 2))
);
assert_eq!(Cell::new(42), u.get_cell(84));
Expand All @@ -367,9 +369,8 @@ trait Trait {
let u = Unimock::new(
TraitMock::mutate
.next_call(matching!(41))
.applies(&|input| {
.answers(&|_, input| {
*input += 1;
respond(())
})
);
Expand All @@ -379,9 +380,52 @@ assert_eq!(number, 42);
```
",
)]
pub fn applies(mut self, apply_fn: &'static F::ApplyFn) -> Quantify<'p, F, O> {
/// # Self borrow
#[doc = concat!("\
```
# use unimock::*;
#[unimock(api=TraitMock)]
trait Trait {
fn get(&self) -> &u32;
}
let u = Unimock::new(
TraitMock::get
.next_call(matching!())
.answers(&|u| u.make_ref(42))
);
assert_eq!(u.get(), &42);
```
",
)]
/// # Recursion
#[doc = concat!("\
```
# use unimock::*;
#[unimock(api=FibMock)]
trait Fib {
fn fib(&self, input: i32) -> i32;
}
let u = Unimock::new((
FibMock::fib
.each_call(matching!(1 | 2))
.returns(1),
FibMock::fib
.each_call(matching!(_))
.answers(&|u, input| {
u.fib(input - 1) + u.fib(input - 2)
})
));
assert_eq!(55, u.fib(10));
```
",
)]
pub fn answers(mut self, answer_fn: &'static F::AnswerFn) -> Quantify<'p, F, O> {
self.wrapper
.push_responder(Applier::<F> { apply_fn }.into_dyn_responder());
.push_responder(Answerer::<F> { answer_closure: AnswerClosure(AnswerClosureInner::Ref(answer_fn)) }.into_dyn_responder());
self.quantify()
}

Expand All @@ -401,10 +445,10 @@ let mutex: Arc<Mutex<i32>> = Arc::new(Mutex::new(0));
let u = Unimock::new(
TraitMock::get
.each_call(matching!())
.applies_closure({
.answers_arc({
let mutex = mutex.clone();
Box::new(move || {
respond(*mutex.lock().unwrap())
Arc::new(move |_| {
*mutex.lock().unwrap()
})
})
);
Expand All @@ -415,12 +459,12 @@ assert_eq!(42, u.get());
```
",
)]
pub fn applies_closure(
pub fn answers_arc(
mut self,
apply_fn: crate::alloc::Box<F::ApplyFn>,
answer_fn: crate::alloc::Arc<F::AnswerFn>,
) -> Quantify<'p, F, O> {
self.wrapper
.push_responder(BoxedApplier::<F> { apply_fn }.into_dyn_responder());
.push_responder(Answerer::<F> { answer_closure: AnswerClosure(AnswerClosureInner::Arc(answer_fn)) }.into_dyn_responder());
self.quantify()
}

Expand Down
6 changes: 3 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub(crate) enum MockError {
NoDefaultImpl {
info: MockFnInfo,
},
NotApplied {
NotAnswered {
info: MockFnInfo,
},
ExplicitPanic {
Expand Down Expand Up @@ -130,10 +130,10 @@ impl core::fmt::Display for MockError {
path = info.path
)
}
Self::NotApplied { info } => {
Self::NotAnswered { info } => {
write!(
f,
"{path} did not apply the function, this is a bug.",
"{path} did not apply the answer function, this is a bug.",
path = info.path
)
}
Expand Down
25 changes: 4 additions & 21 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::error::{self};
use crate::error::{MockError, MockResult};
use crate::fn_mocker::{FnMocker, PatternMatchMode};
use crate::mismatch::Mismatches;
use crate::private::{ApplyClosure, Evaluation, MismatchReporter};
use crate::private::{Evaluation, MismatchReporter};
use crate::responder::{DowncastResponder, DynResponder};
use crate::state::SharedState;
use crate::{debug, MockFnInfo, Unimock};
Expand Down Expand Up @@ -48,27 +48,10 @@ pub(crate) fn eval<'u, 'i, F: MockFn>(
}),
}
}
DynResponder::StaticApply(dyn_responder) => {
let apply_fn_responder =
DynResponder::Answer(dyn_responder) => {
let answerer =
dyn_ctx.downcast_responder::<F, _>(dyn_responder, &eval_responder)?;
Ok(Evaluation::Apply(
ApplyClosure {
unimock,
apply_fn: apply_fn_responder.apply_fn,
},
inputs,
))
}
DynResponder::BoxedApply(dyn_responder) => {
let apply_fn_responder =
dyn_ctx.downcast_responder::<F, _>(dyn_responder, &eval_responder)?;
Ok(Evaluation::Apply(
ApplyClosure {
unimock,
apply_fn: apply_fn_responder.apply_fn.as_ref(),
},
inputs,
))
Ok(Evaluation::Answer(answerer.answer_closure.clone(), inputs))
}
DynResponder::Panic(msg) => Err(MockError::ExplicitPanic {
fn_call: dyn_ctx.fn_call(),
Expand Down
136 changes: 18 additions & 118 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,19 @@
//! ### Mutating inputs
//! Many traits uses the argument mutation pattern, where there are one or more `&mut` parameters.
//!
//! To access the `&mut` parameters, a function is applied to the call pattern using [`applies`](crate::build::DefineResponse::applies):
//! To access the `&mut` parameters, a function is applied to the call pattern using [`answers`](crate::build::DefineResponse::answers):
//!
//! ```rust
//! # use unimock::*;
//! let mocked = Unimock::new(
//! mock::core::fmt::DisplayMock::fmt
//! .next_call(matching!(_))
//! .applies(&|f| respond(write!(f, "mutation!")))
//! .answers(&|_, f| write!(f, "mutation!"))
//! );
//!
//! assert_eq!("mutation!", format!("{mocked}"));
//! ```
//!
//! The applied function also specifies the response, which is constructed by calling [respond].
//! In the `fmt` case, the return type is [core::fmt::Result].
//! The respond function helps with automatic type conversion and automatic borrowing.
//!
//! ## Combining setup clauses
//! `Unimock::new()` accepts as argument anything that implements [Clause].
//! Basic setup clauses can be combined into composite clauses by using _tuples_:
Expand All @@ -142,10 +138,10 @@
//! &Unimock::new((
//! FooMock::foo
//! .some_call(matching!(_))
//! .applies(&|arg| respond(arg * 3)),
//! .answers(&|_, arg| arg * 3),
//! BarMock::bar
//! .some_call(matching!((arg) if *arg > 20))
//! .applies(&|arg| respond(arg * 2)),
//! .answers(&|_, arg| arg * 2),
//! )),
//! 7
//! )
Expand All @@ -159,10 +155,10 @@
//! &Unimock::new((
//! FooMock::foo.stub(|each| {
//! each.call(matching!(1337)).returns(1024);
//! each.call(matching!(_)).applies(&|arg| respond(arg * 3));
//! each.call(matching!(_)).answers(&|_, arg| arg * 3);
//! }),
//! BarMock::bar.stub(|each| {
//! each.call(matching!((arg) if *arg > 20)).applies(&|arg| respond(arg * 2));
//! each.call(matching!((arg) if *arg > 20)).answers(&|_, arg| arg * 2);
//! }),
//! )),
//! 7
Expand Down Expand Up @@ -428,6 +424,9 @@
#![warn(missing_docs)]
#![cfg_attr(feature = "unstable-doc-cfg", feature(doc_auto_cfg))]

#[cfg(not(any(feature = "std", feature = "critical-section")))]
compile_error!("At least one of the features `std` or `critical-section` must be set.");

#[cfg(feature = "std")]
extern crate std;

Expand Down Expand Up @@ -502,7 +501,7 @@ use alloc::Box;
use assemble::MockAssembler;
use call_pattern::DynInputMatcher;
use debug::TraitMethodPath;
use output::{static_ref::Reference, IntoRespond, Kind, StaticRef};
use output::Kind;
use private::{DefaultImplDelegator, Matching};

///
Expand Down Expand Up @@ -862,6 +861,13 @@ impl Unimock {
teardown::teardown_panic(&mut self);
}

/// Convert the given value into a reference.
///
/// This can be useful when returning references from `answers` functions.
pub fn make_ref<T: Send + Sync + 'static>(&self, value: T) -> &T {
self.value_chain.add(value)
}

#[track_caller]
fn from_assembler(
assembler_result: Result<MockAssembler, alloc::String>,
Expand Down Expand Up @@ -1034,7 +1040,7 @@ pub trait MockFn: Sized + 'static {
type OutputKind: output::Kind;

/// The function type used for function application on a call pattern.
type ApplyFn: ?Sized + Send + Sync;
type AnswerFn: ?Sized + Send + Sync;

/// Static information about the mocked method
fn info() -> MockFnInfo;
Expand Down Expand Up @@ -1202,110 +1208,4 @@ pub trait Clause {
fn deconstruct(self, sink: &mut dyn clause::term::Sink) -> Result<(), alloc::String>;
}

/// A mocked response
pub struct Respond<F: MockFn>(RespondInner<F>);

enum RespondInner<F: MockFn> {
Respond(<F::OutputKind as Kind>::Respond),
Mocked(&'static dyn Fn(Unimock) -> <F::OutputKind as Kind>::Respond),
}

/// Turn a value into a mock function response.
///
/// This function is used to produce a return value from within [applies](build::DefineResponse::applies)-functions.
///
/// # Example
/// ```rust
/// # use unimock::*;
/// # use std::cell::Cell;
/// #
/// # #[unimock(api=TraitMock)]
/// # trait Trait {
/// # fn method(&self) -> Cell<u32>;
/// # }
/// let u = Unimock::new(
/// TraitMock::method
/// .each_call(matching!())
/// .applies(&|| respond(Cell::new(42)))
/// );
/// let cell = u.method();
/// ```
pub fn respond<F, T>(input: T) -> Respond<F>
where
F: MockFn,
T: IntoRespond<<F as MockFn>::OutputKind>,
{
Respond(RespondInner::Respond(input.into_respond().unwrap()))
}

/// Make a response that is a static reference to leaked memory.
///
/// This method should only be used when computing a reference based
/// on input parameters is necessary, which should not be a common use case.
///
/// # Example
/// ```rust
/// use unimock::*;
///
/// #[unimock(api=TraitMock)]
/// trait Trait {
/// fn get_static(&self, input: i32) -> &'static i32;
/// }
///
/// let u = Unimock::new(
/// TraitMock::get_static
/// .next_call(matching!(84))
/// .applies(&|input| respond_leaked_ref(input / 2))
/// );
///
/// assert_eq!(&42, u.get_static(84));
/// ```
pub fn respond_leaked_ref<F, T, R>(input: T) -> Respond<F>
where
F: MockFn<OutputKind = StaticRef<R>>,
T: core::borrow::Borrow<R> + 'static,
R: Send + Sync + 'static,
{
let reference = <T as core::borrow::Borrow<R>>::borrow(Box::leak(Box::new(input)));
Respond(RespondInner::Respond(Reference(reference)))
}

/// Respond with the unimock instance itself.
///
/// # Example
/// ```rust
/// use unimock::*;
/// use std::borrow::Borrow;
///
/// #[unimock(api=ThisMock)]
/// pub trait This: Send + Sync {
/// fn this(&self) -> &dyn This;
/// }
///
/// impl Borrow<dyn This + 'static> for Unimock {
/// fn borrow(&self) -> &(dyn This + 'static) {
/// self
/// }
/// }
///
/// let u = Unimock::new(
/// ThisMock::this
/// .next_call(matching!())
/// .applies(&respond_mocked)
/// .n_times(2)
/// );
///
/// let this = u.this();
/// this.this();
/// ```
pub fn respond_mocked<F>() -> Respond<F>
where
F: MockFn,
Unimock: IntoRespond<<F as MockFn>::OutputKind>,
{
Respond(RespondInner::Mocked(&|unimock| {
unimock.into_respond().unwrap()
}))
}

type AnyBox = Box<dyn Any + Send + Sync + 'static>;
Loading

0 comments on commit 78dca61

Please sign in to comment.