meck-1.0.0/0000775000175000017500000000000014726775702013030 5ustar debalancedebalancemeck-1.0.0/rebar.config0000664000175000017500000000170614726775702015316 0ustar debalancedebalance{minimum_otp_vsn, "25.0"}. {project_plugins, [rebar3_ex_doc]}. {profiles, [ {test, [ {deps, [unite]}, {eunit_opts, [no_tty, {report, {unite_compact, []}}]}, {erl_opts, [debug_info]}, {cover_enabled, true}, {cover_opts, [verbose]} ]}, {prod, [ {erl_opts, [ debug_info, warnings_as_errors, warn_export_all, warn_export_vars, warn_shadow_vars, warn_obsolete_guard, warn_unused_import ]} ]} ]}. {xref_checks, [ locals_not_used, undefined_function_calls, deprecated_function_calls ]}. {dialyzer, [ {warnings, [ unknown, unmatched_returns, error_handling ]} ]}. {hex, [{doc, ex_doc}]}. {ex_doc, [ {source_url, <<"https://github.com/eproxus/meck">>}, {extras, [<<"README.md">>, <<"LICENSE">>]}, {main, <<"readme">>}, {prefix_ref_vsn_with_v, false} ]}. meck-1.0.0/src/0000775000175000017500000000000014726775702013617 5ustar debalancedebalancemeck-1.0.0/src/meck_matcher.erl0000664000175000017500000000677714726775702016766 0ustar debalancedebalance%%%============================================================================ %%% 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 This module introduces Matcher abstraction. It is an entity that can %%% be created from a predicate function or a matcher provided by a supported %%% framework (at the moment only Hamcrest is supported). Then it can be used %%% to check that an arbitrary value meets the matcher's criteria, matches in %%% other words. -module(meck_matcher). -export_type([matcher/0]). %% API -export([new/1]). -export([is_matcher/1]). -export([match_ignore/2]). -ignore_xref({hamcrest, is_matcher, 1}). -ignore_xref({hamcrest, assert_that, 2}). %%%============================================================================ %%% Definitions %%%============================================================================ -record('$meck.matcher', {type :: predicate | hamcrest, impl :: predicate() | hamcrest_matchspec()}). %%%============================================================================ %%% Types %%%============================================================================ -type predicate() :: fun((X::any()) -> any()). -type matcher() :: #'$meck.matcher'{}. -type hamcrest_matchspec() :: tuple(). %% #'hamcrest.matchspec'{} -export_type([hamcrest_matchspec/0]). %%%============================================================================ %%% API %%%============================================================================ -spec new(predicate() | hamcrest_matchspec()) -> matcher(). new(Predicate) when is_function(Predicate) -> {arity, 1} = erlang:fun_info(Predicate, arity), #'$meck.matcher'{type = predicate, impl = Predicate}; new(Something) -> case is_hamcrest_matcher(Something) of true -> #'$meck.matcher'{type = hamcrest, impl = Something}; _Else -> erlang:error({invalid_matcher, Something}) end. -spec is_matcher(any()) -> boolean(). is_matcher(#'$meck.matcher'{}) -> true; is_matcher(_Other) -> false. %% @doc If `Something' is a {@link meck_matcher()} instance then `Value' is %% matched with it, otherwise `true' is returned effectively ignoring %% `Something''s value. -spec match_ignore(Value::any(), Something::any()) -> boolean(). match_ignore(Value, #'$meck.matcher'{type = predicate, impl = Predicate}) -> Predicate(Value) == true; match_ignore(Value, #'$meck.matcher'{type = hamcrest, impl = HamcrestMatcher}) -> (catch erlang:apply(hamcrest, assert_that, [Value, HamcrestMatcher])) == true; match_ignore(_Value, _NotMatcher) -> true. %%%============================================================================ %%% Internal functions %%%============================================================================ -spec is_hamcrest_matcher(any()) -> boolean(). is_hamcrest_matcher(Something) -> try erlang:apply(hamcrest, is_matcher, [Something]) catch _Class:_Reason -> false end. meck-1.0.0/src/meck_ret_spec.erl0000664000175000017500000001325014726775702017127 0ustar debalancedebalance%%%============================================================================ %%% 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 Provides expectation processing functions. -module(meck_ret_spec). -export_type([result_spec/0]). -export_type([ret_spec/0]). %% API -export([passthrough/0]). -export([val/1]). -export([exec/1]). -export([seq/1]). -export([loop/1]). -export([raise/2]). -export([is_meck_exception/1]). -export([is_passthrough/1]). -export([retrieve_result/1]). -export([eval_result/4]). %%%============================================================================ %%% Types %%%============================================================================ -opaque result_spec() :: {meck_value, any()} | {meck_exec, fun()} | {meck_raise, Class::throw | error | exit, Reason::any()} | meck_passthrough. -type ret_spec() :: {meck_seq, [ret_spec()]} | {meck_loop, [ret_spec()], [ret_spec()]} | result_spec() | any(). %%%============================================================================ %%% API %%%============================================================================ -spec passthrough() -> ret_spec(). passthrough() -> meck_passthrough. -spec val(any()) -> ret_spec(). val(Value) -> {meck_value, Value}. -spec exec(fun()) -> ret_spec(). exec(Fun) when is_function(Fun)-> {meck_exec, Fun}. -spec seq([ret_spec()]) -> ret_spec(). seq(Sequence) when is_list(Sequence) -> {meck_seq, Sequence}. -spec loop([ret_spec()]) -> ret_spec(). loop(Loop) when is_list(Loop) -> {meck_loop, Loop, Loop}. -spec raise(Class:: throw | error | exit, Reason::any()) -> ret_spec(). raise(throw, Reason) -> {meck_raise, throw, Reason}; raise(error, Reason) -> {meck_raise, error, Reason}; raise(exit, Reason) -> {meck_raise, exit, Reason}. -spec is_meck_exception(Reason::any()) -> {true, any(), any()} | false. is_meck_exception({meck_raise, MockedClass, MockedReason}) -> {true, MockedClass, MockedReason}; is_meck_exception(_Reason) -> false. -spec is_passthrough(ret_spec()) -> boolean(). is_passthrough(RetSpec) -> RetSpec == meck_passthrough. -spec retrieve_result(RetSpec::ret_spec()) -> {result_spec(), NewRetSpec::ret_spec() | unchanged}. retrieve_result(RetSpec) -> retrieve_result(RetSpec, []). -spec eval_result(Mod::atom(), Func::atom(), Args::[any()], result_spec()) -> Result::any(). eval_result(_Mod, _Func, _Args, {meck_value, Value}) -> Value; eval_result(_Mod, _Func, Args, {meck_exec, Fun}) when is_function(Fun) -> erlang:apply(Fun, Args); eval_result(_Mod, _Func, _Args, MockedEx = {meck_raise, _Class, _Reason}) -> erlang:throw(MockedEx); eval_result(Mod, Func, Args, meck_passthrough) -> erlang:apply(meck_util:original_name(Mod), Func, Args). %%%============================================================================ %%% Internal functions %%%============================================================================ -spec retrieve_result(RetSpec::ret_spec(), ExplodedRs::[ret_spec()]) -> {result_spec(), NewRetSpec::ret_spec() | unchanged}. retrieve_result(RetSpec = {meck_seq, [InnerRs | _Rest]}, ExplodedRs) -> retrieve_result(InnerRs, [RetSpec | ExplodedRs]); retrieve_result(RetSpec = {meck_loop, [InnerRs | _Rest], _Loop}, ExplodedRs) -> retrieve_result(InnerRs, [RetSpec | ExplodedRs]); retrieve_result(RetSpec, ExplodedRs) -> ResultSpec = case is_result_spec(RetSpec) of true -> RetSpec; _ when erlang:is_function(RetSpec) -> exec(RetSpec); _ -> val(RetSpec) end, {ResultSpec, update_rs(RetSpec, ExplodedRs, false)}. -spec is_result_spec(any()) -> boolean(). is_result_spec({meck_value, _Value}) -> true; is_result_spec({meck_exec, _Fun}) -> true; is_result_spec({meck_raise, _Class, _Reason}) -> true; is_result_spec(meck_passthrough) -> true; is_result_spec(_Other) -> false. -spec update_rs(InnerRs::ret_spec(), ExplodedRs::[ret_spec()], Done::boolean()) -> NewRetSpec::ret_spec() | unchanged. update_rs(InnerRs, [], true) -> InnerRs; update_rs(_InnerRs, [], false) -> unchanged; update_rs(InnerRs, [CurrRs = {meck_seq, [InnerRs]} | ExplodedRs], Updated) -> update_rs(CurrRs, ExplodedRs, Updated); update_rs(InnerRs, [{meck_seq, [InnerRs | Rest]} | ExplodedRs], _Updated) -> update_rs({meck_seq, Rest}, ExplodedRs, true); update_rs(NewInnerRs, [{meck_seq, [_InnerRs | Rest]} | ExplodedRs], _Updated) -> update_rs({meck_seq, [NewInnerRs | Rest]}, ExplodedRs, true); update_rs(InnerRs, [{meck_loop, [InnerRs], Loop} | ExplodedRs], _Updated) -> update_rs({meck_loop, Loop, Loop}, ExplodedRs, true); update_rs(InnerRs, [{meck_loop, [InnerRs | Rest], Loop} | ExplodedRs], _Updated) -> update_rs({meck_loop, Rest, Loop}, ExplodedRs, true); update_rs(NewInnerRs, [{meck_loop, [_InnerRs | Rest], Loop} | ExplodedRs], _Updated) -> update_rs({meck_loop, [NewInnerRs | Rest], Loop}, ExplodedRs, true). meck-1.0.0/src/meck_history.erl0000664000175000017500000001327014726775702017026 0ustar debalancedebalance%%%============================================================================ %%% 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 Provides functions for digging information from the recorded call %%% history. -module(meck_history). %% API -export_type([stack_trace_rec_r14b/0]). -export_type([stack_trace_rec_r15b/0]). -export_type([stack_trace/0]). -export_type([meck_mfa/0]). -export_type([successfull_call/0]). -export_type([faulty_call/0]). -export_type([history_record/0]). -export_type([history/0]). -export([get_history/2]). -export([num_calls/4]). -export([capture/6]). -export([result/5]). -export([new_filter/3]). %%%============================================================================ %%% Types %%%============================================================================ -type stack_trace_rec_r14b() :: {Mod::atom(), Func::atom(), AriOrArgs::byte() | [any()]}. -type stack_trace_rec_r15b() :: {Mod::atom(), Func::atom(), AriOrArgs::byte() | [any()], Location::[{atom(), any()}]}. -type stack_trace() :: [stack_trace_rec_r14b() | stack_trace_rec_r15b()]. -type meck_mfa() :: {Mod::atom(), Func::atom(), Args::[term()]}. -type successfull_call() :: {CallerPid::pid(), meck_mfa(), Result::any()}. -type faulty_call() :: {CallerPid::pid(), meck_mfa(), Class::exit|error|throw, Reason::term(), stack_trace()}. -type history_record() :: successfull_call() | faulty_call(). -type history() :: [history_record()]. -type opt_pid() :: pid() | '_'. -type opt_func() :: atom() | '_'. %%%============================================================================ %%% API %%%============================================================================ -spec get_history(opt_pid(), Mod::atom()) -> history(). get_history('_', Mod) -> meck_proc:get_history(Mod); get_history(CallerPid, Mod) -> ArgsMatcher = meck_args_matcher:new('_'), lists:filter(new_filter(CallerPid, '_', ArgsMatcher), meck_proc:get_history(Mod)). -spec num_calls(opt_pid(), Mod::atom(), opt_func(), meck_args_matcher:opt_args_spec()) -> non_neg_integer(). num_calls(CallerPid, Mod, OptFunc, OptArgsSpec) -> ArgsMatcher = meck_args_matcher:new(OptArgsSpec), Filter = new_filter(CallerPid, OptFunc, ArgsMatcher), Filtered = lists:filter(Filter, meck_proc:get_history(Mod)), length(Filtered). -spec capture(Occur::pos_integer(), opt_pid(), Mod::atom(), Func::atom(), meck_args_matcher:opt_args_spec(), ArgNum::pos_integer()) -> ArgValue::any(). capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum) -> ArgsMatcher = meck_args_matcher:new(OptArgsSpec), Filter = new_filter(OptCallerPid, Func, ArgsMatcher), Filtered = lists:filter(Filter, meck_proc:get_history(Mod)), case nth_record(Occur, Filtered) of not_found -> erlang:error(not_found); {_CallerPid, {_Mod, _Func, Args}, _Result} -> lists:nth(ArgNum, Args); {_CallerPid, {_Mod, Func, Args}, _Class, _Reason, _Trace} -> lists:nth(ArgNum, Args) end. -spec result(Occur::pos_integer(), opt_pid(), Mod::atom(), Func::atom(), meck_args_matcher:opt_args_spec()) -> ResultValue::any(). result(Occur, OptCallerPid, Mod, Func, OptArgsSpec) -> ArgsMatcher = meck_args_matcher:new(OptArgsSpec), Filter = new_filter(OptCallerPid, Func, ArgsMatcher), Filtered = lists:filter(Filter, meck_proc:get_history(Mod)), case nth_record(Occur, Filtered) of not_found -> erlang:error(not_found); {_CallerPid, _MFA, Result} -> Result; {_CallerPid, _MFA, Class, Reason, Trace} -> erlang:raise(Class, Reason, Trace) end. -spec new_filter(opt_pid(), opt_func(), meck_args_matcher:args_matcher()) -> fun((history_record()) -> boolean()). new_filter(TheCallerPid, TheFunc, ArgsMatcher) -> fun({CallerPid, {_Mod, Func, Args}, _Result}) when (TheFunc == '_' orelse Func == TheFunc) andalso (TheCallerPid == '_' orelse CallerPid == TheCallerPid) -> meck_args_matcher:match(Args, ArgsMatcher); ({CallerPid, {_Mod, Func, Args}, _Class, _Reason, _StackTrace}) when (TheFunc == '_' orelse Func == TheFunc) andalso (TheCallerPid == '_' orelse CallerPid == TheCallerPid) -> meck_args_matcher:match(Args, ArgsMatcher); (_Other) -> false end. %%%============================================================================ %%% Internal functions %%%============================================================================ -spec nth_record(Occur::pos_integer(), history()) -> history_record() | not_found. nth_record(Occur, History) -> try case Occur of first -> lists:nth(1, History); last -> lists:last(History); _Else -> lists:nth(Occur, History) end catch error:_Reason -> not_found end. meck-1.0.0/src/meck_args_matcher.erl0000664000175000017500000001176014726775702017766 0ustar debalancedebalance%%%============================================================================ %%% 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 -module(meck_args_matcher). -export_type([args_spec/0]). -export_type([opt_args_spec/0]). -export_type([args_matcher/0]). %% API -export([new/1]). -export([arity/1]). -export([match/2]). %%%============================================================================ %%% Definitions %%%============================================================================ -record(args_matcher, {opt_args_pattern :: opt_args_pattern(), comp_match_spec :: ets:comp_match_spec(), has_matchers :: boolean()}). %%%============================================================================ %%% Types %%%============================================================================ -type opt_args_spec() :: args_spec() | '_'. -type args_spec() :: args_pattern() | non_neg_integer(). -type opt_args_pattern() :: args_pattern() | '_'. -type args_pattern() :: [any() | '_' | meck_matcher:matcher()]. -opaque args_matcher() :: #args_matcher{}. %%%============================================================================ %%% API %%%============================================================================ -spec new(opt_args_spec()) -> args_matcher(). new('_') -> MatchSpecItem = meck_util:match_spec_item({'_'}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), #args_matcher{opt_args_pattern = '_', comp_match_spec = CompMatchSpec, has_matchers = false}; new(Arity) when is_number(Arity) -> ArgsPattern = lists:duplicate(Arity, '_'), MatchSpecItem = meck_util:match_spec_item({ArgsPattern}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), #args_matcher{opt_args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = false}; new(ArgsPattern) when is_list(ArgsPattern) -> {HasMatchers, Pattern} = case strip_off_matchers(ArgsPattern) of unchanged -> {false, ArgsPattern}; StrippedArgsSpec -> {true, StrippedArgsSpec} end, MatchSpecItem = meck_util:match_spec_item({Pattern}), CompMatchSpec = ets:match_spec_compile([MatchSpecItem]), #args_matcher{opt_args_pattern = ArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = HasMatchers}. -spec arity(args_matcher()) -> Arity::non_neg_integer(). arity(#args_matcher{opt_args_pattern = ArgsPattern}) -> erlang:length(ArgsPattern). -spec match(Args::any(), args_matcher()) -> boolean(). match(Args, #args_matcher{opt_args_pattern = OptArgsPattern, comp_match_spec = CompMatchSpec, has_matchers = HasMatchers}) -> case ets:match_spec_run([{Args}], CompMatchSpec) of [] -> false; _Matches when HasMatchers andalso erlang:is_list(OptArgsPattern) -> check_by_matchers(Args, OptArgsPattern); _Matches -> true end. %%%============================================================================ %%% Internal functions %%%============================================================================ -spec strip_off_matchers(args_pattern()) -> NewArgsPattern::args_pattern() | unchanged. strip_off_matchers(ArgsPattern) -> strip_off_matchers(ArgsPattern, [], false). -spec strip_off_matchers(args_pattern(), Stripped::[any() | '_'], boolean()) -> NewArgsPattern::args_pattern() | unchanged. strip_off_matchers([ArgPattern | Rest], Stripped, HasMatchers) -> case meck_matcher:is_matcher(ArgPattern) of true -> strip_off_matchers(Rest, ['_' | Stripped], true); _ -> strip_off_matchers(Rest, [ArgPattern | Stripped], HasMatchers) end; strip_off_matchers([], Stripped, true) -> lists:reverse(Stripped); strip_off_matchers([], _Stripped, false) -> unchanged. -spec check_by_matchers(Args ::[any()], MaybeMatchers::[any()]) -> boolean(). check_by_matchers([Arg | RestArgs], [MaybeMatcher | RestMaybeMatchers]) -> case meck_matcher:match_ignore(Arg, MaybeMatcher) of true -> check_by_matchers(RestArgs, RestMaybeMatchers); _Else -> false end; check_by_matchers([], []) -> true.meck-1.0.0/src/meck_expect.erl0000664000175000017500000001271414726775702016617 0ustar debalancedebalance%%%============================================================================ %%% 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 Provides expectation processing functions. -module(meck_expect). %% API -export_type([func_ari/0]). -export_type([expect/0]). -export([new/2]). -export([new/3]). -export([new_passthrough/1]). -export([new_dummy/2]). -export([func_ari/1]). -export([is_passthrough/1]). -export([fetch_result/2]). %%%============================================================================ %%% Types %%%============================================================================ -type func_clause_spec() :: {meck_args_matcher:args_spec(), meck_ret_spec:ret_spec()}. -type func_clause() :: {meck_args_matcher: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() | func_clause_spec()) -> expect(). new(Func, StubFun) when is_function(StubFun) -> {arity, Arity} = erlang:fun_info(StubFun, arity), Clause = {meck_args_matcher:new(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(), meck_args_matcher:args_spec(), meck_ret_spec:ret_spec()) -> expect(). new(Func, ArgsSpec, RetSpec) -> {Ari, Clause} = parse_clause_spec({ArgsSpec, RetSpec}), {{Func, Ari}, [Clause]}. -spec new_passthrough(func_ari()) -> expect(). new_passthrough({Func, Ari}) -> {{Func, Ari}, [{meck_args_matcher:new(Ari), meck_ret_spec:passthrough()}]}. -spec new_dummy(func_ari(), meck_ret_spec:ret_spec()) -> expect(). new_dummy({Func, Ari}, RetSpec) -> {{Func, Ari}, [{meck_args_matcher:new(Ari), RetSpec}]}. -spec func_ari(expect()) -> func_ari(). func_ari({FuncAri, _Clauses}) -> FuncAri. -spec is_passthrough(expect()) -> boolean(). is_passthrough({_FuncAri, [{_Matcher, RetSpec}]}) -> meck_ret_spec:is_passthrough(RetSpec); is_passthrough(_) -> false. -spec fetch_result(Args::[any()], expect()) -> {undefined, unchanged} | {meck_ret_spec:result_spec(), unchanged} | {meck_ret_spec:result_spec(), NewExpect::expect()}. fetch_result(Args, {FuncAri, Clauses}) -> case find_matching_clause(Args, Clauses) of not_found -> {undefined, unchanged}; {ArgsMatcher, RetSpec} -> case meck_ret_spec:retrieve_result(RetSpec) of {ResultSpec, unchanged} -> {ResultSpec, unchanged}; {ResultSpec, NewRetSpec} -> NewClauses = lists:keyreplace(ArgsMatcher, 1, Clauses, {ArgsMatcher, NewRetSpec}), {ResultSpec, {FuncAri, NewClauses}} end end. %%%============================================================================ %%% Internal functions %%%============================================================================ -spec parse_clause_specs([func_clause_spec()]) -> {Ari::byte(), [func_clause()]}. parse_clause_specs([ClauseSpec | Rest]) -> {Ari, Clause} = parse_clause_spec(ClauseSpec), parse_clause_specs(Rest, Ari, [Clause]). -spec parse_clause_specs([func_clause_spec()], FirstClauseAri::byte(), Clauses::[func_clause()]) -> {Ari::byte(), [func_clause()]}. parse_clause_specs([ClauseSpec | Rest], FirstClauseAri, Clauses) -> {Ari, Clause} = parse_clause_spec(ClauseSpec), case Ari of FirstClauseAri -> parse_clause_specs(Rest, FirstClauseAri, [Clause | Clauses]); _ -> erlang:error({invalid_arity, {{expected, FirstClauseAri}, {actual, Ari}, {clause, ClauseSpec}}}) end; parse_clause_specs([], FirstClauseAri, Clauses) -> {FirstClauseAri, lists:reverse(Clauses)}. -spec parse_clause_spec(func_clause_spec()) -> {Ari::byte(), func_clause()}. parse_clause_spec({ArgsSpec, RetSpec}) -> ArgsMatcher = meck_args_matcher:new(ArgsSpec), Ari = meck_args_matcher:arity(ArgsMatcher), Clause = {ArgsMatcher, RetSpec}, {Ari, Clause}. -spec find_matching_clause(Args::[any()], Defined::[func_clause()]) -> Matching::func_clause() | not_found. find_matching_clause(Args, [{ArgsMatcher, RetSpec} | Rest]) -> case meck_args_matcher:match(Args, ArgsMatcher) of true -> {ArgsMatcher, RetSpec}; _Else -> find_matching_clause(Args, Rest) end; find_matching_clause(_Args, []) -> not_found. meck-1.0.0/src/meck_util.erl0000664000175000017500000000331414726775702016300 0ustar debalancedebalance%%%============================================================================ %%% 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 Contains utility functions that used around other meck modules. -module(meck_util). %% API -export_type([match_spec_item/0]). -export([proc_name/1]). -export([original_name/1]). -export([match_spec_item/1]). %%%============================================================================ %%% Types %%%============================================================================ -type match_spec_item() :: {Pattern::tuple(), Guards::[any()], Result::[any()]}. %%%============================================================================ %%% API %%%============================================================================ -spec proc_name(Mod::atom()) -> MockMod::atom(). proc_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_meck"). -spec original_name(Mod::atom()) -> OrigMod::atom(). original_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_meck_original"). -spec match_spec_item(Pattern::tuple()) -> match_spec_item(). match_spec_item(Pattern) -> {Pattern, [], ['$_']}. meck-1.0.0/src/meck.hrl0000664000175000017500000000230614726775702015246 0ustar debalancedebalance%% Copied from basho/riak_core. %% %% Fix to make Erlang programs compile on both OTP20 and OTP21. %% %% Get the stack trace in a way that is backwards compatible. Luckily %% OTP_RELEASE was introduced in the same version as the new preferred way of %% getting the stack trace. A _catch_/2 macro is provided for consistency in %% cases where the stack trace is not needed. %% %% Example use: %% try f(...) %% catch %% ?_exception_(_, Reason, StackToken) -> %% case Reason of %% {fail, Error} -> ok; %% _ -> {'EXIT', Reason, ?_get_stacktrace_(StackToken)} %% end %% end, -ifdef(OTP_RELEASE). %% This implies 21 or higher -define(_exception_(Class, Reason, StackToken), Class:Reason:StackToken). -define(_get_stacktrace_(StackToken), StackToken). -define(_current_stacktrace_(), try exit('$get_stacktrace') catch exit:'$get_stacktrace':__GetCurrentStackTrace -> __GetCurrentStackTrace end). -else. -define(_exception_(Class, Reason, _), Class:Reason). -define(_get_stacktrace_(_), erlang:get_stacktrace()). -define(_current_stacktrace_(), erlang:get_stacktrace()). -endif. meck-1.0.0/src/meck_code.erl0000664000175000017500000001260214726775702016235 0ustar debalancedebalance%%============================================================================= %% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %% %% 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. %%============================================================================= %% @hidden %% @author Adam Lindberg %% @copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %% @doc Module wrangling helper functions. -module(meck_code). %% Interface exports -export([abstract_code/1]). -export([add_exports/2]). -export([beam_file/1]). -export([compile_and_load_forms/1]). -export([compile_and_load_forms/2]). -export([compile_options/1]). -export([enable_on_load/2]). -export([rename_module/3]). %% Types -type erlang_form() :: term(). -type compile_options() :: [term()]. -type export() :: {atom(), byte()}. %%============================================================================= %% Interface exports %%============================================================================= -spec abstract_code(binary()) -> erlang_form(). abstract_code(BeamFile) -> case beam_lib:chunks(BeamFile, [abstract_code]) of {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> Forms; {ok, {_, [{abstract_code, no_abstract_code}]}} -> throw(no_abstract_code) end. -spec add_exports([export()], erlang_form()) -> erlang_form(). add_exports(Exports, AbsCode) -> {attribute, Line, export, OrigExports} = lists:keyfind(export, 3, AbsCode), Attr = {attribute, Line, export, OrigExports ++ Exports}, lists:keyreplace(export, 3, AbsCode, Attr). -spec beam_file(module()) -> binary(). beam_file(Module) -> % code:which/1 cannot be used for cover_compiled modules case code:get_object_code(Module) of {_, Binary, _Filename} -> Binary; error -> throw({object_code_not_found, Module}) end. -spec compile_and_load_forms(erlang_form()) -> binary(). compile_and_load_forms(AbsCode) -> compile_and_load_forms(AbsCode, []). -spec compile_and_load_forms(erlang_form(), compile_options()) -> binary(). compile_and_load_forms(AbsCode, Opts) -> case compile:forms(AbsCode, [return_errors|Opts]) of {ok, ModName, Binary} -> load_binary(ModName, Binary), Binary; {ok, ModName, Binary, _Warnings} -> load_binary(ModName, Binary), Binary; Error -> exit({compile_forms, Error}) end. -spec compile_options(binary() | module()) -> compile_options(). compile_options(BeamFile) when is_binary(BeamFile) -> case beam_lib:chunks(BeamFile, [compile_info]) of {ok, {_, [{compile_info, Info}]}} -> filter_options(proplists:get_value(options, Info)); _ -> [] end; compile_options(Module) -> filter_options(proplists:get_value(options, Module:module_info(compile))). enable_on_load(Forms, false) -> Map = fun({attribute,L,on_load,{F,A}}) -> {attribute,L,export,[{F,A}]}; (Other) -> Other end, lists:map(Map, Forms); enable_on_load(Forms, _) -> Forms. -spec rename_module(erlang_form(), module(), module()) -> erlang_form(). rename_module(Forms, Old, New) -> lists:map(fun(F) -> rename_module_in_form(F, Old, New) end, Forms). %%============================================================================= %% Internal functions %%============================================================================= load_binary(Name, Binary) -> case code:load_binary(Name, "", Binary) of {module, Name} -> ok; {error, Reason} -> exit({error_loading_module, Name, Reason}) end. % parse transforms have already been applied to the abstract code in the % module, and often are not always available when compiling the forms, so % filter them out of the options. % % Furthermore, since Erlang/OTP 20, a code may be compiled from core but % still have abstract code, so we make sure to remove the from_core option % as we always compile it as a form. % % The -MMD option (makedep_side_effect) needs to be removed, otherwise % the compiler will attempt to generate a dependency file. filter_options (Options) -> case Options of undefined -> []; _ -> lists:filter( fun({parse_transform,_}) -> false; (makedep_side_effect) -> false; (from_core) -> false; (_) -> true end, Options) end. rename_module_in_form({attribute, Line, AttrName, AttrData}, Old, New) -> {attribute, Line, AttrName, rename_module_in_attribute(AttrName, AttrData, Old, New) }; rename_module_in_form(Form, _Old, _New) -> Form. rename_module_in_attribute(module, Old, Old, New) -> New; rename_module_in_attribute(spec, {{Old, Fun, Arity}, Spec}, Old, New) -> {{New, Fun, Arity}, Spec}; rename_module_in_attribute(_AttrName, AttrData, _Old, _New) -> AttrData. meck-1.0.0/src/meck_proc.erl0000664000175000017500000007415214726775702016276 0ustar debalancedebalance%%%============================================================================ %%% 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. %%%============================================================================ %%% @hidden %%% @doc Implements a gen_server that maintains the state of a mocked module. %%% The state includes function stubs, call history, etc. Meck starts one such %%% process per mocked module. -module(meck_proc). -behaviour(gen_server). %% API -export([start/2]). -export([set_expect/2]). -export([delete_expect/4]). -export([list_expects/2]). -export([get_history/1]). -export([wait/6]). -export([reset/1]). -export([validate/1]). -export([stop/1]). %% To be accessible from generated modules -export([get_result_spec/3]). -export([add_history_exception/5]). -export([add_history/5]). -export([invalidate/1]). %% gen_server callbacks -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). -export([code_change/3]). %%%============================================================================ %%% Definitions %%%============================================================================ -type meck_dict() :: dict:dict(). -record(state, {mod :: atom(), can_expect :: any | [{Mod::atom(), Ari::byte()}], expects :: meck_dict(), valid = true :: boolean(), history = [] :: meck_history:history() | undefined, original :: term(), was_sticky = false :: boolean(), merge_expects = false :: boolean(), passthrough = false :: boolean(), reload :: {Compiler::pid(), {From::pid(), Tag::any()}} | undefined, trackers = [] :: [tracker()], restore = false :: boolean()}). -record(tracker, {opt_func :: '_' | atom(), args_matcher :: meck_args_matcher:args_matcher(), opt_caller_pid :: '_' | pid(), countdown :: non_neg_integer(), reply_to :: {Caller::pid(), Tag::any()}, expire_at :: erlang:timestamp()}). %%%============================================================================ %%% Types %%%============================================================================ -type tracker() :: #tracker{}. %%%============================================================================ %%% API %%%============================================================================ -spec start(Mod::atom(), Options::[proplists:property()]) -> ok | no_return(). start(Mod, Options) -> StartFunc = case proplists:is_defined(no_link, Options) of true -> start; false -> start_link end, SpawnOpt = proplists:get_value(spawn_opt, Options, []), case gen_server:StartFunc({local, meck_util:proc_name(Mod)}, ?MODULE, [Mod, Options], [{spawn_opt, SpawnOpt}]) of {ok, _Pid} -> ok; {error, Reason} -> erlang:error(Reason, [Mod, Options]) end. -spec get_result_spec(Mod::atom(), Func::atom(), Args::[any()]) -> meck_ret_spec:result_spec() | undefined. get_result_spec(Mod, Func, Args) -> gen_server(call, Mod, {get_result_spec, Func, Args}). -spec set_expect(Mod::atom(), meck_expect:expect()) -> ok | {error, Reason::any()}. set_expect(Mod, Expect) -> Proc = meck_util:proc_name(Mod), try gen_server:call(Proc, {set_expect, Expect}) catch exit:{noproc, _Details} -> Options = [Mod, [passthrough]], case gen_server:start({local, Proc}, ?MODULE, Options, []) of {ok, Pid} -> Result = gen_server:call(Proc, {set_expect, Expect}), true = erlang:link(Pid), Result; {error, {{undefined_module, Mod}, _StackTrace}} -> erlang:error({not_mocked, Mod}) end end. -spec delete_expect(Mod::atom(), Func::atom(), Ari::byte(), Force::boolean()) -> ok. delete_expect(Mod, Func, Ari, Force) -> gen_server(call, Mod, {delete_expect, Func, Ari, Force}). -spec list_expects(Mod::atom(), ExcludePassthrough::boolean()) -> [{Mod::atom(), Func::atom(), Ari::byte()}]. list_expects(Mod, ExcludePassthrough) -> gen_server(call, Mod, {list_expects, ExcludePassthrough}). -spec add_history_exception( Mod::atom(), CallerPid::pid(), Func::atom(), Args::[any()], {Class::error|exit|throw, Reason::any(), StackTrace::any()}) -> ok. add_history_exception(Mod, CallerPid, Func, Args, {Class, Reason, StackTrace}) -> gen_server(cast, Mod, {add_history, {CallerPid, {Mod, Func, Args}, Class, Reason, StackTrace}}). -spec add_history(Mod::atom(), CallerPid::pid(), Func::atom(), Args::[any()], Result::any()) -> ok. add_history(Mod, CallerPid, Func, Args, Result) -> gen_server(cast, Mod, {add_history, {CallerPid, {Mod, Func, Args}, Result}}). -spec get_history(Mod::atom()) -> meck_history:history(). get_history(Mod) -> gen_server(call, Mod, get_history). -spec wait(Mod::atom(), Times::non_neg_integer(), OptFunc::'_' | atom(), meck_args_matcher:args_matcher(), OptCallerPid::'_' | pid(), Timeout::non_neg_integer()) -> ok. wait(Mod, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout) -> EffectiveTimeout = case Timeout of 0 -> infinity; _Else -> Timeout end, Name = meck_util:proc_name(Mod), try gen_server:call(Name, {wait, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout}, EffectiveTimeout) of ok -> ok; {error, timeout} -> erlang:error(timeout) catch exit:{timeout, _Details} -> erlang:error(timeout); exit:_Reason -> erlang:error({not_mocked, Mod}) end. -spec reset(Mod::atom()) -> ok. reset(Mod) -> gen_server(call, Mod, reset). -spec validate(Mod::atom()) -> boolean(). validate(Mod) -> gen_server(call, Mod, validate). -spec invalidate(Mod::atom()) -> ok. invalidate(Mod) -> gen_server(cast, Mod, invalidate). -spec stop(Mod::atom()) -> ok. stop(Mod) -> %% To avoid timeout due to slow original restoration. gen_server:stop/1 %% would be better, but some tests are then tricky to fix. gen_server(call, Mod, stop, infinity). %%%============================================================================ %%% gen_server callbacks %%%============================================================================ %% @hidden validate_options([]) -> ok; validate_options([no_link|Options]) -> validate_options(Options); validate_options([spawn_opt|Options]) -> validate_options(Options); validate_options([unstick|Options]) -> validate_options(Options); validate_options([no_passthrough_cover|Options]) -> validate_options(Options); validate_options([merge_expects|Options]) -> validate_options(Options); validate_options([enable_on_load|Options]) -> validate_options(Options); validate_options([passthrough|Options]) -> validate_options(Options); validate_options([no_history|Options]) -> validate_options(Options); validate_options([non_strict|Options]) -> validate_options(Options); validate_options([stub_all|Options]) -> validate_options(Options); validate_options([{stub_all, _}|Options]) -> validate_options(Options); validate_options([UnknownOption|_]) -> erlang:error({bad_arg, UnknownOption}). %% @hidden init([Mod, Options]) -> validate_options(Options), Restore = code:is_loaded(Mod) =/= false, Exports = normal_exports(Mod), WasSticky = case proplists:get_bool(unstick, Options) of true -> {module, Mod} = code:ensure_loaded(Mod), unstick_original(Mod); _ -> false end, NoPassCover = proplists:get_bool(no_passthrough_cover, Options), MergeExpects = proplists:get_bool(merge_expects, Options), EnableOnLoad = proplists:get_bool(enable_on_load, Options), Passthrough = proplists:get_bool(passthrough, Options), Original = backup_original(Mod, Passthrough, NoPassCover, EnableOnLoad), NoHistory = proplists:get_bool(no_history, Options), History = if NoHistory -> undefined; true -> [] end, CanExpect = resolve_can_expect(Mod, Exports, Options), Expects = init_expects(Exports, Options), process_flag(trap_exit, true), try Forms = meck_code_gen:to_forms(Mod, Expects), _Bin = meck_code:compile_and_load_forms(Forms), {ok, #state{mod = Mod, can_expect = CanExpect, expects = Expects, original = Original, was_sticky = WasSticky, merge_expects = MergeExpects, passthrough = Passthrough, history = History, restore = Restore}} catch exit:{error_loading_module, Mod, sticky_directory} -> {stop, {module_is_sticky, Mod}} end. %% @hidden handle_call({get_result_spec, Func, Args}, _From, S) -> {ResultSpec, NewExpects} = do_get_result_spec(S#state.expects, Func, Args), {reply, ResultSpec, S#state{expects = NewExpects}}; handle_call({set_expect, Expect}, From, S = #state{mod = Mod, expects = Expects, passthrough = Passthrough, merge_expects = MergeExpects, can_expect = CanExpect}) -> check_if_being_reloaded(S), FuncAri = {Func, Ari} = meck_expect:func_ari(Expect), case validate_expect(Mod, Func, Ari, S#state.can_expect) of ok -> case store_expect(Mod, FuncAri, Expect, Expects, MergeExpects, Passthrough, CanExpect) of {no_compile, NewExpects} -> {reply, ok, S#state{expects = NewExpects}}; {CompilerPid, NewExpects} -> {noreply, S#state{expects = NewExpects, reload = {CompilerPid, From}}} end; {error, Reason} -> {reply, {error, Reason}, S} end; handle_call({delete_expect, Func, Ari, Force}, From, S = #state{mod = Mod, expects = Expects, passthrough = PassThrough, can_expect = CanExpect}) -> check_if_being_reloaded(S), ErasePassThrough = Force orelse (not PassThrough), case do_delete_expect(Mod, {Func, Ari}, Expects, ErasePassThrough, PassThrough, CanExpect) of {no_compile, NewExpects} -> {reply, ok, S#state{expects = NewExpects}}; {CompilerPid, NewExpects} -> {noreply, S#state{expects = NewExpects, reload = {CompilerPid, From}}} end; handle_call({list_expects, ExcludePassthrough}, _From, S = #state{mod = Mod, expects = Expects}) -> Result = case ExcludePassthrough of false -> [{Mod, Func, Ari} || {Func, Ari} <- dict:fetch_keys(Expects)]; true -> [{Mod, Func, Ari} || {{Func, Ari}, Expect} <- dict:to_list(Expects), not meck_expect:is_passthrough(Expect)] end, {reply, Result, S}; handle_call(get_history, _From, S = #state{history = undefined}) -> {reply, [], S}; handle_call(get_history, _From, S) -> {reply, lists:reverse(S#state.history), S}; handle_call({wait, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout}, From, S = #state{history = History, trackers = Trackers}) -> case times_called(OptFunc, ArgsMatcher, OptCallerPid, History) of CalledSoFar when CalledSoFar >= Times -> {reply, ok, S}; _CalledSoFar when Timeout =:= 0 -> {reply, {error, timeout}, S}; CalledSoFar -> Tracker = #tracker{opt_func = OptFunc, args_matcher = ArgsMatcher, opt_caller_pid = OptCallerPid, countdown = Times - CalledSoFar, reply_to = From, expire_at = timeout_to_timestamp(Timeout)}, {noreply, S#state{trackers = [Tracker | Trackers]}} end; handle_call(reset, _From, S) -> {reply, ok, S#state{history = []}}; handle_call(validate, _From, S) -> {reply, S#state.valid, S}; handle_call(stop, _From, S) -> {stop, normal, ok, S}. %% @hidden handle_cast(invalidate, S) -> {noreply, S#state{valid = false}}; handle_cast({add_history, HistoryRecord}, S = #state{history = undefined, trackers = Trackers}) -> UpdTrackers = update_trackers(HistoryRecord, Trackers), {noreply, S#state{trackers = UpdTrackers}}; handle_cast({add_history, HistoryRecord}, S = #state{history = History, trackers = Trackers}) -> UpdTrackers = update_trackers(HistoryRecord, Trackers), {noreply, S#state{history = [HistoryRecord | History], trackers = UpdTrackers}}; handle_cast(_Msg, S) -> {noreply, S}. %% @hidden handle_info({'EXIT', Pid, _Reason}, S = #state{reload = Reload}) -> case Reload of {Pid, From} -> gen_server:reply(From, ok), {noreply, S#state{reload = undefined}}; _ -> {noreply, S} end; handle_info(_Info, S) -> {noreply, S}. %% @hidden terminate(_Reason, #state{mod = Mod, original = OriginalState, was_sticky = WasSticky, restore = Restore}) -> BackupCover = export_original_cover(Mod, OriginalState), cleanup(Mod), restore_original(Mod, OriginalState, WasSticky, BackupCover), case Restore andalso false =:= code:is_loaded(Mod) of true -> % We make a best effort to reload the module here. Since this runs % in a terminating process there is nothing we can do to recover if % the loading fails. _ = code:load_file(Mod), ok; _ -> ok end. %% @hidden code_change(_OldVsn, S, _Extra) -> {ok, S}. %%%============================================================================ %%% Internal functions %%%============================================================================ -spec normal_exports(Mod::atom()) -> [meck_expect:func_ari()] | undefined. normal_exports(Mod) -> try [FuncAri || FuncAri = {Func, Ari} <- Mod:module_info(exports), normal == expect_type(Mod, Func, Ari)] catch error:undef -> undefined end. -spec expect_type(Mod::atom(), Func::atom(), Ari::byte()) -> autogenerated | builtin | normal. expect_type(_, module_info, 0) -> autogenerated; expect_type(_, module_info, 1) -> autogenerated; expect_type(Mod, Func, Ari) -> case erlang:is_builtin(Mod, Func, Ari) of true -> builtin; false -> normal end. -spec backup_original(Mod::atom(), Passthrough::boolean(), NoPassCover::boolean(), EnableOnLoad::boolean()) -> {Cover:: false | {File::string(), Data::string(), CompiledOptions::[any()]}, Binary:: no_binary | no_passthrough_cover | binary()}. backup_original(Mod, Passthrough, NoPassCover, EnableOnLoad) -> Cover = get_cover_state(Mod), try Forms0 = meck_code:abstract_code(meck_code:beam_file(Mod)), Forms = meck_code:enable_on_load(Forms0, EnableOnLoad), NewName = meck_util:original_name(Mod), CompileOpts = [debug_info | meck_code:compile_options(meck_code:beam_file(Mod))], Renamed = meck_code:rename_module(Forms, Mod, NewName), Binary = meck_code:compile_and_load_forms(Renamed, CompileOpts), %% At this point we care about `Binary' if and only if we want %% to recompile it to enable cover on the original module code %% so that we can still collect cover stats on functions that %% have not been mocked. Below are the different values %% passed back along with `Cover'. %% %% `no_passthrough_cover' - there is no coverage on the %% original module OR passthrough coverage has been disabled %% via the `no_passthrough_cover' option %% %% `no_binary' - something went wrong while trying to compile %% the original module in `backup_original' %% %% Binary - a `binary()' of the compiled code for the original %% module that is being mocked, this needs to be passed around %% so that it can be passed to Cover later. There is no way %% to use the code server to access this binary without first %% saving it to disk. Instead, it's passed around as state. Binary2 = if (Cover == false) orelse NoPassCover -> no_passthrough_cover; true -> _ = meck_cover:compile_beam(NewName, Binary), Binary end, {Cover, Binary2} catch throw:{object_code_not_found, _Module} -> {Cover, no_binary}; % TODO: What to do here? throw:no_abstract_code -> case Passthrough of true -> exit({abstract_code_not_found, Mod}); false -> {Cover, no_binary} end end. -spec get_cover_state(Mod::atom()) -> {File::string(), Data::string(), CompileOptions::[any()]} | false. get_cover_state(Mod) -> case cover:is_compiled(Mod) of {file, File} -> OriginalCover = meck_cover:dump_coverdata(Mod), CompileOptions = try meck_code:compile_options(meck_code:beam_file(Mod)) catch throw:{object_code_not_found, _Module} -> [] end, {File, OriginalCover, CompileOptions}; _ -> false end. -spec resolve_can_expect(Mod::atom(), Exports::[meck_expect:func_ari()] | undefined, Options::[proplists:property()]) -> any | [meck_expect:func_ari()]. resolve_can_expect(Mod, Exports, Options) -> NonStrict = proplists:get_bool(non_strict, Options), case {Exports, NonStrict} of {_, true} -> any; {undefined, _} -> erlang:error({undefined_module, Mod}); _ -> Exports end. -spec init_expects(Exports::[meck_expect:func_ari()] | undefined, Options::[proplists:property()]) -> meck_dict(). init_expects(Exports, Options) -> Passthrough = proplists:get_bool(passthrough, Options), StubAll = proplists:is_defined(stub_all, Options), Expects = case Exports of undefined -> []; Exports when Passthrough -> [meck_expect:new_passthrough(FuncArity) || FuncArity <- Exports]; Exports when StubAll -> StubRet = case lists:keyfind(stub_all, 1, Options) of {stub_all, RetSpec} -> RetSpec; _ -> meck:val(ok) end, [meck_expect:new_dummy(FuncArity, StubRet) || FuncArity <- Exports]; Exports -> [] end, lists:foldl(fun(Expect, D) -> dict:store(meck_expect:func_ari(Expect), Expect, D) end, dict:new(), Expects). -spec gen_server(Method:: call, Mod::atom(), Msg :: stop, timeout()) -> any(). gen_server(call, Mod, stop, infinity) -> Name = meck_util:proc_name(Mod), try gen_server:call(Name, stop, infinity) catch exit:_Reason -> erlang:error({not_mocked, Mod}) end. -spec gen_server(Method:: call | cast, Mod::atom(), Msg::tuple() | atom()) -> any(). gen_server(Func, Mod, Msg) -> Name = meck_util:proc_name(Mod), try gen_server:Func(Name, Msg) catch exit:_Reason -> erlang:error({not_mocked, Mod}) end. -spec check_if_being_reloaded(#state{}) -> ok. check_if_being_reloaded(#state{reload = undefined}) -> ok; check_if_being_reloaded(#state{passthrough = true}) -> ok; check_if_being_reloaded(_S) -> erlang:error(concurrent_reload). -spec do_get_result_spec(Expects::meck_dict(), Func::atom(), Args::[any()]) -> {meck_ret_spec:result_spec() | undefined, NewExpects::meck_dict()}. do_get_result_spec(Expects, Func, Args) -> FuncAri = {Func, erlang:length(Args)}, Expect = dict:fetch(FuncAri, Expects), {ResultSpec, NewExpect} = meck_expect:fetch_result(Args, Expect), NewExpects = case NewExpect of unchanged -> Expects; _ -> dict:store(FuncAri, NewExpect, Expects) end, {ResultSpec, NewExpects}. -spec validate_expect(Mod::atom(), Func::atom(), Ari::byte(), CanExpect::any | [meck_expect:func_ari()]) -> ok | {error, Reason::any()}. validate_expect(Mod, Func, Ari, CanExpect) -> case expect_type(Mod, Func, Ari) of autogenerated -> {error, {cannot_mock_autogenerated, {Mod, Func, Ari}}}; builtin -> {error, {cannot_mock_builtin, {Mod, Func, Ari}}}; normal -> case CanExpect =:= any orelse lists:member({Func, Ari}, CanExpect) of true -> ok; _ -> {error, {undefined_function, {Mod, Func, Ari}}} end end. -spec store_expect(Mod::atom(), meck_expect:func_ari(), meck_expect:expect(), Expects::meck_dict(), MergeExpects::boolean(), Passthrough::boolean(), CanExpect::term()) -> {CompilerPidOrNoCompile::no_compile | pid(), NewExpects::meck_dict()}. store_expect(Mod, FuncAri, Expect, Expects, true, PassThrough, CanExpect) -> NewExpects = case dict:is_key(FuncAri, Expects) of true -> {FuncAri, ExistingClauses} = dict:fetch(FuncAri, Expects), {FuncAri, NewClauses} = Expect, ToStore = case PassThrough of false -> ExistingClauses ++ NewClauses; true -> RevExistingClauses = lists:reverse(ExistingClauses), [PassthroughClause | OldClauses] = RevExistingClauses, lists:reverse(OldClauses, NewClauses ++ [PassthroughClause]) end, dict:store(FuncAri, {FuncAri, ToStore}, Expects); false -> dict:store(FuncAri, Expect, Expects) end, {compile_expects_if_needed(Mod, NewExpects, PassThrough, CanExpect), NewExpects}; store_expect(Mod, FuncAri, Expect, Expects, false, PassThrough, CanExpect) -> NewExpects = dict:store(FuncAri, Expect, Expects), {compile_expects_if_needed(Mod, NewExpects, PassThrough, CanExpect), NewExpects}. -spec do_delete_expect(Mod::atom(), meck_expect:func_ari(), Expects::meck_dict(), ErasePassThrough::boolean(), Passthrough::boolean(), CanExpect::term()) -> {CompilerPid::no_compile | pid(), NewExpects::meck_dict()}. do_delete_expect(Mod, FuncAri, Expects, ErasePassThrough, Passthrough, CanExpect) -> NewExpects = case ErasePassThrough of true -> dict:erase(FuncAri, Expects); false -> dict:store(FuncAri, meck_expect:new_passthrough(FuncAri), Expects) end, {compile_expects_if_needed(Mod, NewExpects, Passthrough, CanExpect), NewExpects}. -spec compile_expects_if_needed(Mod::atom(), Expects::meck_dict(), Passthrough::boolean(), CanExpect::term()) -> CompilerPidOrNoCompile::no_compile | pid(). compile_expects_if_needed(_Mod, _Expects, true, CanExpect) when CanExpect =/= any -> no_compile; compile_expects_if_needed(Mod, Expects, _, _) -> compile_expects(Mod, Expects). -spec compile_expects(Mod::atom(), Expects::meck_dict()) -> CompilerPid::pid(). compile_expects(Mod, Expects) -> %% If the recompilation is made by the server that executes a module %% no module that is called from meck_code:compile_and_load_forms/2 %% can be mocked by meck. erlang:spawn_link(fun() -> Forms = meck_code_gen:to_forms(Mod, Expects), meck_code:compile_and_load_forms(Forms) end). restore_original(Mod, {false, _Bin}, WasSticky, _BackupCover) -> restick_original(Mod, WasSticky), ok; restore_original(Mod, {{File, OriginalCover, Options}, _Bin}, WasSticky, BackupCover) -> {ok, Mod} = case filename:extension(File) of ".erl" -> cover:compile_module(File, Options); ".beam" -> cover:compile_beam(File) end, restick_original(Mod, WasSticky), if BackupCover =/= undefined -> %% Import the cover data for `_meck_original' but since it was %% modified by `export_original_cover' it will count towards `'. ok = cover:import(BackupCover), ok = file:delete(BackupCover); true -> ok end, ok = cover:import(OriginalCover), ok = file:delete(OriginalCover), ok. %% @doc Export the cover data for `_meck_original' and modify %% the data so it can be imported under `'. export_original_cover(Mod, {_, Bin}) when is_binary(Bin) -> OriginalMod = meck_util:original_name(Mod), BackupCover = meck_cover:dump_coverdata(OriginalMod), ok = meck_cover:rename_module(BackupCover, Mod), BackupCover; export_original_cover(_, _) -> undefined. unstick_original(Module) -> unstick_original(Module, code:is_sticky(Module)). unstick_original(Module, true) -> code:unstick_mod(Module); unstick_original(_,_) -> false. restick_original(Module, true) -> code:stick_mod(Module), {module, Module} = code:ensure_loaded(Module), ok; restick_original(_,_) -> ok. -spec cleanup(Mod::atom()) -> boolean(). cleanup(Mod) -> code:purge(Mod), code:delete(Mod), code:purge(meck_util:original_name(Mod)), Res = code:delete(meck_util:original_name(Mod)), % `cover:export` might still export the meck generated module, % make sure that does not happen. _ = cover:reset(meck_util:original_name(Mod)), Res. -spec times_called(OptFunc::'_' | atom(), meck_args_matcher:args_matcher(), OptCallerPid::'_' | pid(), meck_history:history()) -> non_neg_integer(). times_called(OptFunc, ArgsMatcher, OptCallerPid, History) -> Filter = meck_history:new_filter(OptCallerPid, OptFunc, ArgsMatcher), lists:foldl(fun(HistoryRec, Acc) -> case Filter(HistoryRec) of true -> Acc + 1; _Else -> Acc end end, 0, History). -spec update_trackers(meck_history:history_record(), [tracker()]) -> UpdTracker::[tracker()]. update_trackers(HistoryRecord, Trackers) -> update_trackers(HistoryRecord, Trackers, []). -spec update_trackers(meck_history:history_record(), Trackers::[tracker()], CheckedSoFar::[tracker()]) -> UpdTrackers::[tracker()]. update_trackers(_HistoryRecord, [], UpdatedSoFar) -> UpdatedSoFar; update_trackers(HistoryRecord, [Tracker | Rest], UpdatedSoFar) -> CallerPid = erlang:element(1, HistoryRecord), {_Mod, Func, Args} = erlang:element(2, HistoryRecord), case update_tracker(Func, Args, CallerPid, Tracker) of expired -> update_trackers(HistoryRecord, Rest, UpdatedSoFar); UpdTracker -> update_trackers(HistoryRecord, Rest, [UpdTracker | UpdatedSoFar]) end. -spec update_tracker(Func::atom(), Args::[any()], Caller::pid(), tracker()) -> expired | (UpdTracker::tracker()). update_tracker(Func, Args, CallerPid, #tracker{opt_func = OptFunc, args_matcher = ArgsMatcher, opt_caller_pid = OptCallerPid, countdown = Countdown, reply_to = ReplyTo, expire_at = ExpireAt} = Tracker) when (OptFunc =:= '_' orelse Func =:= OptFunc) andalso (OptCallerPid =:= '_' orelse CallerPid =:= OptCallerPid) -> case meck_args_matcher:match(Args, ArgsMatcher) of false -> Tracker; true -> case is_expired(ExpireAt) of true -> expired; false when Countdown == 1 -> gen_server:reply(ReplyTo, ok), expired; false -> Tracker#tracker{countdown = Countdown - 1} end end; update_tracker(_Func, _Args, _CallerPid, Tracker) -> Tracker. -spec timeout_to_timestamp(Timeout::non_neg_integer()) -> erlang:timestamp(). timeout_to_timestamp(Timeout) -> {MacroSecs, Secs, MicroSecs} = os:timestamp(), MicroSecs2 = MicroSecs + Timeout * 1000, UpdMicroSecs = MicroSecs2 rem 1000000, Secs2 = Secs + MicroSecs2 div 1000000, UpdSecs = Secs2 rem 1000000, UpdMacroSecs = MacroSecs + Secs2 div 1000000, {UpdMacroSecs, UpdSecs, UpdMicroSecs}. -spec is_expired(erlang:timestamp()) -> boolean(). is_expired({MacroSecs, Secs, MicroSecs}) -> {NowMacroSecs, NowSecs, NowMicroSecs} = os:timestamp(), ((NowMacroSecs > MacroSecs) orelse (NowMacroSecs == MacroSecs andalso NowSecs > Secs) orelse (NowMacroSecs == MacroSecs andalso NowSecs == Secs andalso NowMicroSecs > MicroSecs)). meck-1.0.0/src/meck_cover.erl0000664000175000017500000001275214726775702016447 0ustar debalancedebalance%%============================================================================= %% 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 Module containing functions needed by meck to integrate with cover. -module(meck_cover). %% Interface exports -export([compile_beam/2]). -export([rename_module/2]). -export([dump_coverdata/1]). -ignore_xref({cover, compile_beams, 1}). -ignore_xref({cover, compile_beam, 2}). -ignore_xref({cover, get_term, 1}). -ignore_xref({cover, write, 2}). %%============================================================================= %% Interface exports %%============================================================================= %% @doc Enabled cover on `_meck_original'. compile_beam(OriginalMod, Bin) -> CompileBeams = alter_cover(), [{ok, _}] = CompileBeams([{OriginalMod, Bin}]). %% @doc Given a cover file `File' exported by `cover:export' overwrite %% the module name with `Name'. rename_module(File, Name) -> NewTerms = change_cover_mod_name(read_cover_file(File), Name), write_terms(File, NewTerms), ok. %% @doc Dump cover data for `Mod' into a .coverdata file in the current %% directory. Return the absolute path to the backup file. dump_coverdata(Mod) -> {ok, CWD} = file:get_cwd(), File = lists:concat([Mod, ".", os:getpid(), ".coverdata"]), Path = filename:join(CWD, File), ok = cover:export(Path, Mod), Path. %%============================================================================= %% Internal functions %%============================================================================= %% @private %% %% @doc Alter the cover BEAM module to export some of it's private %% functions. This is done for two reasons: %% %% 1. Meck needs to alter the export analysis data on disk and %% therefore needs to understand this format. This is why `get_term' %% and `write' are exposed. %% %% 2. In order to avoid creating temporary files meck needs direct %% access to `compile_beam/2' which allows passing a binary. %% In OTP 18.0 the internal API of cover changed a bit and %% compile_beam/2 was replaced by compile_beams/1. -dialyzer({no_missing_calls, alter_cover/0}). % for cover:compile_beams/1 alter_cover() -> CoverExports = cover:module_info(exports), case {lists:member({compile_beams,1}, CoverExports), lists:member({compile_beam,2}, CoverExports)} of {true, _} -> fun cover:compile_beams/1; {_, true} -> fun compile_beam_wrapper/1; {false, false} -> Beam = meck_code:beam_file(cover), AbsCode = meck_code:abstract_code(Beam), {Exports, CompileBeams} = case lists:member({analyse,0}, CoverExports) of true -> %% new API from OTP 18.0 on {[{compile_beams, 1}, {get_term, 1}, {write, 2}], fun cover:compile_beams/1}; false -> {[{compile_beam, 2}, {get_term, 1}, {write, 2}], fun compile_beam_wrapper/1} end, AbsCode2 = meck_code:add_exports(Exports, AbsCode), _Bin = meck_code:compile_and_load_forms(AbsCode2), CompileBeams end. %% wrap cover's pre-18.0 internal API to simulate the new API -dialyzer({no_missing_calls, compile_beam_wrapper/1}). % for cover:compile_beam/2 compile_beam_wrapper(ModFiles) -> [cover:compile_beam(Mod, Bin)||{Mod, Bin} <- ModFiles]. change_cover_mod_name(CoverTerms, Name) -> {_, Terms} = lists:foldl(fun change_name_in_term/2, {Name,[]}, CoverTerms), Terms. change_name_in_term({file, Mod, File}, {Name, Terms}) -> Term2 = {file, Name, replace_string(File, Mod, Name)}, {Name, [Term2|Terms]}; change_name_in_term({Bump={bump,_,_,_,_,_},_}=Term, {Name, Terms}) -> Bump2 = setelement(2, Bump, Name), Term2 = setelement(1, Term, Bump2), {Name, [Term2|Terms]}; change_name_in_term({_Mod,Clauses}, {Name, Terms}) -> Clauses2 = lists:foldl(fun change_name_in_clause/2, {Name, []}, Clauses), Term2 = {Name, Clauses2}, {Name, [Term2|Terms]}. change_name_in_clause(Clause, {Name, NewClauses}) -> {Name, [setelement(1, Clause, Name)|NewClauses]}. replace_string(File, Old, New) -> Old2 = atom_to_list(Old), New2 = atom_to_list(New), re:replace(File, Old2, New2, [{return, list}]). read_cover_file(File) -> {ok, Fd} = file:open(File, [read, binary, raw]), Terms = get_terms(Fd, []), ok = file:close(Fd), Terms. -dialyzer({no_missing_calls, get_terms/2}). % for cover:get_term/1 get_terms(Fd, Terms) -> case cover:get_term(Fd) of eof -> Terms; Term -> get_terms(Fd, [Term|Terms]) end. write_terms(File, Terms) -> {ok, Fd} = file:open(File, [write, binary, raw]), lists:foreach(write_term(Fd), Terms), ok. -dialyzer({no_missing_calls, write_term/1}). % for cover:write/2 write_term(Fd) -> fun(Term) -> cover:write(Term, Fd) end. meck-1.0.0/src/meck.app.src0000664000175000017500000000063214726775702016027 0ustar debalancedebalance{application, meck, [ {description, "A mocking framework for Erlang"}, {vsn, git}, {modules, []}, {registered, []}, {applications, [kernel, stdlib, tools, compiler]}, {env, []}, % Hex.pm {licenses, ["Apache 2.0"]}, {links, [ {"Changelog", "https://github.com/eproxus/meck/blob/master/CHANGELOG.md"}, {"GitHub", "https://github.com/eproxus/meck"} ]} ]}. meck-1.0.0/src/meck_code_gen.erl0000664000175000017500000002030314726775702017063 0ustar debalancedebalance%%%============================================================================ %%% 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]). -include("meck.hrl"). %%%============================================================================ %%% 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 %%%============================================================================ attribute({Key, _Value}, Attrs) when Key =:= vsn; Key =:= deprecated; Key =:= optional_callbacks; Key =:= dialyzer -> Attrs; attribute({Key, Value}, Attrs) when (Key =:= behaviour orelse Key =:= behavior) andalso is_list(Value) -> lists:foldl(fun(Behavior, Acc) -> [?attribute(Key, Behavior) | Acc] end, Attrs, Value); attribute({Key, Value}, Attrs) -> [?attribute(Key, Value) | Attrs]. attributes(Mod) -> try lists:foldl(fun attribute/2, [], proplists:get_value(attributes, Mod:module_info(), [])) 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 -> eval(Pid, Mod, Func, Args, ResultSpec) catch error:{not_mocked, Mod} -> apply(Mod, Func, Args) end. -spec eval(Pid::pid(), Mod::atom(), Func::atom(), Args::[any()], ResultSpec::any()) -> Result::any() | no_return(). eval(Pid, Mod, Func, Args, ResultSpec) -> PreviousCall = get(?CURRENT_CALL), 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 ?_exception_(Class, Reason, StackToken) -> handle_exception(Pid, Mod, Func, Args, Class, Reason, ?_get_stacktrace_(StackToken)) after put(?CURRENT_CALL, PreviousCall) end. -spec handle_exception(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()], Class:: exit | error | throw, Reason::any(), Stack::meck_history:stack_trace()) -> no_return(). handle_exception(Pid, Mod, Func, Args, Class, Reason, Stack) -> case meck_ret_spec:is_meck_exception(Reason) of {true, MockedClass, MockedReason} -> raise(Pid, Mod, Func, Args, MockedClass, MockedReason, Stack); _ -> meck_proc:invalidate(Mod), raise(Pid, Mod, Func, Args, Class, Reason, Stack) end. -spec raise(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()], Class:: exit | error | throw, Reason::any(), Stack::meck_history:stack_trace()) -> no_return(). raise(Pid, Mod, Func, Args, Class, Reason, Stack) -> StackTrace = inject(Mod, Func, Args, Stack), meck_proc:add_history_exception(Mod, Pid, Func, Args, {Class, Reason, StackTrace}), erlang:raise(Class, Reason, StackTrace). -dialyzer({no_match, inject/4}). % for meck_history:stack_trace in older Erlang/OTP versions -spec inject(Mod::atom(), Func::atom(), Args::[any()], meck_history:stack_trace()) -> NewStackTrace::meck_history:stack_trace(). inject(Mod, Func, Args, []) -> [{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)]. meck-1.0.0/src/meck.erl0000664000175000017500000007434214726775702015254 0ustar debalancedebalance%%%============================================================================ %%% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %%% %%% 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. %%%============================================================================ %%% @author Adam Lindberg %%% @copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %%% @doc Module mocking library for Erlang. -module(meck). %% API -export_type([matcher/0]). -export_type([args_spec/0]). -export_type([ret_spec/0]). -export_type([func_clause_spec/0]). %% Interface exports -export([new/1]). -export([new/2]). -export([expect/3]). -export([expect/4]). -export([sequence/4]). -export([loop/4]). -export([delete/3]). -export([delete/4]). -export([expects/1]). -export([expects/2]). -export([exception/2]). -export([passthrough/1]). -export([history/1]). -export([history/2]). -export([validate/1]). -export([unload/0]). -export([unload/1]). -export([called/3]). -export([called/4]). -export([num_calls/3]). -export([num_calls/4]). -export([reset/1]). -export([capture/5]). -export([capture/6]). -export([wait/4]). -export([wait/5]). -export([wait/6]). -export([mocked/0]). %% Syntactic sugar -export([loop/1]). -export([seq/1]). -export([val/1]). -export([raise/2]). -export([passthrough/0]). -export([exec/1]). -export([is/1]). %%%============================================================================ %%% Types %%%============================================================================ -type meck_mfa() :: {Mod::atom(), Func::atom(), Args::[any()]}. %% Module, function and arguments that the mock module got called with. -type stack_trace() :: [{Mod::atom(), Func::atom(), AriOrArgs::byte()|[any()]} | {Mod::atom(), Func::atom(), AriOrArgs::byte()|[any()], Location::[{atom(), any()}]}]. %% Erlang stack trace. -type history() :: [{CallerPid::pid(), meck_mfa(), Result::any()} | {CallerPid::pid(), meck_mfa(), Class::throw|error|exit, Reason::any(), stack_trace()}]. %% Represents a list of either successful function calls with a returned %% result or function calls that resulted in an exception with a type, %% reason and a stack trace. Each tuple begins with the pid of the process %% that made the call to the function. -opaque matcher() :: meck_matcher:matcher(). %% Matcher is an entity that is used to check that a particular value meets %% some criteria. They are used in defining expectation where Erlang patterns %% are not enough. E.g. to check that a numeric value is within bounds. %% Instances of `matcher' can be created by {@link is/1} function from either a %% predicate function or a hamcrest matcher. (see {@link is/1} for details). %% An instance of this type may be specified in any or even all positions of an %% {@link arg_spec()}. -type args_spec() :: [any() | '_' | matcher()] | non_neg_integer(). %% Argument specification is used to specify argument patterns throughout Meck. %% In particular it is used in definition of expectation clauses by %% {@link expect/3}, {@link expect/4}, and by history digging functions %% {@link num_called/3}, {@link called/3} to specify what arguments of a %% function call of interest should look like. %% %% An argument specification can be given as a argument pattern list or %% as a non-negative integer that represents function clause/call arity. %% %% If an argument specification is given as an argument pattern, then every %% pattern element corresponds to a function argument at the respective %% position. '_' is a wildcard that matches any value. In fact you can specify %% atom wildcard '_' at any level in the value structure. %% (E.g.: {1, [blah, {'_', "bar", 2} | '_'], 3}). It is also possible to use a %% {@link matcher()} created by {@link is/1} in-place of a value pattern. %% %% If an argument specification is given by an arity, then it is equivalent to %% a pattern based argument specification that consists solely of wildcards, %% and has the length of arity (e.g.: 3 is equivalent to ['_', '_', '_']). -type 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}, %% {@link val/1}, {@link exec/1}, and {@link raise/2} functions. They are used %% to specify return values in {@link expect/3} and {@link expect/4} functions, %% and also as a parameter of the `stub_all' option of {@link new/2} function. %% %% Note that any Erlang term `X' is a valid `ret_spec()' equivalent to %% `meck:val(X)'. -type func_clause_spec() :: {args_spec(), ret_spec()}. %% It is used in {@link expect/3} and {@link expect/4} to define a function %% clause of complex multi-clause expectations. %%%============================================================================ %%% Interface exports %%%============================================================================ %% @equiv new(Mod, []) -spec new(Mods) -> ok when Mods :: Mod | [Mod], Mod :: atom(). new(Mod) when is_atom(Mod) -> new(Mod, []); new(Mod) when is_list(Mod) -> lists:foreach(fun new/1, Mod), ok. %% @doc Creates new mocked module(s). %% %% This replaces the current version (if any) of the modules in `Mod' %% with an empty module. %% %% Since this library is intended to use from test code, this %% function links a process for each mock to the calling process. %% %% The valid options are: %%
%%
`passthrough'
%%
Retains the original functions, if not mocked by meck. If used along %% with `stub_all' then `stub_all' is ignored.
%% %%
`no_link'
%%
Does not link the meck process to the caller process (needed for using %% meck in rpc calls).
%% %%
`unstick'
%%
Unstick the module to be mocked (e.g. needed for using meck with %% kernel and stdlib modules).
%% %%
`no_passthrough_cover'
%%
If cover is enabled on the module to be mocked then meck will continue %% to capture coverage on passthrough calls. This option allows you to %% disable that feature if it causes problems.
%% %%
`{spawn_opt, list()}'
%%
Specify Erlang process spawn options. Typically used to specify %% non-default, garbage collection options.
%% %%
`no_history'
%%
Do not store history of meck calls.
%% %%
`non_strict'
%%
A mock created with this option will allow setting expectations on %% functions that does not exist in the mocked module. With this %% option on it is even possible to mock non existing modules.
%% %%
`{stub_all, '{@link ret_spec()}`}'
%%
Stubs all functions exported from the mocked module. The stubs will %% return whatever defined by {@link ret_spec()} regardless of arguments %% passed in. It is possible to specify this option as just `stub_all' %% then stubs will return atom `ok'. If used along with `passthrough' %% then `stub_all' is ignored.
%% %%
`merge_expects'
%%
The expectations for the function/arity signature are merged with %% existing ones instead of replacing all of them each time an %% expectation is added. Expectations are added to the end of the %% function clause list, meaning that pattern matching will be performed %% in the order the expectations were added.
%%
%% %% Possible exceptions: %%
%%
`error:{undefined_module, Mod}'
%%
The module to be mocked does not exist. This error exists to prevent %% mocking of misspelled module names. To bypass this and create a new %% mocked module anyway, use the option `non_strict'.
%%
`error:{module_is_sticky, Mod}'
%%
The module to be mocked resides in a sticky directory. To unstick the %% module and mock it anyway, use the option `unstick'.
%%
`error:{abstract_code_not_found, Mod}'
%%
The option `passthrough' was used but the original module has no %% abstract code which can be called. Make sure the module is compiled %% with the compiler option `debug_info'.
%%
-spec new(Mods, Options) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Options :: [proplists:property()]. new(Mod, Options) when is_atom(Mod), is_list(Options) -> meck_proc:start(Mod, Options); new(Mod, Options) when is_list(Mod) -> lists:foreach(fun(M) -> new(M, Options) end, Mod), ok. %% @doc Add expectation for a function `Func' to the mocked modules `Mod'. %% %% An expectation is either of the following: %%
%%
`function()'
a stub function that is executed whenever the %% function `Func' is called. The arity of `function()' identifies for which %% particular `Func' variant an expectation is created for (that is, a function %% with arity 2 will generate an expectation for `Func/2').
%%
`['{@link func_clause_spec()}`]'
a list of {@link %% arg_spec()}/{@link ret_spec()} pairs. Whenever the function `Func' is called %% the arguments are matched against the {@link arg_spec()} in the list. As %% soon as the first match is found then a value defined by the corresponding %% {@link ret_spec()} is returned.
%%
%% %% It affects the validation status of the mocked module(s). If an %% expectation is called with the wrong number of arguments or invalid %% arguments the mock module(s) is invalidated. It is also invalidated if %% an unexpected exception occurs. -spec expect(Mods, Func, Expectation) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Expectation :: function() | [func_clause_spec()]. expect(Mod, Func, Expectation) when is_list(Mod) -> lists:foreach(fun(M) -> expect(M, Func, Expectation) end, Mod), ok; expect(_Mod, _Func, []) -> erlang:error(empty_clause_list); expect(Mod, Func, Expectation) when is_atom(Mod), is_atom(Func) -> Expect = meck_expect:new(Func, Expectation), check_expect_result(meck_proc:set_expect(Mod, Expect)). %% @doc Adds an expectation with the supplied arity and return value. %% %% This creates an expectation that has the only clause {`ArgsSpec', `RetSpec'}. %% %% @equiv expect(Mod, Func, [{ArgsSpec, RetSpec}]) -spec expect(Mods, Func, ArgsSpec, RetSpec) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), ArgsSpec :: args_spec(), RetSpec :: ret_spec(). expect(Mod, Func, ArgsSpec, RetSpec) when is_list(Mod) -> lists:foreach(fun(M) -> expect(M, Func, ArgsSpec, RetSpec) end, Mod), ok; expect(Mod, Func, ArgsSpec, RetSpec) when is_atom(Mod), is_atom(Func) -> Expect = meck_expect:new(Func, ArgsSpec, RetSpec), check_expect_result(meck_proc:set_expect(Mod, Expect)). %% @equiv expect(Mod, Func, Ari, seq(Sequence)) %% @deprecated Please use {@link expect/3} or {@link expect/4} along with %% {@link ret_spec()} generated by {@link seq/1}. -spec sequence(Mods, Func, Ari, Sequence) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Ari :: byte(), 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, 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), ok. %% @equiv expect(Mod, Func, Ari, loop(Loop)) %% @deprecated Please use {@link expect/3} or {@link expect/4} along with %% {@link ret_spec()} generated by {@link loop/1}. -spec loop(Mods, Func, Ari, Loop) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Ari :: byte(), 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, 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), ok. %% @doc Deletes an expectation. %% %% Deletes the expectation for the function `Func' with the matching %% arity `Arity'. %% `Force' is a flag to delete the function even if it is passthrough. -spec delete(Mods, Func, Ari, Force) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Ari :: byte(), Force :: boolean(). delete(Mod, Func, Ari, Force) when is_atom(Mod), is_atom(Func), Ari >= 0 -> meck_proc:delete_expect(Mod, Func, Ari, Force); delete(Mod, Func, Ari, Force) when is_list(Mod) -> lists:foreach(fun(M) -> delete(M, Func, Ari, Force) end, Mod), ok. %% @doc Deletes an expectation. %% %% Deletes the expectation for the function `Func' with the matching %% arity `Arity'. %% If the mock has passthrough enabled, this function restores the %% expectation to the original function. See {@link delete/4}. -spec delete(Mods, Func, Ari) -> ok when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Ari :: byte(). delete(Mod, Func, Ari) -> delete(Mod, Func, Ari, false). %% @doc Returns the list of expectations. %% %% Returns the list of MFAs that were replaced by expectations %% If `ExcludePassthrough' is on, only expectations that are not %% direct passthroughs are returned -spec expects(Mods, ExcludePassthrough) -> [{Mod, Func, Ari}] when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Ari :: byte(), ExcludePassthrough :: boolean(). expects(Mod, ExcludePassthrough) when is_atom(Mod) -> meck_proc:list_expects(Mod, ExcludePassthrough); expects(Mods, ExcludePassthrough) when is_list(Mods) -> [Expect || Mod <- Mods, Expect <- expects(Mod, ExcludePassthrough)]. %% @doc Returns the list of expectations. %% %% Returns the list of MFAs that were replaced by expectations -spec expects(Mods) -> [{Mod, Func, Ari}] when Mods :: Mod | [Mod], Mod :: atom(), Func :: atom(), Ari :: byte(). expects(Mod) -> expects(Mod, false). %% @doc Throws an expected exception inside an expect fun. %% %% This exception will get thrown without invalidating the mocked %% module. That is, the code using the mocked module is expected to %% handle this exception. %% %% Note: this code should only be used inside an expect fun. -spec exception(Class, Reason) -> no_return() when Class :: throw | error | exit, Reason :: any(). exception(Class, Reason) when Class == throw; Class == error; Class == exit -> erlang:throw(meck_ret_spec:raise(Class, Reason)). %% @doc Calls the original function (if existing) inside an expectation fun. %% %% Note: this code should only be used inside an expect fun. -spec passthrough(Args) -> Result when Args :: [any()], Result :: any(). passthrough(Args) when is_list(Args) -> {Mod, Func} = meck_code_gen:get_current_call(), erlang:apply(meck_util:original_name(Mod), Func, Args). %% @doc Validate the state of the mock module(s). %% %% The function returns `true' if the mocked module(s) has been used %% according to its expectations. It returns `false' if a call has %% failed in some way. Reasons for failure are wrong number of %% arguments or non-existing function (undef), wrong arguments %% (function clause) or unexpected exceptions. %% %% Validation can detect: %% %%
    %%
  • When a function was called with the wrong argument types %% (`function_clause')
  • %%
  • When an exception was thrown
  • %%
  • When an exception was thrown and expected (via meck:exception/2), %% which still results in `true' being returned
  • %%
%% %% Validation cannot detect: %% %%
    %%
  • When you didn't call a function
  • %%
  • When you called a function with the wrong number of arguments %% (`undef')
  • %%
  • When you called an undefined function (`undef')
  • %%
%% %% The reason Meck cannot detect these cases is because of how it is implemented. %% Meck replaces the module with a mock and a process that maintains the mock. %% Everything Meck get goes through that mock module. Meck does not insert %% itself at the caller level (i.e. in your module or in your test case), so it %% cannot know that you failed to call a module. %% %% Use the {@link history/1} or {@link history/2} function to analyze errors. -spec validate(Mods) -> boolean() when Mods :: Mod | [Mod], Mod :: atom(). validate(Mod) when is_atom(Mod) -> meck_proc:validate(Mod); validate(Mod) when is_list(Mod) -> not lists:member(false, [validate(M) || M <- Mod]). %% @doc Return the call history of the mocked module for all processes. %% %% @equiv history(Mod, '_') -spec history(Mod) -> history() when Mod :: atom(). history(Mod) when is_atom(Mod) -> meck_history:get_history('_', Mod). %% @doc Return the call history of the mocked module for the specified process. %% %% Returns a list of calls to the mocked module and their results for %% the specified `Pid'. Results can be either normal Erlang terms or %% exceptions that occurred. %% %% @see history/1 %% @see called/3 %% @see called/4 %% @see num_calls/3 %% @see num_calls/4 -spec history(Mod, OptCallerPid) -> history() when Mod :: atom(), OptCallerPid :: '_' | pid(). history(Mod, OptCallerPid) when is_atom(Mod), is_pid(OptCallerPid) orelse OptCallerPid == '_' -> meck_history:get_history(OptCallerPid, Mod). %% @doc Unloads all mocked modules from memory. %% %% The function returns the list of mocked modules that were unloaded %% in the process. -spec unload() -> Unloaded when Unloaded :: [Mod], Mod :: atom(). unload() -> fold_mocks(fun(Mod, Acc) -> try unload(Mod), [Mod | Acc] catch error:{not_mocked, Mod} -> Acc end end, []). %% @doc Unload a mocked module or a list of mocked modules. %% %% This will purge and delete the module(s) from the Erlang virtual %% machine. If the mocked module(s) replaced an existing module, this %% module will still be in the Erlang load path and can be loaded %% manually or when called. -spec unload(Mods) -> ok when Mods :: Mod | [Mod], Mod :: atom(). unload(Mod) when is_atom(Mod) -> meck_proc:stop(Mod), wait_for_exit(Mod); unload(Mods) when is_list(Mods) -> lists:foreach(fun unload/1, Mods), ok. %% @doc Returns whether `Mod:Func' has been called with `Args'. %% %% @equiv called(Mod, Fun, Args, '_') -spec called(Mod, OptFun, OptArgsSpec) -> boolean() when Mod :: atom(), OptFun :: '_' | atom(), OptArgsSpec :: '_' | args_spec(). called(Mod, OptFun, OptArgsSpec) -> meck_history:num_calls('_', Mod, OptFun, OptArgsSpec) > 0. %% @doc Returns whether `Pid' has called `Mod:Func' with `Args'. %% %% This will check the history for the module, `Mod', to determine %% whether process `Pid' call the function, `Fun', with arguments, `Args'. If %% so, this function returns true, otherwise false. %% %% Wildcards can be used, at any level in any term, by using the underscore %% atom: ``'_' '' %% %% @see called/3 -spec called(Mod, OptFun, OptArgsSpec, OptCallerPid) -> boolean() when Mod :: atom(), OptFun :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), OptCallerPid :: '_' | pid(). called(Mod, OptFun, OptArgsSpec, OptPid) -> meck_history:num_calls(OptPid, Mod, OptFun, OptArgsSpec) > 0. %% @doc Returns the number of times `Mod:Func' has been called with `Args'. %% %% @equiv num_calls(Mod, Fun, Args, '_') -spec num_calls(Mod, OptFun, OptArgsSpec) -> non_neg_integer() when Mod :: atom(), OptFun :: '_' | atom(), OptArgsSpec :: '_' | args_spec(). num_calls(Mod, OptFun, OptArgsSpec) -> meck_history:num_calls('_', Mod, OptFun, OptArgsSpec). %% @doc Returns the number of times process `Pid' has called `Mod:Func' %% with `Args'. %% %% This will check the history for the module, `Mod', to determine how %% many times process `Pid' has called the function, `Fun', with %% arguments, `Args' and returns the result. %% %% @see num_calls/3 -spec num_calls(Mod, OptFun, OptArgsSpec, OptCallerPid) -> non_neg_integer() when Mod :: atom(), OptFun :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), OptCallerPid :: '_' | pid(). num_calls(Mod, OptFun, OptArgsSpec, OptPid) -> meck_history:num_calls(OptPid, Mod, OptFun, OptArgsSpec). %% @doc Blocks until either function `Mod:Func' is called at least once with %% arguments matching `OptArgsSpec', or `Timeout' has elapsed. In the latter %% case the call fails with `error:timeout'. %% %% The number of calls is counted starting from the most resent call to %% {@link reset/1} on the mock or from the mock creation, whichever occurred %% latter. If a matching call has already occurred, then the function returns %% `ok' immediately. %% %% @equiv wait(1, Mod, OptFunc, OptArgsSpec, '_', Timeout) -spec wait(Mod, OptFunc, OptArgsSpec, Timeout) -> ok when Mod :: atom(), OptFunc :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), Timeout :: non_neg_integer(). wait(Mod, OptFunc, OptArgsSpec, Timeout) -> wait(1, Mod, OptFunc, OptArgsSpec, '_', Timeout). %% @doc Blocks until either function `Mod:Func' is called at least `Times' with %% arguments matching `OptArgsSpec', or `Timeout' has elapsed. In the latter %% case the call fails with `error:timeout'. %% %% The number of calls is counted starting from the most resent call to %% {@link reset/1} on the mock or from the mock creation, whichever occurred %% latter. If `Times' number of matching calls has already occurred, then the %% function returns `ok' immediately. %% %% @equiv wait(Times, Mod, OptFunc, OptArgsSpec, '_', Timeout) -spec wait(Times, Mod, OptFunc, OptArgsSpec, Timeout) -> ok when Times :: pos_integer(), Mod :: atom(), OptFunc :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), Timeout :: non_neg_integer(). wait(Times, Mod, OptFunc, OptArgsSpec, Timeout) -> wait(Times, Mod, OptFunc, OptArgsSpec, '_', Timeout). %% @doc Blocks until either function `Mod:Func' is called at least `Times' with %% arguments matching `OptArgsSpec' by process `OptCallerPid', or `Timeout' has %% elapsed. In the latter case the call fails with `error:timeout'. %% %% The number of calls is counted starting from the most resent call to %% {@link reset/1} on the mock or from the mock creation, whichever occurred %% latter. If `Times' number of matching call has already occurred, then the %% function returns `ok' immediately. -spec wait(Times, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) -> ok when Times :: pos_integer(), Mod :: atom(), OptFunc :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), OptCallerPid :: '_' | pid(), Timeout :: non_neg_integer(). wait(0, _Mod, _OptFunc, _OptArgsSpec, _OptCallerPid, _Timeout) -> ok; wait(Times, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) when is_integer(Times) andalso Times > 0 andalso is_integer(Timeout) andalso Timeout >= 0 -> ArgsMatcher = meck_args_matcher:new(OptArgsSpec), meck_proc:wait(Mod, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout). %% @doc Erases the call history for a mocked module or a list of mocked modules. %% %% This function will erase all calls made heretofore from the history of the %% specified modules. It is intended to prevent cluttering of test results with %% calls to mocked modules made during the test setup phase. -spec reset(Mods) -> ok when Mods :: Mod | [Mod], Mod :: atom(). reset(Mod) when is_atom(Mod) -> meck_proc:reset(Mod); reset(Mods) when is_list(Mods) -> lists:foreach(fun(Mod) -> reset(Mod) end, Mods). %% @doc Converts a list of terms into {@link ret_spec()} defining a loop of %% values. It is intended to be in construction of clause specs for the %% {@link expect/3} function. %% %% Calls to an expect, created with {@link ret_spec()} returned by this function, %% will return one element at a time from the `Loop' list and will restart at %% the first element when the end is reached. -spec loop(Loop) -> ret_spec() when Loop :: [ret_spec()]. loop(Loop) -> meck_ret_spec:loop(Loop). %% @doc Converts a list of terms into {@link ret_spec()} defining a sequence of %% values. It is intended to be in construction of clause specs for the %% {@link expect/3} function. %% %% Calls to an expect, created with {@link ret_spec} returned by this function, %% will exhaust the `Sequence' list of return values in order until the last %% value is reached. That value is then returned for all subsequent calls. -spec seq(Sequence) -> ret_spec() when Sequence :: [ret_spec()]. 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_ret_spec:val(Value). %% @doc Creates a {@link ret_spec()} that defines an exception. %% %% Calls to an expect, created with {@link ret_spec()} returned by this function, %% will raise the specified exception. -spec raise(Class, Reason) -> ret_spec() when Class :: throw | error | exit, Reason :: term(). 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). %% @doc creates a {@link matcher/0} instance from either `Predicate' or %% `HamcrestMatcher'. %%
    %%
  • `Predicate' - is a single parameter function. If it returns `true' then %% the argument passed to it is considered as meeting the matcher criteria, %% otherwise as not.
  • %%
  • `HamcrestMatcher' - is a matcher created by %% Hamcrest-Erlang %% library
  • %%
-spec is(MatcherImpl) -> matcher() when MatcherImpl :: Predicate | HamcrestMatcher, Predicate :: fun((any()) -> any()), HamcrestMatcher :: meck_matcher:hamcrest_matchspec(). is(MatcherImpl) -> meck_matcher:new(MatcherImpl). %% @doc Returns the value of an argument as it was passed to a particular %% function call made by a particular process. It fails with `not_found' error %% if a function call of interest has never been made. %% %% It retrieves the value of argument at `ArgNum' position as it was passed %% to function call `Mod:Func' with arguments that match `OptArgsSpec' made by %% process `CallerPid' that occurred `Occur''th according to the call history. %% %% Atoms `first' and `last' can be used in place of the occurrence number to %% retrieve the argument value passed when the function was called the first %% or the last time respectively. %% %% If an occurrence of a function call irrespective of the calling process needs %% to be captured then `_' might be passed as `OptCallerPid', but it is better %% to use {@link capture/5} instead. -spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> ArgValue when Occur :: first | last | pos_integer(), Mod :: atom(), Func :: atom(), OptArgsSpec :: '_' | args_spec(), ArgNum :: pos_integer(), OptCallerPid :: '_' | pid(), ArgValue :: any(). capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> meck_history:capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum). %% @doc Returns the value of an argument as it was passed to a particular %% function call, It fails with `not_found' error if a function call of %% interest has never been made. %% %% It retrieves the value of argument at `ArgNum' position as it was passed %% to function call `Mod:Func' with arguments that match `OptArgsSpec' that %% occurred `Occur''th according to the call history. %% %% Atoms `first' and `last' can be used in place of the occurrence number to %% retrieve the argument value passed when the function was called the first %% or the last time respectively. %% %% @equiv capture(Occur, Mod, Func, OptArgsSpec, ArgNum, '_') -spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> ArgValue when Occur :: first | last | pos_integer(), Mod::atom(), Func::atom(), OptArgsSpec :: args_spec(), ArgNum :: pos_integer(), ArgValue :: any(). capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> meck_history:capture(Occur, '_', Mod, Func, OptArgsSpec, ArgNum). %% @doc Returns the currently mocked modules. -spec mocked() -> list(atom()). mocked() -> fold_mocks(fun(M, Acc) -> [M | Acc] end, []). %%%============================================================================ %%% Internal functions %%%============================================================================ -spec wait_for_exit(Mod::atom()) -> ok. wait_for_exit(Mod) -> MonitorRef = erlang:monitor(process, meck_util:proc_name(Mod)), receive {'DOWN', MonitorRef, _Type, _Object, _Info} -> ok end. -spec fold_mocks(Fun, AccIn) -> AccOut when Fun :: fun((Elem :: module(), AccIn) -> AccOut), AccIn :: term(), AccOut :: term(). fold_mocks(Fun, Acc0) when is_function(Fun, 2) -> lists:foldl(fun(Mod, Acc) -> ModName = atom_to_list(Mod), case lists:split(max(length(ModName) - 5, 0), ModName) of {Name, "_meck"} -> Fun(erlang:list_to_existing_atom(Name), Acc); _Else -> Acc end end, Acc0, erlang:registered()). -spec check_expect_result(ok | {error, Reason::any()}) -> ok. check_expect_result(ok) -> ok; check_expect_result({error, Reason}) -> erlang:error(Reason). meck-1.0.0/LICENSE0000664000175000017500000002367714726775702014054 0ustar debalancedebalance Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS meck-1.0.0/rebar.lock0000664000175000017500000000000414726775702014767 0ustar debalancedebalance[]. meck-1.0.0/README.md0000664000175000017500000002364714726775702014323 0ustar debalancedebalance

Meck

A mocking library for Erlang

GitHub Actions Hex.pm version Hex.pm license Erlang versions hex.pm license

* [Features](#features) * [Examples](#examples) * [Use](#use) * [Manual Build](#manual-build) * [Caveats](#caveats) * [Contribute](#contribute) ## Features See what's new in [0.8 Release Notes][release_notes_0.8]. * Dynamic return values using sequences and loops of static values * Compact definition of mock arguments, clauses and return values * Pass through: call functions in the original module * Complete call history showing calls, return values and exceptions * Mock validation, will invalidate mocks that were not called correctly * Throwing of expected exceptions that keeps the module valid * Throws an error when mocking a module that doesn't exist or has been renamed (disable with option `non_strict`) * Support for [Hamcrest][hamcrest] matchers * Automatic backup and restore of cover data * Mock is linked to the creating process and will unload automatically when a crash occurs (disable with option `no_link`) * Mocking of sticky modules (using the option `unstick`) ## Examples Here's an example of using Meck in the Erlang shell: ```erlang Eshell V5.8.4 (abort with ^G) 1> meck:new(dog, [non_strict]). % non_strict is used to create modules that don't exist ok 2> meck:expect(dog, bark, fun() -> "Woof!" end). ok 3> dog:bark(). "Woof!" 4> meck:validate(dog). true 5> meck:unload(dog). ok 6> dog:bark(). ** exception error: undefined function dog:bark/0 ``` Exceptions can be anticipated by Meck (resulting in validation still passing). This is intended to be used to test code that can and should handle certain exceptions indeed does take care of them: ```erlang 5> meck:expect(dog, meow, fun() -> meck:exception(error, not_a_cat) end). ok 6> catch dog:meow(). {'EXIT',{not_a_cat,[{meck,exception,2}, {meck,exec,4}, {dog,meow,[]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,6}, {shell,eval_exprs,6}, {shell,eval_loop,3}]}} 7> meck:validate(dog). true ``` Normal Erlang exceptions result in a failed validation. The following example is just to demonstrate the behavior, in real test code the exception would normally come from the code under test (which should, if not expected, invalidate the mocked module): ```erlang 8> meck:expect(dog, jump, fun(Height) when Height > 3 -> erlang:error(too_high); (Height) -> ok end). ok 9> dog:jump(2). ok 10> catch dog:jump(5). {'EXIT',{too_high,[{meck,exec,4}, {dog,jump,[5]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,6}, {shell,eval_exprs,6}, {shell,eval_loop,3}]}} 11> meck:validate(dog). false ``` Here's an example of using Meck inside an EUnit test case: ```erlang my_test() -> meck:new(my_library_module), meck:expect(my_library_module, fib, fun(8) -> 21 end), ?assertEqual(21, code_under_test:run(fib, 8)), % Uses my_library_module ?assert(meck:validate(my_library_module)), meck:unload(my_library_module). ``` Pass-through is used when the original functionality of a module should be kept. When the option `passthrough` is used when calling `new/2` all functions in the original module will be kept in the mock. These can later be overridden by calling `expect/3` or `expect/4`. ```erlang Eshell V5.8.4 (abort with ^G) 1> meck:new(string, [unstick, passthrough]). ok 2> string:strip(" test "). "test" ``` It's also possible to pass calls to the original function allowing us to override only a certain behavior of a function (this usage is compatible with the `passthrough` option). `passthrough/1` will always call the original function with the same name as the expect is defined in): ```erlang Eshell V5.8.4 (abort with ^G) 1> meck:new(string, [unstick, passthrough]). ok 2> meck:expect(string, strip, fun ("foo") -> "bar"; (String) -> meck:passthrough([String]) end). ok 3> string:strip(" test "). "test" 4> string:strip("foo"). "bar" 5> meck:unload(string). ok 5> string:strip("foo"). "foo" ``` ## Use Meck is best used via [Rebar 3][rebar_3]. Add Meck to the test dependencies in your `rebar.config`: ```erlang {profiles, [{test, [{deps, [meck]}]}]}. ``` ### Manual Build Meck uses [Rebar 3][rebar_3]. To build Meck go to the Meck directory and simply type: ```sh rebar3 compile ``` In order to run all tests for Meck type the following command from the same directory: ```sh rebar3 eunit ``` Documentation can be generated through the use of the following command: ```sh rebar3 edoc ``` ### Test Output Normally the test output is hidden, but if EUnit is run directly, two things might seem alarming when running the tests: 1. Warnings emitted by cover 2. An exception printed by SASL Both are expected due to the way Erlang currently prints errors. The important line you should look for is `All XX tests passed`, if that appears all is correct. ## Caveats ### Global Namespace Meck will have trouble mocking certain modules since it works by recompiling and reloading modules in the global Erlang module namespace. Replacing a module affects the whole Erlang VM and any running processes using that module. This means certain modules cannot be mocked or will cause trouble. In general, if a module is used by running processes or include Native Implemented Functions (NIFs) they will be hard or impossible to mock. You may be lucky and it could work, until it breaks one day. The following is a non-exhaustive list of modules that can either be problematic to mock or not possible at all: * `erlang` * `supervisor` * All `gen_` family of modules (`gen_server`, `gen_statem` etc.) * `os` * `crypto` * `compile` * `global` * `timer` (possible to mock, but used by some test frameworks, like Elixir's ExUnit) ### Local Functions A meck expectation set up for a function _f_ does not apply to the module- local invocation of _f_ within the mocked module. Consider the following module: ```erlang -module(test). -export([a/0, b/0, c/0]). a() -> c(). b() -> ?MODULE:c(). c() -> original. ``` Note how the module-local call to `c/0` in `a/0` stays unchanged even though the expectation changes the externally visible behaviour of `c/0`: ```erlang 3> meck:new(test, [passthrough]). ok 4> meck:expect(test,c,0,changed). ok 5> test:a(). original 6> test:b(). changed 6> test:c(). changed ``` ### Common Test When using `meck` under Erlang/OTP's Common Test, one should pay special attention to this bit in the chapter on [Writing Tests](https://erlang.org/doc/apps/common_test/write_test_chapter.html): > `init_per_suite` and `end_per_suite` execute on dedicated Erlang processes, > just like the test cases do. Common Test runs `init_per_suite` in an isolated process which terminates when done, before the test case runs. A mock that is created there will also terminate and unload itself before the test case runs. This is because it is linked to the process creating it. This can be especially tricky to detect if `passthrough` is used when creating the mock, since it is hard to know if it is the mock responding to function calls or the original module. To avoid this, you can pass the `no_link` flag to `meck:new/2` which will unlink the mock from the process that created it. When using `no_link` you should make sure that `meck:unload/1` is called properly (for all test outcomes, or crashes) so that a left-over mock does not interfere with subsequent test cases. ## Contribute Patches are greatly appreciated! For a much nicer history, please [write good commit messages][commit_messages]. Use a branch name prefixed by `feature/` (e.g. `feature/my_example_branch`) for easier integration when developing new features or fixes for meck. Should you find yourself using Meck and have issues, comments or feedback please [create an issue here on GitHub][issues]. Meck has been greatly improved by [many contributors](https://github.com/eproxus/meck/graphs/contributors)! ### Donations If you or your company use Meck and find it useful, a [sponsorship][sponsors] or [donations][liberapay] are greatly appreciated! [release_notes_0.8]: https://github.com/eproxus/meck/wiki/0.8-Release-Notes [hamcrest]: https://github.com/hyperthunk/hamcrest-erlang [rebar_3]: https://github.com/erlang/rebar3 [issues]: http://github.com/eproxus/meck/issues [commit_messages]: http://chris.beams.io/posts/git-commit/ [sponsors]: https://github.com/sponsors/eproxus [liberapay]: https://liberapay.com/eproxus/ meck-1.0.0/test/0000775000175000017500000000000014726775702014007 5ustar debalancedebalancemeck-1.0.0/test/meck_expect_tests.erl0000664000175000017500000001127014726775702020225 0ustar debalancedebalance%%%============================================================================ %%% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %%% %%% 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. %%%============================================================================ -module(meck_expect_tests). -include_lib("eunit/include/eunit.hrl"). expect_explicit_values_test() -> %% When E = meck_expect:new(blah, [1000, a, {1002, [{<<"b">>, 1003}]}], 2001), %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1000, a, {1002, [{<<"b">>, 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1001, a, {1002, [{<<"b">>, 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1000, b, {1002, [{<<"b">>, 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1000, a, {1003, [{<<"b">>, 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1000, a, {1002, [{<<"c">>, 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1000, a, {1002, [{<<"b">>, 1004}]}], E)). expect_wildcard_test() -> %% When E = meck_expect:new(blah, [1000, '_', {'_', [{'_', 1003}]}], 2001), %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1000, a, {1002, [{<<"b">>, 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1001, a, {1002, [{<<"b">>, 1003}]}], E)), ?assertMatch({V2001, _}, meck_expect:fetch_result([1000, b, {1002, [{<<"b">>, 1003}]}], E)), ?assertMatch({V2001, _}, meck_expect:fetch_result([1000, a, {1003, [{<<"b">>, 1003}]}], E)), ?assertMatch({V2001, _}, meck_expect:fetch_result([1000, a, {1002, [{[1, {2}, 3], 1003}]}], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1000, a, {1002, [{<<"b">>, 1004}]}], E)). expect_matchers_test() -> %% Given Is1003 = meck_matcher:new(fun(X) -> X == 1003 end), LessThen1004 = meck_matcher:new(fun(X) -> X < 1004 end), %% When E = meck_expect:new(blah, [Is1003, LessThen1004], 2001), %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1003, 1002], E)), ?assertMatch({V2001, _}, meck_expect:fetch_result([1003, 1003], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1003, 1004], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1002, 1002], E)). expect_with_matchers_multiclause_test() -> %% Given Is1003 = meck_matcher:new(fun(X) -> X == 1003 end), LessThen1004 = meck_matcher:new(fun(X) -> X < 1004 end), %% When E = meck_expect:new(blah, [{['_', Is1003, 1004], 2001}, {['_', Is1003, LessThen1004], 2002}, {['_', '_', LessThen1004], 2003}]), %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1002, 1003, 1004], E)), V2002 = meck_ret_spec:val(2002), ?assertMatch({V2002, _}, meck_expect:fetch_result([1002, 1003, 1003], E)), V2003 = meck_ret_spec:val(2003), ?assertMatch({V2003, _}, meck_expect:fetch_result([1002, 1004, 1003], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1002, 1003, 1005], E)). expect_with_matchers_masked_clause_test() -> %% Given Is1003 = meck_matcher:new(fun(X) -> X == 1003 end), LessThen1004 = meck_matcher:new(fun(X) -> X < 1004 end), %% When E = meck_expect:new(blah, [{[Is1003, LessThen1004], 2001}, {[Is1003, Is1003], 2002}]), %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1003, 1003], E)). expect_with_arity_test() -> %% When E = meck_expect:new(foo, [{2, 2001}]), %% Then V2001 = meck_ret_spec:val(2001), ?assertMatch({V2001, _}, meck_expect:fetch_result([1, 2], E)), ?assertMatch({undefined, _}, meck_expect:fetch_result([1, 2, 3], E)). meck-1.0.0/test/meck_matcher_tests.erl0000664000175000017500000000402714726775702020362 0ustar debalancedebalance%%%============================================================================ %%% 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. %%%============================================================================ -module(meck_matcher_tests). -include_lib("eunit/include/eunit.hrl"). match_predicate_test() -> Matcher = meck_matcher:new(fun(X) -> X == 1000 end), ?assertMatch(true, meck_matcher:match_ignore(1000, Matcher)), ?assertMatch(false, meck_matcher:match_ignore(1001, Matcher)). match_predicate_not_bool_test() -> Matcher = meck_matcher:new(fun(1000) -> true; (Other) -> Other end), ?assertMatch(true, meck_matcher:match_ignore(1000, Matcher)), ?assertMatch(false, meck_matcher:match_ignore(1001, Matcher)). match_hamcrest_test() -> Matcher = meck_matcher:new(fun(X) -> X == 1000 end), ?assertMatch(true, meck_matcher:match_ignore(1000, Matcher)), ?assertMatch(false, meck_matcher:match_ignore(1001, Matcher)). match_not_matcher_test() -> ?assertMatch(true, meck_matcher:match_ignore(something, '_')), ?assertMatch(true, meck_matcher:match_ignore({1, [2, 3], undefined}, {1, [2, 3], undefined})). predicate_wrong_arity_test() -> Predicate = fun(X, Y) -> X == Y end, ?assertError(_, meck_matcher:new(Predicate)). is_matcher_test() -> ?assertMatch(true, meck_matcher:is_matcher(meck_matcher:new(fun(X) -> X == 1000 end))), ?assertMatch(false, meck_matcher:is_matcher(fun(X) -> X == 1000 end)), ?assertMatch(false, meck_matcher:is_matcher(blah)). meck-1.0.0/test/meck_on_load_tests.erl0000664000175000017500000000222114726775702020344 0ustar debalancedebalance-module(meck_on_load_tests). -include_lib("eunit/include/eunit.hrl"). on_load_test_() -> {foreach, fun setup/0, fun teardown/1, [fun no_enable_on_load/0, fun enable_on_load/0]}. setup() -> ok. teardown(_) -> meck:unload(). no_enable_on_load() -> % We _don't_ want on_load to be called. Listen out for it. register(on_load_listener, self()), meck:new(meck_on_load_module, [passthrough]), ?assertEqual(pong, meck_on_load_module:ping()), receive on_load_called -> erlang:error(unexpected_call_to_on_load) after 100 -> % Use a relatively short timeout, because the happy path goes % through here. ok end. enable_on_load() -> % We _do_ want on_load to be called. register(on_load_listener, self()), meck:new(meck_on_load_module, [passthrough, enable_on_load]), ?assertEqual(pong, meck_on_load_module:ping()), receive on_load_called -> ok after 200 -> % Use a longer timeout, because testing for not-called is harder, % and this is the sad path. erlang:error(expected_call_to_on_load) end. meck-1.0.0/test/meck_tests.erl0000664000175000017500000017232414726775702016665 0ustar debalancedebalance%%============================================================================= %% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %% %% 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. %%============================================================================= -module(meck_tests). -include_lib("eunit/include/eunit.hrl"). -include("../src/meck.hrl"). -define(assertTerminated(MonitorRef, Reason, Timeout), (fun() -> receive {'DOWN', MonitorRef, process, _Pid, Reason} -> ok; {'DOWN', MonitorRef, process, _Pid, AnotherReason} -> erlang:error({dead_for_another_reason, AnotherReason}) after Timeout -> erlang:error(still_alive) end end)()). meck_test_() -> {foreach, fun setup/0, fun teardown/1, [{with, [T]} || T <- [fun new_/1, fun unload_/1, fun double_new_/1, fun validate_/1, fun expect_/1, fun exports_/1, fun call_return_value_/1, fun call_return_value_improper_list_/1, fun call_argument_/1, fun call_undef_/1, fun call_function_clause_/1, fun validate_unexpected_error_/1, fun validate_expected_error_/1, fun validate_chained_/1, fun stacktrace_/1, fun stacktrace_function_clause_/1, fun change_func_/1, fun caller_does_not_crash_on_reload_/1, fun call_original_undef_/1, fun history_empty_/1, fun history_call_/1, fun history_throw_/1, fun history_throw_fun_/1, fun history_exit_/1, fun history_error_/1, fun history_error_args_/1, fun history_meck_throw_/1, fun history_meck_throw_fun_/1, fun history_meck_exit_/1, fun history_meck_error_/1, fun history_by_pid_/1, fun reset_/1, fun shortcut_expect_/1, fun shortcut_expect_negative_arity_/1, fun shortcut_call_return_value_/1, fun shortcut_call_argument_/1, fun shortcut_re_add_/1, fun shortcut_opaque_/1, fun shortcut_stacktrace_/1, fun delete_/1, fun expects_/1, fun called_false_no_args_/1, fun called_true_no_args_/1, fun called_true_two_functions_/1, fun called_false_one_arg_/1, fun called_true_one_arg_/1, fun called_false_few_args_/1, fun called_true_few_args_/1, fun called_few_args_matchers_/1, fun called_false_error_/1, fun called_true_error_/1, fun called_with_pid_no_args_/1, fun num_calls_/1, fun num_calls_error_/1, fun num_calls_with_pid_no_args_/1, fun called_wildcard_/1, fun sequence_/1, fun sequence_fun_/1, fun expect_args_sequence_/1, fun expect_arity_sequence_/1, fun expect_complex_sequence_/1, fun sequence_multi_/1, fun loop_/1, fun expect_empty_clause_list_/1, fun expect_args_value_/1, fun expect_args_invalid_call_/1, fun expect_arity_value_/1, fun expect_args_loop_/1, fun expect_arity_loop_/1, fun expect_complex_loop_/1, fun expect_loop_in_seq_/1, fun expect_args_exception_/1, fun expect_arity_exception_/1, fun expect_arity_clause_/1, fun loop_multi_/1, fun expect_args_pattern_override_/1, fun expect_args_pattern_shadow_/1, fun expect_args_pattern_missing_/1, fun expect_args_pattern_invalid_/1, fun expect_args_matchers_/1, fun expect_ret_specs_/1 ]]}. setup() -> % Uncomment to run tests with dbg: % dbg:tracer(), % dbg:p(all, call), % dbg:tpl(meck, []), ok = meck:new(mymod, [non_strict]), mymod. teardown(Module) -> catch meck:unload(Module). %% --- Tests using setup and teardown ----------------------------------------- new_(Mod) -> Info = Mod:module_info(), ?assert(is_list(Info)). unload_(Mod) -> ok = meck:unload(Mod), ?assertEqual(false, code:is_loaded(Mod)). double_new_(Mod) -> ?assertError({already_started, _}, meck:new(Mod)). validate_(Mod) -> ?assertEqual(true, meck:validate(Mod)). expect_(Mod) -> ok = meck:expect(Mod, test, fun() -> ok end), ?assertEqual(true, meck:validate(Mod)). exports_(Mod) -> ok = meck:expect(Mod, test1, fun() -> ok end), ok = meck:expect(Mod, test2, fun(_) -> ok end), ok = meck:expect(Mod, test3, fun(_, _) -> ok end), ?assertEqual(0, proplists:get_value(test1, Mod:module_info(exports))), ?assertEqual(1, proplists:get_value(test2, Mod:module_info(exports))), ?assertEqual(2, proplists:get_value(test3, Mod:module_info(exports))), ?assertEqual(true, meck:validate(Mod)). call_return_value_(Mod) -> ok = meck:expect(Mod, test, fun() -> apa end), ?assertEqual(apa, Mod:test()), ?assertEqual(true, meck:validate(Mod)). call_return_value_improper_list_(Mod) -> Dict = dict:store(hest, apa, dict:new()), ok = meck:expect(Mod, test, 0, Dict), ?assertEqual(Dict, Mod:test()), ?assertEqual(true, meck:validate(Mod)). call_argument_(Mod) -> ok = meck:expect(Mod, test, fun(hest, 1) -> apa end), ?assertEqual(apa, Mod:test(hest, 1)), ?assertEqual(true, meck:validate(Mod)). call_function_clause_(Mod) -> ok = meck:expect(Mod, test, fun(hest, 1) -> apa end), ?assertError(function_clause, Mod:test(hest, 2)), ?assertEqual(false, meck:validate(Mod)). validate_unexpected_error_(Mod) -> ok = meck:expect(Mod, test, fun(hest, 1) -> erlang:error(timeout) end), ?assertError(timeout, Mod:test(hest, 1)), ?assertEqual(false, meck:validate(Mod)). validate_expected_error_(Mod) -> ok = meck:expect(Mod, test, fun(hest, 1) -> meck:exception(error, timeout) end), ?assertError(timeout, Mod:test(hest, 1)), ?assertEqual(true, meck:validate(Mod)). validate_chained_(Mod) -> ok = meck:new(mymod2, [non_strict]), ok = meck:expect(mymod2, test, fun() -> meck:exception(error, test_error) end), ok = meck:expect(Mod, test, fun() -> mymod2:test() end), ?assertError(test_error, Mod:test()), ?assertEqual(false, meck:validate(Mod)), ?assertEqual(true, meck:validate(mymod2)), ok = meck:unload(mymod2). stacktrace_(Mod) -> ok = meck:expect(Mod, test, fun() -> erlang:error(test_error) end), try Mod:test(), throw(failed) catch ?_exception_(error, test_error, StackToken) -> ?assert(lists:any(fun({M, test, []}) when M == Mod -> true; ({M, test, [],[]}) when M == Mod -> true; (_) -> false end, ?_get_stacktrace_(StackToken))) end. stacktrace_function_clause_(Mod) -> ok = meck:expect(Mod, test, fun(1) -> ok end), try Mod:test(error), throw(failed) catch ?_exception_(error, function_clause, StackToken) -> ?assert(lists:any( fun ({M, test, [error]}) when M == Mod -> true; ({M, test, [error], []}) when M == Mod -> true; (_) -> false end, ?_get_stacktrace_(StackToken))) end. call_undef_(Mod) -> ok = meck:expect(Mod, test, fun(hest, 1) -> apa end), ?assertError(undef, Mod:test(hest)). caller_does_not_crash_on_reload_(Mod) -> ok = meck:expect(Mod, test, fun() -> timer:sleep(infinity) end), Pid = spawn(fun() -> Mod:test() end), ok = meck:expect(Mod, new1, fun() -> ok end), ok = meck:expect(Mod, new2, fun() -> ok end), ok = meck:expect(Mod, new3, fun() -> ok end), ?assertEqual(true, is_process_alive(Pid)). change_func_(Mod) -> ok = meck:expect(Mod, test, fun() -> 1 end), ?assertEqual(1, Mod:test()), ok = meck:expect(Mod, test, fun() -> 2 end), ?assertEqual(2, Mod:test()). call_original_undef_(Mod) -> ok = meck:expect(Mod, test, fun() -> meck:passthrough([]) end), ?assertError(undef, Mod:test()), ?assert(not meck:validate(Mod)), ?assertEqual(undefined, get('$meck_call')). history_empty_(Mod) -> ?assertEqual([], meck:history(Mod)). history_call_(Mod) -> ok = meck:expect(Mod, test, fun() -> ok end), ok = meck:expect(Mod, test2, fun(_, _) -> result end), ok = meck:expect(Mod, test3, 0, 3), ok = meck:expect(Mod, test4, 0, {1,2,3}), Mod:test(), Mod:test2(a, b), Mod:test3(), Mod:test4(), ?assertEqual([{self(), {Mod, test, []}, ok}, {self(), {Mod, test2, [a, b]}, result}, {self(), {Mod, test3, []}, 3}, {self(), {Mod, test4, []}, {1,2,3}}], meck:history(Mod)). history_throw_(Mod) -> ok = meck:expect(Mod, test, fun() -> throw(test_exception) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, throw, test_exception, _Stacktrace}], meck:history(Mod)). history_throw_fun_(Mod) -> Fun = fun() -> exception_fun end, ok = meck:expect(Mod, test, fun() -> throw(Fun) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, throw, Fun, _Stacktrace}], meck:history(Mod)). history_exit_(Mod) -> ok = meck:expect(Mod, test, fun() -> exit(test_exit) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, exit, test_exit, _Stacktrace}], meck:history(Mod)). history_error_(Mod) -> ok = meck:expect(Mod, test, fun() -> erlang:error(test_error) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}], meck:history(Mod)). history_error_args_(Mod) -> ok = meck:expect(Mod, test, fun() -> erlang:error(test_error, [fake_args]) end), catch Mod:test(), History = meck:history(Mod), ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}], meck:history(Mod)), [{_Pid, _MFA, error, test_error, Stacktrace}] = History, ?assert(lists:any(fun({_M, _F, [fake_args]}) -> true; ({_M, _F, [fake_args], [{file,_},{line,_}]}) -> true; (_) -> false end, Stacktrace)). history_meck_throw_(Mod) -> ok = meck:expect(Mod, test, fun() -> meck:exception(throw, test_exception) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, throw, test_exception, _Stacktrace}], meck:history(Mod)). history_meck_throw_fun_(Mod) -> Fun = fun() -> exception_fun end, ok = meck:expect(Mod, test, fun() -> meck:exception(throw, Fun) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, throw, Fun, _Stacktrace}], meck:history(Mod)). history_meck_exit_(Mod) -> ok = meck:expect(Mod, test, fun() -> meck:exception(exit, test_exit) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, exit, test_exit, _Stacktrace}], meck:history(Mod)). history_meck_error_(Mod) -> ok = meck:expect(Mod, test, fun() -> meck:exception(error, test_error) end), catch Mod:test(), ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}], meck:history(Mod)). history_by_pid_(Mod) -> ok = meck:expect(Mod, test1, fun() -> ok end), ok = meck:expect(Mod, test2, fun() -> ok end), TestPid = self(), Fun = fun() -> Mod:test1(), TestPid ! {self(), done} end, Pid = spawn(Fun), Mod:test1(), Mod:test2(), receive {Pid, done} -> ok end, ?assertEqual([{Pid, {Mod, test1, []}, ok}], meck:history(Mod, Pid)), ?assertEqual([{TestPid, {Mod, test1, []}, ok}, {TestPid, {Mod, test2, []}, ok}], meck:history(Mod, TestPid)), ?assertEqual(meck:history(Mod), meck:history(Mod, '_')). reset_(Mod) -> % Given meck:expect(Mod, test1, fun() -> ok end), meck:expect(Mod, test2, fun() -> ok end), Mod:test1(), Mod:test2(), % When meck:reset(Mod), Mod:test1(), % Then ?assertMatch([{_Pid, {Mod, test1, []}, ok}], meck:history(Mod)). shortcut_expect_(Mod) -> ok = meck:expect(Mod, test, 0, ok), ?assertEqual(true, meck:validate(Mod)). shortcut_expect_negative_arity_(Mod) -> ?assertError(function_clause, meck:expect(Mod, test, -1, ok)). shortcut_call_return_value_(Mod) -> ok = meck:expect(Mod, test, 0, apa), ?assertEqual(apa, Mod:test()), ?assertEqual(true, meck:validate(Mod)). shortcut_call_argument_(Mod) -> ok = meck:expect(Mod, test, 2, apa), ?assertEqual(apa, Mod:test(hest, 1)), ?assertEqual(true, meck:validate(Mod)). shortcut_re_add_(Mod) -> ok = meck:expect(Mod, test, 2, apa), ?assertEqual(apa, Mod:test(hest, 1)), ok = meck:expect(Mod, test, 2, new), ?assertEqual(new, Mod:test(hest, 1)), ?assertEqual(true, meck:validate(Mod)). shortcut_opaque_(Mod) -> Ref = make_ref(), ok = meck:expect(Mod, test, 0, {test, [a, self()], Ref}), ?assertMatch({test, [a, P], Ref} when P == self(), Mod:test()). shortcut_stacktrace_(Mod) -> ok = meck:expect(Mod, test, [true], ok), ?assertEqual( {'EXIT', {function_clause, [{mymod, test, [false], []}]}}, catch(Mod:test(false)) ). delete_(Mod) -> ok = meck:expect(Mod, test, 2, ok), ?assertEqual(ok, meck:delete(Mod, test, 2)), ?assertError(undef, Mod:test(a, b)), ?assert(meck:validate(Mod)). expects_(Mod) -> ?assertEqual([], meck:expects(Mod)), ok = meck:expect(Mod, test, 2, ok), ?assertEqual([{Mod, test, 2}], meck:expects(Mod)), ok = meck:expect(Mod, test2, 0, ok), ?assertEqual([{Mod, test, 2}, {Mod, test2, 0}], lists:sort(meck:expects(Mod))). called_false_no_args_(Mod) -> Args = [], ok = meck:expect(Mod, test, length(Args), ok), assert_called(Mod, test, Args, false), ok. called_true_no_args_(Mod) -> Args = [], ok = meck:expect(Mod, test, length(Args), ok), ok = apply(Mod, test, Args), assert_called(Mod, test, Args, true), ok. called_true_two_functions_(Mod) -> Args = [], ok = meck:expect(Mod, test1, length(Args), ok), ok = meck:expect(Mod, test2, length(Args), ok), ok = apply(Mod, test1, Args), ok = apply(Mod, test2, Args), assert_called(Mod, test2, Args, true), ok. called_false_one_arg_(Mod) -> Args = ["hello"], ok = meck:expect(Mod, test, length(Args), ok), assert_called(Mod, test, Args, false), ok. called_true_one_arg_(Mod) -> Args = ["hello"], ok = meck:expect(Mod, test, length(Args), ok), ok = apply(Mod, test, Args), assert_called(Mod, test, Args, true), ok. called_false_few_args_(Mod) -> Args = [one, 2, {three, 3}, "four"], ok = meck:expect(Mod, test, length(Args), ok), assert_called(Mod, test, Args, false), ok. called_true_few_args_(Mod) -> Args = [one, 2, {three, 3}, "four"], ok = meck:expect(Mod, test, length(Args), ok), ok = apply(Mod, test, Args), assert_called(Mod, test, Args, true), ok. called_few_args_matchers_(Mod) -> Args = [one, 2, {three, 3}, "four"], ok = meck:expect(Mod, test, length(Args), ok), ok = apply(Mod, test, Args), assert_called(Mod, test, ['_', meck:is(fun(X) -> X == 2 end), {'_', 3}, "four"], true), assert_called(Mod, test, ['_', meck:is(fun(X) -> X == 3 end), {'_', 3}, "four"], false), ok. called_false_error_(Mod) -> Args = [one, "two", {3, 3}], TestFun = fun (_, _, _) -> meck:exception(error, my_error) end, ok = meck:expect(Mod, test, TestFun), assert_called(Mod, test, Args, false), ok. called_true_error_(Mod) -> Args = [one, "two", {3, 3}], expect_catch_apply(Mod, test, Args), assert_called(Mod, test, Args, true), ok. called_with_pid_no_args_(Mod) -> Args = [], ok = meck:expect(Mod, test, length(Args), ok), Pid = spawn_caller_and_sync(Mod, test, Args), assert_called(Mod, test, Args, self(), false), assert_called(Mod, test, Args, Pid, true), ok = apply(Mod, test, Args), assert_called(Mod, test, Args, self(), true), ?assertEqual(meck:called(Mod, test, Args, '_'), meck:called(Mod, test, Args)). spawn_caller_and_sync(Mod, Func, Args) -> TestPid = self(), Fun = fun() -> catch apply(Mod, Func, Args), TestPid ! {self(), done} end, Pid = spawn(Fun), receive {Pid, done} -> ok end, % sync with the spawned process Pid. num_calls_(Mod) -> Args = [], IncorrectArgs = [foo], ok = meck:expect(Mod, test1, length(Args), ok), ?assertEqual(0, meck:num_calls(Mod, test1, Args)), ok = apply(Mod, test1, Args), ?assertEqual(1, meck:num_calls(Mod, test1, Args)), ?assertEqual(0, meck:num_calls(Mod, test1, IncorrectArgs)). num_calls_error_(Mod) -> Args = [one, "two", {3, 3}], expect_catch_apply(Mod, test, Args), ?assertEqual(1, meck:num_calls(Mod, test, Args)). num_calls_with_pid_no_args_(Mod) -> Args = [], ok = meck:expect(Mod, test, length(Args), ok), Pid = spawn_caller_and_sync(Mod, test, Args), ?assertEqual(0, meck:num_calls(Mod, test, Args, self())), ?assertEqual(1, meck:num_calls(Mod, test, Args, Pid)), ok = apply(Mod, test, Args), ?assertEqual(1, meck:num_calls(Mod, test, Args, self())), ?assertEqual(meck:num_calls(Mod, test, Args, '_'), meck:num_calls(Mod, test, Args)). expect_catch_apply(Mod, Func, Args) -> TestFun = fun (_, _, _) -> meck:exception(error, my_error) end, ok = meck:expect(Mod, Func, TestFun), catch apply(Mod, Func, Args). called_wildcard_(Mod) -> Args = [one, 2, {three, 3}, "four"], ok = meck:expect(Mod, test, length(Args), ok), ok = apply(Mod, test, Args), assert_called(Mod, test, [one, '_', {three, '_'}, "four"], true), ok. sequence_(Mod) -> Sequence = [a, b, c, d, e], ?assertEqual(ok, meck:sequence(Mod, s, 2, Sequence)), ?assertEqual(Sequence, [Mod:s(a, b) || _ <- lists:seq(1, length(Sequence))]), ?assertEqual([e, e, e, e, e], [Mod:s(a, b) || _ <- lists:seq(1, 5)]), ?assert(meck:validate(Mod)). sequence_fun_(Mod) -> Sequence = [fun(A) -> A + 1 end, fun(A) -> A + 2 end, fun(_A) -> 1 end], ?assertEqual(ok, meck:sequence(Mod, s, 1, Sequence)), ?assertEqual([2,4,1], [Mod:s(N) || N <- lists:seq(1, length(Sequence))]), ?assertEqual([1,1,1,1], [Mod:s(N) || N <- lists:seq(1, 4)]), ?assert(meck:validate(Mod)). sequence_multi_(Mod) -> meck:new(mymod2, [non_strict]), Mods = [Mod, mymod2], Sequence = [a, b, c, d, e], ?assertEqual(ok, meck:sequence(Mods, s, 2, Sequence)), ?assertEqual(Sequence, [Mod:s(a, b) || _ <- lists:seq(1, length(Sequence))]), ?assertEqual([e, e, e, e, e], [Mod:s(a, b) || _ <- lists:seq(1, 5)]), ?assertEqual(Sequence, [mymod2:s(a, b) || _ <- lists:seq(1, length(Sequence))]), ?assertEqual([e, e, e, e, e], [mymod2:s(a, b) || _ <- lists:seq(1, 5)]), ?assert(meck:validate(Mods)). expect_empty_clause_list_(Mod) -> ?assertError(empty_clause_list, meck:expect(Mod, dummy, [])). expect_args_value_(Mod) -> %% When meck:expect(Mod, val, [1001], meck:val(a)), %% Then ?assertEqual(a, Mod:val(1001)), ?assertEqual(a, Mod:val(1001)). expect_args_invalid_call_(Mod) -> %% When meck:expect(Mod, val, [1001], meck:val(a)), %% Then ?assertError(function_clause, Mod:val(1002)). expect_arity_value_(Mod) -> %% When meck:expect(Mod, val, 1, meck:val(a)), %% Then ?assertEqual(a, Mod:val(1001)), ?assertEqual(a, Mod:val(1001)). expect_args_sequence_(Mod) -> %% When meck:expect(Mod, seq, [1001], meck:seq([a, b, c])), %% Then ?assertEqual(a, Mod:seq(1001)), ?assertEqual(b, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)). expect_arity_sequence_(Mod) -> %% When meck:expect(Mod, seq, 1, meck:seq([a, b, c])), %% Then ?assertEqual(a, Mod:seq(1001)), ?assertEqual(b, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)). expect_complex_sequence_(Mod) -> %% When meck:expect(Mod, seq, 1, meck:seq([meck:val(a), meck:seq([b, c]), meck:seq([meck:raise(error, d), meck:seq([e, f, g]), h, meck:val(i)])])), %% Then ?assertEqual(a, Mod:seq(1001)), ?assertEqual(b, Mod:seq(1001)), ?assertEqual(c, Mod:seq(1001)), ?assertException(error, d, Mod:seq(1001)), ?assertEqual(e, Mod:seq(1001)), ?assertEqual(f, Mod:seq(1001)), ?assertEqual(g, Mod:seq(1001)), ?assertEqual(h, Mod:seq(1001)), ?assertEqual(i, Mod:seq(1001)), ?assertEqual(i, Mod:seq(1001)), ?assertEqual(i, Mod:seq(1001)). loop_(Mod) -> Loop = [a, b, c, d, e], ?assertEqual(ok, meck:loop(Mod, l, 2, Loop)), [?assertEqual(V, Mod:l(a, b)) || _ <- lists:seq(1, length(Loop)), V <- Loop], ?assert(meck:validate(Mod)). expect_args_loop_(Mod) -> %% When meck:expect(Mod, loop, [1001], meck:loop([a, b, c])), %% Then ?assertEqual(a, Mod:loop(1001)), ?assertEqual(b, Mod:loop(1001)), ?assertEqual(c, Mod:loop(1001)), ?assertEqual(a, Mod:loop(1001)), ?assertEqual(b, Mod:loop(1001)), ?assertEqual(c, Mod:loop(1001)), ?assertEqual(a, Mod:loop(1001)). expect_arity_loop_(Mod) -> %% When meck:expect(Mod, loop, 1, meck:loop([a, b, c])), %% Then ?assertEqual(a, Mod:loop(1001)), ?assertEqual(b, Mod:loop(1001)), ?assertEqual(c, Mod:loop(1001)), ?assertEqual(a, Mod:loop(1001)), ?assertEqual(b, Mod:loop(1001)), ?assertEqual(c, Mod:loop(1001)), ?assertEqual(a, Mod:loop(1001)). expect_complex_loop_(Mod) -> %% When meck:expect(Mod, loop, 1, meck:loop([meck:val(a), meck:seq([b, c]), meck:seq([meck:raise(error, d), meck:seq([e, f, g]), h, meck:val(i)])])), %% Then ?assertEqual(a, Mod:loop(1001)), ?assertEqual(b, Mod:loop(1001)), ?assertEqual(c, Mod:loop(1001)), ?assertException(error, d, Mod:loop(1001)), ?assertEqual(e, Mod:loop(1001)), ?assertEqual(f, Mod:loop(1001)), ?assertEqual(g, Mod:loop(1001)), ?assertEqual(h, Mod:loop(1001)), ?assertEqual(i, Mod:loop(1001)), %% The second round ?assertEqual(a, Mod:loop(1001)), ?assertEqual(b, Mod:loop(1001)), ?assertEqual(c, Mod:loop(1001)), ?assertException(error, d, Mod:loop(1001)), ?assertEqual(e, Mod:loop(1001)), ?assertEqual(f, Mod:loop(1001)), ?assertEqual(g, Mod:loop(1001)), ?assertEqual(h, Mod:loop(1001)), ?assertEqual(i, Mod:loop(1001)), %% The third round ?assertEqual(a, Mod:loop(1001)). expect_loop_in_seq_(Mod) -> %% When meck:expect(Mod, seq, 1, meck:seq([meck:val(a), meck:loop([b, meck:raise(throw, c), d]), meck:val(e), % Never returned meck:raise(exit, f)])), %% Then ?assertEqual(a, Mod:seq(1001)), ?assertEqual(b, Mod:seq(1001)), ?assertException(throw, c, Mod:seq(1001)), ?assertEqual(d, Mod:seq(1001)), %% The second round ?assertEqual(b, Mod:seq(1001)), ?assertException(throw, c, Mod:seq(1001)), ?assertEqual(d, Mod:seq(1001)), %% The third round ?assertEqual(b, Mod:seq(1001)). expect_args_exception_(Mod) -> %% Given meck:expect(Mod, f, [{[1001], meck:raise(error, a)}, {[1002], meck:raise(throw, b)}, {[1003], meck:raise(exit, c)}, {[1004], meck:val(d)}]), %% When/Then ?assertException(error, a, Mod:f(1001)), ?assertException(throw, b, Mod:f(1002)), ?assertException(exit, c, Mod:f(1003)), ?assertMatch(d, Mod:f(1004)). expect_arity_exception_(Mod) -> %% Given meck:expect(Mod, f, 1, meck:raise(error, a)), %% When/Then ?assertError(a, Mod:f(1001)). expect_arity_clause_(Mod) -> %% Given meck:expect(Mod, foo, [{2, blah}]), %% When/Then ?assertMatch(blah, Mod:foo(1, 2)), ?assertError(_, Mod:foo(1, 2, 3)). loop_multi_(Mod) -> meck:new(mymod2, [non_strict]), Mods = [Mod, mymod2], Loop = [a, b, c, d, e], ?assertEqual(ok, meck:loop(Mods, l, 2, Loop)), [[?assertEqual(V, M:l(a, b)) || _ <- lists:seq(1, length(Loop)), V <- Loop] || M <- Mods], ?assert(meck:validate(Mods)). expect_args_pattern_override_(Mod) -> %% When meck:expect(Mod, f, [{[1, 1], a}, {[1, '_'], b}, {['_', '_'], c}]), %% Then ?assertEqual(a, Mod:f(1, 1)), ?assertEqual(b, Mod:f(1, 2)), ?assertEqual(c, Mod:f(2, 2)). expect_args_pattern_shadow_(Mod) -> %% When meck:expect(Mod, f, [{[1, 1], a}, {['_', '_'], c}, {[1, '_'], b}]), %% Then ?assertEqual(a, Mod:f(1, 1)), ?assertEqual(c, Mod:f(1, 2)), ?assertEqual(c, Mod:f(2, 2)). expect_args_pattern_missing_(Mod) -> %% When meck:expect(Mod, f, [{[1, 1], a}, {[1, '_'], b}]), %% Then ?assertError(function_clause, Mod:f(2, 2)), ?assertEqual(a, Mod:f(1, 1)), ?assertEqual(b, Mod:f(1, 2)). expect_args_pattern_invalid_(Mod) -> %% When/Then ?assertError({invalid_arity, {{expected, 2}, {actual, 3}, {clause, {[1, 2, 3], b}}}}, meck:expect(Mod, f, [{[1, 2], a}, {[1, 2, 3], b}])). expect_args_matchers_(Mod) -> %% When meck:expect(Mod, f, [{[1, meck:is(fun(X) -> X == 1 end)], a}, {[1, meck:is(fun(X) -> X < 3 end)], b}, {['_', '_'], c}]), %% Then ?assertEqual(a, Mod:f(1, 1)), ?assertEqual(b, Mod:f(1, 2)), ?assertEqual(c, Mod:f(2, 2)). expect_ret_specs_(Mod) -> %% When meck:expect(Mod, f, [{[1, 1], meck:seq([a, b, c])}, {[1, '_'], meck:loop([d, e])}, {['_', '_'], meck:val(f)}]), %% Then ?assertEqual(d, Mod:f(1, 2)), ?assertEqual(f, Mod:f(2, 2)), ?assertEqual(e, Mod:f(1, 2)), ?assertEqual(a, Mod:f(1, 1)), ?assertEqual(d, Mod:f(1, 2)), ?assertEqual(b, Mod:f(1, 1)), ?assertEqual(c, Mod:f(1, 1)), ?assertEqual(f, Mod:f(2, 2)), ?assertEqual(c, Mod:f(1, 1)), ?assertEqual(e, Mod:f(1, 2)), ?assertEqual(c, Mod:f(1, 1)). %% --- Tests with own setup ---------------------------------------------------- validate_options_test() -> Mod = validate_options, try meck:new(Mod, passthrough), throw(failed) catch error:function_clause -> ok end. merge_expects_module_test() -> Mod = merge_mod, meck:new(Mod, [non_strict, merge_expects]), %% Given meck:expect(Mod, f, [2001], meck:raise(error, a)), meck:expect(Mod, f, [2002], meck:raise(throw, b)), meck:expect(Mod, f, [2003], meck:raise(exit, c)), meck:expect(Mod, f, [2004], meck:val(d)), %% When/Then ?assertException(error, a, Mod:f(2001)), ?assertException(throw, b, Mod:f(2002)), ?assertException(exit, c, Mod:f(2003)), ?assertMatch(d, Mod:f(2004)), meck:unload(Mod). merge_expects_ret_specs_test() -> Mod = merge_mod, meck:new(Mod, [non_strict, merge_expects]), %% When meck:expect(Mod, f, [1, 1], meck:seq([a, b, c])), meck:expect(Mod, f, [1, '_'], meck:loop([d, e])), meck:expect(Mod, f, ['_', '_'], meck:val(f)), %% Then ?assertEqual(d, Mod:f(1, 2)), ?assertEqual(f, Mod:f(2, 2)), ?assertEqual(e, Mod:f(1, 2)), ?assertEqual(a, Mod:f(1, 1)), ?assertEqual(d, Mod:f(1, 2)), ?assertEqual(b, Mod:f(1, 1)), ?assertEqual(c, Mod:f(1, 1)), ?assertEqual(f, Mod:f(2, 2)), ?assertEqual(c, Mod:f(1, 1)), ?assertEqual(e, Mod:f(1, 2)), ?assertEqual(c, Mod:f(1, 1)), meck:unload(Mod). merge_expects_passthrough_test() -> meck:new(meck_test_module, [passthrough, merge_expects]), %% When meck:expect(meck_test_module, c, [1, 1], meck:seq([a, b, c])), %% Then ?assertEqual({a, b}, meck_test_module:c(a, b)), ?assertEqual(a, meck_test_module:c(1, 1)), ?assertEqual(b, meck_test_module:c(1, 1)), ?assertEqual(c, meck_test_module:c(1, 1)), %% When meck:expect(meck_test_module, c, [1, '_'], meck:loop([d, e])), %% Then ?assertEqual({a, b}, meck_test_module:c(a, b)), ?assertEqual(d, meck_test_module:c(1, 2)), ?assertEqual(e, meck_test_module:c(1, 2)), ?assertEqual(d, meck_test_module:c(1, 2)), ?assertEqual(e, meck_test_module:c(1, 2)), %% And ?assertEqual(c, meck_test_module:c(1, 1)), %% When meck:expect(meck_test_module, c, ['_', '_'], meck:val(f)), %% Then ?assertEqual(f, meck_test_module:c(a, b)), %% And ?assertEqual(d, meck_test_module:c(1, 2)), ?assertEqual(e, meck_test_module:c(1, 2)), ?assertEqual(d, meck_test_module:c(1, 2)), ?assertEqual(e, meck_test_module:c(1, 2)), ?assertEqual(c, meck_test_module:c(1, 1)), meck:delete(meck_test_module, c, 2), ?assertEqual({1, 1}, meck_test_module:c(1, 1)), meck:unload(meck_test_module). undefined_module_test() -> %% When/Then ?assertError({{undefined_module, blah}, _}, meck:new(blah, [no_link])). undefined_function_test() -> %% Given meck:new(meck_test_module), %% When/Then meck:expect(meck_test_module, b, 0, ok), ?assertError({undefined_function, {meck_test_module, b, 1}}, meck:expect(meck_test_module, b, 1, ok)), meck:unload(meck_test_module). call_original_test() -> false = code:purge(meck_test_module), ?assertEqual({module, meck_test_module}, code:load_file(meck_test_module)), ok = meck:new(meck_test_module, [no_passthrough_cover]), ?assertEqual({file, ""}, code:is_loaded(meck_test_module_meck_original)), ok = meck:expect(meck_test_module, a, fun() -> c end), ok = meck:expect(meck_test_module, b, fun() -> meck:passthrough([]) end), ?assertEqual(c, meck_test_module:a()), ?assertEqual(b, meck_test_module:b()), ok = meck:unload(meck_test_module). unload_renamed_original_test() -> ok = meck:new(meck_test_module), ok = meck:unload(meck_test_module), ?assertEqual(false, code:is_loaded(meck_test_module_meck_original)). unload_all_test() -> Mods = [test_a, test_b, test_c, test_d, test_e], ok = meck:new(Mods, [non_strict]), ?assertEqual(lists:sort(Mods), lists:sort(meck:unload())), [?assertEqual(false, code:is_loaded(M)) || M <- Mods]. reload_module_test() -> false = code:purge(meck_test_module), ?assertEqual({module, meck_test_module}, code:load_file(meck_test_module)), ok = meck:new(meck_test_module), ?assertEqual(ok, meck:unload(meck_test_module)), ?assertMatch({file, _}, code:is_loaded(meck_test_module)), false = code:purge(meck_test_module), true = code:delete(meck_test_module), ?assertEqual(false, code:is_loaded(meck_test_module)), ok = meck:new(meck_test_module), ?assertMatch({file, _}, code:is_loaded(meck_test_module)), ?assertEqual(ok, meck:unload(meck_test_module)), ?assertEqual(false, code:is_loaded(meck_test_module)). original_no_file_test() -> {ok, Mod, Beam} = compile:forms([{attribute, 1, module, meck_not_on_disk}]), {module, Mod} = code:load_binary(Mod, "", Beam), ?assertEqual(ok, meck:new(meck_not_on_disk)), ok = meck:unload(meck_not_on_disk). original_has_no_object_code_test() -> {ok, Mod, Beam} = compile:forms([{attribute, 1, module, meck_on_disk}]), ok = file:write_file("meck_on_disk.beam", Beam), {module, Mod} = code:load_binary(Mod, "meck_on_disk.beam", Beam), ?assertEqual(ok, meck:new(meck_on_disk)), ok = file:delete("meck_on_disk.beam"), ok = meck:unload(meck_on_disk). passthrough_with_no_object_code_test() -> {ok, Mod, Beam} = compile:forms([{attribute, 1, module, no_abstract_code}]), ok = file:write_file("no_abstract_code.beam", Beam), {module, Mod} = code:load_binary(Mod, "no_abstract_code.beam", Beam), ?assertError( {abstract_code_not_found, Mod}, meck:new(no_abstract_code, [passthrough, no_link]) ), ok = file:delete("no_abstract_code.beam"). passthrough_nonexisting_module_test() -> ok = meck:new(mymod, [passthrough, non_strict]), ok = meck:expect(mymod, test, fun() -> ok end), ?assertEqual(ok, mymod:test()), ok = meck:unload(mymod). passthrough_test() -> passthrough_test([]). passthrough_test(Opts) -> ok = meck:new(meck_test_module, [passthrough|Opts]), ok = meck:expect(meck_test_module, a, fun() -> c end), ?assertEqual(c, meck_test_module:a()), ?assertEqual(b, meck_test_module:b()), ?assertEqual({1, 2}, meck_test_module:c(1, 2)), ok = meck:unload(meck_test_module). passthrough_different_arg_test() -> ok = meck:new(meck_test_module), ok = meck:expect(meck_test_module, c, fun(_, _) -> meck:passthrough([x, y]) end), ?assertEqual({x, y}, meck_test_module:c(1, 2)), ok = meck:unload(meck_test_module). passthrough_bif_test() -> ?assertEqual(ok, meck:new(file, [unstick, passthrough])), ?assertEqual(ok, meck:unload(file)). stub_all_test() -> ok = meck:new(meck_test_module, [{stub_all, meck:seq([a, b])}]), ok = meck:expect(meck_test_module, a, [], c), ?assertEqual(c, meck_test_module:a()), ?assertEqual(a, meck_test_module:b()), ?assertEqual(b, meck_test_module:b()), ?assertEqual(b, meck_test_module:b()), ?assertEqual(a, meck_test_module:c(1, 2)), ?assertEqual(b, meck_test_module:c(1, 2)), ?assertEqual(b, meck_test_module:c(1, 2)), ok = meck:unload(meck_test_module). stub_all_default_test() -> ok = meck:new(meck_test_module, [stub_all]), ?assertEqual(ok, meck_test_module:c(1, 2)), ok = meck:unload(meck_test_module). stub_all_undefined_test() -> ok = meck:new(meck_test_module, [{stub_all, undefined}]), ?assertEqual(undefined, meck_test_module:c(1, 2)), ok = meck:unload(meck_test_module). stub_all_true_test() -> ok = meck:new(meck_test_module, [{stub_all, true}]), ?assertEqual(true, meck_test_module:c(1, 2)), ok = meck:unload(meck_test_module). stub_all_overridden_by_passthrough_test() -> ok = meck:new(meck_test_module, [stub_all, passthrough]), ?assertEqual(a, meck_test_module:a()), ok = meck:unload(meck_test_module). mock_file_existing_test() -> %% Given ExistingFile = test_file(?MODULE, ".erl"), {ok, ExistsInfo} = file:read_file_info(ExistingFile), meck:new(file, [unstick, passthrough]), %% When meck:expect(file, read_file_info, fun(Path) -> meck:passthrough([Path]) end), %% Then ?assertEqual({ok, ExistsInfo}, file:read_file_info(ExistingFile)), %% Cleanup meck:unload(file). mock_file_missing_test() -> %% Given MissingFile = "blah.erl", {error, enoent} = file:read_file_info(MissingFile), meck:new(file, [unstick, passthrough]), %% When meck:expect(file, read_file_info, 1, {ok, no_info}), %% Then ?assertEqual({ok, no_info}, file:read_file_info(MissingFile)), %% Cleanup meck:unload(file). cover_test() -> {ok, _} = cover:compile(test_file(meck_test_module, ".erl")), a = meck_test_module:a(), b = meck_test_module:b(), {1, 2} = meck_test_module:c(1, 2), {ok, {meck_test_module, {3,0}}} = cover:analyze(meck_test_module, module), run_mock_no_cover_file(meck_test_module), {ok, {meck_test_module, {3,0}}} = cover:analyze(meck_test_module, module). cover_options_test_() -> {foreach, fun compile_options_setup/0, fun compile_options_teardown/1, [{with, [T]} || T <- [fun cover_options_/1, fun cover_options_fail_/1 ]]}. compile_options_setup() -> Module = cover_test_module, % Our test module won't compile without compiler options that % rebar won't give it, thus the rename dance. Src = test_file(Module, ".erl"), ok = file:rename(test_file(Module, ".dontcompile"), Src), OldPath = code:get_path(), code:add_path(test_dir()), {OldPath, Src, Module}. compile_options_teardown({OldPath, Src, Module}) -> file:rename(Src, test_file(Module, ".dontcompile")), code:purge(Module), code:delete(Module), code:set_path(OldPath). cover_options_({_OldPath, Src, Module}) -> % Test that compilation options (include paths and preprocessor % definitions) are used when un-mecking previously cover compiled % modules. CompilerOptions = [{i, test_include()}, {d, 'TEST', true}], % The option recover feature depends on having the BEAM file % available. {ok, _} = compile:file(Src, [{outdir, test_dir()}|CompilerOptions]), {ok, _} = cover:compile(Src, CompilerOptions), a = Module:a(), b = Module:b(), {1, 2} = Module:c(1, 2), % We get 2 instead of 3 as expected. Maybe because cover doesn't % count include files? ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)), run_mock_no_cover_file(Module), % 2 instead of 3, as above ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)). cover_options_fail_({_OldPath, Src, Module}) -> %% This may look like the test above but there is a subtle %% difference. When `cover:compile_beam' is called it squashes %% compile options. This test verifies that function `b/0', which %% relies on the `TEST' directive being set can still be called %% after the module is meck'ed. CompilerOptions = [ debug_info, {i, test_include()}, {outdir, test_dir()}, {d, 'TEST', true} ], {ok, _} = compile:file(Src, CompilerOptions), ?assertEqual( proplists:delete(outdir, lists:sort(CompilerOptions)), proplists:delete(outdir, lists:sort(meck_code:compile_options(Module))) ), {ok, _} = cover:compile_beam(Module), ActualCompileOpts = meck_code:compile_options(Module), ?assertEqual({i, test_include()}, lists:keyfind(i, 1, ActualCompileOpts)), ?assertEqual({d, 'TEST', true}, lists:keyfind(d, 1, ActualCompileOpts)), a = Module:a(), b = Module:b(), {1, 2} = Module:c(1, 2), ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)), ok = meck:new(Module, [passthrough]), ok = meck:expect(Module, a, fun () -> c end), ?assertEqual(c, Module:a()), ?assertEqual(b, Module:b()), ?assertEqual({1, 2}, Module:c(1, 2)), ok = meck:unload(Module), %% Verify passthru calls went to cover ?assertEqual({ok, {Module, 4}}, cover:analyze(Module, calls, module)). test_file(Module, Ext) -> filename:join(test_dir(), atom_to_list(Module) ++ Ext). test_dir() -> case code:which(?MODULE) of Filename when is_list(Filename) -> filename:dirname(Filename); Atom when is_atom(Atom) -> error({test_dir_not_found, ?MODULE, Atom}) end. test_include() -> filename:join(test_dir(), "include"). run_mock_no_cover_file(Module) -> ok = meck:new(Module), ok = meck:expect(Module, a, fun () -> c end), ?assertEqual(c, Module:a()), ok = meck:unload(Module), ?assert(not filelib:is_file(atom_to_list(Module) ++ ".coverdata")). %% @doc Verify that passthrough calls _don't_ appear in cover %% analysis. no_cover_passthrough_test() -> {ok, _} = cover:compile("test/meck_test_module.erl"), {ok, {meck_test_module, {0,3}}} = cover:analyze(meck_test_module, module), passthrough_test([no_passthrough_cover]), {ok, {meck_test_module, {0,3}}} = cover:analyze(meck_test_module, module). %% @doc Verify that passthrough calls appear in cover analysis. cover_passthrough_test() -> {ok, _} = cover:compile("test/meck_test_module.erl"), ?assertEqual({ok, {meck_test_module, {0,3}}}, cover:analyze(meck_test_module, module)), passthrough_test([]), ?assertEqual({ok, {meck_test_module, {2,1}}}, cover:analyze(meck_test_module, module)). cover_no_meck_original_in_cover_export_test() -> {ok, _} = cover:compile("test/meck_test_module.erl"), passthrough_test([]), Filename = lists:flatten( io_lib:format( "tmp_~b_~p.coverdata", [rand:uniform(100_000), meck_util:original_name(meck_test_module)] ) ), try ok = cover:export(Filename), ok = cover:import(Filename) after _ = file:delete(Filename) end, ?assertNot( lists:member(meck_util:original_name(meck_test_module), cover:imported_modules()), "the meck generated module should not be in the exported cover data" ). cover_path_test() -> {ok, _} = cover:compile("test/meck_test_module.erl"), ?assertEqual({ok, {meck_test_module, {0,3}}}, cover:analyze(meck_test_module, module)), ok = meck:new(meck_test_module, [passthrough]), ok = meck:expect(meck_test_module, a, fun() -> c end), ?assertEqual(c, meck_test_module:a()), ?assertEqual(b, meck_test_module:b()), ?assertEqual({1, 2}, meck_test_module:c(1, 2)), {ok, CWD} = file:get_cwd(), try ok = file:set_cwd("/tmp"), ok = meck:unload(meck_test_module), ?assertEqual({ok, {meck_test_module, {2,1}}}, cover:analyze(meck_test_module, module)) after ok = file:set_cwd(CWD) end. % @doc The mocked module is unloaded if the meck process crashes. unload_when_crashed_test() -> ok = meck:new(mymod, [non_strict]), ?assertMatch({file, _}, code:is_loaded(mymod)), SaltedName = mymod_meck, Pid = whereis(SaltedName), ?assertEqual(true, is_pid(Pid)), unlink(Pid), error_logger:tty(false), exit(Pid, expected_test_exit), timer:sleep(100), error_logger:tty(true), ?assertEqual(undefined, whereis(SaltedName)), ?assertEqual(false, code:is_loaded(mymod)). % @doc The mocked module is unloaded if the meck process crashes. unlink_test() -> ok = meck:new(mymod, [no_link, non_strict]), SaltedName = mymod_meck, {links, Links} = process_info(whereis(SaltedName), links), ?assert(not lists:member(self(), Links)), ok = meck:unload(mymod). %% @doc A concurrent process calling into the mocked module while it's %% being unloaded gets either the mocked response or the original %% response, but won't crash. atomic_unload_test() -> ok = meck:new(meck_test_module), ok = meck:expect(meck_test_module, a, fun () -> c end), %% Suspend the meck_proc in order to ensure all messages are in %% its inbox in the correct order before it would process them Proc = meck_util:proc_name(meck_test_module), sys:suspend(Proc), StopReq = concurrent_req( Proc, fun () -> ?assertEqual(ok, meck:unload(meck_test_module)) end), SpecReq = concurrent_req( Proc, fun () -> ?assertMatch(V when V =:= a orelse V =:= c, meck_test_module:a()) end), sys:resume(Proc), ?assertEqual(normal, wait_concurrent_req(StopReq)), ?assertEqual(normal, wait_concurrent_req(SpecReq)). %% @doc Exception is thrown when you run expect on a non-existing (and not yet %% mocked) module. expect_without_new_test() -> ?assertError({not_mocked, othermod}, meck:expect(othermod, test, fun() -> ok end)). history_passthrough_test() -> ok = meck:new(meck_test_module, [passthrough]), ok = meck:expect(meck_test_module, a, fun() -> c end), c = meck_test_module:a(), b = meck_test_module:b(), ?assertEqual([{self(), {meck_test_module, a, []}, c}, {self(), {meck_test_module, b, []}, b}], meck:history(meck_test_module)), ok = meck:unload(meck_test_module). multi_test() -> Mods = [mod1, mod2, mod3], ok = meck:new(Mods, [non_strict]), ok = meck:expect(Mods, test, fun() -> ok end), ok = meck:expect(Mods, test2, 0, ok), [?assertEqual(ok, M:test()) || M <- Mods], ?assert(meck:validate(Mods)), ok = meck:unload(Mods). multi_invalid_test() -> Mods = [mod1, mod2, mod3], ok = meck:new(Mods, [non_strict]), ok = meck:expect(Mods, test, fun(1) -> ok end), ?assertError(function_clause, mod2:test(2)), ?assert(not meck:validate(Mods)), ok = meck:unload(Mods). multi_option_test() -> Mods = [mod1, mod2, mod3], ok = meck:new(Mods, [passthrough, non_strict]), ok = meck:expect(Mods, test, fun() -> ok end), [?assertEqual(ok, M:test()) || M <- Mods], ?assert(meck:validate(Mods)), ok = meck:unload(Mods). multi_shortcut_test() -> Mods = [mod1, mod2, mod3], ok = meck:new(Mods, [non_strict]), ok = meck:expect(Mods, test, 0, ok), [?assertEqual(ok, M:test()) || M <- Mods], ?assert(meck:validate(Mods)), ok = meck:unload(Mods). multi_delete_test() -> Mods = [mod1, mod2, mod3], ok = meck:new(Mods, [non_strict]), ok = meck:expect(Mods, test, 0, ok), ?assertEqual(ok, meck:delete(Mods, test, 0)), [?assertError(undef, M:test()) || M <- Mods], ?assert(meck:validate(Mods)), ok = meck:unload(Mods). multi_expects_test() -> Mods = [mod1, mod2, mod3], ok = meck:new(Mods, [non_strict]), ok = meck:expect(Mods, test, 0, ok), ?assertEqual([{mod1, test, 0}, {mod2, test, 0}, {mod3, test, 0}], lists:sort(meck:expects(Mods))), ok = meck:unload(Mods). multi_reset_test() -> % Given Mods = [mod1, mod2, mod3], meck:new(Mods, [non_strict]), meck:expect(Mods, test1, 0, ok), meck:expect(Mods, test2, 0, ok), mod1:test1(), mod1:test2(), mod2:test1(), mod3:test2(), % When meck:reset(Mods), mod1:test1(), mod1:test1(), % Then ?assertMatch([{_, {mod1, test1, []}, ok}, {_, {mod1, test1, []}, ok}], meck:history(mod1)), ?assertMatch([], meck:history(mod2)), ?assertMatch([], meck:history(mod3)). handle_cast_unmodified_state_test() -> S = dummy_state, ?assertEqual({noreply, S}, meck_proc:handle_cast(dummy_msg, S)). code_change_unmodified_state_test() -> S = dummy_state, ?assertEqual({ok, S}, meck_proc:code_change(old_version, S, [])). remote_meck_test_() -> {foreach, fun remote_setup/0, fun remote_teardown/1, [{with, [T]} || T <- [fun remote_meck_/1, fun remote_meck_cover_/1]]}. remote_setup() -> [] = os:cmd("epmd -daemon"), net_kernel:start([meck_eunit_test]), {ok, Pid, Node} = peer:start_link(#{ name => meck_remote_test, args => ["-pa", test_dir()] }), {Mod, Bin, File} = code:get_object_code(meck), true = rpc:call(Node, code, add_path, [filename:dirname(File)]), {module, Mod} = rpc:call(Node, code, load_binary, [Mod, File, Bin]), {module, meck_test_module} = rpc:call(Node, code, load_file, [meck_test_module]), {Pid, Node, meck_test_module}. remote_teardown({Pid, _Node, _Mod}) -> ok = peer:stop(Pid), ok = net_kernel:stop(). remote_meck_({_Pid, Node, Mod}) -> ?assertEqual(ok, rpc:call(Node, meck, new, [Mod, [no_link, non_strict]])), ?assertEqual(ok, rpc:call(Node, meck, expect, [Mod, test, 0, true])), ?assertEqual(true, rpc:call(Node, Mod, test, [])). remote_meck_cover_({_Pid, Node, Mod}) -> {ok, Mod} = cover:compile(test_file(Mod, ".erl")), {ok, _Nodes} = cover:start([Node]), ?assertEqual(ok, rpc:call(Node, meck, new, [Mod])). can_mock_sticky_modules_test() -> code:stick_mod(meck_test_module), meck:new(meck_test_module, [unstick]), ?assertNot(code:is_sticky(meck_test_module)), meck:unload(meck_test_module), ?assert(code:is_sticky(meck_test_module)), code:unstick_mod(meck_test_module). meck_reentrant_test() -> meck:new(string, [unstick, passthrough]), meck:expect(string, strip, fun(String) -> meck:passthrough([string:reverse(String)]) end), ?assertEqual(string:strip(" ABC "), "CBA"), meck:unload(string). sticky_directory_test_() -> {foreach, fun sticky_setup/0, fun sticky_teardown/1, [{with, [T]} || T <- [fun can_mock_sticky_module_not_yet_loaded_/1, fun cannot_mock_sticky_module_without_unstick_/1]]}. sticky_setup() -> % Find out where the beam file is (purge because it is cover compiled) Module = meck_test_module, false = code:purge(Module), {module, Module} = code:load_file(Module), Beam = code:which(Module), % Unload module so it's not loaded when running meck false = code:purge(Module), true = code:delete(Module), % Create new sticky dir and copy beam file Dir = "sticky_test", ok = filelib:ensure_dir(filename:join(Dir, "dummy")), Dest = filename:join(Dir, filename:basename(Beam)), {ok, _BytesCopied} = file:copy(Beam, Dest), true = code:add_patha(Dir), ok = code:stick_dir(Dir), code:load_file(Module), {Module, {Dir, Dest}}. sticky_teardown({Module, {Dir, Dest}}) -> % Clean up ok = code:unstick_dir(Dir), false = code:purge(Module), true = code:del_path(Dir), ok = file:delete(Dest), ok = file:del_dir(Dir). can_mock_sticky_module_not_yet_loaded_({Mod, _}) -> ?assertEqual(ok, meck:new(Mod, [unstick])), ?assertNot(code:is_sticky(Mod)), ?assertEqual(ok, meck:unload(Mod)), ?assert(code:is_sticky(Mod)). cannot_mock_sticky_module_without_unstick_({Mod, _}) -> error_logger:tty(false), ?assertError({module_is_sticky, Mod}, meck:new(Mod, [no_link])), error_logger:tty(true). can_mock_non_sticky_module_test() -> ?assertNot(code:is_sticky(meck_test_module)), ?assertEqual(ok, meck:new(meck_test_module, [unstick])), ?assertNot(code:is_sticky(meck_test_module)), ?assertEqual(ok, meck:unload(meck_test_module)), ?assertNot(code:is_sticky(meck_test_module)). cannot_expect_bif_or_autogenerated_test() -> ?assertEqual(ok, meck:new(unicode, [unstick, passthrough])), ?assertError({cannot_mock_builtin, {unicode, characters_to_binary, 2}}, meck:expect(unicode, characters_to_binary, 2, doh)), ?assertError({cannot_mock_autogenerated, {unicode, module_info, 0}}, meck:expect(unicode, module_info, 0, doh)), ?assertEqual(ok, meck:unload(unicode)). meck_module_attributes_test() -> ?assertEqual(ok, meck:new(meck_test_module)), ?assertEqual([foobar], proplists:get_value(tag, proplists:get_value(attributes, meck_test_module:module_info()))), ?assertEqual(ok, meck:unload(meck_test_module)). meck_implicit_new_test()-> meck:expect(meck_test_module, c, [{[1, 1], foo}, {['_', '_'], bar}]), ?assertMatch(foo, meck_test_module:c(1, 1)), meck:unload(). wait_for_zero_calls_test() -> %% Given meck:new(test, [non_strict]), %% When/Then ?assertMatch(ok, meck:wait(0, test, foo, [1, '_'], 100)), %% Clean meck:unload(). wait_already_called_test() -> %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), %% When test:foo(1, 2), test:foo(1, 2), %% Then ?assertMatch(ok, meck:wait(2, test, foo, [1, '_'], 100)), %% Clean meck:unload(). wait_not_called_zero_timeout_test() -> %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), %% When test:foo(1, 2), test:foo(1, 2), %% Then ?assertError(timeout, meck:wait(3, test, foo, [1, '_'], 0)), %% Clean meck:unload(). wait_not_called_another_proc_test() -> %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), %% When test:foo(1, 2), % Called, but not by the expected proc. Pid = erlang:spawn(fun() -> test:foo(2, 2) % Unexpected first argument end), %% Then ?assertError(timeout, meck:wait(1, test, foo, [1, '_'], Pid, 100)), %% Clean meck:unload(). wait_called_another_proc_test() -> %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), %% When Pid = erlang:spawn(fun() -> timer:sleep(50), test:foo(1, 2), test:foo(2, 2), % Unexpected first argument test:foo(1, 2) end), %% Then ?assertMatch(ok, meck:wait(2, test, foo, [1, '_'], Pid, 500)), %% Clean meck:unload(). wait_timeout_test() -> %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), %% When test:foo(1, 2), %% Then ?assertError(timeout, meck:wait(2, test, foo, [1, '_'], '_', 10)), %% Clean meck:unload(). wait_for_the_same_pattern_on_different_processes_test() -> error_logger:tty(false), %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), Pid1 = erlang:spawn(fun() -> ?assertMatch(ok, meck:wait(2, test, foo, [1, 2], 100)) end), MonitorRef1 = erlang:monitor(process, Pid1), Pid2 = erlang:spawn(fun() -> ?assertMatch(ok, meck:wait(3, test, foo, [1, 2], 100)) end), MonitorRef2 = erlang:monitor(process, Pid2), %% When timer:sleep(50), test:foo(1, 2), test:foo(1, 2), %% Then ?assertTerminated(MonitorRef1, normal, 300), ?assertTerminated(MonitorRef2, {timeout, _}, 300), %% Clean meck:unload(), error_logger:tty(true). wait_for_different_patterns_on_different_processes_test() -> error_logger:tty(false), %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 1, ok), meck:expect(test, bar, 2, ok), Pid1 = erlang:spawn(fun() -> ?assertMatch(ok, meck:wait(2, test, foo, [1], 100)) end), MonitorRef1 = erlang:monitor(process, Pid1), Pid2 = erlang:spawn(fun() -> ?assertMatch(ok, meck:wait(3, test, bar, [1, 2], 100)) end), MonitorRef2 = erlang:monitor(process, Pid2), %% When timer:sleep(50), test:bar(1, 2), test:foo(1), test:bar(1, 2), test:bar(1, 2), %% Then ?assertTerminated(MonitorRef1, {timeout, _}, 300), ?assertTerminated(MonitorRef2, normal, 300), %% Clean meck:unload(), error_logger:tty(true). wait_purge_expired_tracker_test() -> %% Given meck:new(test, [non_strict]), meck:expect(test, foo, 2, ok), ?assertError(timeout, meck:wait(1, test, foo, [1, '_'], 1)), %% When timer:sleep(50), % Makes expired tracker be purged. There is no way to check that from the % code only in the coverage report. But at least we exercise this code path % here. test:foo(1, 2), %% Clean meck:unload(). mocked_test() -> %% At start, no modules should be mocked: [] = meck:mocked(), %% Mocking a new module adds it to the list of mocked modules: meck:new(mocked_test, [non_strict]), [mocked_test] = meck:mocked(), %% Mocking with implicit `meck:new` also adds to the list of mocked modules: meck:expect(meck_test_module, c, 2, ok), [meck_test_module, mocked_test] = lists:sort(meck:mocked()), meck:new([foo, bar], [non_strict]), [bar, foo, meck_test_module, mocked_test] = lists:sort(meck:mocked()), ok = meck:unload([foo, meck_test_module]), [bar, mocked_test] = lists:sort(meck:mocked()), meck:unload(), %% After unload all, no modules should be mocked again [] = meck:mocked(). meck_passthrough_test_() -> {foreach, fun setup_passthrough/0, fun teardown/1, [{with, [T]} || T <- [ fun delete_passthrough_/1, fun delete_passthrough_force_/1, fun expects_passthrough_/1 ]]}. setup_passthrough() -> % Uncomment to run tests with dbg: % dbg:tracer(), % dbg:p(all, call), % dbg:tpl(meck, []), ok = meck:new(meck_test_module, [passthrough, non_strict]), meck_test_module. delete_passthrough_(Mod) -> ok = meck:expect(Mod, c, 2, {c, d}), ?assertMatch({c, d}, Mod:c(a, b)), ?assertEqual(ok, meck:delete(Mod, c, 2)), ?assertMatch({a, b}, Mod:c(a, b)), ?assert(meck:validate(Mod)). delete_passthrough_force_(Mod) -> ok = meck:expect(Mod, c, 2, ok), ?assertEqual(ok, meck:delete(Mod, c, 2, true)), ?assertError(undef, Mod:test(a, b)), ?assert(meck:validate(Mod)). expects_passthrough_(Mod) -> ok = meck:expect(Mod, test, 2, ok), ?assertEqual([{Mod, a, 0}, {Mod, b, 0}, {Mod, c, 2}, {Mod, test, 2}], lists:sort(meck:expects(Mod, false))), ?assertEqual([{Mod, test, 2}], meck:expects(Mod, true)). %%============================================================================= %% Internal Functions %%============================================================================= assert_called(Mod, Function, Args, WasCalled) -> ?assertEqual(WasCalled, meck:called(Mod, Function, Args)), ?assert(meck:validate(Mod)). assert_called(Mod, Function, Args, Pid, WasCalled) -> ?assertEqual(WasCalled, meck:called(Mod, Function, Args, Pid)), ?assert(meck:validate(Mod)). %% @doc Spawn a new process to concurrently call `Fun'. `Fun' is %% expected to send a request to the specified process, and this %% function will wait for this message to arrive. (Therefore the %% process should be suspended and not consuming its message queue.) %% %% The returned request handle can be used later in in {@link %% wait_concurrent_req/1} to wait for the concurrent process to %% terminate. concurrent_req(Name, Fun) when is_atom(Name) -> case whereis(Name) of Pid when is_pid(Pid) -> concurrent_req(Pid, Fun); undefined -> exit(noproc) end; concurrent_req(Pid, Fun) when is_pid(Pid) -> {message_queue_len, Msgs} = process_info(Pid, message_queue_len), Req = spawn_monitor(Fun), wait_message(Pid, Msgs + 1, 100), Req. %% @doc Wait for a concurrent request started with {@link %% concurrent_req/2} to terminate. The return value is the exit reason %% of the process. wait_concurrent_req(Req = {Pid, Monitor}) -> receive {'DOWN', Monitor, process, Pid, Reason} -> Reason after 1000 -> exit(Pid, kill), wait_concurrent_req(Req) end. wait_message(Pid, _ExpMsgs, Retries) when Retries < 0 -> exit(Pid, kill), exit(wait_message_timeout); wait_message(Pid, ExpMsgs, Retries) -> {message_queue_len, Msgs} = process_info(Pid, message_queue_len), if Msgs >= ExpMsgs -> ok; true -> timer:sleep(1), wait_message(Pid, ExpMsgs, Retries - 1) end. meck-1.0.0/test/meck_test_module.erl0000664000175000017500000000024014726775702020032 0ustar debalancedebalance-module(meck_test_module). -tag(foobar). -deprecated([a/0]). -export([a/0, b/0, c/2]). -spec ?MODULE:a() -> a | b. a() -> a. b() -> b. c(A, B) -> {A, B}. meck-1.0.0/test/meck_performance_test.erl0000664000175000017500000000445314726775702021060 0ustar debalancedebalance%% @doc -module(meck_performance_test). %% Interface exports -export([run/0]). -export([run/1]). -export([run/2]). -export([new_unload/3]). %%============================================================================= %% Interface exports %%============================================================================= run() -> run(meck, 100). run(N) -> run(meck, N). run(Mod, N) -> header(Mod), run("new+unload", ?MODULE, new_unload, [Mod, test, [non_strict]], N), Mod:new(test, [non_strict]), run("expect/3", Mod, expect, [test, normal, fun() -> ok end], N), run("expect/3+args", Mod, expect, [test, normal_args, fun(_, _) -> ok end], N), run("expect/4", Mod, expect, [test, shortcut, 0, ok], N), run("expect/4+args", Mod, expect, [test, shortcut_args, 2, ok], N), header("calls"), Mod:expect(test, shortcut_opaque, 0, self()), run("normal", test, normal, [], N), run("normal_args", test, normal_args, [a, b], N), run("shortcut", test, shortcut, [], N), run("shortcut_args", test, shortcut_args, [a, b], N), run("shortcut_opaque", test, shortcut_opaque, [], N), Mod:unload(test), header("history"), Mod:new(test, [non_strict]), Mod:expect(test, func, 1, ok), [test:func(I) || I <- lists:seq(1, 100)], run("called", Mod, called, [test, func, 50], N), Mod:unload(test), ok. new_unload(Mod, Mock, Opts) -> Mod:new(Mock, Opts), Mod:unload(Mock). %%============================================================================= %% Internal functions %%============================================================================= header(Name) -> io:format("~n~s\t\tMin\tMax\tMed\tAvg~n", [Name]), io:format("------------------------------------------------~n"). run(Name, M, F, A, N) -> io:fwrite("~-16s", [Name]), io:format("~p\t~p\t~p\t~p~n", test_avg(M, F, A, N)). test_avg(M, F, A, N) when N > 0 -> L = test_loop(M, F, A, N, []), Length = length(L), Min = lists:min(L), Max = lists:max(L), Med = lists:nth(round((Length / 2)), lists:sort(L)), Avg = round(lists:foldl(fun(X, Sum) -> X + Sum end, 0, L) / Length), [Min, Max, Med, Avg]. test_loop(_M, _F, _A, 0, List) -> List; test_loop(M, F, A, N, List) -> {T, _Result} = timer:tc(M, F, A), test_loop(M, F, A, N - 1, [T|List]). meck-1.0.0/test/meck_history_tests.erl0000664000175000017500000001546614726775702020451 0ustar debalancedebalance%%%============================================================================ %%% Copyright 2013 Maxim Vladimirsky %%% %%% 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. %%%============================================================================ -module(meck_history_tests). -include_lib("eunit/include/eunit.hrl"). history_test_() -> {foreach, fun setup/0, fun teardown/1, [ fun num_calls_with_arity/0, fun capture_different_positions/0, fun capture_different_args_specs/0, fun result_different_positions/0, fun result_different_args_specs/0, fun result_exception/0, fun result_different_caller/0, fun history_kept_while_reloading/0 ]}. setup() -> %% Given meck:new(test, [non_strict]), ok. teardown(_) -> %% Cleanup meck:unload(). num_calls_with_arity() -> meck:expect(test, foo, 2, ok), meck:expect(test, foo, 3, ok), %% When test:foo(1, 2, 3), test:foo(1, 2), test:foo(1, 2, 3), test:foo(1, 2, 3), test:foo(1, 2), %% Then ?assertMatch(2, meck:num_calls(test, foo, 2)), ?assertMatch(3, meck:num_calls(test, foo, 3)), ?assertMatch(0, meck:num_calls(test, foo, 4)). capture_different_positions() -> meck:expect(test, foo, 3, ok), meck:expect(test, foo, 4, ok), meck:expect(test, bar, 3, ok), test:foo(1001, 2001, 3001, 4001), test:bar(1002, 2002, 3002), test:foo(1003, 2003, 3003), test:bar(1004, 2004, 3004), test:foo(1005, 2005, 3005), test:foo(1006, 2006, 3006), test:bar(1007, 2007, 3007), test:foo(1008, 2008, 3008), %% When/Then ?assertMatch(2003, meck:capture(first, test, foo, ['_', '_', '_'], 2)), ?assertMatch(2008, meck:capture(last, test, foo, ['_', '_', '_'], 2)), ?assertMatch(2006, meck:capture(3, test, foo, ['_', '_', '_'], 2)), ?assertError(not_found, meck:capture(5, test, foo, ['_', '_', '_'], 2)). capture_different_args_specs() -> meck:expect(test, foo, 2, ok), meck:expect(test, foo, 3, ok), meck:expect(test, foo, 4, ok), meck:expect(test, bar, 3, ok), test:foo(1001, 2001, 3001, 4001), test:bar(1002, 2002, 3002), test:foo(1003, 2003, 3003), test:bar(1004, 2004, 3004), test:foo(1005, 2005), test:foo(1006, 2006, 3006), test:bar(1007, 2007, 3007), test:foo(1008, 2008, 3008), %% When/Then ?assertMatch(2001, meck:capture(first, test, foo, '_', 2)), ?assertMatch(2003, meck:capture(first, test, foo, 3, 2)), ?assertMatch(2005, meck:capture(first, test, foo, ['_', '_'], 2)), ?assertMatch(2006, meck:capture(first, test, foo, [1006, '_', '_'], 2)), ?assertMatch(2008, meck:capture(first, test, foo, ['_', '_', meck:is(fun(X) -> X > 3006 end)], 2)). result_different_positions() -> meck:expect(test, foo, fun(_, A, _) -> A end), meck:expect(test, foo, 4, ok), meck:expect(test, bar, 3, ok), %% When test:foo(1001, 2001, 3001, 4001), test:bar(1002, 2002, 3002), test:foo(1003, 2003, 3003), test:bar(1004, 2004, 3004), test:foo(1005, 2005, 3005), test:foo(1006, 2006, 3006), test:bar(1007, 2007, 3007), test:foo(1008, 2008, 3008), %% Then ?assertMatch(2003, meck_history:result(first, '_', test, foo, ['_', '_', '_'])), ?assertMatch(2008, meck_history:result(last, '_', test, foo, ['_', '_', '_'])), ?assertMatch(2006, meck_history:result(3, '_', test, foo, ['_', '_', '_'])), ?assertError(not_found, meck_history:result(5, '_', test, foo, ['_', '_', '_'])). result_different_args_specs() -> meck:expect(test, foo, fun(_, A) -> A end), meck:expect(test, foo, fun(_, A, _) -> A end), meck:expect(test, foo, fun(_, A, _, _) -> A end), meck:expect(test, bar, 3, ok), %% When test:foo(1001, 2001, 3001, 4001), test:bar(1002, 2002, 3002), test:foo(1003, 2003, 3003), test:bar(1004, 2004, 3004), test:foo(1005, 2005), test:foo(1006, 2006, 3006), test:bar(1007, 2007, 3007), test:foo(1008, 2008, 3008), %% Then ?assertMatch(2001, meck_history:result(first, '_', test, foo, '_')), ?assertMatch(2003, meck_history:result(first, '_', test, foo, 3)), ?assertMatch(2005, meck_history:result(first, '_', test, foo, ['_', '_'])), ?assertMatch(2006, meck_history:result(first, '_', test, foo, [1006, '_', '_'])), Matcher = meck:is(fun(X) -> X > 3006 end), ?assertMatch(2008, meck_history:result(first, '_', test, foo, ['_', '_', Matcher])). result_exception() -> meck:expect(test, error, fun(R) -> erlang:error(R) end), meck:expect(test, throw, fun(R) -> throw(R) end), meck:expect(test, exit, fun(R) -> exit(R) end), %% When catch test:error(foo), catch test:throw(bar), catch test:exit(baz), %% Then ?assertException(error, foo, meck_history:result(first, '_', test, error, 1)), ?assertException(throw, bar, meck_history:result(first, '_', test, throw, 1)), ?assertException(exit, baz, meck_history:result(first, '_', test, exit, 1)), ?assertError(not_found, meck_history:result(first, '_', test, foo, 0)). result_different_caller() -> meck:expect(test, foo, fun(_, A, _) -> A end), %% When test:foo(1001, 2001, 3001), Self = self(), Pid = spawn(fun() -> test:foo(1002, 2002, 3002), Self ! {self(), done} end), test:foo(1003, 2003, 3003), receive {Pid, done} -> ok end, %% Then ?assertMatch(2003, meck_history:result(2, self(), test, foo, 3)), ?assertMatch(2002, meck_history:result(last, Pid, test, foo, 3)). history_kept_while_reloading() -> NumCalls = 10, meck:new(historical, [non_strict, passthrough]), meck:expect(historical, test_fn, fun(Arg) -> {mocked, Arg} end), Test = self(), Caller = spawn(fun() -> io:format("~nCalls: ~p~n", [lists:reverse(lists:foldl(fun(N, Acc) -> timer:sleep(1), Result = historical:test_fn(N), [{self(), {historical, test_fn, [N]}, Result}|Acc] end, [], lists:seq(1, NumCalls)))]), Test ! {done, self()} end), [ meck:expect(historical, test_fn, fun(Arg) -> {mocked2, Arg} end) || _ <- lists:seq(1, 5) ], receive {done, Caller} -> ok after 1000 -> error(caller_timeout) end, io:format("History: ~p~n", [meck:history(historical)]), ?assertEqual(NumCalls, meck:num_calls(historical, test_fn, '_')). meck-1.0.0/test/cover_test_module.dontcompile0000664000175000017500000000105314726775702021767 0ustar debalancedebalance%% -*- mode: erlang -*- %% This file needs not to have the extension .erl, since otherwise %% rebar will try to compile it, which won't work since it requires %% special compilation options. See meck_tests:cover_test_. -module(cover_test_module). -export([a/0, b/0, c/2]). %% a/0 is defined in include/cover_test.hrl. We don't put the full %% path here, since it should be provided as a compiler option. -include("cover_test.hrl"). %% Also, make that this module was compiled with -DTEST. -ifdef(TEST). b() -> b. -endif. c(A, B) -> {A, B}. meck-1.0.0/test/meck_on_load_module.erl0000664000175000017500000000031414726775702020470 0ustar debalancedebalance-module(meck_on_load_module). -on_load(on_load/0). -export([ping/0]). on_load() -> % Assumes that there's an on_load_listener. catch (on_load_listener ! on_load_called), ok. ping() -> pong. meck-1.0.0/test/meck_ret_spec_tests.erl0000664000175000017500000000376314726775702020551 0ustar debalancedebalance%%%============================================================================ %%% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd %%% %%% 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. %%%============================================================================ -module(meck_ret_spec_tests). -include_lib("eunit/include/eunit.hrl"). ret_spec_test_() -> {foreach, fun setup/0, fun teardown/1, [fun passthrough/0, fun explicit_exec/0, fun exec/0, fun deep_exec/0, fun invalid_arity_exec/0 ] }. setup() -> %% Given meck:new(meck_test_module), ok. teardown(_) -> %% Cleanup meck:unload(). passthrough() -> %% When meck:expect(meck_test_module, c, 2, meck:passthrough()), %% Then ?assertEqual({1, 3}, meck_test_module:c(1, 3)). explicit_exec() -> %% When meck:expect(meck_test_module, c, 2, meck:exec(fun(A, B) -> {B, A} end)), %% Then ?assertEqual({3, 1}, meck_test_module:c(1, 3)). exec() -> %% When meck:expect(meck_test_module, c, 2, fun(A, B) -> {B, A} end), %% Then ?assertEqual({3, 1}, meck_test_module:c(1, 3)). deep_exec() -> %% When meck:expect(meck_test_module, c, 2, meck_ret_spec:seq([fun(A, B) -> {B, A} end])), %% Then ?assertEqual({3, 1}, meck_test_module:c(1, 3)). invalid_arity_exec() -> %% When meck:expect(meck_test_module, c, 2, meck_ret_spec:seq([fun(A, B) -> {B, A} end])), %% Then ?assertError(undef, meck_test_module:c(1, 2, 3)). meck-1.0.0/test/meck_args_matcher_tests.erl0000664000175000017500000000606714726775702021404 0ustar debalancedebalance%%%============================================================================ %%% 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. %%%============================================================================ -module(meck_args_matcher_tests). -include_lib("eunit/include/eunit.hrl"). from_wildcard_test() -> ArgsMatcher = meck_args_matcher:new('_'), ?assertMatch(true, meck_args_matcher:match([], ArgsMatcher)), ?assertMatch(true, meck_args_matcher:match([1], ArgsMatcher)), ?assertMatch(true, meck_args_matcher:match([1, 2, 3], ArgsMatcher)). from_arity_test() -> ArgsMatcher = meck_args_matcher:new(3), ?assertMatch(true, meck_args_matcher:match([1, 2, 3], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 3, 4], ArgsMatcher)). from_zero_arity_test() -> ArgsMatcher = meck_args_matcher:new(0), ?assertMatch(true, meck_args_matcher:match([], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 3], ArgsMatcher)). from_args_test() -> ArgsMatcher = meck_args_matcher:new([1, {2, [<<"3">>]}]), ?assertMatch(true, meck_args_matcher:match([1, {2, [<<"3">>]}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, {2, [<<"3">>]}, 1], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, {0, [<<"3">>]}], ArgsMatcher)). from_empty_args_test() -> ArgsMatcher = meck_args_matcher:new([]), ?assertMatch(true, meck_args_matcher:match([], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 3], ArgsMatcher)). matcher_featured_test() -> ArgsSpec = [meck:is(fun(X) -> X == 1 end), 2, meck:is(fun(X) -> X == 3 end), {4, [5, '_'], <<"7">>}], ArgsMatcher = meck_args_matcher:new(ArgsSpec), ?assertMatch(true, meck_args_matcher:match([1, 2, 3, {4, [5, 6], <<"7">>}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([0, 2, 3, {4, [5, 6], <<"7">>}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 0, 3, {4, [5, 6], <<"7">>}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 0, {4, [5, 6], <<"7">>}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 3, {0, [5, 6], <<"7">>}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 3, {4, [0, 6], <<"7">>}], ArgsMatcher)), ?assertMatch(true, meck_args_matcher:match([1, 2, 3, {4, [5, 0], <<"7">>}], ArgsMatcher)), ?assertMatch(false, meck_args_matcher:match([1, 2, 3, {4, [5, 6], <<"0">>}], ArgsMatcher)). meck-1.0.0/test/include/0000775000175000017500000000000014726775702015432 5ustar debalancedebalancemeck-1.0.0/test/include/cover_test.hrl0000664000175000017500000000001214726775702020307 0ustar debalancedebalancea() -> a. meck-1.0.0/CHANGELOG.md0000664000175000017500000004467114726775702014655 0ustar debalancedebalance# Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. ## [Unreleased] ## [1.0.0] - 2024-06-28 ### Added - Official support for Erlang 27.0 - Reload modules that were loaded before mocking ([\#228](https://github.com/eproxus/meck/pull/228)) ### Fixed - Fix misleading not_mocked errors when unloading a mock [\#231](https://github.com/eproxus/meck/pull/231) - Fix calling mocked modules from expectations fun [\#232](https://github.com/eproxus/meck/pull/232) - Fix spec of meck:raise/2 - Increase meck_proc stop timeout to infinity to prevent confusing errors - Code coverage leak from temporary backup modules in Erlang.mk [\#246](https://github.com/eproxus/meck/pull/246) ### Removed - Compatibility for Erlang versions below 22. Meck will follow the officially supported Erlang versions for future releases (latest major and two previous versions). Older versions might still work but there is no guarantee and no support. ## [0.9.2] - 2021-03-06 ### Fixed - Fix a Dialyzer warning on OTP 24 [\#223](https://github.com/eproxus/meck/pull/223) ## [0.9.1] - 2021-02-17 ### Fixed - Module references in type specs were not included when renaming modules [\#206](https://github.com/eproxus/meck/issues/206) - The passthrough clause was not always the last clause in an expect with multiple clauses [\#216](https://github.com/eproxus/meck/pull/216) ## [0.9.0] - 2020-06-25 ### Added - Support Erlang 23.0 [8c16751](https://github.com/eproxus/meck/commit/8c16751613c7e4ed594e0675004b1c8f68ea8ddd) - Add a new function `mocked/0` that returns which modules are currently mocked [\#210](https://github.com/eproxus/meck/pull/210) ### Changed - Validate the options being passed to meck:new [\#204](https://github.com/eproxus/meck/pull/204) ### Fixed - Do not attempt to generate dependencies when mocking [9b3ce75](https://github.com/eproxus/meck/commit/9b3ce754bd69e84127f82d482b8b23b22f7bf866) - Add compiler application to dependencies [\#209](https://github.com/eproxus/meck/pull/209) - meck:ret_spec() opaqueness violates documented usage patterns [\#212](https://github.com/eproxus/meck/issues/212) ## [0.8.13] - 2019-01-08 ### Removed - Remove compatibility for Erlang R15 and R16 [\#198](https://github.com/eproxus/meck/issues/198) ### Fixed - Crash when mocking Elixir 1.8-rc.0 compiled module [\#201](https://github.com/eproxus/meck/issues/201) - Exclude from\_core option from compile\_info when compiling [\#202](https://github.com/eproxus/meck/pull/202) ([josevalim](https://github.com/josevalim)) - Isolate backup \*.coverdata from other beam instances [\#200](https://github.com/eproxus/meck/pull/200) ([dcsommer](https://github.com/dcsommer)) ## [0.8.12] - 2018-08-08 ### Fixed - History item is not kept while module compiler is running [\#194](https://github.com/eproxus/meck/issues/194) ## [0.8.11] - 2018-07-12 ### Fixed - OTP 21 compatibility when using stack traces [\#193](https://github.com/eproxus/meck/pull/193) ([massemanet](https://github.com/massemanet)) ## [0.8.10] - 2018-06-26 ### Added - Support Erlang/OTP 21.0 [\#190](https://github.com/eproxus/meck/pull/190) ([michalwski](https://github.com/michalwski)) - Add meck:expects/1,2 [\#187](https://github.com/eproxus/meck/pull/187) ([elbrujohalcon](https://github.com/elbrujohalcon)) ## [0.8.9] - 2017-11-27 ### Changed - Migrate to Rebar 3 and rebar3\_hex [\#155](https://github.com/eproxus/meck/issues/155) ### Fixed - Support running meck with modules built with '+deterministic' compile… [\#185](https://github.com/eproxus/meck/pull/185) ([nablaa](https://github.com/nablaa)) - How do I run coverage results after using meck? [\#181](https://github.com/eproxus/meck/issues/181) - Fix for Rebar 2.x.y on secondary arches [\#183](https://github.com/eproxus/meck/pull/183) ([lemenkov](https://github.com/lemenkov)) ## [0.8.8] - 2017-08-29 ### Changed - Always add debug\_info to compile opts for mocks [\#180](https://github.com/eproxus/meck/pull/180) ([ericentin](https://github.com/ericentin)) ### Fixed - `{:error, {:no\_abstract\_code, ...}}` with Elixir 1.5.0-rc.0 and Erlang 20.0 [\#179](https://github.com/eproxus/meck/issues/179) ## [0.8.7] - 2017-06-29 ### Fixed - Remove dialyzer attributes when creating mock \(Erlang 20.0\) [\#178](https://github.com/eproxus/meck/issues/178) ## [0.8.6] - 2017-06-28 ### Fixed - Remove dialyzer attributes when creating mock [3b772d0](https://github.com/eproxus/meck/commit/3b772d0afc7dd3e7fcae4f256f7728e9975fb412) ## [0.8.5] - 2017-06-28 ### Added - Support Erlang 20 [\#175](https://github.com/eproxus/meck/issues/175) - Support Erlang 19 [\#168](https://github.com/eproxus/meck/pull/168) ([WramblinWreck](https://github.com/WramblinWreck)) - Add meck:get\_state/0, meck:reset\_state/0 [\#125](https://github.com/eproxus/meck/issues/125) - Add `meck:result/4-5` that returns the result value of a particular function [\#163](https://github.com/eproxus/meck/pull/163) ([amutake](https://github.com/amutake)) ### Deprecated - Deprecate history and provide history digging functions instead [\#85](https://github.com/eproxus/meck/issues/85) - fix \#88 [\#162](https://github.com/eproxus/meck/pull/162) ([yutopp](https://github.com/yutopp)) ### Fixed - Mocked module will return empty function clause error [\#167](https://github.com/eproxus/meck/issues/167) - Deleting an expectation in passthrough mode does not restore original function [\#88](https://github.com/eproxus/meck/issues/88) - Passthrough crashes without +debug\_info [\#14](https://github.com/eproxus/meck/issues/14) - fix behavio\(u\)r attributes validation on Erlang R20 [\#176](https://github.com/eproxus/meck/pull/176) ([RoadRunnr](https://github.com/RoadRunnr)) - Fix errors in capture/5, capture/6 documentation [\#172](https://github.com/eproxus/meck/pull/172) ([marco-m](https://github.com/marco-m)) - Fix eunit compile failure on Erlang 17+ \(hamcrest\) [\#161](https://github.com/eproxus/meck/pull/161) ([marco-m](https://github.com/marco-m)) ## [0.8.4] - 2015-12-29 ### Added - Add merge\_expects option to meck\_proc [\#153](https://github.com/eproxus/meck/pull/153) ([edgurgel](https://github.com/edgurgel)) ### Changed - Update 'problematic modules list' [\#156](https://github.com/eproxus/meck/pull/156) ([lilrooness](https://github.com/lilrooness)) - Document the caveat with mocking module-local calls. [\#145](https://github.com/eproxus/meck/pull/145) ([bpuzon](https://github.com/bpuzon)) ### Fixed - FIX: optional\_callbacks [\#151](https://github.com/eproxus/meck/pull/151) ([soranoba](https://github.com/soranoba)) - Fix race condition between meck:unload/1 and calls to the mocked module [\#150](https://github.com/eproxus/meck/pull/150) ([dszoboszlay](https://github.com/dszoboszlay)) ## [0.8.3] - 2015-06-09 ### Added - Support Erlang 18.0 [\#139](https://github.com/eproxus/meck/pull/139) ([gomoripeti](https://github.com/gomoripeti)) - Allow hiding 'on\_load' attribute. [\#131](https://github.com/eproxus/meck/pull/131) ([rlipscombe](https://github.com/rlipscombe)) ### Changed - Removed test and doc from target all in Makefile [\#126](https://github.com/eproxus/meck/pull/126) ([jfacorro](https://github.com/jfacorro)) - Fix typo [\#143](https://github.com/eproxus/meck/pull/143) ([derek121](https://github.com/derek121)) - Run tests in travis [\#138](https://github.com/eproxus/meck/pull/138) ([gomoripeti](https://github.com/gomoripeti)) ### Fixed - Please document that modules can be not meck'able [\#135](https://github.com/eproxus/meck/issues/135) - crypto module [\#59](https://github.com/eproxus/meck/issues/59) - Fix variable exported from case [\#128](https://github.com/eproxus/meck/pull/128) ([hazardfn](https://github.com/hazardfn)) ## [0.8.2] - 2014-05-05 ### Added - Support Erlang 17.0 and Erlang R16B03-1 [\#118](https://github.com/eproxus/meck/pull/118) ([myers](https://github.com/myers)) Add Erlang 17.0 to the test matrix [\#122](https://github.com/eproxus/meck/pull/122) ([myers](https://github.com/myers)) - Implicit new [\#80](https://github.com/eproxus/meck/issues/80) - Should return compilation errors [\#33](https://github.com/eproxus/meck/issues/33) - Better documentation [\#79](https://github.com/eproxus/meck/issues/79) ### Changed - Put non-strict option in the README.md [\#117](https://github.com/eproxus/meck/issues/117) - Split tests into several test suites [\#83](https://github.com/eproxus/meck/issues/83) ### Fixed - With a bogus test instantiator, meck fails with {error, enoent} in meck\_cover:read\_cover\_file/1 [\#114](https://github.com/eproxus/meck/issues/114) - Unable to mock lists module [\#87](https://github.com/eproxus/meck/issues/87) - Do not consider a 3-tuple return value as an exception [\#113](https://github.com/eproxus/meck/pull/113) ([lucafavatella](https://github.com/lucafavatella)) ## [0.8.1] - 2013-08-29 ### Fixed - Attribute errors [\#110](https://github.com/eproxus/meck/pull/110) ([twonds](https://github.com/twonds)) ## [0.8] - 2013-08-17 ### Added - Support R16B [\#100](https://github.com/eproxus/meck/pull/100) ([rufrozen](https://github.com/rufrozen)) - Capture argument [\#86](https://github.com/eproxus/meck/issues/86) Feature/capture [\#97](https://github.com/eproxus/meck/pull/97) ([horkhe](https://github.com/horkhe)) - Wait for a number of function calls [\#81](https://github.com/eproxus/meck/issues/81) Wait for a number of calls feature \(\#81\) [\#99](https://github.com/eproxus/meck/pull/99) ([horkhe](https://github.com/horkhe)) - Mocking of parameterized modules [\#4](https://github.com/eproxus/meck/issues/4) - Allow calling original function from within expect fun [\#2](https://github.com/eproxus/meck/issues/2) - Make remote\_setup more robust [\#109](https://github.com/eproxus/meck/pull/109) ([i11](https://github.com/i11)) - Implement 'implicit new' feature \#80 [\#104](https://github.com/eproxus/meck/pull/104) ([horkhe](https://github.com/horkhe)) - Make `undefined\_module` error contain module name [\#96](https://github.com/eproxus/meck/pull/96) ([horkhe](https://github.com/horkhe)) - Introduce support for matchers: [\#89](https://github.com/eproxus/meck/pull/89) ([horkhe](https://github.com/horkhe)) - Feature/file bif passthrough [\#84](https://github.com/eproxus/meck/pull/84) ([horkhe](https://github.com/horkhe)) - Two new options for meck [\#77](https://github.com/eproxus/meck/pull/77) ([norton](https://github.com/norton)) - Feature/honest mocks [\#75](https://github.com/eproxus/meck/pull/75) ([horkhe](https://github.com/horkhe)) - Feature/new exception syntax [\#74](https://github.com/eproxus/meck/pull/74) ([horkhe](https://github.com/horkhe)) - Extended expect syntax and more [\#73](https://github.com/eproxus/meck/pull/73) ([horkhe](https://github.com/horkhe)) - Introduce 'stub\_all' option [\#78](https://github.com/eproxus/meck/pull/78) ([horkhe](https://github.com/horkhe)) - Support for location included in stack traces in Erlang R15 [\#52](https://github.com/eproxus/meck/pull/52) ([bjnortier](https://github.com/bjnortier)) ### Changed - Make `passthrough/1` and `func/1` into a `ret\_spec`and func [\#91](https://github.com/eproxus/meck/pull/91) ([horkhe](https://github.com/horkhe) - Refactor meck into smaller functional modules [\#82](https://github.com/eproxus/meck/pull/82) ([horkhe](https://github.com/horkhe)) ### Removed - R16A preview - parameterized modules are no longer supported [\#94](https://github.com/eproxus/meck/issues/94) - Remove unsupported option from the app.src file [\#101](https://github.com/eproxus/meck/pull/101) ([amiramix](https://github.com/amiramix)) - Remove parametrized module test [\#95](https://github.com/eproxus/meck/pull/95) ([norton](https://github.com/norton)) ### Fixed - Warning from reltool on unexpected item `build\_dependencies` [\#92](https://github.com/eproxus/meck/issues/92) - http://eproxus.github.io/meck 404 [\#103](https://github.com/eproxus/meck/issues/103) - meck eunit tests fail on R15B [\#51](https://github.com/eproxus/meck/issues/51) - meck:new fails if running in embedded mode and module not loaded [\#35](https://github.com/eproxus/meck/issues/35) - Support meck:expect with improper list mock data [\#102](https://github.com/eproxus/meck/pull/102) ([adbl](https://github.com/adbl)) - Fix failing build. [\#98](https://github.com/eproxus/meck/pull/98) ([cmeiklejohn](https://github.com/cmeiklejohn)) - fix path of rebar [\#69](https://github.com/eproxus/meck/pull/69) ([yamt](https://github.com/yamt)) ## [0.7.2] - 2012-05-06 ### Added - Remove Erlang R15B support [\#54](https://github.com/eproxus/meck/pull/54) ([michaelklishin](https://github.com/michaelklishin)) - Mocking of sticky modules [\#7](https://github.com/eproxus/meck/issues/7) - Rz passthrough cover [\#56](https://github.com/eproxus/meck/pull/56) ([rzezeski](https://github.com/rzezeski)) - Mock parametrized modules [\#55](https://github.com/eproxus/meck/pull/55) ([shino](https://github.com/shino)) - Clean test directory [\#50](https://github.com/eproxus/meck/pull/50) ([norton](https://github.com/norton)) - New features - pid in history and count\_calls and wildcard\_count\_calls functions [\#40](https://github.com/eproxus/meck/pull/40) ([daha](https://github.com/daha)) - Include meck:new/2 arguments in errors [\#39](https://github.com/eproxus/meck/pull/39) ([legoscia](https://github.com/legoscia)) - .travis.yml config without rebar [\#38](https://github.com/eproxus/meck/pull/38) ([wardbekker](https://github.com/wardbekker)) - Filter out parse\_transforms from compilation options [\#32](https://github.com/eproxus/meck/pull/32) ([djnym](https://github.com/djnym)) ### Changed - remove repetition; typo [\#57](https://github.com/eproxus/meck/pull/57) ([Erkan-Yilmaz](https://github.com/Erkan-Yilmaz)) - Improved tests: Added an ok in the end of the tests that use a helper function with asserts [\#43](https://github.com/eproxus/meck/pull/43) ([daha](https://github.com/daha)) - Making all the test funs in the foreach in meck\_test\_/0 fully qualified funs [\#44](https://github.com/eproxus/meck/pull/44) ([daha](https://github.com/daha)) ### Removed - Remove IDE project artifacts [\#46](https://github.com/eproxus/meck/pull/46) ([xenolinguist](https://github.com/xenolinguist)) - Remove Erlang R13B support [\#54](https://github.com/eproxus/meck/pull/54) ([michaelklishin](https://github.com/michaelklishin)) ### Fixed - dialyzer warnings with meck \(73c0b3e\) [\#58](https://github.com/eproxus/meck/issues/58) - Inconsistency in documentation [\#49](https://github.com/eproxus/meck/issues/49) - meck:unload/0 sometimes crashes [\#48](https://github.com/eproxus/meck/issues/48) - Add test/cover\_test\_module.beam to rebar.config's clean files [\#47](https://github.com/eproxus/meck/issues/47) - Fix typo in no\_passthrough\_cover atom [\#62](https://github.com/eproxus/meck/pull/62) ([garret-smith](https://github.com/garret-smith)) - Verify history/2 returns events in the correct order & fix to flaky history\_by\_pid\_/1 test [\#42](https://github.com/eproxus/meck/pull/42) ([daha](https://github.com/daha)) ## [0.7.1] - 2011-07-18 ### Fixed - Can I call original function with different arguments? [\#30](https://github.com/eproxus/meck/issues/30) ## [0.7] - 2011-07-13 ### Added - Enable mocking of sticky modules \(not used by code\_server\) [\#29](https://github.com/eproxus/meck/pull/29) ([xenolinguist](https://github.com/xenolinguist)) ## [0.6.3] - 2011-06-30 ### Changed - Interface inconsistency [\#8](https://github.com/eproxus/meck/issues/8) ## [0.6.2] - 2011-06-09 - Fix re adding shortcut expects [9b8934a](https://github.com/eproxus/meck/commit/9b8934a33e4a1d427a25e9a0d128f728ee1ab9b9) - Fix returning of opaque terms in shortcut expectations [b1904a2](https://github.com/eproxus/meck/commit/b1904a2fb7f9d7d553cf9392cab742683d411066) ## [0.6.1] - 2011-06-08 ### Added - use localhost for remote test rather than hostname [\#27](https://github.com/eproxus/meck/pull/27) ([joewilliams](https://github.com/joewilliams)) ### Fixed - Makefile requires local rebar and documentation says rebar on path [\#28](https://github.com/eproxus/meck/issues/28) ## [0.6] - 2011-05-25 ### Added - Add `loop/4` expect function [8d86012](https://github.com/eproxus/meck/commit/8d86012c851b7ee6eb26831f1822129ee82c8f2e) - Add `sequence/4` expect function [35de01e](https://github.com/eproxus/meck/commit/35de01eca6b1d952997b86638f33f180461b38f5) ## [0.5.1] - 2011-05-23 ### Changed - Replace fail_on_warning with warnings_as_errors [ddd9e3b](https://github.com/eproxus/meck/commit/ddd9e3bcc896d3cf8092db341d57acfa2208fe8a) ## [0.5] - 2011-04-12 ### Added - Add meck:received/3 API for easier history checking [\#23](https://github.com/eproxus/meck/pull/23) ([mbbx6spp](https://github.com/mbbx6spp)) ### Fixed - dialyzer unmatched return errors [\#24](https://github.com/eproxus/meck/issues/24) [Unreleased]: https://github.com/eproxus/meck/compare/0.9.2...HEAD [0.9.2]: https://github.com/eproxus/meck/compare/0.9.1...0.9.2 [0.9.1]: https://github.com/eproxus/meck/compare/0.9.0...0.9.1 [0.9.0]: https://github.com/eproxus/meck/compare/0.8.13...0.9.0 [0.8.13]: https://github.com/eproxus/meck/compare/0.8.12...0.8.13 [0.8.12]: https://github.com/eproxus/meck/compare/0.8.11...0.8.12 [0.8.11]: https://github.com/eproxus/meck/compare/0.8.10...0.8.11 [0.8.10]: https://github.com/eproxus/meck/compare/0.8.9...0.8.10 [0.8.9]: https://github.com/eproxus/meck/compare/0.8.8...0.8.9 [0.8.8]: https://github.com/eproxus/meck/compare/0.8.7...0.8.8 [0.8.7]: https://github.com/eproxus/meck/compare/0.8.6...0.8.7 [0.8.6]: https://github.com/eproxus/meck/compare/0.8.5...0.8.6 [0.8.5]: https://github.com/eproxus/meck/compare/0.8.4...0.8.5 [0.8.4]: https://github.com/eproxus/meck/compare/0.8.3...0.8.4 [0.8.3]: https://github.com/eproxus/meck/compare/0.8.2...0.8.3 [0.8.2]: https://github.com/eproxus/meck/compare/0.8.1...0.8.2 [0.8.1]: https://github.com/eproxus/meck/compare/0.8...0.8.1 [0.8]: https://github.com/eproxus/meck/compare/0.7.2...0.8 [0.7.2]: https://github.com/eproxus/meck/compare/0.7.1...0.7.2 [0.7.1]: https://github.com/eproxus/meck/compare/0.7...0.7.1 [0.7]: https://github.com/eproxus/meck/compare/0.6.3...0.7 [0.6.3]: https://github.com/eproxus/meck/compare/0.6.2...0.6.3 [0.6.2]: https://github.com/eproxus/meck/compare/0.6.1...0.6.2 [0.6.1]: https://github.com/eproxus/meck/compare/0.6...0.6.1 [0.6]: https://github.com/eproxus/meck/compare/0.5.1...0.6 [0.5.1]: https://github.com/eproxus/meck/compare/0.5...0.5.1 [0.5]: https://github.com/eproxus/meck/releases/tag/0.5 [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html meck-1.0.0/CODE_OF_CONDUCT.md0000664000175000017500000000621614726775702015634 0ustar debalancedebalance# Meck Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [meck at alind dot io]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ meck-1.0.0/.scripts/0000775000175000017500000000000014726775702014575 5ustar debalancedebalancemeck-1.0.0/.scripts/cut.sh0000775000175000017500000000201014726775702015720 0ustar debalancedebalance#!/bin/bash set -e # Abort on first failure, so we don't mess something up check_path() { if ! [ -x "$(command -v "$1")" ]; then echo >&2 "error: $1 not available on path" if [ -n "$2" ]; then echo "($2)" fi exit 1 fi } check_path git check_path sed check_path rebar3 "http://www.rebar3.org" if [ -z "$1" ]; then # Missing tag name echo "usage: cut " >&2 exit 129 fi if [ ! -z "$(git status --short)" ]; then # Sanity check echo "fatal: dirty repository" >&2 exit 128 fi VSN="$1" # Update version sed -i "" -e "s/{vsn, .*}/{vsn, \"$VSN\"}/g" src/*.app.src git add src/*.app.src if [ -f doc/overview.edoc ]; then sed -i "" -e "s/@version .*/@version $VSN/g" doc/overview.edoc git add doc/overview.edoc fi # Commit, tag and push git commit -m "Version $VSN" git tag -s "$VSN" -m "Version $VSN" git push && git push --tags # Clean and publish package and docs rm -rf ebin rm -rf src/**/*.beam rm -rf test/**/*.beam rebar3 hex publish meck-1.0.0/CONTRIBUTING.md0000664000175000017500000000744214726775702015270 0ustar debalancedebalance# Contributing to Meck :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: The following is a set of guidelines for contributing to Meck. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. ## What should I know before I get started? ### Code of Conduct This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [meck at alind dot io]. ## How Can I Contribute? You can: * Submit an issue (see [Reporting Bugs](#reporting-bugs)) * Suggest an enhancement (see [Suggesting Enhancements](#suggesting-enhancements)) * Submit a pull request (see [Pull Requests](#pull-requests)) ### Your First Contribution If you want to contribute with documentation, wiki pages, examples, code or tests and are unsure of how to start, just open an issue to start a discussion. It could be a proposal, a question for support or further direction or any other feedback you might have. ### Reporting Bugs Submitting a good bug report will help identifying, debugging and solving an issue. Please check first if any similar issues have already been reported. If so, add to the discussion by commenting on one of those instead. When you're ready to submit a bug report you can use the [bug report template](.github/ISSUE_TEMPLATE.md) defined in the project (it's automatically used whenever you create a new issue on GitHub). Make sure to fill in which versions you are using and instructions of how to reproduce the problem. ### Suggesting Enhancements Suggesting enhancements doesn't have to be as structured as a bug report, but should still contain the motivation for the enhancement, an example use case and some reasoning why this enhancement should go into Meck itself (reasons it should not might include that it works as an external library, that it is more test framework related than mocking related etc.) ### Pull Requests Pull requests really appreciated. To increase the chance of getting your code merged, make sure the pull request is small and well structured. You should prepare your pull request to try to meet the following guidelines where it makes sense: 1. Squash all changes into one commit. If you have many independent changes, submit each in its own pull request. 2. Document any external API functions changed or added via EDoc. 3. Run the existing tests to make sure you didn't break anything. 3. Add working tests that illustrate and cover the changes, or detects an issue to be fixed. A good example is to create a failing test case that exposes the issue you are trying to fix, before fixing it. 4. Make sure the code and commit follow the [style guides](#styleguides). 5. (Optional) Add type specifications and run Dialyzer where it makes sense. ## Styleguides ### Commit Messages Commit messages should be limited to 50 characters without a period on the subject line and be written in imperative mood. Longer explanations should be in the body, two lines below the message, wrapped at 72 characters. See [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). ### Code * Lines should be no longer than 80 characters. This is isn't some arbitrary length based on nostalgia, it's just a choice of fitting limit if you want to have several files open at once next to each other on a modern wide screen monitor. * Functions should be exported one by one in their own export statement. This is so that one export can easily be rearranged or removed without messing with commas and Erlang attributes. If you are unsure, try to find some similar code already in the repository and mimic that. Otherwise just submit the pull request to get some stylistic feedback if necessary. meck-1.0.0/dialyzer.ignore-warnings0000664000175000017500000000041614726775702017707 0ustar debalancedebalance### meck:code_cover.erl has to call this unexported functions to do its ### cover passthrough magic: Call to missing or unexported function cover:compile_beam/2 Call to missing or unexported function cover:get_term/1 Call to missing or unexported function cover:write/2 meck-1.0.0/NOTICE0000664000175000017500000000025114726775702013732 0ustar debalancedebalanceCopyright 2010-2020 Adam Lindberg Copyright 2010-2011 Erlang Solutions Ltd This product contains code developed at Erlang Solutions. (http://www.erlang-solutions.com/)