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)),