Skip to content

Commit

Permalink
tmp: AnswerFn
Browse files Browse the repository at this point in the history
  • Loading branch information
audunhalland committed Feb 25, 2024
1 parent 59e29e8 commit a64917f
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 18 deletions.
76 changes: 76 additions & 0 deletions src/answer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use core::ops::Deref;

use crate::{
output::{Output, Respond},
MockFn, Unimock,
};

/// A borrowed Answering function.
pub struct AnswerFn<'u, F: MockFn> {
pub(crate) answer_fn: &'u F::AnswerFn,
}

impl<'u, F: MockFn> Deref for AnswerFn<'u, F> {
type Target = F::AnswerFn;

fn deref(&self) -> &Self::Target {
self.answer_fn
}
}

impl<'u, F: MockFn> AnswerFn<'u, F> {
/// Convert the answering function's response to a mock output.
pub fn make_output(
&self,
response: <F::Response as Respond>::Type,
unimock: &'u Unimock,
) -> <F::Output<'u> as Output<'u, F::Response>>::Type {
<F::Output<'u> as Output<'u, F::Response>>::from_response(response, &unimock.value_chain)
}
}

/// Trait for converting a closure into [MockFn::AnswerFn]
pub trait IntoAnswerFn<Args> {
/// The resulting AnswerFn, some `dyn Fn`.
type AnswerFn: ?Sized + Send + Sync;

/// Performs the conversion.
fn into_answer_fn(self) -> Box<Self::AnswerFn>;
}

impl<F, O> IntoAnswerFn<()> for F
where
F: Fn() -> O + Send + Sync + 'static,
{
type AnswerFn = dyn (Fn() -> O) + Send + Sync;

fn into_answer_fn(self) -> Box<Self::AnswerFn> {
Box::new(self)
}
}

macro_rules! arg_tuples {
($($a:ident),+) => {
impl<F, $($a),+, O> IntoAnswerFn<($($a),+,)> for F
where
F: Fn($($a),+) -> O + Send + Sync + 'static,
{
type AnswerFn = dyn (Fn($($a),+) -> O) + Send + Sync;

fn into_answer_fn(self) -> Box<Self::AnswerFn> {
Box::new(self)
}
}
};
}

arg_tuples!(A0);
arg_tuples!(A0, A1);
arg_tuples!(A0, A1, A2);
arg_tuples!(A0, A1, A2, A3);
arg_tuples!(A0, A1, A2, A3, A4);
arg_tuples!(A0, A1, A2, A3, A4, A5);
arg_tuples!(A0, A1, A2, A3, A4, A5, A6);
arg_tuples!(A0, A1, A2, A3, A4, A5, A6, A7);
arg_tuples!(A0, A1, A2, A3, A4, A5, A6, A7, A8);
arg_tuples!(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9);
21 changes: 21 additions & 0 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::marker::PhantomData;

use crate::answer::IntoAnswerFn;
use crate::call_pattern::*;
use crate::clause::{self};
use crate::fn_mocker::PatternMatchMode;
Expand Down Expand Up @@ -278,6 +279,26 @@ macro_rules! define_response_common_impl {
self.quantify()
}

/// Specify the response of the call pattern by invoking the given closure that can then compute it based on input parameters.
pub fn answers_fn<C, Args>(mut self, answer_fn: C) -> Quantify<'p, F, O>
where
C: IntoAnswerFn<Args, AnswerFn = F::AnswerFn>,
{
self.wrapper.push_responder(
AnswerFnResponder::<F> {
answer_fn: answer_fn.into_answer_fn(),
}
.into_dyn_responder(),
);
self.quantify()
}

pub fn answers_fn_box(mut self, answer_fn: Box<F::AnswerFn>) -> Quantify<'p, F, O> {
self.wrapper
.push_responder(AnswerFnResponder::<F> { answer_fn }.into_dyn_responder());
self.quantify()
}

/// Specify the response of the call pattern by invoking the given closure that can then compute it based on input parameters.
///
/// This variant passes an [AnswerContext] as the second parameter.
Expand Down
20 changes: 20 additions & 0 deletions src/call_pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub(crate) enum DynResponder {
Cell(DynCellResponder),
Borrow(DynBorrowResponder),
Function(DynFunctionResponder),
AnswerFn(DynAnswerFnResponder),
Panic(String),
Unmock,
CallDefaultImpl,
Expand Down Expand Up @@ -170,6 +171,7 @@ impl DynResponder {
pub(crate) struct DynCellResponder(AnyBox);
pub(crate) struct DynBorrowResponder(AnyBox);
pub(crate) struct DynFunctionResponder(AnyBox);
pub(crate) struct DynAnswerFnResponder(AnyBox);

pub trait DowncastResponder<F: MockFn> {
type Downcasted;
Expand Down Expand Up @@ -201,6 +203,14 @@ impl<F: MockFn> DowncastResponder<F> for DynFunctionResponder {
}
}

impl<F: MockFn> DowncastResponder<F> for DynAnswerFnResponder {
type Downcasted = AnswerFnResponder<F>;

fn downcast(&self) -> PatternResult<&Self::Downcasted> {
downcast_box(&self.0)
}
}

pub(crate) struct CellResponder<F: MockFn> {
pub cell: Box<dyn Cell<<F::Response as Respond>::Type>>,
}
Expand All @@ -218,6 +228,10 @@ pub(crate) struct FunctionResponder<F: MockFn> {
>,
}

pub(crate) struct AnswerFnResponder<F: MockFn> {
pub answer_fn: Box<F::AnswerFn>,
}

impl<F: MockFn> CellResponder<F> {
pub fn into_dyn_responder(self) -> DynResponder {
DynResponder::Cell(DynCellResponder(Box::new(self)))
Expand All @@ -239,6 +253,12 @@ impl<F: MockFn> FunctionResponder<F> {
}
}

impl<F: MockFn> AnswerFnResponder<F> {
pub fn into_dyn_responder(self) -> DynResponder {
DynResponder::AnswerFn(DynAnswerFnResponder(Box::new(self)))
}
}

fn find_responder_by_call_index(
responders: &[DynCallOrderResponder],
call_index: usize,
Expand Down
10 changes: 10 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub(crate) enum MockError {
NoDefaultImpl {
info: MockFnInfo,
},
NoAnswer {
info: MockFnInfo,
},
ExplicitPanic {
fn_call: debug::FnActualCall,
pattern: debug::CallPatternDebug,
Expand Down Expand Up @@ -127,6 +130,13 @@ impl core::fmt::Display for MockError {
path = info.path
)
}
Self::NoAnswer { info } => {
write!(
f,
"{path} did not produce an answer, this is a bug.",
path = info.path
)
}
Self::ExplicitPanic {
fn_call,
pattern,
Expand Down
11 changes: 11 additions & 0 deletions src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::answer::AnswerFn;
use crate::build::AnswerContext;
use crate::call_pattern::{
CallPattern, DowncastResponder, DynResponder, PatIndex, PatternError, PatternResult,
Expand Down Expand Up @@ -84,6 +85,16 @@ pub(crate) fn eval<'u, 'i, F: MockFn>(
);
Ok(Evaluation::Evaluated(output))
}
DynResponder::AnswerFn(dyn_answer_fn_responder) => {
let answer_fn_responder =
dyn_ctx.downcast_responder::<F, _>(dyn_answer_fn_responder, &eval_responder)?;
Ok(Evaluation::Answer(
AnswerFn {
answer_fn: answer_fn_responder.answer_fn.as_ref(),
},
inputs,
))
}
DynResponder::Panic(msg) => Err(MockError::ExplicitPanic {
fn_call: dyn_ctx.fn_call(),
pattern: eval_responder
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,8 @@
#[cfg(not(feature = "std"))]
extern crate alloc;

/// Answer functions.
pub mod answer;
/// Builder pattern types used for defining mocked behaviour.
pub mod build;
/// Function outputs.
Expand Down Expand Up @@ -1016,6 +1018,9 @@ pub trait MockFn: Sized + 'static {
/// For methods without any mutable parameters, this type should be `()`.
type Mutation<'m>: ?Sized;

/// Experimental: answer Fn
type AnswerFn: ?Sized + Send + Sync;

/// A type that describes how the mocked function responds.
///
/// The Respond trait describes a type used internally to store a response value.
Expand Down
6 changes: 5 additions & 1 deletion src/mock/std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ pub mod process {
/// Unimock mock API for [std::process::Termination].
#[allow(non_snake_case)]
pub mod TerminationMock {
use crate::{output::Owned, MockFn};
use crate::{
output::{Owned, Respond},
MockFn,
};

#[allow(non_camel_case_types)]
/// MockFn for [`Termination::report() -> ExitCode`](std::process::Termination::report).
Expand All @@ -87,6 +90,7 @@ pub mod process {
impl MockFn for report {
type Inputs<'i> = ();
type Mutation<'m> = ();
type AnswerFn = dyn Fn() -> <Self::Response as Respond>::Type + Send + Sync;
type Response = Owned<std::process::ExitCode>;
type Output<'u> = Self::Response;

Expand Down
4 changes: 4 additions & 0 deletions src/private.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::answer::AnswerFn;
use crate::call_pattern::InputIndex;
use crate::debug;
use crate::mismatch::{Mismatch, MismatchKind};
Expand All @@ -17,6 +18,8 @@ pub use crate::default_impl_delegator::*;
pub enum Evaluation<'u, 'i, F: MockFn> {
/// Function evaluated to its output.
Evaluated(<F::Output<'u> as Output<'u, F::Response>>::Type),
/// Function should be answered
Answer(AnswerFn<'u, F>, F::Inputs<'i>),
/// Function not yet evaluated, should be unmocked.
Unmocked(F::Inputs<'i>),
/// Function not yet evaluated, should call default implementation.
Expand All @@ -30,6 +33,7 @@ impl<'u, 'i, F: MockFn> Evaluation<'u, 'i, F> {
pub fn unwrap(self, unimock: &Unimock) -> <F::Output<'u> as Output<'u, F::Response>>::Type {
let error = match self {
Self::Evaluated(output) => return output,
Self::Answer(..) => error::MockError::NoAnswer { info: F::info() },
Self::Unmocked(_) => error::MockError::CannotUnmock { info: F::info() },
Self::CallDefaultImpl(_) => error::MockError::NoDefaultImpl { info: F::info() },
};
Expand Down
27 changes: 26 additions & 1 deletion tests/it/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ mod mutated_args {
}
}

struct LifetimeArg<'a> {
pub struct LifetimeArg<'a> {
data: PhantomData<&'a ()>,
}

Expand Down Expand Up @@ -1110,3 +1110,28 @@ mod debug_mut_arg {
fn f(&self, arg1: &mut Arg, arg2: &mut Arg);
}
}

mod answer_fn {
use unimock::*;

#[unimock(api = TraitMock)]
trait Trait {
fn foo(&self, a: &i32, b: &mut i32) -> i32;
}

#[test]
fn test() {
let u = Unimock::new(
TraitMock::foo
.next_call(matching!(44, _))
// .answers_fn(|a, b| 1337)
.answers_fn_box(Box::new(|_, b| {
*b += 1;
1337
})),
);
let mut b = 0;
assert_eq!(1337, u.foo(&42, &mut b));
assert_eq!(1, b);
}
}
2 changes: 1 addition & 1 deletion unimock_macros/src/unimock/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ pub fn extract_methods<'s>(
ident: pat_ident.ident.clone(),
ty: util::substitute_lifetimes(
type_ref.elem.as_ref().clone(),
&syn::parse_quote!('m),
Some(&syn::parse_quote!('m)),
),
})
}
Expand Down
Loading

0 comments on commit a64917f

Please sign in to comment.