| %%%============================================================================ |
| %%% Licensed under the Apache License, Version 2.0 (the "License"); |
| %%% you may not use this file except in compliance with the License. |
| %%% You may obtain a copy of the License at |
| %%% |
| %%% http://www.apache.org/licenses/LICENSE-2.0 |
| %%% |
| %%% Unless required by applicable law or agreed to in writing, software |
| %%% distributed under the License is distributed on an "AS IS" BASIS, |
| %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| %%% See the License for the specific language governing permissions and |
| %%% limitations under the License. |
| %%%============================================================================ |
| |
| %%% @private |
| %%% @doc Implements code generation for mocked module and also contains code |
| %%% pieces that are called in the generated module context. |
| -module(meck_code_gen). |
| |
| %% API |
| -export([to_forms/2]). |
| -export([get_current_call/0]). |
| |
| %% Exported to be accessible from generated modules. |
| -export([exec/4]). |
| |
| %%%============================================================================ |
| %%% Definitions |
| %%%============================================================================ |
| |
| -define(CURRENT_CALL, '$meck_call'). |
| -define(call(Module, Function, Arguments), |
| {call, ?LINE, |
| {remote, ?LINE, ?atom(Module), ?atom(Function)}, |
| Arguments}). |
| |
| -define(atom(Atom), {atom, ?LINE, Atom}). |
| -define(integer(Integer), {integer, ?LINE, Integer}). |
| -define(var(Name), {var, ?LINE, Name}). |
| -define(attribute(Attribute, Args), {attribute, ?LINE, Attribute, Args}). |
| -define(function(Name, Arity, Clauses), |
| {function, ?LINE, Name, Arity, Clauses}). |
| -define(clause(Arguments, Body), {clause, ?LINE, Arguments, [], Body}). |
| -define(tuple(Elements), {tuple, ?LINE, Elements}). |
| |
| %%%============================================================================ |
| %%% API |
| %%%============================================================================ |
| |
| to_forms(Mod, Expects) -> |
| {Exports, Functions} = functions(Mod, Expects), |
| [?attribute(module, Mod)] ++ attributes(Mod) ++ Exports ++ Functions. |
| |
| -spec get_current_call() -> {Mod::atom(), Func::atom()}. |
| get_current_call() -> |
| get(?CURRENT_CALL). |
| |
| %%%============================================================================ |
| %%% Internal functions |
| %%%============================================================================ |
| |
| attributes(Mod) -> |
| try |
| [?attribute(Key, Val) || {Key, Val} <- |
| proplists:get_value(attributes, Mod:module_info(), []), |
| Key =/= vsn, Key =/= deprecated, Key =/= optional_callbacks] |
| catch |
| error:undef -> [] |
| end. |
| |
| functions(Mod, Expects) -> |
| dict:fold(fun(Export, Expect, {Exports, Functions}) -> |
| {[?attribute(export, [Export]) | Exports], |
| [func(Mod, Export, Expect) | Functions]} |
| end, |
| {[], []}, |
| Expects). |
| |
| func(Mod, {Func, Arity}, {anon, Arity, Result}) -> |
| case contains_opaque(Result) of |
| true -> |
| func_exec(Mod, Func, Arity); |
| false -> |
| func_native(Mod, Func, Arity, Result) |
| end; |
| func(Mod, {Func, Arity}, _Expect) -> |
| func_exec(Mod, Func, Arity). |
| |
| func_exec(Mod, Func, Arity) -> |
| Args = args(Arity), |
| ?function(Func, Arity, |
| [?clause(Args, |
| [?call(?MODULE, exec, |
| [?call(erlang, self, []), |
| ?atom(Mod), |
| ?atom(Func), |
| list(Args)])])]). |
| |
| func_native(Mod, Func, Arity, Result) -> |
| Args = args(Arity), |
| AbsResult = erl_parse:abstract(Result), |
| ?function( |
| Func, Arity, |
| [?clause( |
| Args, |
| [?call(gen_server, cast, |
| [?atom(meck_util:proc_name(Mod)), |
| ?tuple([?atom(add_history), |
| ?tuple([?call(erlang, self, []), |
| ?tuple([?atom(Mod), ?atom(Func), |
| list(Args)]), |
| AbsResult])])]), |
| AbsResult])]). |
| |
| contains_opaque(Term) when is_pid(Term); is_port(Term); is_function(Term); |
| is_reference(Term) -> |
| true; |
| contains_opaque(Term) when is_list(Term) -> |
| lists_any(fun contains_opaque/1, Term); |
| contains_opaque(Term) when is_tuple(Term) -> |
| lists_any(fun contains_opaque/1, tuple_to_list(Term)); |
| contains_opaque(_Term) -> |
| false. |
| |
| %% based on lists.erl but accepts improper lists. |
| lists_any(Pred, []) when is_function(Pred, 1) -> false; |
| lists_any(Pred, [Hd|Tail]) -> |
| case Pred(Hd) of |
| true -> true; |
| false -> lists_any(Pred, Tail) |
| end; |
| lists_any(Pred, Improper) -> |
| Pred(Improper). |
| |
| args(0) -> []; |
| args(Arity) -> [?var(var_name(N)) || N <- lists:seq(1, Arity)]. |
| |
| list([]) -> {nil, ?LINE}; |
| list([H|T]) -> {cons, ?LINE, H, list(T)}. |
| |
| var_name(A) -> list_to_atom("A"++integer_to_list(A)). |
| |
| %% @hidden |
| -spec exec(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()]) -> |
| Result::any(). |
| exec(Pid, Mod, Func, Args) -> |
| try 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 |
| catch |
| error:{not_mocked, Mod} -> |
| apply(Mod, Func, Args) |
| end. |
| |
| -spec handle_exception(CallerPid::pid(), Mod::atom(), Func::atom(), |
| Args::[any()], Class:: exit | error | throw, |
| Reason::any()) -> |
| no_return(). |
| 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 raise(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()], |
| Class:: exit | error | throw, Reason::any()) -> |
| no_return(). |
| raise(Pid, Mod, Func, Args, Class, Reason) -> |
| StackTrace = inject(Mod, Func, Args, erlang:get_stacktrace()), |
| meck_proc:add_history_exception(Mod, Pid, Func, Args, |
| {Class, Reason, StackTrace}), |
| erlang:raise(Class, Reason, StackTrace). |
| |
| -spec inject(Mod::atom(), Func::atom(), Args::[any()], |
| meck_history:stack_trace()) -> |
| NewStackTrace::meck_history:stack_trace(). |
| inject(_Mod, _Func, _Args, []) -> |
| []; |
| inject(Mod, Func, Args, [{?MODULE, exec, _AriOrArgs, _Loc}|Stack]) -> |
| [{Mod, Func, Args} | Stack]; |
| inject(Mod, Func, Args, [{?MODULE, exec, _AriOrArgs}|Stack]) -> |
| [{Mod, Func, Args} | Stack]; |
| inject(Mod, Func, Args, [Call|Stack]) when element(1, Call) == ?MODULE -> |
| inject(Mod, Func, Args, Stack); |
| inject(Mod, Func, Args, [H | Stack]) -> |
| [H | inject(Mod, Func, Args, Stack)]. |