Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make passthrough/1' and func/1 into a ret_spec` #90

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions src/meck.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
-export([seq/1]).
-export([val/1]).
-export([raise/2]).
-export([passthrough/0]).
-export([func/1]).


%%%============================================================================
Expand Down Expand Up @@ -80,7 +82,7 @@
%% length of the list defines the arity of the function an expectation is
%% created for.

-opaque ret_spec() :: meck_expect:ret_spec().
-opaque ret_spec() :: meck_ret_spec:ret_spec().
%% Opaque data structure that specifies a value or a set of values to be returned
%% by a mock stub function defined by either {@link expect/3} and {@link expect/4}.
%% Values of `ret_spec()' are constructed by {@link seq/1}, {@link loop/1},
Expand Down Expand Up @@ -233,7 +235,7 @@ expect(Mod, Func, AriOrArgs, RetSpec) when is_atom(Mod), is_atom(Func) ->
Sequence :: [any()].
sequence(Mod, Func, Ari, Sequence)
when is_atom(Mod), is_atom(Func), is_integer(Ari), Ari >= 0 ->
Expect = meck_expect:new(Func, Ari, seq(Sequence)),
Expect = meck_expect:new(Func, Ari, meck_ret_spec:seq(Sequence)),
check_expect_result(meck_proc:set_expect(Mod, Expect));
sequence(Mod, Func, Ari, Sequence) when is_list(Mod) ->
lists:foreach(fun(M) -> sequence(M, Func, Ari, Sequence) end, Mod),
Expand All @@ -251,7 +253,7 @@ sequence(Mod, Func, Ari, Sequence) when is_list(Mod) ->
Loop :: [any()].
loop(Mod, Func, Ari, Loop)
when is_atom(Mod), is_atom(Func), is_integer(Ari), Ari >= 0 ->
Expect = meck_expect:new(Func, Ari, loop(Loop)),
Expect = meck_expect:new(Func, Ari, meck_ret_spec:loop(Loop)),
check_expect_result(meck_proc:set_expect(Mod, Expect));
loop(Mod, Func, Ari, Loop) when is_list(Mod) ->
lists:foreach(fun(M) -> loop(M, Func, Ari, Loop) end, Mod),
Expand Down Expand Up @@ -286,7 +288,7 @@ delete(Mod, Func, Ari) when is_list(Mod) ->
Class :: throw | error | exit,
Reason :: any().
exception(Class, Reason) when Class == throw; Class == error; Class == exit ->
meck_code_gen:throw_exception(Class, Reason).
erlang:throw(meck_ret_spec:exception(Class, Reason)).


%% @doc Calls the original function (if existing) inside an expectation fun.
Expand Down Expand Up @@ -452,7 +454,7 @@ reset(Mods) when is_list(Mods) ->
%% the first element when the end is reached.
-spec loop(Loop) -> ret_spec() when
Loop :: [ret_spec()].
loop(L) when is_list(L) -> {meck_loop, L, L}.
loop(Loop) -> meck_ret_spec:loop(Loop).


%% @doc Converts a list of terms into {@link ret_spec()} defining a sequence of
Expand All @@ -464,15 +466,15 @@ loop(L) when is_list(L) -> {meck_loop, L, L}.
%% value is reached. That value is then returned for all subsequent calls.
-spec seq(Sequence) -> ret_spec() when
Sequence :: [ret_spec()].
seq(S) when is_list(S) -> {meck_seq, S}.
seq(Sequence) -> meck_ret_spec:seq(Sequence).


%% @doc Converts a term into {@link ret_spec()} defining an individual value.
%% It is intended to be in construction of clause specs for the
%% {@link expect/3} function.
-spec val(Value) -> ret_spec() when
Value :: any().
val(Value) -> {meck_value, Value}.
val(Value) -> meck_ret_spec:val(Value).


%% @doc Creates a {@link ret_spec()} that defines an exception.
Expand All @@ -482,11 +484,24 @@ val(Value) -> {meck_value, Value}.
-spec raise(Class, Reason) -> ret_spec() when
Class :: throw | error | exit,
Reason :: term.
raise(throw, Reason) -> {meck_raise, throw, Reason};
raise(error, Reason) -> {meck_raise, error, Reason};
raise(exit, Reason) -> {meck_raise, exit, Reason}.
raise(Class, Reason) -> meck_ret_spec:exception(Class, Reason).


%% @doc Creates a {@link ret_spec()} that makes the original module function be
%% called.
%%
%% Calls to an expect, created with {@link ret_spec()} returned by this function,
%% will be forwarded to the original function.
-spec passthrough() -> ret_spec().
passthrough() -> meck_ret_spec:passthrough().


%% @doc Creates a {@link ret_spec()} from a function. Calls to an expect,
%% created with {@link ret_spec()} returned by this function, will be forwarded
%% to the specified function.
-spec func(fun()) -> ret_spec().
func(Fun) -> meck_ret_spec:func(Fun).

%%%============================================================================
%%% Internal functions
%%%============================================================================
Expand Down
88 changes: 26 additions & 62 deletions src/meck_code_gen.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@

%% API
-export([to_forms/2,
get_current_call/0,
throw_exception/2]).
get_current_call/0]).

%% Exported to be accessible from generated modules.
-export([exec/4]).
Expand Down Expand Up @@ -155,63 +154,39 @@ var_name(A) -> list_to_atom("A"++integer_to_list(A)).
-spec exec(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()]) ->
Result::any().
exec(Pid, Mod, Func, Args) ->
RetSpec = meck_proc:get_ret_spec(Mod, Func, Args),
try
put(?CURRENT_CALL, {Mod, Func}),
Result = simulate_call(Mod, Func, Args, RetSpec),
meck_proc:add_history(Mod, Pid, Func, Args, Result),
Result
catch
throw:Fun when is_function(Fun) ->
case is_mock_exception(Fun) of
true -> handle_mock_exception(Pid, Mod, Func, Fun, Args);
false -> invalidate_and_raise(Pid, Mod, Func, Args, throw, Fun)
end;
Class:Reason ->
invalidate_and_raise(Pid, Mod, Func, Args, Class, Reason)
after
erase('$meck_call')
case meck_proc:get_result_spec(Mod, Func, Args) of
undefined ->
meck_proc:invalidate(Mod),
raise(Pid, Mod, Func, Args, error, function_clause);
ResultSpec ->
put(?CURRENT_CALL, {Mod, Func}),
try
Result = meck_ret_spec:eval_result(Mod, Func, Args, ResultSpec),
meck_proc:add_history(Mod, Pid, Func, Args, Result),
Result
catch
Class:Reason ->
handle_exception(Pid, Mod, Func, Args, Class, Reason)
after
erase(?CURRENT_CALL)
end
end.


-spec simulate_call(Mod::atom(), Func::atom(), Args::[any()],
meck_expect:ret_spec() | meck_undefined) ->
Result::any().
simulate_call(_Mod, _Func, _Args, meck_undefined) ->
erlang:error(function_clause);
simulate_call(_Mod, _Func, _Args, {meck_value, Value}) ->
Value;
simulate_call(_Mod, _Func, Args, {meck_func, Fun}) when is_function(Fun) ->
apply(Fun, Args);
simulate_call(_Mod, _Func, _Args, {meck_raise, Class, Reason}) ->
throw_exception(Class, Reason);
simulate_call(Mod, Func, Args, meck_passthrough) ->
apply(meck_util:original_name(Mod), Func, Args);
simulate_call(_Mod, _Func, _Args, Value) ->
Value.


-spec handle_mock_exception(CallerPid::pid(), Mod::atom(), Func::atom(),
Body::fun(), Args::[any()]) ->
-spec handle_exception(CallerPid::pid(), Mod::atom(), Func::atom(),
Args::[any()], Class:: exit | error | throw,
Reason::any()) ->
no_return().
handle_mock_exception(Pid, Mod, Func, Fun, Args) ->
case Fun() of
{exception, Class, Reason} ->
% exception created with the mock:exception function,
% do not invalidate Mod
handle_exception(Pid, Mod, Func, Args, Class, Reason) ->
case meck_ret_spec:is_meck_exception(Reason) of
{true, MockedClass, MockedReason} ->
raise(Pid, Mod, Func, Args, MockedClass, MockedReason);
_ ->
meck_proc:invalidate(Mod),
raise(Pid, Mod, Func, Args, Class, Reason)
end.


-spec invalidate_and_raise(CallerPid::pid(), Mod::atom(), Func::atom(),
Args::[any()], Class:: exit | error | throw,
Reason::any()) ->
no_return().
invalidate_and_raise(Pid, Mod, Func, Args, Class, Reason) ->
meck_proc:invalidate(Mod),
raise(Pid, Mod, Func, Args, Class, Reason).


-spec raise(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()],
Class:: exit | error | throw, Reason::any()) ->
no_return().
Expand All @@ -221,17 +196,6 @@ raise(Pid, Mod, Func, Args, Class, Reason) ->
erlang:raise(Class, Reason, StackTrace).


-spec throw_exception(Class:: exit | error | throw, Reason::any()) -> fun().
throw_exception(Class, Reason) ->
erlang:throw(fun() -> {exception, Class, Reason} end).


-spec is_mock_exception(Fun::fun()) -> boolean().
is_mock_exception(Fun) ->
{module, Mod} = erlang:fun_info(Fun, module),
?MODULE == Mod.


-spec inject(Mod::atom(), Func::atom(), Args::[any()],
meck_history:stack_trace()) ->
NewStackTrace::meck_history:stack_trace().
Expand Down
70 changes: 15 additions & 55 deletions src/meck_expect.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
-module(meck_expect).

%% API
-export_type([ret_spec/0,
func_ari/0,
-export_type([func_ari/0,
func_clause/0,
expect/0]).

Expand All @@ -33,21 +32,13 @@
%%% Types
%%%============================================================================

-type ret_spec() :: {meck_val, any()} |
{meck_seq, [ret_spec()]} |
{meck_loop, [ret_spec()], [ret_spec()]} |
{meck_func, fun()} |
{meck_raise, throw | error | exit, any()} |
meck_passthrough |
term().

-type pattern_matcher() :: {pattern, meck:args_spec(), ets:comp_match_spec()}.

-type hamcrest_matcher() :: {hamcrest, any()}. % TODO define 'any'.

-type args_matcher() :: pattern_matcher() | hamcrest_matcher().

-opaque func_clause() :: {args_matcher(), ret_spec()}.
-opaque func_clause() :: {args_matcher(), meck_ret_spec:ret_spec()}.

-type func_ari() :: {Func::atom(), Ari::byte()}.

Expand All @@ -61,14 +52,15 @@
-spec new(Func::atom(), fun() | meck:func_clause_spec()) -> expect().
new(Func, StubFun) when is_function(StubFun) ->
{arity, Arity} = erlang:fun_info(StubFun, arity),
Clause = {arity_2_matcher(Arity), {meck_func, StubFun}},
Clause = {arity_2_matcher(Arity), meck_ret_spec:func(StubFun)},
{{Func, Arity}, [Clause]};
new(Func, ClauseSpecs) when is_list(ClauseSpecs) ->
{Arity, Clauses} = parse_clause_specs(ClauseSpecs),
{{Func, Arity}, Clauses}.


-spec new(Func::atom(), byte() | meck:args_spec(), ret_spec()) -> expect().
-spec new(Func::atom(), byte() | meck:args_spec(), meck_ret_spec:ret_spec()) ->
expect().
new(Func, Ari, RetSpec) when is_integer(Ari), Ari >= 0 ->
Clause = {arity_2_matcher(Ari), RetSpec},
{{Func, Ari}, [Clause]};
Expand All @@ -82,27 +74,27 @@ new_passthrough({Func, Ari}) ->
{{Func, Ari}, [{arity_2_matcher(Ari), meck_passthrough}]}.


-spec new_dummy(func_ari(), ret_spec()) -> expect().
-spec new_dummy(func_ari(), meck_ret_spec:ret_spec()) -> expect().
new_dummy({Func, Ari}, RetSpec) ->
{{Func, Ari}, [{arity_2_matcher(Ari), RetSpec}]}.


-spec match(Args::[any()], Clauses::[func_clause()]) ->
{meck_undefined, unchanged} |
{ret_spec(), unchanged} |
{ret_spec(), NewClauses::[func_clause()]}.
{undefined, unchanged} |
{meck_ret_spec:result_spec(), unchanged} |
{meck_ret_spec:result_spec(), NewClauses::[func_clause()]}.
match(Args, Clauses) ->
case find_match(Args, Clauses) of
not_found ->
{meck_undefined, unchanged};
{undefined, unchanged};
{ArgsMatcher, RetSpec} ->
case next_result(RetSpec, []) of
{ScalarRs, unchanged} ->
{ScalarRs, Clauses};
{ScalarRs, NewRetSpec} ->
case meck_ret_spec:retrieve_result(RetSpec) of
{ResultSpec, unchanged} ->
{ResultSpec, Clauses};
{ResultSpec, NewRetSpec} ->
NewClauses = lists:keyreplace(ArgsMatcher, 1, Clauses,
{ArgsMatcher, NewRetSpec}),
{ScalarRs, NewClauses}
{ResultSpec, NewClauses}
end
end.

Expand Down Expand Up @@ -169,35 +161,3 @@ find_match(Args, [{ArgsMatcher, RetSpec} | Rest]) ->
end;
find_match(_Args, []) ->
not_found.


-spec next_result(RetSpec::ret_spec(), Stack::[ret_spec()]) ->
{ScalarRs::ret_spec(), NewRetSpec::ret_spec() | unchanged}.
next_result(RetSpec = {meck_seq, [InnerRs | _Rest]}, Stack) ->
next_result(InnerRs, [RetSpec | Stack]);
next_result(RetSpec = {meck_loop, [InnerRs | _Rest], _Loop}, Stack) ->
next_result(InnerRs, [RetSpec | Stack]);
next_result(LeafRetSpec, Stack) ->
{LeafRetSpec, unwind_stack(LeafRetSpec, Stack, false)}.


-spec unwind_stack(InnerRs::ret_spec(), Stack::[ret_spec()], Done::boolean()) ->
NewRetSpec::ret_spec() | unchanged.
unwind_stack(InnerRs, [], true) ->
InnerRs;
unwind_stack(_InnerRs, [], false) ->
unchanged;
unwind_stack(InnerRs, [CurrRs = {meck_seq, [InnerRs]} | Stack], Updated) ->
unwind_stack(CurrRs, Stack, Updated);
unwind_stack(InnerRs, [{meck_seq, [InnerRs | Rest]} | Stack], _Updated) ->
unwind_stack({meck_seq, Rest}, Stack, true);
unwind_stack(NewInnerRs, [{meck_seq, [_InnerRs | Rest]} | Stack], _Updated) ->
unwind_stack({meck_seq, [NewInnerRs | Rest]}, Stack, true);
unwind_stack(InnerRs, [{meck_loop, [InnerRs], Loop} | Stack], _Updated) ->
unwind_stack({meck_loop, Loop, Loop}, Stack, true);
unwind_stack(InnerRs, [{meck_loop, [InnerRs | Rest], Loop} | Stack],
_Updated) ->
unwind_stack({meck_loop, Rest, Loop}, Stack, true);
unwind_stack(NewInnerRs, [{meck_loop, [_InnerRs | Rest], Loop} | Stack],
_Updated) ->
unwind_stack({meck_loop, [NewInnerRs | Rest], Loop}, Stack, true).
Loading