Skip to content

Commit

Permalink
tmp: RespondFn
Browse files Browse the repository at this point in the history
  • Loading branch information
audunhalland committed Feb 26, 2024
1 parent ccee808 commit c8aa18f
Show file tree
Hide file tree
Showing 17 changed files with 371 additions and 175 deletions.
105 changes: 12 additions & 93 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use core::marker::PhantomData;
use crate::call_pattern::*;
use crate::clause::{self};
use crate::fn_mocker::PatternMatchMode;
use crate::output::{IntoCloneResponder, IntoOnceResponder, IntoResponse, Respond, StaticRef};
use crate::output::{IntoCloneResponder, IntoOnceResponder, Respond};
use crate::private::lib::vec;
use crate::property::*;
use crate::Clause;
Expand Down Expand Up @@ -253,94 +253,28 @@ macro_rules! define_response_common_impl {
<F::Response as Respond>::Type: Default,
{
self.wrapper.push_responder(
FunctionResponder::<F> {
func: crate::private::lib::Box::new(|_, _| Default::default()),
InputsFnResponder::<F> {
func: crate::private::lib::Box::new(|_| Default::default()),
}
.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.
pub fn answers<C, R>(mut self, func: C) -> Quantify<'p, F, O>
where
C: (Fn(F::Inputs<'_>) -> R) + Send + Sync + 'static,
R: IntoResponse<F::Response>,
{
self.wrapper.push_responder(
FunctionResponder::<F> {
func: crate::private::lib::Box::new(move |inputs, _ctx| {
func(inputs).into_response()
}),
}
.into_dyn_responder(),
);
pub fn responds(mut self, respond_fn: &'static F::RespondFn) -> Quantify<'p, F, O> {
self.wrapper
.push_responder(StaticFnResponder::<F> { respond_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.
pub fn answers_ctx<C, R>(mut self, func: C) -> Quantify<'p, F, O>
where
C: (Fn(F::Inputs<'_>, AnswerContext<'_, '_, '_, F>) -> R) + Send + Sync + 'static,
R: IntoResponse<F::Response>,
{
self.wrapper.push_responder(
FunctionResponder::<F> {
func: crate::private::lib::Box::new(move |inputs, ctx| {
func(inputs, ctx).into_response()
}),
}
.into_dyn_responder(),
);
self.quantify()
}

/// Specify the response of the call pattern by invoking the given closure that supports mutating _one_ `&mut` parameter from the mocked signature.
pub fn mutates<C, R>(mut self, func: C) -> Quantify<'p, F, O>
where
C: (Fn(&mut F::Mutation<'_>, F::Inputs<'_>) -> R) + Send + Sync + 'static,
R: IntoResponse<F::Response>,
{
self.wrapper.push_responder(
FunctionResponder::<F> {
func: crate::private::lib::Box::new(move |inputs, ctx| {
func(ctx.mutation, inputs).into_response()
}),
}
.into_dyn_responder(),
);
self.quantify()
}

/// Specify the response of the call pattern to be a static reference to leaked memory.
///
/// The value may be based on the value of input parameters.
///
/// This version will produce a new memory leak for _every invocation_ of the answer function.
///
/// This method should only be used when computing a reference based
/// on input parameters is necessary, which should not be a common use case.
pub fn answers_leaked_ref<C, R, T>(mut self, func: C) -> Quantify<'p, F, O>
where
F: MockFn<Response = StaticRef<T>>,
C: (Fn(F::Inputs<'_>) -> R) + Send + Sync + 'static,
R: core::borrow::Borrow<T> + 'static,
T: 'static,
{
use crate::private::lib::Box;
self.wrapper.push_responder(
FunctionResponder::<F> {
func: Box::new(move |inputs, _| {
let value = func(inputs);
let leaked_ref = Box::leak(Box::new(value));

<R as core::borrow::Borrow<T>>::borrow(leaked_ref)
}),
}
.into_dyn_responder(),
);
pub fn responds_boxed(
mut self,
respond_fn: crate::private::lib::Box<F::RespondFn>,
) -> Quantify<'p, F, O> {
self.wrapper
.push_responder(BoxedFnResponder::<F> { respond_fn }.into_dyn_responder());
self.quantify()
}

Expand Down Expand Up @@ -595,18 +529,3 @@ where
sink.push(F::info(), self.wrapper.into_owned())
}
}

/// AnswerContext represents known information in the current mocking context.
pub struct AnswerContext<'u, 'm0, 'm1, F: MockFn> {
pub(crate) unimock: &'u Unimock,

/// The mutation of the currently executing mock function.
pub mutation: &'m0 mut F::Mutation<'m1>,
}

impl<'u, 'm0, 'm1, F: MockFn> AnswerContext<'u, 'm0, 'm1, F> {
/// Construct a new unimock instance as a clone of the one currently in the context.
pub fn clone_instance(&self) -> Unimock {
self.unimock.clone()
}
}
61 changes: 48 additions & 13 deletions src/call_pattern.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::private::lib::{Box, String, Vec};
use core::any::Any;

use crate::build;
use crate::cell::{Cell, CloneCell, FactoryCell};
use crate::debug;
use crate::output::{Respond, ResponderError};
Expand Down Expand Up @@ -103,7 +102,9 @@ pub(crate) struct DynCallOrderResponder {
pub(crate) enum DynResponder {
Cell(DynCellResponder),
Borrow(DynBorrowResponder),
Function(DynFunctionResponder),
InputsFn(DynInputsFnResponder),
StaticFn(DynStaticFnResponder),
BoxedFn(DynBoxedFnResponder),
Panic(String),
Unmock,
CallDefaultImpl,
Expand Down Expand Up @@ -169,7 +170,9 @@ impl DynResponder {

pub(crate) struct DynCellResponder(AnyBox);
pub(crate) struct DynBorrowResponder(AnyBox);
pub(crate) struct DynFunctionResponder(AnyBox);
pub(crate) struct DynInputsFnResponder(AnyBox);
pub(crate) struct DynStaticFnResponder(AnyBox);
pub(crate) struct DynBoxedFnResponder(AnyBox);

pub trait DowncastResponder<F: MockFn> {
type Downcasted;
Expand All @@ -193,8 +196,24 @@ impl<F: MockFn> DowncastResponder<F> for DynBorrowResponder {
}
}

impl<F: MockFn> DowncastResponder<F> for DynFunctionResponder {
type Downcasted = FunctionResponder<F>;
impl<F: MockFn> DowncastResponder<F> for DynInputsFnResponder {
type Downcasted = InputsFnResponder<F>;

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

impl<F: MockFn> DowncastResponder<F> for DynStaticFnResponder {
type Downcasted = StaticFnResponder<F>;

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

impl<F: MockFn> DowncastResponder<F> for DynBoxedFnResponder {
type Downcasted = BoxedFnResponder<F>;

fn downcast(&self) -> PatternResult<&Self::Downcasted> {
downcast_box(&self.0)
Expand All @@ -209,13 +228,17 @@ pub(crate) struct BorrowResponder<F: MockFn> {
pub borrowable: <F::Response as Respond>::Type,
}

pub(crate) struct FunctionResponder<F: MockFn> {
pub(crate) struct InputsFnResponder<F: MockFn> {
#[allow(clippy::type_complexity)]
pub func: Box<
dyn (Fn(F::Inputs<'_>, build::AnswerContext<'_, '_, '_, F>) -> <F::Response as Respond>::Type)
+ Send
+ Sync,
>,
pub func: Box<dyn (Fn(F::Inputs<'_>) -> <F::Response as Respond>::Type) + Send + Sync>,
}

pub(crate) struct StaticFnResponder<F: MockFn> {
pub respond_fn: &'static F::RespondFn,
}

pub(crate) struct BoxedFnResponder<F: MockFn> {
pub respond_fn: Box<F::RespondFn>,
}

impl<F: MockFn> CellResponder<F> {
Expand All @@ -233,9 +256,21 @@ where
}
}

impl<F: MockFn> FunctionResponder<F> {
impl<F: MockFn> InputsFnResponder<F> {
pub fn into_dyn_responder(self) -> DynResponder {
DynResponder::InputsFn(DynInputsFnResponder(Box::new(self)))
}
}

impl<F: MockFn> StaticFnResponder<F> {
pub fn into_dyn_responder(self) -> DynResponder {
DynResponder::StaticFn(DynStaticFnResponder(Box::new(self)))
}
}

impl<F: MockFn> BoxedFnResponder<F> {
pub fn into_dyn_responder(self) -> DynResponder {
DynResponder::Function(DynFunctionResponder(Box::new(self)))
DynResponder::BoxedFn(DynBoxedFnResponder(Box::new(self)))
}
}

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
30 changes: 26 additions & 4 deletions src/eval.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::build::AnswerContext;
use crate::call_pattern::{
CallPattern, DowncastResponder, DynResponder, PatIndex, PatternError, PatternResult,
};
Expand All @@ -9,6 +8,7 @@ use crate::mismatch::Mismatches;
use crate::output::Output;
use crate::private::lib::{String, Vec};
use crate::private::{Evaluation, MismatchReporter};
use crate::respond::RespondFn;
use crate::state::SharedState;
use crate::{debug, MockFnInfo, Unimock};
use crate::{FallbackMode, MockFn};
Expand All @@ -28,7 +28,7 @@ struct EvalResponder<'u> {
pub(crate) fn eval<'u, 'i, F: MockFn>(
unimock: &'u Unimock,
inputs: F::Inputs<'i>,
mutation: &mut F::Mutation<'_>,
_mutation: &mut F::Mutation<'_>,
) -> MockResult<Evaluation<'u, 'i, F>> {
let dyn_ctx = DynCtx {
info: F::info(),
Expand Down Expand Up @@ -75,15 +75,37 @@ pub(crate) fn eval<'u, 'i, F: MockFn>(
),
}
}
DynResponder::Function(dyn_fn_responder) => {
DynResponder::InputsFn(dyn_fn_responder) => {
let fn_responder =
dyn_ctx.downcast_responder::<F, _>(dyn_fn_responder, &eval_responder)?;
let output = <F::Output<'u> as Output<'u, F::Response>>::from_response(
(fn_responder.func)(inputs, AnswerContext { unimock, mutation }),
(fn_responder.func)(inputs),
&unimock.value_chain,
);
Ok(Evaluation::Evaluated(output))
}
DynResponder::StaticFn(dyn_responder) => {
let answer_fn_responder =
dyn_ctx.downcast_responder::<F, _>(dyn_responder, &eval_responder)?;
Ok(Evaluation::Respond(
RespondFn {
unimock,
respond_fn: answer_fn_responder.respond_fn,
},
inputs,
))
}
DynResponder::BoxedFn(dyn_responder) => {
let answer_fn_responder =
dyn_ctx.downcast_responder::<F, _>(dyn_responder, &eval_responder)?;
Ok(Evaluation::Respond(
RespondFn {
unimock,
respond_fn: answer_fn_responder.respond_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 @@ -439,6 +439,8 @@ pub mod build;
pub mod output;
/// Traits and types used for describing the properties of various mock types.
pub mod property;
/// Answer functions.
pub mod respond;

/// Mock APIs for various crates.
pub mod mock;
Expand Down Expand Up @@ -1028,6 +1030,9 @@ pub trait MockFn: Sized + 'static {
/// A type that describes the mocked function's actual output type.
type Output<'u>: output::Output<'u, Self::Response>;

/// Experimental: respond Fn
type RespondFn: ?Sized + Send + Sync;

/// Static information about the mocked method
fn info() -> MockFnInfo;

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 RespondFn = 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
Expand Up @@ -2,6 +2,7 @@ use crate::call_pattern::InputIndex;
use crate::debug;
use crate::mismatch::{Mismatch, MismatchKind};
use crate::output::Output;
use crate::respond::RespondFn;
use crate::{call_pattern::MatchingFn, *};

use lib::{Box, String, Vec};
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
Respond(RespondFn<'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::Respond(..) => error::MockError::NoAnswer { info: F::info() },
Self::Unmocked(_) => error::MockError::CannotUnmock { info: F::info() },
Self::CallDefaultImpl(_) => error::MockError::NoDefaultImpl { info: F::info() },
};
Expand Down
Loading

0 comments on commit c8aa18f

Please sign in to comment.