Merge branch 'develop'
Conflicts:
README.md
diff --git a/.gitignore b/.gitignore
index 2c991d1..76a2b11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,6 @@
.*.swp
.settings
.project
+*.sublime-workspace
+*.sublime-project
+deps/*
diff --git a/.travis.yml b/.travis.yml
index 6e520db..a261c8c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,10 @@
language: erlang
notifications:
- disabled: true
+ email:
+ - hello@alind.io
otp_release:
- - R15B
+ - R16B
+ - R15B03
- R14B04
- - R14B03
- - R14B02
+before_script: "make get-deps"
+script: "make all"
diff --git a/CHANGELOG b/CHANGELOG
index 8b13789..91d5f91 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1 +1,2 @@
-
+- The return value from passthrough/1 can now be used in expect
+ functions before returning
diff --git a/Makefile b/Makefile
index 5ac14ee..ec7eec8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,63 @@
-REBAR=`which rebar || ./rebar`
+PLTFILE=$(CURDIR)/.deps.plt
+APP_DEPS=kernel stdlib eunit tools compiler
+ERLFLAGS= -pa $(CURDIR)/.eunit -pa $(CURDIR)/ebin -pa $(CURDIR)/deps/*/ebin
-all: compile
+REBAR="./rebar"
+ifeq ($(REBAR),)
+$(error "Rebar not available on this system")
+endif
+
+ERL = $(shell which erl)
+ifeq ($(ERL),)
+$(error "Erlang must be available on this system")
+endif
+
+BUILD_PLT_INC=$(shell test -d deps && echo '-r deps')
+DIALYZER_INC=$(shell test -d include && echo '-I include') $(shell test -d deps && echo '-I deps')
+
+.PHONY: all rebuild compile doc clean test dialyzer typer get-deps clean-deps \
+ shell clean-plt clean-doc distclean
+
+all: get-deps compile test doc
+
+rebuild: distclean get-deps all
+
+get-deps:
+ @$(REBAR) -C test.config get-deps
+ @$(REBAR) -C test.config compile
compile:
- @$(REBAR) compile
+ @$(REBAR) -C test.config skip_deps=true compile
-test: force
- @$(REBAR) eunit
+doc:
+ @$(REBAR) -C test.config skip_deps=true doc
clean:
- @$(REBAR) clean
+ @$(REBAR) -C test.config skip_deps=true clean
-force: ;
+test:
+ @$(REBAR) -C test.config skip_deps=true eunit
+
+$(PLTFILE):
+ - dialyzer --build_plt --apps $(APP_DEPS) $(BUILD_PLT_INC) --output_plt $(PLTFILE)
+
+dialyzer: compile $(PLTFILE)
+ @dialyzer --fullpath --plt $(PLTFILE) $(DIALYZER_INC) -pa $(CURDIR)/ebin -c src --src | \
+ fgrep -v -f ./dialyzer.ignore-warnings
+
+typer:
+ typer --plt $(PLTFILE) $(DIALYZER_INC) -r src
+
+shell:
+ @$(ERL) $(ERLFLAGS)
+
+clean-plt:
+ @rm -rf $(PLTFILE)
+
+clean-doc:
+ @cd doc; ls * | grep -v overview.edoc | xargs rm -f
+
+clean-deps:
+ @rm -rvf $(CURDIR)/deps/*
+
+distclean: clean clean-plt clean-doc clean-deps
diff --git a/dialyzer.ignore-warnings b/dialyzer.ignore-warnings
new file mode 100644
index 0000000..dab1610
--- /dev/null
+++ b/dialyzer.ignore-warnings
@@ -0,0 +1,5 @@
+### 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
diff --git a/rebar b/rebar
new file mode 100755
index 0000000..04e0fec
--- /dev/null
+++ b/rebar
Binary files differ
diff --git a/rebar.config b/rebar.config
index c589f4c..7a5569e 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,6 +1,5 @@
+%% Compiler Options ===========================================================
{erl_opts, [warnings_as_errors, debug_info]}.
-{xref_checks, [undefined_function_calls]}.
-{dialyzer_opts, [{warnings, [unmatched_returns]}]}.
-{cover_enabled, true}.
+%% Misc =======================================================================
{clean_files, [".eunit", "ebin/*.beam", "test/*.beam"]}.
diff --git a/src/meck.erl b/src/meck.erl
index 1669c32..d0ab62a 100644
--- a/src/meck.erl
+++ b/src/meck.erl
@@ -1,25 +1,30 @@
-%%==============================================================================
-%% Copyright 2011 Adam Lindberg & 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.
-%%==============================================================================
+%%%============================================================================
+%%% Copyright 2011 Adam Lindberg & 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 <eproxus@gmail.com>
-%% @copyright 2011, Adam Lindberg & Erlang Solutions Ltd
-%% @doc Module mocking library for Erlang.
+%%% @author Adam Lindberg <eproxus@gmail.com>
+%%% @copyright 2011, Adam Lindberg & Erlang Solutions Ltd
+%%% @doc Module mocking library for Erlang.
-module(meck).
--behaviour(gen_server).
+
+%% 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]).
@@ -40,54 +45,98 @@
-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]).
-%% Callback exports
--export([init/1]).
--export([handle_call/3]).
--export([handle_cast/2]).
--export([handle_info/2]).
--export([terminate/2]).
--export([code_change/3]).
--export([exec/5]).
+%% 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::list(term())}.
+%%%============================================================================
+%%% Types
+%%%============================================================================
+
+-type meck_mfa() :: {Mod::atom(), Func::atom(), Args::[any()]}.
%% Module, function and arguments that the mock module got called with.
--type meck_mfa() :: {Mod::atom(), Func::atom(), Args::[term()]}.
-%% @type history() = [{pid(), meck_mfa(), Result::term()}
-%% | {pid(), meck_mfa(), Class:: exit | error | throw,
-%% Reason::term(), Stacktrace::list(mfa())}].
-%% History is a list of either successful function calls with a returned
+-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.
--type history() :: [{pid(), meck_mfa(), Result::term()}
- | {pid(), meck_mfa(), Class:: exit | error | throw,
- Reason::term(), Stacktrace::[mfa()]}].
-%% Records
--record(state, {mod :: atom(),
- expects :: dict(),
- valid = true :: boolean(),
- history = [] :: history(),
- original :: term(),
- was_sticky :: boolean()}).
+-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()}.
-%% Includes
--include("meck_abstract.hrl").
+-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 ['_', '_', '_']).
-%%==============================================================================
-%% Interface exports
-%%==============================================================================
+-opaque ret_spec() :: meck_ret_spec:ret_spec().
+%% Opaque data structure that specifies a value or a set of values to be returned
+%% by a mock stub function defined by either {@link expect/3} and {@link expect/4}.
+%% Values of `ret_spec()' are constructed by {@link seq/1}, {@link loop/1},
+%% {@link val/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)'.
-%% @spec new(Mod:: atom() | list(atom())) -> ok
+-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(Mod:: atom() | [atom()]) -> ok.
+-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.
-%% @spec new(Mod:: atom() | list(atom()), Options::list(term())) -> ok
%% @doc Creates new mocked module(s).
%%
%% This replaces the current version (if any) of the modules in `Mod'
@@ -98,120 +147,153 @@
%%
%% The valid options are:
%% <dl>
-%% <dt>`passthrough'</dt><dd>Retains the original functions, if not
-%% mocked by meck.</dd>
-%% <dt>`no_link'</dt> <dd>Does not link the meck process to the caller
-%% process (needed for using meck in rpc calls).
-%% </dd>
-%% <dt>`unstick'</dt> <dd>Unstick the module to be mocked (e.g. needed
-%% for using meck with kernel and stdlib modules).
-%% </dd>
-%% <dt>`no_passthrough_cover'</dt><dd>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.
-%% </dd>
+%% <dt>`passthrough'</dt>
+%% <dd>Retains the original functions, if not mocked by meck. If used along
+%% with `stub_all' then `stub_all' is ignored.</dd>
+%%
+%% <dt>`no_link'</dt>
+%% <dd>Does not link the meck process to the caller process (needed for using
+%% meck in rpc calls).</dd>
+%%
+%% <dt>`unstick'</dt>
+%% <dd>Unstick the module to be mocked (e.g. needed for using meck with
+%% kernel and stdlib modules).</dd>
+%%
+%% <dt>`no_passthrough_cover'</dt>
+%% <dd>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.</dd>
+%%
+%% <dt>`{spawn_opt, list()}'</dt>
+%% <dd>Specify Erlang process spawn options. Typically used to specify
+%% non-default, garbage collection options.</dd>
+%%
+%% <dt>`no_history'</dt>
+%% <dd>Do not store history of meck calls.</dd>
+%%
+%% <dt>`non_strict'</dt>
+%% <dd>A mock created with this option will allow setting expectations on
+%% functions that are not exported from the mocked module. With this
+%% option on it is even possible to mock non existing modules.</dd>
+%%
+%% <dt>`{stub_all, '{@link ret_spec()}`}'</dt>
+%% <dd>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. </dd>
%% </dl>
--spec new(Mod:: atom() | [atom()], Options::[term()]) -> ok.
+-spec new(Mods, Options) -> ok when
+ Mods :: Mod | [Mod],
+ Mod :: atom(),
+ Options :: [proplists:property()].
new(Mod, Options) when is_atom(Mod), is_list(Options) ->
- case start(Mod, Options) of
- {ok, _Pid} -> ok;
- {error, Reason} -> erlang:error(Reason, [Mod, Options])
- end;
+ meck_proc:start(Mod, Options);
new(Mod, Options) when is_list(Mod) ->
lists:foreach(fun(M) -> new(M, Options) end, Mod),
ok.
-%% @spec expect(Mod:: atom() | list(atom()), Func::atom(), Expect::fun()) -> ok
%% @doc Add expectation for a function `Func' to the mocked modules `Mod'.
%%
-%% An expectation is a fun that is executed whenever the function
-%% `Func' is called.
+%% An expectation is either of the following:
+%% <dl>
+%% <dt>`function()'</dt><dd>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').</dd>
+%% <dt>`['{@link func_clause_spec()}`]'</dt><dd>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.</dd>
+%% </dl>
%%
%% 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(Mod:: atom() | [atom()], Func::atom(), Expect::fun()) -> ok.
-expect(Mod, Func, Expect)
- when is_atom(Mod), is_atom(Func), is_function(Expect) ->
- call(Mod, {expect, Func, Expect});
-expect(Mod, Func, Expect) when is_list(Mod) ->
- lists:foreach(fun(M) -> expect(M, Func, Expect) end, Mod),
- ok.
+-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)).
-%% @spec expect(Mod:: atom() | list(atom()), Func::atom(),
-%% Arity::pos_integer(), Result::term()) -> ok
%% @doc Adds an expectation with the supplied arity and return value.
%%
-%% This creates an expectation which takes `Arity' number of functions
-%% and always returns `Result'.
+%% This creates an expectation that has the only clause {`ArgsSpec', `RetSpec'}.
%%
-%% @see expect/3.
--spec expect(Mod:: atom() | [atom()], Func::atom(),
- Arity::pos_integer(), Result::term()) -> ok.
-expect(Mod, Func, Arity, Result)
- when is_atom(Mod), is_atom(Func), is_integer(Arity), Arity >= 0 ->
- valid_expect(Mod, Func, Arity),
- call(Mod, {expect, Func, Arity, Result});
-expect(Mod, Func, Arity, Result) when is_list(Mod) ->
- lists:foreach(fun(M) -> expect(M, Func, Arity, Result) end, Mod),
+%% @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.
-%% @spec sequence(Mod:: atom() | list(atom()), Func::atom(),
-%% Arity::pos_integer(), Sequence::[term()]) -> ok
-%% @doc Adds an expectation which returns a value from `Sequence'
-%% until exhausted.
-%%
-%% This creates an expectation which takes `Arity' number of arguments
-%% and returns one element from `Sequence' at a time. Thus, calls to
-%% this expect will exhaust the list of return values in order until
-%% the last value is reached. That value is then returned for all
-%% subsequent calls.
--spec sequence(Mod:: atom() | [atom()], Func::atom(),
- Arity::pos_integer(), Sequence::[term()]) -> ok.
-sequence(Mod, Func, Arity, Sequence)
- when is_atom(Mod), is_atom(Func), is_integer(Arity), Arity >= 0 ->
- call(Mod, {sequence, Func, Arity, Sequence});
-sequence(Mod, Func, Arity, Sequence) when is_list(Mod) ->
- lists:foreach(fun(M) -> sequence(M, Func, Arity, Sequence) end, Mod),
+%% @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.
-%% @spec loop(Mod:: atom() | list(atom()), Func::atom(),
-%% Arity::pos_integer(), Loop::[term()]) -> ok
-%% @doc Adds an expectation which returns a value from `Loop'
-%% infinitely.
-%%
-%% This creates an expectation which takes `Arity' number of arguments
-%% and returns one element from `Loop' at a time. Thus, calls to this
-%% expect will return one element at a time from the list and will
-%% restart at the first element when the end is reached.
--spec loop(Mod:: atom() | [atom()], Func::atom(),
- Arity::pos_integer(), Loop::[term()]) -> ok.
-loop(Mod, Func, Arity, Loop)
- when is_atom(Mod), is_atom(Func), is_integer(Arity), Arity >= 0 ->
- call(Mod, {loop, Func, Arity, Loop});
-loop(Mod, Func, Arity, Loop) when is_list(Mod) ->
- lists:foreach(fun(M) -> loop(M, Func, Arity, Loop) end, Mod),
- ok.
-
-%% @spec delete(Mod:: atom() | list(atom()), Func::atom(),
-%% Arity::pos_integer()) -> ok
%% @doc Deletes an expectation.
%%
%% Deletes the expectation for the function `Func' with the matching
%% arity `Arity'.
--spec delete(Mod:: atom() | [atom()], Func::atom(), Arity::pos_integer()) ->
- ok.
-delete(Mod, Func, Arity)
- when is_atom(Mod), is_atom(Func), Arity >= 0 ->
- call(Mod, {delete, Func, Arity});
-delete(Mod, Func, Arity) when is_list(Mod) ->
- lists:foreach(fun(M) -> delete(M, Func, Arity) end, Mod),
+-spec delete(Mods, Func, Ari) -> ok when
+ Mods :: Mod | [Mod],
+ Mod :: atom(),
+ Func :: atom(),
+ Ari :: byte().
+delete(Mod, Func, Ari)
+ when is_atom(Mod), is_atom(Func), Ari >= 0 ->
+ meck_proc:delete_expect(Mod, Func, Ari);
+delete(Mod, Func, Ari) when is_list(Mod) ->
+ lists:foreach(fun(M) -> delete(M, Func, Ari) end, Mod),
ok.
-%% @spec exception(Class:: throw | error | exit, Reason::term()) -> no_return()
%% @doc Throws an expected exception inside an expect fun.
%%
%% This exception will get thrown without invalidating the mocked
@@ -219,21 +301,22 @@
%% handle this exception.
%%
%% <em>Note: this code should only be used inside an expect fun.</em>
--spec exception(Class:: throw | error | exit, Reason::term()) -> no_return().
+-spec exception(Class, Reason) -> no_return() when
+ Class :: throw | error | exit,
+ Reason :: any().
exception(Class, Reason) when Class == throw; Class == error; Class == exit ->
- throw(mock_exception_fun(Class, Reason)).
+ erlang:throw(meck_ret_spec:raise(Class, Reason)).
-%% @spec passthrough(Args::list(term())) -> no_return()
%% @doc Calls the original function (if existing) inside an expectation fun.
%%
-%% This call does not return, thus everything after this call inside
-%% an expectation fun will be ignored.
-%%
%% <em>Note: this code should only be used inside an expect fun.</em>
--spec passthrough(Args::[term()]) -> no_return().
-passthrough(Args) -> throw(passthrough_fun(Args)).
+-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).
-%% @spec validate(Mod:: atom() | list(atom())) -> boolean()
%% @doc Validate the state of the mock module(s).
%%
%% The function returns `true' if the mocked module(s) has been used
@@ -243,20 +326,21 @@
%% (function clause) or unexpected exceptions.
%%
%% Use the {@link history/1} or {@link history/2} function to analyze errors.
--spec validate(Mod:: atom() | [atom()]) -> boolean().
+-spec validate(Mods) -> boolean() when
+ Mods :: Mod | [Mod],
+ Mod :: atom().
validate(Mod) when is_atom(Mod) ->
- call(Mod, validate);
+ meck_proc:validate(Mod);
validate(Mod) when is_list(Mod) ->
not lists:member(false, [validate(M) || M <- Mod]).
-%% @spec history(Mod::atom()) -> history()
%% @doc Return the call history of the mocked module for all processes.
%%
%% @equiv history(Mod, '_')
--spec history(Mod::atom()) -> history().
-history(Mod) when is_atom(Mod) -> call(Mod, history).
+-spec history(Mod) -> history() when
+ Mod :: atom().
+history(Mod) when is_atom(Mod) -> meck_history:get_history('_', Mod).
-%% @spec history(Mod::atom(), Pid::pid()) -> history()
%% @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
@@ -268,38 +352,47 @@
%% @see called/4
%% @see num_calls/3
%% @see num_calls/4
--spec history(Mod::atom(), Pid:: pid() | '_') -> history().
-history(Mod, Pid) when is_atom(Mod), is_pid(Pid) orelse Pid == '_' ->
- match_history(match_mfa('_', Pid), call(Mod, history)).
+-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).
-%% @spec unload() -> list(atom())
%% @doc Unloads all mocked modules from memory.
%%
%% The function returns the list of mocked modules that were unloaded
%% in the process.
--spec unload() -> [atom()].
+-spec unload() -> Unloaded when
+ Unloaded :: [Mod],
+ Mod :: atom().
unload() -> lists:foldl(fun unload_if_mocked/2, [], registered()).
-%% @spec unload(Mod:: atom() | list(atom())) -> ok
%% @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:: atom() | [atom()]) -> ok.
-unload(Mod) when is_atom(Mod) -> call(Mod, stop), wait_for_exit(Mod);
-unload(Mods) when is_list(Mods) -> lists:foreach(fun unload/1, Mods), ok.
+-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.
-%% @spec called(Mod:: atom(), Fun:: atom(), Args:: list(term())) -> boolean()
%% @doc Returns whether `Mod:Func' has been called with `Args'.
%%
%% @equiv called(Mod, Fun, Args, '_')
-called(Mod, Fun, Args) ->
- has_call({Mod, Fun, Args}, meck:history(Mod)).
+-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.
-%% @spec called(Mod:: atom(), Fun:: atom(), Args:: list(term()),
-%% Pid::pid()) -> boolean()
%% @doc Returns whether `Pid' has called `Mod:Func' with `Args'.
%%
%% This will check the history for the module, `Mod', to determine
@@ -310,20 +403,24 @@
%% atom: ``'_' ''
%%
%% @see called/3
--spec called(Mod::atom(), Fun::atom(), Args::list(), Pid::pid()) -> boolean().
-called(Mod, Fun, Args, Pid) ->
- has_call({Mod, Fun, Args}, meck:history(Mod, Pid)).
+-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.
-%% @spec num_calls(Mod:: atom(), Fun:: atom(), Args:: list(term()))
-%% -> non_neg_integer()
%% @doc Returns the number of times `Mod:Func' has been called with `Args'.
%%
%% @equiv num_calls(Mod, Fun, Args, '_')
-num_calls(Mod, Fun, Args) ->
- num_calls({Mod, Fun, Args}, meck:history(Mod)).
+-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).
-%% @spec num_calls(Mod:: atom(), Fun:: atom(), Args:: list(term()),
-%% Pid::pid()) -> non_neg_integer()
%% @doc Returns the number of times process `Pid' has called `Mod:Func'
%% with `Args'.
%%
@@ -332,482 +429,235 @@
%% arguments, `Args' and returns the result.
%%
%% @see num_calls/3
--spec num_calls(Mod::atom(), Fun::atom(), Args::list(), Pid::pid()) ->
- non_neg_integer().
-num_calls(Mod, Fun, Args, Pid) ->
- num_calls({Mod, Fun, Args}, meck:history(Mod, Pid)).
+-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).
-%%==============================================================================
-%% Callback functions
-%%==============================================================================
+%% @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).
-%% @hidden
-init([Mod, Options]) ->
- WasSticky = case proplists:is_defined(unstick, Options) of
- true -> {module, Mod} = code:ensure_loaded(Mod),
- unstick_original(Mod);
- _ -> false
- end,
- NoPassCover = proplists:get_bool(no_passthrough_cover, Options),
- Original = backup_original(Mod, NoPassCover),
- process_flag(trap_exit, true),
- Expects = init_expects(Mod, Options),
- try
- _Bin = meck_mod:compile_and_load_forms(to_forms(Mod, Expects)),
- {ok, #state{mod = Mod, expects = Expects, original = Original,
- was_sticky = WasSticky}}
- catch
- exit:{error_loading_module, Mod, sticky_directory} ->
- {stop, module_is_sticky}
- end.
+%% @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).
-%% @hidden
-handle_call({get_expect, Func, Arity}, _From, S) ->
- {Expect, NewExpects} = get_expect(S#state.expects, Func, Arity),
- {reply, Expect, S#state{expects = NewExpects}};
-handle_call({expect, Func, Expect}, _From, S) ->
- NewExpects = store_expect(S#state.mod, Func, Expect, S#state.expects),
- {reply, ok, S#state{expects = NewExpects}};
-handle_call({expect, Func, Arity, Result}, _From, S) ->
- NewExpects = store_expect(S#state.mod, Func, {anon, Arity, Result},
- S#state.expects),
- {reply, ok, S#state{expects = NewExpects}};
-handle_call({sequence, Func, Arity, Sequence}, _From, S) ->
- NewExpects = store_expect(S#state.mod, Func, {sequence, Arity, Sequence},
- S#state.expects),
- {reply, ok, S#state{expects = NewExpects}};
-handle_call({loop, Func, Arity, Loop}, _From, S) ->
- NewExpects = store_expect(S#state.mod, Func, {loop, Arity, Loop, Loop},
- S#state.expects),
- {reply, ok, S#state{expects = NewExpects}};
-handle_call({delete, Func, Arity}, _From, S) ->
- NewExpects = delete_expect(S#state.mod, Func, Arity, S#state.expects),
- {reply, ok, S#state{expects = NewExpects}};
-handle_call(history, _From, S) ->
- {reply, lists:reverse(S#state.history), S};
-handle_call(invalidate, _From, S) ->
- {reply, ok, S#state{valid = false}};
-handle_call(validate, _From, S) ->
- {reply, S#state.valid, S};
-handle_call(stop, _From, S) ->
- {stop, normal, ok, S}.
+%% @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).
-%% @hidden
-handle_cast({add_history, Item}, S) ->
- {noreply, S#state{history = [Item| S#state.history]}};
-handle_cast(_Msg, S) ->
- {noreply, S}.
+%% @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).
-%% @hidden
-handle_info(_Info, S) -> {noreply, S}.
+%% @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).
-%% @hidden
-terminate(_Reason, #state{mod = Mod, original = OriginalState,
- was_sticky = WasSticky}) ->
- export_original_cover(Mod, OriginalState),
- cleanup(Mod),
- restore_original(Mod, OriginalState, WasSticky),
- ok.
+%% @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).
-%% @hidden
-code_change(_OldVsn, S, _Extra) -> {ok, S}.
+%% @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).
-%% @hidden
-exec(Pid, Mod, Func, Arity, Args) ->
- Expect = call(Mod, {get_expect, Func, Arity}),
- try Result = call_expect(Mod, Func, Expect, Args),
- add_history(Pid, Mod, Func, Args, Result),
- Result
- catch
- throw:Fun when is_function(Fun) ->
- case is_mock_exception(Fun) of
- true -> handle_mock_exception(Pid, Mod, Func, Fun, Args);
- false -> invalidate_and_raise(Pid, Mod, Func, Args, throw, Fun)
- end;
- Class:Reason ->
- invalidate_and_raise(Pid, Mod, Func, Args, Class, Reason)
- end.
+%% @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).
-%%==============================================================================
-%% Internal functions
-%%==============================================================================
+%% @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().
-%% --- Process functions -------------------------------------------------------
+%% @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).
-start(Mod, Options) ->
- case proplists:is_defined(no_link, Options) of
- true -> start(start, Mod, Options);
- false -> start(start_link, Mod, Options)
- end.
+%% @doc creates a {@link matcher/0} instance from either `Predicate' or
+%% `HamcrestMatcher'.
+%% <ul>
+%% <li>`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.</li>
+%% <li>`HamcrestMatcher' - is a matcher created by
+%% <a href="https://github.com/hyperthunk/hamcrest-erlang">Hamcrest-Erlang</a>
+%% library</li>
+%% </ul>
+-spec is(MatcherImpl) -> matcher() when
+ MatcherImpl :: Predicate | HamcrestMatcher,
+ Predicate :: fun((any()) -> any()),
+ HamcrestMatcher :: hamcrest:matchspec().
+is(MatcherImpl) ->
+ meck_matcher:new(MatcherImpl).
-start(Func, Mod, Options) ->
- gen_server:Func({local, proc_name(Mod)}, ?MODULE, [Mod, Options], []).
+%% @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/3} 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).
-cast(Mod, Msg) -> gen_server(cast, Mod, Msg).
-call(Mod, Msg) -> gen_server(call, Mod, Msg).
+%% @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).
-gen_server(Func, Mod, Msg) ->
- Name = proc_name(Mod),
- try gen_server:Func(Name, Msg)
- catch exit:_Reason -> erlang:error({not_mocked, Mod}) end.
+%%%============================================================================
+%%% Internal functions
+%%%============================================================================
-proc_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_meck").
-
-original_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_meck_original").
-
+-spec wait_for_exit(Mod::atom()) -> ok.
wait_for_exit(Mod) ->
- MonitorRef = erlang:monitor(process, proc_name(Mod)),
+ MonitorRef = erlang:monitor(process, meck_util:proc_name(Mod)),
receive {'DOWN', MonitorRef, _Type, _Object, _Info} -> ok end.
-unload_if_mocked(P, L) when is_atom(P) ->
- unload_if_mocked(atom_to_list(P), L);
-unload_if_mocked(P, L) when length(P) > 5 ->
- case lists:split(length(P) - 5, P) of
+-spec unload_if_mocked(Mod::atom() | string(), Unloaded::[atom()]) ->
+ NewUnloaded::[atom()].
+unload_if_mocked(Mod, Unloaded) when is_atom(Mod) ->
+ unload_if_mocked(atom_to_list(Mod), Unloaded);
+unload_if_mocked(ModName, Unloaded) when length(ModName) > 5 ->
+ case lists:split(length(ModName) - 5, ModName) of
{Name, "_meck"} ->
- Mocked = list_to_existing_atom(Name),
+ Mocked = erlang:list_to_existing_atom(Name),
try
unload(Mocked)
catch error:{not_mocked, Mocked} ->
ok
end,
- [Mocked|L];
+ [Mocked | Unloaded];
_Else ->
- L
+ Unloaded
end;
-unload_if_mocked(_P, L) ->
- L.
+unload_if_mocked(_P, Unloaded) ->
+ Unloaded.
-%% --- Mock handling -----------------------------------------------------------
-
-valid_expect(M, F, A) ->
- case expect_type(M, F, A) of
- autogenerated -> erlang:error({cannot_mock_autogenerated, {M, F, A}});
- builtin -> erlang:error({cannot_mock_builtin, {M, F, A}});
- normal -> ok
- end.
-
-init_expects(Mod, Options) ->
- case proplists:get_value(passthrough, Options, false) andalso exists(Mod) of
- true -> dict:from_list([{FA, passthrough} || FA <- exports(Mod)]);
- _ -> dict:new()
- end.
-
-
-get_expect(Expects, Func, Arity) ->
- case e_fetch(Expects, Func, Arity) of
- {sequence, Arity, [Result]} ->
- {{sequence, Arity, Result}, Expects};
- {sequence, Arity, [Result|Rest]} ->
- {{sequence, Arity, Result},
- e_store(Expects, Func, {sequence, Arity, Rest})};
- {loop, Arity, [Result], Loop} ->
- {{loop, Arity, Result},
- e_store(Expects, Func, {loop, Arity, Loop, Loop})};
- {loop, Arity, [Result|Rest], Loop} ->
- {{loop, Arity, Result},
- e_store(Expects, Func, {loop, Arity, Rest, Loop})};
- Other ->
- {Other, Expects}
- end.
-
-store_expect(Mod, Func, Expect, Expects) ->
- change_expects(fun e_store/3, Mod, Func, Expect, Expects).
-
-delete_expect(Mod, Func, Arity, Expects) ->
- change_expects(fun e_delete/3, Mod, Func, Arity, Expects).
-
-change_expects(Op, Mod, Func, Value, Expects) ->
- NewExpects = Op(Expects, Func, Value),
- _Bin = meck_mod:compile_and_load_forms(to_forms(Mod, NewExpects)),
- NewExpects.
-
-e_store(Expects, Func, Expect) ->
- dict:store({Func, arity(Expect)}, Expect, Expects).
-
-e_fetch(Expects, Func, Arity) ->
- dict:fetch({Func, Arity}, Expects).
-
-e_delete(Expects, Func, Arity) ->
- dict:erase({Func, Arity}, Expects).
-
-%% --- Code generation ---------------------------------------------------------
-
-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),
- ?integer(Arity),
- 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(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) ->
- 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.
-
-
-to_forms(Mod, Expects) ->
- {Exports, Functions} = functions(Mod, Expects),
- [?attribute(module, Mod)] ++ Exports ++ Functions.
-
-functions(Mod, Expects) ->
- dict:fold(fun(Export, Expect, {Exports, Functions}) ->
- {[?attribute(export, [Export])|Exports],
- [func(Mod, Export, Expect)|Functions]}
- end, {[], []}, Expects).
-
-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)).
-
-arity({anon, Arity, _Result}) ->
- Arity;
-arity({sequence, Arity, _Sequence}) ->
- Arity;
-arity({loop, Arity, _Current, _Loop}) ->
- Arity;
-arity(Fun) when is_function(Fun) ->
- {arity, Arity} = erlang:fun_info(Fun, arity),
- Arity.
-
-%% --- Execution utilities -----------------------------------------------------
-
-is_local_function(Fun) ->
- {module, Module} = erlang:fun_info(Fun, module),
- ?MODULE == Module.
-
-handle_mock_exception(Pid, Mod, Func, Fun, Args) ->
- case Fun() of
- {exception, Class, Reason} ->
- % exception created with the mock:exception function,
- % do not invalidate Mod
- raise(Pid, Mod, Func, Args, Class, Reason);
- {passthrough, PassthroughArgs} ->
- % call_original(Args) called from mock function
- Result = apply(original_name(Mod), Func, PassthroughArgs),
- add_history(Pid, Mod, Func, PassthroughArgs, Result),
- Result
- end.
-
--spec invalidate_and_raise(_, _, _, _, _, _) -> no_return().
-invalidate_and_raise(Pid, Mod, Func, Args, Class, Reason) ->
- call(Mod, invalidate),
- raise(Pid, Mod, Func, Args, Class, Reason).
-
-raise(Pid, Mod, Func, Args, Class, Reason) ->
- Stacktrace = inject(Mod, Func, Args, erlang:get_stacktrace()),
- add_history(Pid, Mod, Func, Args, Class, Reason, Stacktrace),
- erlang:raise(Class, Reason, Stacktrace).
-
-mock_exception_fun(Class, Reason) -> fun() -> {exception, Class, Reason} end.
-
-passthrough_fun(Args) -> fun() -> {passthrough, Args} end.
-
-call_expect(_Mod, _Func, {_Type, Arity, Return}, VarList)
- when Arity == length(VarList) ->
- Return;
-call_expect(Mod, Func, passthrough, VarList) ->
- apply(original_name(Mod), Func, VarList);
-call_expect(_Mod, _Func, Fun, VarList) when is_function(Fun) ->
- apply(Fun, VarList).
-
-inject(_Mod, _Func, _Args, []) ->
- [];
-inject(Mod, Func, Args, [{meck, exec, _Arity} = Meck|Stack]) ->
- [Meck, {Mod, Func, Args}|Stack];
-inject(Mod, Func, Args, [{meck, exec, _Arity, _Location} = Meck|Stack]) ->
- [Meck, {Mod, Func, Args}|Stack];
-inject(Mod, Func, Args, [H|Stack]) ->
- [H|inject(Mod, Func, Args, Stack)].
-
-is_mock_exception(Fun) -> is_local_function(Fun).
-
-%% --- Original module handling ------------------------------------------------
-
-backup_original(Module, NoPassCover) ->
- Cover = get_cover_state(Module),
- try
- Forms = meck_mod:abstract_code(meck_mod:beam_file(Module)),
- NewName = original_name(Module),
- CompileOpts = meck_mod:compile_options(meck_mod:beam_file(Module)),
- Renamed = meck_mod:rename_module(Forms, NewName),
- Binary = meck_mod: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.
- if (Cover == false) orelse NoPassCover ->
- Binary2 = no_passthrough_cover;
- true ->
- Binary2 = Binary,
- meck_cover:compile_beam(NewName, Binary2)
- end,
- {Cover, Binary2}
- catch
- throw:{object_code_not_found, _Module} ->
- {Cover, no_binary}; % TODO: What to do here?
- throw:no_abstract_code ->
- {Cover, no_binary} % TODO: What to do here?
- end.
-
-restore_original(Mod, {false, _}, WasSticky) ->
- restick_original(Mod, WasSticky),
- ok;
-restore_original(Mod, OriginalState={{File, Data, Options},_}, WasSticky) ->
- case filename:extension(File) of
- ".erl" ->
- {ok, Mod} = cover:compile_module(File, Options);
- ".beam" ->
- cover:compile_beam(File)
- end,
- restick_original(Mod, WasSticky),
- import_original_cover(Mod, OriginalState),
- ok = cover:import(Data),
- ok = file:delete(Data),
- ok.
-
-%% @doc Import the cover data for `<name>_meck_original' but since it
-%% was modified by `export_original_cover' it will count towards
-%% `<name>'.
-import_original_cover(Mod, {_,Bin}) when is_binary(Bin) ->
- OriginalData = atom_to_list(original_name(Mod)) ++ ".coverdata",
- ok = cover:import(OriginalData),
- ok = file:delete(OriginalData);
-import_original_cover(_, _) ->
- ok.
-
-%% @doc Export the cover data for `<name>_meck_original' and modify
-%% the data so it can be imported under `<name>'.
-export_original_cover(Mod, {_, Bin}) when is_binary(Bin) ->
- OriginalMod = original_name(Mod),
- File = atom_to_list(OriginalMod) ++ ".coverdata",
- ok = cover:export(File, OriginalMod),
- ok = meck_cover:rename_module(File, Mod);
-export_original_cover(_, _) ->
- ok.
-
-
-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.
-
-get_cover_state(Module) -> get_cover_state(Module, cover:is_compiled(Module)).
-
-get_cover_state(Module, {file, File}) ->
- Data = atom_to_list(Module) ++ ".coverdata",
- ok = cover:export(Data, Module),
- CompileOptions =
- try
- meck_mod:compile_options(meck_mod:beam_file(Module))
- catch
- throw:{object_code_not_found, _Module} -> []
- end,
- {File, Data, CompileOptions};
-get_cover_state(_Module, _IsCompiled) ->
- false.
-
-exists(Module) ->
- code:which(Module) /= non_existing.
-
-exports(M) ->
- [ FA || FA = {F, A} <- M:module_info(exports),
- normal == expect_type(M, F, A)].
-
-%% Functions we should not create expects for (auto-generated and BIFs)
-expect_type(_, module_info, 0) -> autogenerated;
-expect_type(_, module_info, 1) -> autogenerated;
-expect_type(M, F, A) -> expect_type(erlang:is_builtin(M, F, A)).
-
-expect_type(true) -> builtin;
-expect_type(false) -> normal.
-
-cleanup(Mod) ->
- code:purge(Mod),
- code:delete(Mod),
- code:purge(original_name(Mod)),
- code:delete(original_name(Mod)).
-
-%% --- History utilities -------------------------------------------------------
-
-add_history(Pid, Mod, Func, Args, Result) ->
- add_history(Mod, {Pid, {Mod, Func, Args}, Result}).
-add_history(Pid, Mod, Func, Args, Class, Reason, Stacktrace) ->
- add_history(Mod, {Pid, {Mod, Func, Args}, Class, Reason, Stacktrace}).
-
-add_history(Mod, Item) ->
- cast(Mod, {add_history, Item}).
-
-has_call(MFA, History) ->
- [] =/= match_history(match_mfa(MFA), History).
-
-num_calls(MFA, History) ->
- length(match_history(match_mfa(MFA), History)).
-
-match_history(MatchSpec, History) ->
- MS = ets:match_spec_compile(MatchSpec),
- ets:match_spec_run(History, MS).
-
-match_mfa(MFA) -> match_mfa(MFA, '_').
-
-match_mfa(MFA, Pid) ->
- [{{Pid, MFA, '_'}, [], ['$_']},
- {{Pid, MFA, '_', '_', '_'}, [], ['$_']}].
+-spec check_expect_result(ok | {error, Reason::any()}) -> ok.
+check_expect_result(ok) -> ok;
+check_expect_result({error, Reason}) -> erlang:error(Reason).
diff --git a/src/meck_abstract.hrl b/src/meck_abstract.hrl
deleted file mode 100644
index 8f3b982..0000000
--- a/src/meck_abstract.hrl
+++ /dev/null
@@ -1,19 +0,0 @@
--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}).
diff --git a/src/meck_args_matcher.erl b/src/meck_args_matcher.erl
new file mode 100644
index 0000000..73d50c6
--- /dev/null
+++ b/src/meck_args_matcher.erl
@@ -0,0 +1,126 @@
+%%%============================================================================
+%%% 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.
\ No newline at end of file
diff --git a/src/meck_mod.erl b/src/meck_code.erl
similarity index 96%
rename from src/meck_mod.erl
rename to src/meck_code.erl
index 22a237d..895ded0 100644
--- a/src/meck_mod.erl
+++ b/src/meck_code.erl
@@ -1,4 +1,4 @@
-%%==============================================================================
+%%=============================================================================
%% Copyright 2011 Adam Lindberg & Erlang Solutions Ltd.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +12,14 @@
%% 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 <eproxus@gmail.com>
%% @copyright 2011, Adam Lindberg & Erlang Solutions Ltd
%% @doc Module wrangling helper functions.
--module(meck_mod).
+-module(meck_code).
%% Interface exports
-export([abstract_code/1]).
@@ -35,9 +35,9 @@
-type compile_options() :: [term()].
-type export() :: {atom(), byte()}.
-%%==============================================================================
+%%=============================================================================
%% Interface exports
-%%==============================================================================
+%%=============================================================================
-spec abstract_code(binary()) -> erlang_form().
abstract_code(BeamFile) ->
@@ -100,9 +100,9 @@
rename_module([H|T], NewName) ->
[H|rename_module(T, NewName)].
-%%==============================================================================
+%%=============================================================================
%% Internal functions
-%%==============================================================================
+%%=============================================================================
load_binary(Name, Binary) ->
case code:load_binary(Name, "", Binary) of
diff --git a/src/meck_code_gen.erl b/src/meck_code_gen.erl
new file mode 100644
index 0000000..cbfc75b
--- /dev/null
+++ b/src/meck_code_gen.erl
@@ -0,0 +1,198 @@
+%%%============================================================================
+%%% Licensed under the Apache License, Version 2.0 (the "License");
+%%% you may not use this file except in compliance with the License.
+%%% You may obtain a copy of the License at
+%%%
+%%% http://www.apache.org/licenses/LICENSE-2.0
+%%%
+%%% Unless required by applicable law or agreed to in writing, software
+%%% distributed under the License is distributed on an "AS IS" BASIS,
+%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%%% See the License for the specific language governing permissions and
+%%% limitations under the License.
+%%%============================================================================
+
+%%% @private
+%%% @doc Implements code generation for mocked module and also contains code
+%%% pieces that are called in the generated module context.
+-module(meck_code_gen).
+
+%% API
+-export([to_forms/2]).
+-export([get_current_call/0]).
+
+%% Exported to be accessible from generated modules.
+-export([exec/4]).
+
+%%%============================================================================
+%%% Definitions
+%%%============================================================================
+
+-define(CURRENT_CALL, '$meck_call').
+-define(call(Module, Function, Arguments),
+ {call, ?LINE,
+ {remote, ?LINE, ?atom(Module), ?atom(Function)},
+ Arguments}).
+
+-define(atom(Atom), {atom, ?LINE, Atom}).
+-define(integer(Integer), {integer, ?LINE, Integer}).
+-define(var(Name), {var, ?LINE, Name}).
+-define(attribute(Attribute, Args), {attribute, ?LINE, Attribute, Args}).
+-define(function(Name, Arity, Clauses),
+ {function, ?LINE, Name, Arity, Clauses}).
+-define(clause(Arguments, Body), {clause, ?LINE, Arguments, [], Body}).
+-define(tuple(Elements), {tuple, ?LINE, Elements}).
+
+%%%============================================================================
+%%% API
+%%%============================================================================
+
+to_forms(Mod, Expects) ->
+ {Exports, Functions} = functions(Mod, Expects),
+ [?attribute(module, Mod)] ++ attributes(Mod) ++ Exports ++ Functions.
+
+-spec get_current_call() -> {Mod::atom(), Func::atom()}.
+get_current_call() ->
+ get(?CURRENT_CALL).
+
+%%%============================================================================
+%%% Internal functions
+%%%============================================================================
+
+attributes(Mod) ->
+ try
+ [?attribute(Key, Val) || {Key, Val} <-
+ proplists:get_value(attributes, Mod:module_info(), []),
+ Key =/= vsn]
+ 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) ->
+ case meck_proc:get_result_spec(Mod, Func, Args) of
+ undefined ->
+ meck_proc:invalidate(Mod),
+ raise(Pid, Mod, Func, Args, error, function_clause);
+ ResultSpec ->
+ put(?CURRENT_CALL, {Mod, Func}),
+ try
+ Result = meck_ret_spec:eval_result(Mod, Func, Args, ResultSpec),
+ meck_proc:add_history(Mod, Pid, Func, Args, Result),
+ Result
+ catch
+ Class:Reason ->
+ handle_exception(Pid, Mod, Func, Args, Class, Reason)
+ after
+ erase(?CURRENT_CALL)
+ end
+ end.
+
+-spec handle_exception(CallerPid::pid(), Mod::atom(), Func::atom(),
+ Args::[any()], Class:: exit | error | throw,
+ Reason::any()) ->
+ no_return().
+handle_exception(Pid, Mod, Func, Args, Class, Reason) ->
+ case meck_ret_spec:is_meck_exception(Reason) of
+ {true, MockedClass, MockedReason} ->
+ raise(Pid, Mod, Func, Args, MockedClass, MockedReason);
+ _ ->
+ meck_proc:invalidate(Mod),
+ raise(Pid, Mod, Func, Args, Class, Reason)
+ end.
+
+-spec raise(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()],
+ Class:: exit | error | throw, Reason::any()) ->
+ no_return().
+raise(Pid, Mod, Func, Args, Class, Reason) ->
+ StackTrace = inject(Mod, Func, Args, erlang:get_stacktrace()),
+ meck_proc:add_history(Mod, Pid, Func, Args, {Class, Reason, StackTrace}),
+ erlang:raise(Class, Reason, StackTrace).
+
+-spec inject(Mod::atom(), Func::atom(), Args::[any()],
+ meck_history:stack_trace()) ->
+ NewStackTrace::meck_history:stack_trace().
+inject(_Mod, _Func, _Args, []) ->
+ [];
+inject(Mod, Func, Args, [{?MODULE, exec, _AriOrArgs, _Loc}|Stack]) ->
+ [{Mod, Func, Args} | Stack];
+inject(Mod, Func, Args, [{?MODULE, exec, _AriOrArgs}|Stack]) ->
+ [{Mod, Func, Args} | Stack];
+inject(Mod, Func, Args, [Call|Stack]) when element(1, Call) == ?MODULE ->
+ inject(Mod, Func, Args, Stack);
+inject(Mod, Func, Args, [H | Stack]) ->
+ [H | inject(Mod, Func, Args, Stack)].
diff --git a/src/meck_cover.erl b/src/meck_cover.erl
index da8888c..7ef2963 100644
--- a/src/meck_cover.erl
+++ b/src/meck_cover.erl
@@ -1,4 +1,4 @@
-%%==============================================================================
+%%=============================================================================
%% 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
@@ -10,8 +10,9 @@
%% 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).
@@ -20,9 +21,9 @@
-export([compile_beam/2]).
-export([rename_module/2]).
-%%==============================================================================
+%%=============================================================================
%% Interface exports
-%%==============================================================================
+%%=============================================================================
%% @doc Enabled cover on `<name>_meck_original'.
compile_beam(OriginalMod, Bin) ->
@@ -36,9 +37,9 @@
write_terms(File, NewTerms),
ok.
-%%==============================================================================
+%%=============================================================================
%% Internal functions
-%%==============================================================================
+%%=============================================================================
%% @private
%%
@@ -56,11 +57,11 @@
true ->
ok;
false ->
- Beam = meck_mod:beam_file(cover),
- AbsCode = meck_mod:abstract_code(Beam),
+ Beam = meck_code:beam_file(cover),
+ AbsCode = meck_code:abstract_code(Beam),
Exports = [{compile_beam, 2}, {get_term, 1}, {write, 2}],
- AbsCode2 = meck_mod:add_exports(Exports, AbsCode),
- _Bin = meck_mod:compile_and_load_forms(AbsCode2),
+ AbsCode2 = meck_code:add_exports(Exports, AbsCode),
+ _Bin = meck_code:compile_and_load_forms(AbsCode2),
ok
end.
@@ -107,4 +108,3 @@
write_term(Fd) ->
fun(Term) -> cover:write(Term, Fd) end.
-
diff --git a/src/meck_expect.erl b/src/meck_expect.erl
new file mode 100644
index 0000000..d95951c
--- /dev/null
+++ b/src/meck_expect.erl
@@ -0,0 +1,139 @@
+%%%============================================================================
+%%% 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([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()}.
+-opaque 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 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.
diff --git a/src/meck_history.erl b/src/meck_history.erl
new file mode 100644
index 0000000..526186a
--- /dev/null
+++ b/src/meck_history.erl
@@ -0,0 +1,131 @@
+%%%============================================================================
+%%% 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/0]).
+
+-export([get_history/2]).
+-export([num_calls/4]).
+-export([capture/6]).
+-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 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.
diff --git a/src/meck_matcher.erl b/src/meck_matcher.erl
new file mode 100644
index 0000000..e6eb47c
--- /dev/null
+++ b/src/meck_matcher.erl
@@ -0,0 +1,83 @@
+%%%============================================================================
+%%% 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]).
+
+%%%============================================================================
+%%% Definitions
+%%%============================================================================
+
+-record('$meck.matcher', {type :: predicate | hamcrest,
+ impl :: predicate() | hamcrest:matchspec()}).
+
+%%%============================================================================
+%%% Types
+%%%============================================================================
+
+-type predicate() :: fun((X::any()) -> any()).
+-type matcher() :: #'$meck.matcher'{}.
+
+%%%============================================================================
+%%% 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 hamcrest:assert_that(Value, HamcrestMatcher)) == true;
+match_ignore(_Value, _NotMatcher) ->
+ true.
+
+%%%============================================================================
+%%% Internal functions
+%%%============================================================================
+
+-spec is_hamcrest_matcher(any()) -> boolean().
+is_hamcrest_matcher(Something) ->
+ try hamcrest:is_matcher(Something)
+ catch _Class:_Reason -> false
+ end.
diff --git a/src/meck_proc.erl b/src/meck_proc.erl
new file mode 100644
index 0000000..060d8fe
--- /dev/null
+++ b/src/meck_proc.erl
@@ -0,0 +1,641 @@
+%%%============================================================================
+%%% 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/3]).
+-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/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
+%%%============================================================================
+
+-record(state, {mod :: atom(),
+ can_expect :: any | [{Mod::atom(), Ari::byte()}],
+ expects :: dict(),
+ valid = true :: boolean(),
+ history = [] :: meck_history:history() | undefined,
+ original :: term(),
+ was_sticky = false :: boolean(),
+ reload :: {Compiler::pid(), {From::pid(), Tag::any()}} |
+ undefined,
+ trackers = [] :: [tracker()]}).
+
+-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, MockProcPid::pid()} |
+ {error, Reason::any()}.
+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()) -> ok.
+delete_expect(Mod, Func, Ari) ->
+ gen_server(call, Mod, {delete_expect, Func, Ari}).
+
+-spec add_history(Mod::atom(), CallerPid::pid(), Func::atom(), Args::[any()],
+ Result::any()) ->
+ ok.
+add_history(Mod, CallerPid, Func, Args, {Class, Reason, StackTrace}) ->
+ gen_server(cast, Mod, {add_history, {CallerPid, {Mod, Func, Args}, Class, Reason, StackTrace}});
+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(call, Mod, invalidate).
+
+-spec stop(Mod::atom()) -> ok.
+stop(Mod) ->
+ gen_server(call, Mod, stop).
+
+%%%============================================================================
+%%% gen_server callbacks
+%%%============================================================================
+
+%% @hidden
+init([Mod, Options]) ->
+ 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),
+ Original = backup_original(Mod, NoPassCover),
+ 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,
+ history = History}}
+ catch
+ exit:{error_loading_module, Mod, sticky_directory} ->
+ {stop, module_is_sticky}
+ 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}) ->
+ 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 ->
+ {NewExpects, CompilerPid} = store_expect(Mod, FuncAri, Expect,
+ Expects),
+ {noreply, S#state{expects = NewExpects,
+ reload = {CompilerPid, From}}};
+ {error, Reason} ->
+ {reply, {error, Reason}, S}
+ end;
+handle_call({delete_expect, Func, Ari}, From,
+ S = #state{mod = Mod, expects = Expects}) ->
+ check_if_being_reloaded(S),
+ {NewExpects, CompilerPid} = do_delete_expect(Mod, {Func, Ari}, Expects),
+ {noreply, S#state{expects = NewExpects,
+ reload = {CompilerPid, From}}};
+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(invalidate, _From, S) ->
+ {reply, ok, S#state{valid = false}};
+handle_call(validate, _From, S) ->
+ {reply, S#state.valid, S};
+handle_call(stop, _From, S) ->
+ {stop, normal, ok, S}.
+
+%% @hidden
+handle_cast({add_history, HistoryRecord}, S = #state{history = undefined,
+ trackers = Trackers}) ->
+ UpdTracker = update_trackers(HistoryRecord, Trackers),
+ {noreply, S#state{trackers = UpdTracker}};
+handle_cast({add_history, HistoryRecord}, S = #state{history = History,
+ trackers = Trackers,
+ reload = Reload}) ->
+ case Reload of
+ undefined ->
+ UpdTrackers = update_trackers(HistoryRecord, Trackers),
+ {noreply, S#state{history = [HistoryRecord | History],
+ trackers = UpdTrackers}};
+ _ ->
+ % Skip Item if the mocked module compiler is running.
+ {noreply, S}
+ end;
+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}) ->
+ export_original_cover(Mod, OriginalState),
+ cleanup(Mod),
+ restore_original(Mod, OriginalState, WasSticky),
+ ok.
+
+%% @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(), NoPassCover::boolean()) ->
+ {Cover:: false |
+ {File::string(), Data::string(), CompiledOptions::[any()]},
+ Binary:: no_binary |
+ no_passthrough_cover |
+ binary()}.
+backup_original(Mod, NoPassCover) ->
+ Cover = get_cover_state(Mod),
+ try
+ Forms = meck_code:abstract_code(meck_code:beam_file(Mod)),
+ NewName = meck_util:original_name(Mod),
+ CompileOpts = meck_code:compile_options(meck_code:beam_file(Mod)),
+ Renamed = meck_code:rename_module(Forms, 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.
+ if (Cover == false) orelse NoPassCover ->
+ Binary2 = no_passthrough_cover;
+ true ->
+ Binary2 = Binary,
+ meck_cover:compile_beam(NewName, Binary2)
+ end,
+ {Cover, Binary2}
+ catch
+ throw:{object_code_not_found, _Module} ->
+ {Cover, no_binary}; % TODO: What to do here?
+ throw:no_abstract_code ->
+ {Cover, no_binary} % TODO: What to do here?
+ 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} ->
+ Data = atom_to_list(Mod) ++ ".coverdata",
+ ok = cover:export(Data, Mod),
+ CompileOptions =
+ try
+ meck_code:compile_options(meck_code:beam_file(Mod))
+ catch
+ throw:{object_code_not_found, _Module} -> []
+ end,
+ {File, Data, 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()]) ->
+ 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 | 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(_S) ->
+ erlang:error(concurrent_reload).
+
+-spec do_get_result_spec(Expects::dict(), Func::atom(), Args::[any()]) ->
+ {meck_ret_spec:result_spec() | undefined, NewExpects::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::dict()) ->
+ {NewExpects::dict(), CompilerPid::pid()}.
+store_expect(Mod, FuncAri, Expect, Expects) ->
+ NewExpects = dict:store(FuncAri, Expect, Expects),
+ compile_expects(Mod, NewExpects).
+
+-spec do_delete_expect(Mod::atom(), meck_expect:func_ari(), Expects::dict()) ->
+ {NewExpects::dict(), CompilerPid::pid()}.
+do_delete_expect(Mod, FuncAri, Expects) ->
+ NewExpects = dict:erase(FuncAri, Expects),
+ compile_expects(Mod, NewExpects).
+
+-spec compile_expects(Mod::atom(), Expects::dict()) ->
+ {NewExpects::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.
+ CompilerPid =
+ erlang:spawn_link(fun() ->
+ Forms = meck_code_gen:to_forms(Mod, Expects),
+ meck_code:compile_and_load_forms(Forms)
+ end),
+ {Expects, CompilerPid}.
+
+restore_original(Mod, {false, _}, WasSticky) ->
+ restick_original(Mod, WasSticky),
+ ok;
+restore_original(Mod, OriginalState={{File, Data, Options},_}, WasSticky) ->
+ case filename:extension(File) of
+ ".erl" ->
+ {ok, Mod} = cover:compile_module(File, Options);
+ ".beam" ->
+ cover:compile_beam(File)
+ end,
+ restick_original(Mod, WasSticky),
+ import_original_cover(Mod, OriginalState),
+ ok = cover:import(Data),
+ ok = file:delete(Data),
+ ok.
+
+%% @doc Import the cover data for `<name>_meck_original' but since it
+%% was modified by `export_original_cover' it will count towards
+%% `<name>'.
+import_original_cover(Mod, {_,Bin}) when is_binary(Bin) ->
+ OriginalData = atom_to_list(meck_util:original_name(Mod)) ++ ".coverdata",
+ ok = cover:import(OriginalData),
+ ok = file:delete(OriginalData);
+import_original_cover(_, _) ->
+ ok.
+
+%% @doc Export the cover data for `<name>_meck_original' and modify
+%% the data so it can be imported under `<name>'.
+export_original_cover(Mod, {_, Bin}) when is_binary(Bin) ->
+ OriginalMod = meck_util:original_name(Mod),
+ File = atom_to_list(OriginalMod) ++ ".coverdata",
+ ok = cover:export(File, OriginalMod),
+ ok = meck_cover:rename_module(File, Mod);
+export_original_cover(_, _) ->
+ ok.
+
+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)),
+ code:delete(meck_util:original_name(Mod)).
+
+-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)).
+
+
+
diff --git a/src/meck_ret_spec.erl b/src/meck_ret_spec.erl
new file mode 100644
index 0000000..04017e5
--- /dev/null
+++ b/src/meck_ret_spec.erl
@@ -0,0 +1,140 @@
+%%%============================================================================
+%%% 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([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()) -> boolean().
+is_meck_exception({meck_raise, MockedClass, MockedReason}) ->
+ {true, MockedClass, MockedReason};
+is_meck_exception(_Reason) ->
+ false.
+
+-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).
diff --git a/src/meck_util.erl b/src/meck_util.erl
new file mode 100644
index 0000000..04fced5
--- /dev/null
+++ b/src/meck_util.erl
@@ -0,0 +1,44 @@
+%%%============================================================================
+%%% 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, [], ['$_']}.
diff --git a/test.config b/test.config
new file mode 100644
index 0000000..f9f6c33
--- /dev/null
+++ b/test.config
@@ -0,0 +1,15 @@
+%% Dependencies ===============================================================
+{deps,
+ [{hamcrest, ".*", {git, "https://github.com/hyperthunk/hamcrest-erlang.git",
+ {branch, "master"}}}]}.
+
+%% Compiler Options ===========================================================
+% FIXME: Add warnings_as_errors once Hamcrest is fixed
+{erl_opts, [debug_info]}.
+
+%% Eunit Options ==============================================================
+{cover_enabled, true}.
+{cover_print_enabled, true}.
+
+%% Misc =======================================================================
+{clean_files, [".eunit", "ebin/*.beam", "test/*.beam"]}.
diff --git a/test/meck_args_matcher_tests.erl b/test/meck_args_matcher_tests.erl
new file mode 100644
index 0000000..0b943c9
--- /dev/null
+++ b/test/meck_args_matcher_tests.erl
@@ -0,0 +1,59 @@
+%%%============================================================================
+%%% 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(hamcrest_matchers:equal_to(1)),
+ 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)).
diff --git a/test/meck_expect_tests.erl b/test/meck_expect_tests.erl
new file mode 100644
index 0000000..a18bf39
--- /dev/null
+++ b/test/meck_expect_tests.erl
@@ -0,0 +1,107 @@
+%%%============================================================================
+%%% Copyright 2010 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(hamcrest_matchers:less_than(1004)),
+ %% 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(hamcrest_matchers:less_than(1004)),
+ %% 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(hamcrest_matchers:less_than(1004)),
+ %% 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)).
diff --git a/test/meck_history_tests.erl b/test/meck_history_tests.erl
new file mode 100644
index 0000000..104a642
--- /dev/null
+++ b/test/meck_history_tests.erl
@@ -0,0 +1,83 @@
+%%%============================================================================
+%%% 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").
+
+num_calls_with_arity_test() ->
+ %% Given
+ meck:new(test, [non_strict]),
+ 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)),
+ %% Clean
+ meck:unload().
+
+capture_different_positions_test() ->
+ %% Given
+ meck:new(test, [non_strict]),
+ 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)),
+ %% Clean
+ meck:unload().
+
+capture_different_args_specs_test() ->
+ %% Given
+ meck:new(test, [non_strict]),
+ 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(hamcrest_matchers:greater_than(3006))], 2)),
+ %% Clean
+ meck:unload().
diff --git a/test/meck_matcher_tests.erl b/test/meck_matcher_tests.erl
new file mode 100644
index 0000000..543aee9
--- /dev/null
+++ b/test/meck_matcher_tests.erl
@@ -0,0 +1,48 @@
+%%%============================================================================
+%%% 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(hamcrest_matchers:equal_to(1000)),
+ ?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(true, meck_matcher:is_matcher(meck_matcher:new(hamcrest_matchers:equal_to(1000)))),
+ ?assertMatch(false, meck_matcher:is_matcher(hamcrest_matchers:equal_to(1000))),
+ ?assertMatch(false, meck_matcher:is_matcher(blah)).
diff --git a/test/meck_performance_test.erl b/test/meck_performance_test.erl
index 71af107..f49aac4 100644
--- a/test/meck_performance_test.erl
+++ b/test/meck_performance_test.erl
@@ -4,12 +4,12 @@
%% Interface exports
-export([run/1]).
-%%==============================================================================
+%%=============================================================================
%% Interface exports
-%%==============================================================================
+%%=============================================================================
run(N) ->
- meck:new(test),
+ meck:new(test, [non_strict]),
io:format("\t\tMin\tMax\tMed\tAvg~n"),
io:format("expect/3\t~p\t~p\t~p\t~p~n",
test_avg(meck, expect, [test, normal, fun() -> ok end], N)),
@@ -36,7 +36,7 @@
test_avg(test, shortcut_opaque, [], N)),
meck:unload(test),
- meck:new(test),
+ meck:new(test, [non_strict]),
meck:expect(test, func, 1, ok),
[test:func(I) || I <- lists:seq(1, 100)],
io:format("~n\t\tMin\tMax\tMed\tAvg~n"),
@@ -45,9 +45,9 @@
meck:unload(test),
ok.
-%%==============================================================================
+%%=============================================================================
%% Internal functions
-%%==============================================================================
+%%=============================================================================
test_avg(M, F, A, N) when N > 0 ->
L = test_loop(M, F, A, N, []),
diff --git a/test/meck_ret_spec_tests.erl b/test/meck_ret_spec_tests.erl
new file mode 100644
index 0000000..485f99d
--- /dev/null
+++ b/test/meck_ret_spec_tests.erl
@@ -0,0 +1,68 @@
+%%%============================================================================
+%%% Copyright 2010 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").
+
+passthrough_test() ->
+ %% Given
+ meck:new(meck_test_module),
+ %% When
+ meck:expect(meck_test_module, c, 2, meck:passthrough()),
+ %% Then
+ ?assertEqual({1, 3}, meck_test_module:c(1, 3)),
+ %% Cleanup
+ meck:unload().
+
+explicit_exec_test() ->
+ %% Given
+ meck:new(meck_test_module),
+ %% 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)),
+ %% Cleanup
+ meck:unload().
+
+exec_test() ->
+ %% Given
+ meck:new(meck_test_module),
+ %% When
+ meck:expect(meck_test_module, c, 2, fun(A, B) -> {B, A} end),
+ %% Then
+ ?assertEqual({3, 1}, meck_test_module:c(1, 3)),
+ %% Cleanup
+ meck:unload().
+
+deep_exec_test() ->
+ %% Given
+ meck:new(meck_test_module),
+ %% 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)),
+ %% Cleanup
+ meck:unload().
+
+invalid_arity_exec_test() ->
+ %% Given
+ meck:new(meck_test_module),
+ %% 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)),
+ %% Cleanup
+ meck:unload().
diff --git a/test/meck_test_module.erl b/test/meck_test_module.erl
index 6dee52b..1f5e51a 100644
--- a/test/meck_test_module.erl
+++ b/test/meck_test_module.erl
@@ -1,4 +1,5 @@
-module(meck_test_module).
+-tag(foobar).
-export([a/0, b/0, c/2]).
a() -> a.
diff --git a/test/meck_test_parametrized_module.erl b/test/meck_test_parametrized_module.erl
deleted file mode 100644
index 1912679..0000000
--- a/test/meck_test_parametrized_module.erl
+++ /dev/null
@@ -1,7 +0,0 @@
--module(meck_test_parametrized_module, [Var1, Var2]).
--export([which/0, var1/0, var2/0]).
-
-which() -> original.
-
-var1() -> {original, Var1}.
-var2() -> {original, Var2}.
diff --git a/test/meck_tests.erl b/test/meck_tests.erl
index 9e01952..0c1e37c 100644
--- a/test/meck_tests.erl
+++ b/test/meck_tests.erl
@@ -1,4 +1,4 @@
-%%==============================================================================
+%%=============================================================================
%% Copyright 2010 Erlang Solutions Ltd.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,13 +12,28 @@
%% 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).
-compile(export_all).
-include_lib("eunit/include/eunit.hrl").
+-include_lib("hamcrest/include/hamcrest.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,
@@ -29,6 +44,7 @@
fun ?MODULE:expect_/1,
fun ?MODULE:exports_/1,
fun ?MODULE:call_return_value_/1,
+ fun ?MODULE:call_return_value_improper_list_/1,
fun ?MODULE:call_argument_/1,
fun ?MODULE:call_undef_/1,
fun ?MODULE:call_function_clause_/1,
@@ -52,6 +68,7 @@
fun ?MODULE:history_meck_exit_/1,
fun ?MODULE:history_meck_error_/1,
fun ?MODULE:history_by_pid_/1,
+ fun ?MODULE:reset_/1,
fun ?MODULE:shortcut_expect_/1,
fun ?MODULE:shortcut_expect_negative_arity_/1,
fun ?MODULE:shortcut_call_return_value_/1,
@@ -66,6 +83,7 @@
fun ?MODULE:called_true_one_arg_/1,
fun ?MODULE:called_false_few_args_/1,
fun ?MODULE:called_true_few_args_/1,
+ fun ?MODULE:called_few_args_matchers_/1,
fun ?MODULE:called_false_error_/1,
fun ?MODULE:called_true_error_/1,
fun ?MODULE:called_with_pid_no_args_/1,
@@ -74,9 +92,29 @@
fun ?MODULE:num_calls_with_pid_no_args_/1,
fun ?MODULE:called_wildcard_/1,
fun ?MODULE:sequence_/1,
+ fun ?MODULE:expect_args_sequence_/1,
+ fun ?MODULE:expect_arity_sequence_/1,
+ fun ?MODULE:expect_complex_sequence_/1,
fun ?MODULE:sequence_multi_/1,
fun ?MODULE:loop_/1,
- fun ?MODULE:loop_multi_/1
+ fun ?MODULE:expect_empty_clause_list_/1,
+ fun ?MODULE:expect_args_value_/1,
+ fun ?MODULE:expect_args_invalid_call_/1,
+ fun ?MODULE:expect_arity_value_/1,
+ fun ?MODULE:expect_args_loop_/1,
+ fun ?MODULE:expect_arity_loop_/1,
+ fun ?MODULE:expect_complex_loop_/1,
+ fun ?MODULE:expect_loop_in_seq_/1,
+ fun ?MODULE:expect_args_exception_/1,
+ fun ?MODULE:expect_arity_exception_/1,
+ fun ?MODULE:expect_arity_clause_/1,
+ fun ?MODULE:loop_multi_/1,
+ fun ?MODULE:expect_args_pattern_override_/1,
+ fun ?MODULE:expect_args_pattern_shadow_/1,
+ fun ?MODULE:expect_args_pattern_missing_/1,
+ fun ?MODULE:expect_args_pattern_invalid_/1,
+ fun ?MODULE:expect_args_matchers_/1,
+ fun ?MODULE:expect_ret_specs_/1
]]}.
setup() ->
@@ -84,13 +122,13 @@
% dbg:tracer(),
% dbg:p(all, call),
% dbg:tpl(meck, []),
- ok = meck:new(mymod),
+ ok = meck:new(mymod, [non_strict]),
mymod.
teardown(Module) ->
catch meck:unload(Module).
-%% --- Tests using setup and teardown ------------------------------------------
+%% --- Tests using setup and teardown -----------------------------------------
new_(Mod) ->
Info = Mod:module_info(),
@@ -124,6 +162,12 @@
?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)),
@@ -147,7 +191,7 @@
?assertEqual(true, meck:validate(Mod)).
validate_chained_(Mod) ->
- ok = meck:new(mymod2),
+ ok = meck:new(mymod2, [non_strict]),
ok = meck:expect(mymod2, test, fun() ->
meck:exception(error, test_error)
end),
@@ -180,13 +224,13 @@
catch
error:function_clause ->
Stacktrace = erlang:get_stacktrace(),
- ?assert(lists:any(fun({M, test, [error]}) when M == Mod -> true;
- ({M, test, [error], []}) when M == Mod -> true;
- (_) -> false
- end, Stacktrace))
+ ?assert(lists:any(
+ fun ({M, test, [error]}) when M == Mod -> true;
+ ({M, test, [error], []}) when M == Mod -> true;
+ (_) -> false
+ end, Stacktrace))
end.
-
call_undef_(Mod) ->
ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
?assertError(undef, Mod:test(hest)).
@@ -207,7 +251,9 @@
call_original_undef_(Mod) ->
ok = meck:expect(Mod, test, fun() -> meck:passthrough([]) end),
- ?assertError(undef, Mod:test()).
+ ?assertError(undef, Mod:test()),
+ ?assert(not meck:validate(Mod)),
+ ?assertEqual(undefined, get('$meck_call')).
history_empty_(Mod) ->
?assertEqual([], meck:history(Mod)).
@@ -249,7 +295,8 @@
meck:history(Mod)).
history_error_args_(Mod) ->
- ok = meck:expect(Mod, test, fun() -> erlang:error(test_error, [fake_args]) end),
+ 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}],
@@ -260,7 +307,8 @@
(_) -> false end, Stacktrace)).
history_meck_throw_(Mod) ->
- ok = meck:expect(Mod, test, fun() -> meck:exception(throw, test_exception) end),
+ 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)).
@@ -279,7 +327,8 @@
meck:history(Mod)).
history_meck_error_(Mod) ->
- ok = meck:expect(Mod, test, fun() -> meck:exception(error, test_error) end),
+ 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)).
@@ -299,9 +348,22 @@
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)),
+ {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)).
@@ -327,8 +389,9 @@
?assertEqual(true, meck:validate(Mod)).
shortcut_opaque_(Mod) ->
- ok = meck:expect(Mod, test, 0, {test, [a, self()]}),
- ?assertMatch({test, [a, P]} when P == self(), Mod:test()).
+ Ref = make_ref(),
+ ok = meck:expect(Mod, test, 0, {test, [a, self()], Ref}),
+ ?assertMatch({test, [a, P], Ref} when P == self(), Mod:test()).
delete_(Mod) ->
ok = meck:expect(Mod, test, 2, ok),
@@ -384,6 +447,14 @@
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(equal_to(2)), {'_', 3}, "four"], true),
+ assert_called(Mod, test, ['_', meck:is(equal_to(3)), {'_', 3}, "four"], false),
+ ok.
+
called_false_error_(Mod) ->
Args = [one, "two", {3, 3}],
TestFun = fun (_, _, _) -> meck:exception(error, my_error) end,
@@ -469,7 +540,7 @@
?assert(meck:validate(Mod)).
sequence_multi_(Mod) ->
- meck:new(mymod2),
+ meck:new(mymod2, [non_strict]),
Mods = [Mod, mymod2],
Sequence = [a, b, c, d, e],
?assertEqual(ok, meck:sequence(Mods, s, 2, Sequence)),
@@ -483,14 +554,178 @@
[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),
+ meck:new(mymod2, [non_strict]),
Mods = [Mod, mymod2],
Loop = [a, b, c, d, e],
?assertEqual(ok, meck:loop(Mods, l, 2, Loop)),
@@ -498,8 +733,86 @@
|| 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(less_than(3))], 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 ----------------------------------------------------
+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)),
@@ -518,7 +831,7 @@
unload_all_test() ->
Mods = [test_a, test_b, test_c, test_d, test_e],
- ok = meck:new(Mods),
+ ok = meck:new(Mods, [non_strict]),
?assertEqual(lists:sort(Mods), lists:sort(meck:unload())),
[?assertEqual(false, code:is_loaded(M)) || M <- Mods].
@@ -537,7 +850,7 @@
ok = meck:unload(meck_on_disk).
passthrough_nonexisting_module_test() ->
- ok = meck:new(mymod, [passthrough]),
+ ok = meck:new(mymod, [passthrough, non_strict]),
ok = meck:expect(mymod, test, fun() -> ok end),
?assertEqual(ok, mymod:test()),
ok = meck:unload(mymod).
@@ -564,6 +877,62 @@
?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 = atom_to_list(?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/meck_test_module.erl"),
a = meck_test_module:a(),
@@ -623,9 +992,9 @@
CompilerOptions = [{i, "../test/include"}, {d, 'TEST', true},
{outdir, "../test"}, debug_info],
{ok, _} = compile:file(Src, CompilerOptions),
- ?assertEqual(CompilerOptions, meck_mod:compile_options(Module)),
+ ?assertEqual(CompilerOptions, meck_code:compile_options(Module)),
{ok, _} = cover:compile_beam(Module),
- ?assertEqual([], meck_mod:compile_options(Module)),
+ ?assertEqual([], meck_code:compile_options(Module)),
a = Module:a(),
b = Module:b(),
{1, 2} = Module:c(1, 2),
@@ -667,7 +1036,7 @@
% @doc The mocked module is unloaded if the meck process crashes.
unload_when_crashed_test() ->
- ok = meck:new(mymod),
+ ok = meck:new(mymod, [non_strict]),
?assertMatch({file, _}, code:is_loaded(mymod)),
SaltedName = mymod_meck,
Pid = whereis(SaltedName),
@@ -680,13 +1049,14 @@
% @doc The mocked module is unloaded if the meck process crashes.
unlink_test() ->
- ok = meck:new(mymod, [no_link]),
+ 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 Exception is thrown when you run expect on a non-existing (and not yet mocked) module.
+%% @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)).
@@ -703,7 +1073,7 @@
multi_test() ->
Mods = [mod1, mod2, mod3],
- ok = meck:new(Mods),
+ 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],
@@ -712,7 +1082,7 @@
multi_invalid_test() ->
Mods = [mod1, mod2, mod3],
- ok = meck:new(Mods),
+ 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)),
@@ -720,7 +1090,7 @@
multi_option_test() ->
Mods = [mod1, mod2, mod3],
- ok = meck:new(Mods, [passthrough]),
+ 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)),
@@ -728,7 +1098,7 @@
multi_shortcut_test() ->
Mods = [mod1, mod2, mod3],
- ok = meck:new(Mods),
+ ok = meck:new(Mods, [non_strict]),
ok = meck:expect(Mods, test, 0, ok),
[?assertEqual(ok, M:test()) || M <- Mods],
?assert(meck:validate(Mods)),
@@ -736,20 +1106,40 @@
multi_delete_test() ->
Mods = [mod1, mod2, mod3],
- ok = meck:new(Mods),
+ 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_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([{_Pid, {mod1, test1, []}, ok},
+ {_Pid, {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:handle_cast(dummy_msg, S)).
+ ?assertEqual({noreply, S}, meck_proc:handle_cast(dummy_msg, S)).
code_change_unmodified_state_test() ->
S = dummy_state,
- ?assertEqual({ok, S}, meck:code_change(old_version, S, [])).
+ ?assertEqual({ok, S}, meck_proc:code_change(old_version, S, [])).
remote_meck_test_() ->
{foreach, fun remote_setup/0, fun remote_teardown/1,
@@ -770,10 +1160,11 @@
{Node, meck_test_module}.
remote_teardown({Node, _Mod}) ->
- ok = slave:stop(Node).
+ ok = slave:stop(Node),
+ ok = net_kernel:stop().
remote_meck_({Node, Mod}) ->
- ?assertEqual(ok, rpc:call(Node, meck, new, [Mod, [no_link]])),
+ ?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, [])).
@@ -851,35 +1242,164 @@
meck:expect(unicode, module_info, 0, doh)),
?assertEqual(ok, meck:unload(unicode)).
-meck_parametrized_module_test() ->
- ?assertEqual(ok, meck:new(meck_test_parametrized_module)),
- ?assertEqual(ok, meck:expect(meck_test_parametrized_module, new,
- fun(V1, V2) ->
- {meck_test_parametrized_module, V1, V2}
- end)),
- ?assertEqual(ok, meck:expect(meck_test_parametrized_module, which, 1, mecked)),
- Object = meck_test_parametrized_module:new(var1, var2),
- ?assertEqual(mecked, Object:which()),
- ?assertEqual(ok, meck:unload(meck_test_parametrized_module)).
+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_parametrized_module_passthrough_test() ->
- ?assertEqual(ok, meck:new(meck_test_parametrized_module, [passthrough])),
- ?assertEqual(ok, meck:expect(meck_test_parametrized_module, new,
- fun(V1, V2) ->
- {meck_test_parametrized_module, V1, V2}
- end)),
- ?assertEqual(ok, meck:expect(meck_test_parametrized_module, var2,
- fun({_, _Var1, Var2} = _This) ->
- {mecked, Var2}
- end)),
- Object = meck_test_parametrized_module:new(var1, var2),
- ?assertEqual({original, var1}, Object:var1()),
- ?assertEqual({mecked, var2}, Object:var2()),
- ?assertEqual(ok, meck:unload(meck_test_parametrized_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() ->
+ %% 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().
+
+wait_for_different_patterns_on_different_processes_test() ->
+ %% 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().
+
+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().
+
+%%=============================================================================
%% Internal Functions
-%%==============================================================================
+%%=============================================================================
assert_called(Mod, Function, Args, WasCalled) ->
?assertEqual(WasCalled, meck:called(Mod, Function, Args)),