Skip to content

Commit

Permalink
Merge pull request #91 from horkhe/feature/passthrough-and-func
Browse files Browse the repository at this point in the history
(Cont.) Make `passthrough/1' and `func/1` into a `ret_spec`and func
  • Loading branch information
eproxus committed Dec 21, 2012
2 parents e4b387b + e06e4d7 commit 8433cf2
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 151 deletions.
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([exec/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:raise(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:raise(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 exec(fun()) -> ret_spec().
exec(Fun) -> meck_ret_spec:exec(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
81 changes: 15 additions & 66 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 @@ -28,85 +27,71 @@
new_dummy/2,
match/2]).


%%%============================================================================
%%% 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()}.

-type expect() :: {func_ari(), [func_clause()]}.


%%%============================================================================
%%% API
%%%============================================================================

-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:exec(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]};
new(Func, ArgsSpec, RetSpec) when is_list(ArgsSpec) ->
{Ari, Clause} = parse_clause_spec({ArgsSpec, RetSpec}),
{{Func, Ari}, [Clause]}.


-spec new_passthrough(func_ari()) -> expect().
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.


%%%============================================================================
%%% Internal functions
%%%============================================================================
Expand All @@ -118,13 +103,11 @@ arity_2_matcher(Ari) ->
MatchSpec = ets:match_spec_compile([MatchSpecItem]),
{pattern, ArgsPattern, MatchSpec}.


-spec parse_clause_specs([meck:func_clause_spec()]) ->
{Ari::byte(), [func_clause()]}.
parse_clause_specs(ClauseSpecs) ->
parse_clause_specs(ClauseSpecs, undefined, []).


-spec parse_clause_specs([meck:func_clause_spec()],
DeducedAri::byte() | undefined,
ParsedClauses::[func_clause()]) ->
Expand All @@ -145,7 +128,6 @@ parse_clause_specs([ClauseSpec | Rest], DeducedAri, Clauses) ->
parse_clause_specs([], DeducedArity, Clauses) ->
{DeducedArity, lists:reverse(Clauses)}.


-spec parse_clause_spec(meck:func_clause_spec()) ->
{Ari::byte(), func_clause()}.
parse_clause_spec({ArgsSpec, RetSpec}) ->
Expand All @@ -154,7 +136,6 @@ parse_clause_spec({ArgsSpec, RetSpec}) ->
Clause = {{pattern, ArgsSpec, MatchSpec}, RetSpec},
{Ari, Clause}.


-spec find_match(Args::[any()], Defined::[func_clause()]) ->
Matching:: func_clause() | not_found.
find_match(Args, [{ArgsMatcher, RetSpec} | Rest]) ->
Expand All @@ -169,35 +150,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

0 comments on commit 8433cf2

Please sign in to comment.