summaryrefslogtreecommitdiff
path: root/deps/meck
diff options
context:
space:
mode:
Diffstat (limited to 'deps/meck')
-rwxr-xr-xdeps/meck/.scripts/tag_with_changelog.sh41
-rw-r--r--deps/meck/.travis.yml8
-rw-r--r--deps/meck/CHANGELOG1
-rw-r--r--deps/meck/LICENSE178
-rw-r--r--deps/meck/Makefile14
-rw-r--r--deps/meck/NOTICE5
-rw-r--r--deps/meck/README.md210
-rw-r--r--deps/meck/doc/overview.edoc25
-rw-r--r--deps/meck/rebar.config6
-rw-r--r--deps/meck/src/meck.app.src9
-rw-r--r--deps/meck/src/meck.erl813
-rw-r--r--deps/meck/src/meck_abstract.hrl19
-rw-r--r--deps/meck/src/meck_cover.erl110
-rw-r--r--deps/meck/src/meck_mod.erl118
-rw-r--r--deps/meck/test/cover_test_module.dontcompile21
-rw-r--r--deps/meck/test/include/cover_test.hrl1
-rw-r--r--deps/meck/test/meck_performance_test.erl65
-rw-r--r--deps/meck/test/meck_test_module.erl8
-rw-r--r--deps/meck/test/meck_test_parametrized_module.erl7
-rw-r--r--deps/meck/test/meck_tests.erl890
20 files changed, 2549 insertions, 0 deletions
diff --git a/deps/meck/.scripts/tag_with_changelog.sh b/deps/meck/.scripts/tag_with_changelog.sh
new file mode 100755
index 00000000..e6a4d3d1
--- /dev/null
+++ b/deps/meck/.scripts/tag_with_changelog.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+# Install for alias 'tag-cl' with:
+# git config alias.tag-cl '!.scripts/tag_with_changelog.sh'
+
+set -e # Abort on first failure, so we don't mess something up
+
+if [ -z "$1" ]; then
+ # Missing tag name
+ echo "usage: git tag-cl <tag>" >&2
+ exit 129
+fi
+if [ ! -f CHANGELOG ]; then
+ # No changelog to be used
+ echo "fatal: CHANGELOG missing" >&2
+ exit 128
+fi
+if [ ! -z "$(git status --short)" ]; then
+ # Sanity check
+ echo "fatal: dirty repository" >&2
+ exit 128
+fi
+
+CHANGELOG=$(cat CHANGELOG)
+
+# Clean up changelog
+echo "" > CHANGELOG
+git add CHANGELOG
+
+# Update version in .app file
+sed -i "" -e "s/{vsn, .*}/{vsn, \"$1\"}/g" src/meck.app.src
+sed -i "" -e "s/@version .*/@version \"$1\"/g" doc/overview.edoc
+git add src/meck.app.src
+git add doc/overview.edoc
+
+# Commit, tag and push
+git commit -m "Version $1"
+git tag -s $1 -m "Version $
+
+$CHANGELOG"
+git push && git push --tags
diff --git a/deps/meck/.travis.yml b/deps/meck/.travis.yml
new file mode 100644
index 00000000..6e520dbe
--- /dev/null
+++ b/deps/meck/.travis.yml
@@ -0,0 +1,8 @@
+language: erlang
+notifications:
+ disabled: true
+otp_release:
+ - R15B
+ - R14B04
+ - R14B03
+ - R14B02
diff --git a/deps/meck/CHANGELOG b/deps/meck/CHANGELOG
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/deps/meck/CHANGELOG
@@ -0,0 +1 @@
+
diff --git a/deps/meck/LICENSE b/deps/meck/LICENSE
new file mode 100644
index 00000000..e454a525
--- /dev/null
+++ b/deps/meck/LICENSE
@@ -0,0 +1,178 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/deps/meck/Makefile b/deps/meck/Makefile
new file mode 100644
index 00000000..5ac14ee2
--- /dev/null
+++ b/deps/meck/Makefile
@@ -0,0 +1,14 @@
+REBAR=`which rebar || ./rebar`
+
+all: compile
+
+compile:
+ @$(REBAR) compile
+
+test: force
+ @$(REBAR) eunit
+
+clean:
+ @$(REBAR) clean
+
+force: ;
diff --git a/deps/meck/NOTICE b/deps/meck/NOTICE
new file mode 100644
index 00000000..18ce5a73
--- /dev/null
+++ b/deps/meck/NOTICE
@@ -0,0 +1,5 @@
+Copyright 2011 Adam Lindberg
+
+Copyright 2011 Erlang Solutions
+This product contains code developed at Erlang Solutions.
+(http://www.erlang-solutions.com/)
diff --git a/deps/meck/README.md b/deps/meck/README.md
new file mode 100644
index 00000000..6613a088
--- /dev/null
+++ b/deps/meck/README.md
@@ -0,0 +1,210 @@
+[![Build Status](https://secure.travis-ci.org/eproxus/meck.png)](http://travis-ci.org/eproxus/meck)
+
+ * [Introduction](#introduction)
+ * [Features](#features)
+ * [Examples](#examples)
+ * [Build](#build)
+ * [Install](#install)
+ * [Contribute](#contribute)
+
+meck
+====
+A mocking library for Erlang.
+
+
+<a name='introduction'>
+
+Introduction
+------------
+
+With meck you can easily mock modules in Erlang. You can also perform
+some basic validations on the mocked modules, such as making sure no
+unexpected exceptions occurred or looking at the call history.
+
+
+<a name='features'>
+
+Features
+--------
+
+ * Automatic renaming and restoration of original modules
+ * Automatic backup and restore of cover data
+ * Changing return values using sequences and loops of static values
+ * Pass through: use functions from the original module
+ * Mock is linked to the creating process (disable with `no_link`)
+ * Complete call history showing calls, results and exceptions
+ * Mocking of sticky modules (using the option `unstick`)
+ * Throwing of expected exceptions that keeps the module valid
+
+
+<a name='examples'>
+
+Examples
+--------
+Here's an example of using meck in the Erlang shell:
+
+```erl
+Eshell V5.8.4 (abort with ^G)
+1> meck:new(dog).
+ok
+2> meck:expect(dog, bark, fun() -> "Woof!" end).
+ok
+3> dog:bark().
+"Woof!"
+4> meck:validate(dog).
+true
+5> meck:unload(dog).
+ok
+6> dog:bark().
+** exception error: undefined function dog:bark/0
+```
+
+Exceptions can be anticipated by meck (resulting in validation still
+passing). This is intended to be used to test code that can and should
+handle certain exceptions indeed does take care of them:
+
+```erl
+5> meck:expect(dog, meow, fun() -> meck:exception(error, not_a_cat) end).
+ok
+6> catch dog:meow().
+{'EXIT',{not_a_cat,[{meck,exception,2},
+ {meck,exec,4},
+ {dog,meow,[]},
+ {erl_eval,do_apply,5},
+ {erl_eval,expr,5},
+ {shell,exprs,6},
+ {shell,eval_exprs,6},
+ {shell,eval_loop,3}]}}
+7> meck:validate(dog).
+true
+```
+
+Normal Erlang exceptions result in a failed validation. The following
+example is just to demonstrate the behavior, in real test code the
+exception would normally come from the code under test (which should,
+if not expected, invalidate the mocked module):
+
+```erl
+8> meck:expect(dog, jump, fun(Height) when Height > 3 ->
+ erlang:error(too_high);
+ (Height) ->
+ ok
+ end).
+ok
+9> dog:jump(2).
+ok
+10> catch dog:jump(5).
+{'EXIT',{too_high,[{meck,exec,4},
+ {dog,jump,[5]},
+ {erl_eval,do_apply,5},
+ {erl_eval,expr,5},
+ {shell,exprs,6},
+ {shell,eval_exprs,6},
+ {shell,eval_loop,3}]}}
+11> meck:validate(dog).
+false
+```
+
+Here's an example of using meck inside an EUnit test case:
+
+```erlang
+my_test() ->
+ meck:new(my_library_module),
+ meck:expect(my_library_module, fib, fun(8) -> 21 end),
+ ?assertEqual(21, code_under_test:run(fib, 8)), % Uses my_library_module
+ ?assert(meck:validate(my_library_module)),
+ meck:unload(my_library_module).
+```
+
+Pass-through is used when the original functionality of a module
+should be kept. When the option `passthrough` is used when calling
+`new/2` all functions in the original module will be kept in the
+mock. These can later be overridden by calling `expect/3` or
+`expect/4`.
+
+```erl
+Eshell V5.8.4 (abort with ^G)
+1> meck:new(string, [unstick, passthrough]).
+ok
+2> string:strip(" test ").
+"test"
+```
+
+It's also possible to pass calls to the original function allowing us
+to override only a certain behavior of a function (this usage is
+compatible with the `passthrough` option). `passthrough/1` will always
+call the original function with the same name as the expect is
+defined in):
+
+```erl
+Eshell V5.8.4 (abort with ^G)
+1> meck:new(string, [unstick]).
+ok
+2> meck:expect(string, strip, fun(String) -> meck:passthrough([String]) end).
+ok
+3> string:strip(" test ").
+"test"
+4> meck:unload(string).
+ok
+5> string:strip(" test ").
+"test"
+```
+
+<a name='build'>
+
+Build
+-----
+
+meck requires [rebar][1] to build. To build meck, go to the meck
+directory and simply type:
+
+```sh
+rebar compile
+```
+
+To make sure meck works on your platform, run the tests:
+
+```sh
+rebar eunit
+```
+
+Two things might seem alarming when running the tests:
+
+ 1. Warnings emitted by cover
+ 2. En exception printed by SASL
+
+Both are expected due to the way Erlang currently prints errors. The
+important line you should look for is `All XX tests passed`, if that
+appears all is correct.
+
+
+<a name='install'>
+
+Install
+-------
+
+To install meck permanently, use of [Agner][2] is recommended:
+
+```sh
+agner install meck
+```
+
+If you want to install your own built version of meck add the ebin
+directory to your Erlang code path or move the meck folder into your
+release folder and make sure that folder is in your `ERL_LIBS`
+environment variable.
+
+
+<a name='contribute'>
+
+Contribute
+----------
+
+Patches are greatly appreciated!
+
+Should you find yourself using meck and have issues, comments or
+feedback please [create an issue here on GitHub.] [3]
+
+ [1]: https://github.com/basho/rebar "Rebar - A build tool for Erlang"
+ [2]: http://erlagner.org/ "Agner - Erlang Package Index & Package Manager"
+ [3]: http://github.com/eproxus/meck/issues "meck issues"
diff --git a/deps/meck/doc/overview.edoc b/deps/meck/doc/overview.edoc
new file mode 100644
index 00000000..b04686e6
--- /dev/null
+++ b/deps/meck/doc/overview.edoc
@@ -0,0 +1,25 @@
+%%==============================================================================
+%% 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.
+%%==============================================================================
+
+@author Adam Lindberg <eproxus@gmail.com>
+@copyright 2011, Adam Lindberg & Erlang Solutions Ltd
+@version "0.7.2"
+@title meck, a Mocking Library for Erlang
+
+@doc
+
+== About ==
+meck is a mocking library for Erlang.
diff --git a/deps/meck/rebar.config b/deps/meck/rebar.config
new file mode 100644
index 00000000..c589f4c7
--- /dev/null
+++ b/deps/meck/rebar.config
@@ -0,0 +1,6 @@
+{erl_opts, [warnings_as_errors, debug_info]}.
+{xref_checks, [undefined_function_calls]}.
+{dialyzer_opts, [{warnings, [unmatched_returns]}]}.
+
+{cover_enabled, true}.
+{clean_files, [".eunit", "ebin/*.beam", "test/*.beam"]}.
diff --git a/deps/meck/src/meck.app.src b/deps/meck/src/meck.app.src
new file mode 100644
index 00000000..d9f57979
--- /dev/null
+++ b/deps/meck/src/meck.app.src
@@ -0,0 +1,9 @@
+%% -*- mode: erlang; -*-
+{application, meck,
+ [{description, "A mocking framework for Erlang"},
+ {vsn, "0.7.2"},
+ {modules, []},
+ {registered, []},
+ {applications, [kernel, stdlib]},
+ {build_dependencies, []},
+ {env, []}]}.
diff --git a/deps/meck/src/meck.erl b/deps/meck/src/meck.erl
new file mode 100644
index 00000000..1669c32f
--- /dev/null
+++ b/deps/meck/src/meck.erl
@@ -0,0 +1,813 @@
+%%==============================================================================
+%% 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.
+
+-module(meck).
+-behaviour(gen_server).
+
+%% Interface exports
+-export([new/1]).
+-export([new/2]).
+-export([expect/3]).
+-export([expect/4]).
+-export([sequence/4]).
+-export([loop/4]).
+-export([delete/3]).
+-export([exception/2]).
+-export([passthrough/1]).
+-export([history/1]).
+-export([history/2]).
+-export([validate/1]).
+-export([unload/0]).
+-export([unload/1]).
+-export([called/3]).
+-export([called/4]).
+-export([num_calls/3]).
+-export([num_calls/4]).
+
+%% 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]).
+
+%% Types
+%% @type meck_mfa() = {Mod::atom(), Func::atom(), Args::list(term())}.
+%% 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
+%% 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()}).
+
+%% Includes
+-include("meck_abstract.hrl").
+
+%%==============================================================================
+%% Interface exports
+%%==============================================================================
+
+%% @spec new(Mod:: atom() | list(atom())) -> ok
+%% @equiv new(Mod, [])
+-spec new(Mod:: atom() | [atom()]) -> ok.
+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'
+%% with an empty module.
+%%
+%% Since this library is intended to use from test code, this
+%% function links a process for each mock to the calling process.
+%%
+%% The valid options are:
+%% <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>
+%% </dl>
+-spec new(Mod:: atom() | [atom()], Options::[term()]) -> ok.
+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;
+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.
+%%
+%% 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(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'.
+%%
+%% @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),
+ 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),
+ 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),
+ 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
+%% module. That is, the code using the mocked module is expected to
+%% 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().
+exception(Class, Reason) when Class == throw; Class == error; Class == exit ->
+ throw(mock_exception_fun(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 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
+%% according to its expectations. It returns `false' if a call has
+%% failed in some way. Reasons for failure are wrong number of
+%% arguments or non-existing function (undef), wrong arguments
+%% (function clause) or unexpected exceptions.
+%%
+%% Use the {@link history/1} or {@link history/2} function to analyze errors.
+-spec validate(Mod:: atom() | [atom()]) -> boolean().
+validate(Mod) when is_atom(Mod) ->
+ call(Mod, validate);
+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::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
+%% the specified `Pid'. Results can be either normal Erlang terms or
+%% exceptions that occurred.
+%%
+%% @see history/1
+%% @see called/3
+%% @see called/4
+%% @see num_calls/3
+%% @see num_calls/4
+-spec history(Mod::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 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()].
+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 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:: 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
+%% whether process `Pid' call the function, `Fun', with arguments, `Args'. If
+%% so, this function returns true, otherwise false.
+%%
+%% Wildcards can be used, at any level in any term, by using the underscore
+%% atom: ``'_' ''
+%%
+%% @see called/3
+-spec called(Mod::atom(), Fun::atom(), Args::list(), Pid::pid()) -> boolean().
+called(Mod, Fun, Args, Pid) ->
+ has_call({Mod, Fun, Args}, meck:history(Mod, Pid)).
+
+%% @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:: 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'.
+%%
+%% This will check the history for the module, `Mod', to determine how
+%% many times process `Pid' has called the function, `Fun', with
+%% arguments, `Args' and returns the result.
+%%
+%% @see num_calls/3
+-spec num_calls(Mod::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)).
+
+%%==============================================================================
+%% Callback functions
+%%==============================================================================
+
+%% @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.
+
+%% @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}.
+
+%% @hidden
+handle_cast({add_history, Item}, S) ->
+ {noreply, S#state{history = [Item| S#state.history]}};
+handle_cast(_Msg, S) ->
+ {noreply, S}.
+
+%% @hidden
+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}.
+
+%% @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.
+
+%%==============================================================================
+%% Internal functions
+%%==============================================================================
+
+%% --- Process functions -------------------------------------------------------
+
+start(Mod, Options) ->
+ case proplists:is_defined(no_link, Options) of
+ true -> start(start, Mod, Options);
+ false -> start(start_link, Mod, Options)
+ end.
+
+start(Func, Mod, Options) ->
+ gen_server:Func({local, proc_name(Mod)}, ?MODULE, [Mod, Options], []).
+
+cast(Mod, Msg) -> gen_server(cast, Mod, Msg).
+call(Mod, Msg) -> gen_server(call, Mod, Msg).
+
+gen_server(Func, Mod, Msg) ->
+ Name = proc_name(Mod),
+ try gen_server:Func(Name, Msg)
+ catch exit:_Reason -> erlang:error({not_mocked, Mod}) end.
+
+proc_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_meck").
+
+original_name(Name) -> list_to_atom(atom_to_list(Name) ++ "_meck_original").
+
+wait_for_exit(Mod) ->
+ MonitorRef = erlang:monitor(process, 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
+ {Name, "_meck"} ->
+ Mocked = list_to_existing_atom(Name),
+ try
+ unload(Mocked)
+ catch error:{not_mocked, Mocked} ->
+ ok
+ end,
+ [Mocked|L];
+ _Else ->
+ L
+ end;
+unload_if_mocked(_P, L) ->
+ L.
+
+%% --- 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, '_', '_', '_'}, [], ['$_']}].
diff --git a/deps/meck/src/meck_abstract.hrl b/deps/meck/src/meck_abstract.hrl
new file mode 100644
index 00000000..8f3b9829
--- /dev/null
+++ b/deps/meck/src/meck_abstract.hrl
@@ -0,0 +1,19 @@
+-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/deps/meck/src/meck_cover.erl b/deps/meck/src/meck_cover.erl
new file mode 100644
index 00000000..da8888c8
--- /dev/null
+++ b/deps/meck/src/meck_cover.erl
@@ -0,0 +1,110 @@
+%%==============================================================================
+%% 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.
+%%==============================================================================
+
+%% @doc Module containing functions needed by meck to integrate with cover.
+
+-module(meck_cover).
+
+%% Interface exports
+-export([compile_beam/2]).
+-export([rename_module/2]).
+
+%%==============================================================================
+%% Interface exports
+%%==============================================================================
+
+%% @doc Enabled cover on `<name>_meck_original'.
+compile_beam(OriginalMod, Bin) ->
+ alter_cover(),
+ {ok, _} = cover:compile_beam(OriginalMod, Bin).
+
+%% @doc Given a cover file `File' exported by `cover:export' overwrite
+%% the module name with `Name'.
+rename_module(File, Name) ->
+ NewTerms = change_cover_mod_name(read_cover_file(File), Name),
+ write_terms(File, NewTerms),
+ ok.
+
+%%==============================================================================
+%% Internal functions
+%%==============================================================================
+
+%% @private
+%%
+%% @doc Alter the cover BEAM module to export some of it's private
+%% functions. This is done for two reasons:
+%%
+%% 1. Meck needs to alter the export analysis data on disk and
+%% therefore needs to understand this format. This is why `get_term'
+%% and `write' are exposed.
+%%
+%% 2. In order to avoid creating temporary files meck needs direct
+%% access to `compile_beam/2' which allows passing a binary.
+alter_cover() ->
+ case lists:member({compile_beam,2}, cover:module_info(exports)) of
+ true ->
+ ok;
+ false ->
+ Beam = meck_mod:beam_file(cover),
+ AbsCode = meck_mod: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),
+ ok
+ end.
+
+change_cover_mod_name(CoverTerms, Name) ->
+ {_, Terms} = lists:foldl(fun change_name_in_term/2, {Name,[]}, CoverTerms),
+ Terms.
+
+change_name_in_term({file, Mod, File}, {Name, Terms}) ->
+ Term2 = {file, Name, replace_string(File, Mod, Name)},
+ {Name, [Term2|Terms]};
+change_name_in_term({Bump={bump,_,_,_,_,_},_}=Term, {Name, Terms}) ->
+ Bump2 = setelement(2, Bump, Name),
+ Term2 = setelement(1, Term, Bump2),
+ {Name, [Term2|Terms]};
+change_name_in_term({_Mod,Clauses}, {Name, Terms}) ->
+ Clauses2 = lists:foldl(fun change_name_in_clause/2, {Name, []}, Clauses),
+ Term2 = {Name, Clauses2},
+ {Name, [Term2|Terms]}.
+
+change_name_in_clause(Clause, {Name, NewClauses}) ->
+ {Name, [setelement(1, Clause, Name)|NewClauses]}.
+
+replace_string(File, Old, New) ->
+ Old2 = atom_to_list(Old),
+ New2 = atom_to_list(New),
+ re:replace(File, Old2, New2, [{return, list}]).
+
+read_cover_file(File) ->
+ {ok, Fd} = file:open(File, [read, binary, raw]),
+ Terms = get_terms(Fd, []),
+ ok = file:close(Fd),
+ Terms.
+
+get_terms(Fd, Terms) ->
+ case cover:get_term(Fd) of
+ eof -> Terms;
+ Term -> get_terms(Fd, [Term|Terms])
+ end.
+
+write_terms(File, Terms) ->
+ {ok, Fd} = file:open(File, [write, binary, raw]),
+ lists:foreach(write_term(Fd), Terms),
+ ok.
+
+write_term(Fd) ->
+ fun(Term) -> cover:write(Term, Fd) end.
+
diff --git a/deps/meck/src/meck_mod.erl b/deps/meck/src/meck_mod.erl
new file mode 100644
index 00000000..22a237d8
--- /dev/null
+++ b/deps/meck/src/meck_mod.erl
@@ -0,0 +1,118 @@
+%%==============================================================================
+%% 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.
+%%==============================================================================
+
+%% @hidden
+%% @author Adam Lindberg <eproxus@gmail.com>
+%% @copyright 2011, Adam Lindberg & Erlang Solutions Ltd
+%% @doc Module wrangling helper functions.
+
+-module(meck_mod).
+
+%% Interface exports
+-export([abstract_code/1]).
+-export([add_exports/2]).
+-export([beam_file/1]).
+-export([compile_and_load_forms/1]).
+-export([compile_and_load_forms/2]).
+-export([compile_options/1]).
+-export([rename_module/2]).
+
+%% Types
+-type erlang_form() :: term().
+-type compile_options() :: [term()].
+-type export() :: {atom(), byte()}.
+
+%%==============================================================================
+%% Interface exports
+%%==============================================================================
+
+-spec abstract_code(binary()) -> erlang_form().
+abstract_code(BeamFile) ->
+ case beam_lib:chunks(BeamFile, [abstract_code]) of
+ {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} ->
+ Forms;
+ {ok, {_, [{abstract_code, no_abstract_code}]}} ->
+ throw(no_abstract_code)
+ end.
+
+-spec add_exports([export()], erlang_form()) -> erlang_form().
+add_exports(Exports, AbsCode) ->
+ {attribute, Line, export, OrigExports} = lists:keyfind(export, 3, AbsCode),
+ Attr = {attribute, Line, export, OrigExports ++ Exports},
+ lists:keyreplace(export, 3, AbsCode, Attr).
+
+-spec beam_file(module()) -> binary().
+beam_file(Module) ->
+ % code:which/1 cannot be used for cover_compiled modules
+ case code:get_object_code(Module) of
+ {_, Binary, _Filename} -> Binary;
+ error -> throw({object_code_not_found, Module})
+ end.
+
+-spec compile_and_load_forms(erlang_form()) -> binary().
+compile_and_load_forms(AbsCode) -> compile_and_load_forms(AbsCode, []).
+
+-spec compile_and_load_forms(erlang_form(), compile_options()) -> binary().
+compile_and_load_forms(AbsCode, Opts) ->
+ case compile:forms(AbsCode, [return_errors|Opts]) of
+ {ok, ModName, Binary} ->
+ load_binary(ModName, Binary),
+ Binary;
+ {ok, ModName, Binary, _Warnings} ->
+ load_binary(ModName, Binary),
+ Binary;
+ Error ->
+ exit({compile_forms, Error})
+ end.
+
+-spec compile_options(binary() | module()) -> compile_options().
+compile_options(BeamFile) when is_binary(BeamFile) ->
+ case beam_lib:chunks(BeamFile, [compile_info]) of
+ {ok, {_, [{compile_info, Info}]}} ->
+ filter_options(proplists:get_value(options, Info));
+ _ ->
+ []
+ end;
+compile_options(Module) ->
+ filter_options(proplists:get_value(options, Module:module_info(compile))).
+
+-spec rename_module(erlang_form(), module()) -> erlang_form().
+rename_module([{attribute, Line, module, OldAttribute}|T], NewName) ->
+ case OldAttribute of
+ {_OldName, Variables} ->
+ [{attribute, Line, module, {NewName, Variables}}|T];
+ _OldName ->
+ [{attribute, Line, module, NewName}|T]
+ end;
+rename_module([H|T], NewName) ->
+ [H|rename_module(T, NewName)].
+
+%%==============================================================================
+%% Internal functions
+%%==============================================================================
+
+load_binary(Name, Binary) ->
+ case code:load_binary(Name, "", Binary) of
+ {module, Name} -> ok;
+ {error, Reason} -> exit({error_loading_module, Name, Reason})
+ end.
+
+% parse transforms have already been applied to the abstract code in the
+% module, and often are not always available when compiling the forms, so
+% filter them out of the options
+filter_options (Options) ->
+ lists:filter(fun({parse_transform,_}) -> false; (_) -> true end, Options).
+
diff --git a/deps/meck/test/cover_test_module.dontcompile b/deps/meck/test/cover_test_module.dontcompile
new file mode 100644
index 00000000..06e88967
--- /dev/null
+++ b/deps/meck/test/cover_test_module.dontcompile
@@ -0,0 +1,21 @@
+%% -*- mode: erlang -*-
+
+%% This file needs not to have the extension .erl, since otherwise
+%% rebar will try to compile it, which won't work since it requires
+%% special compilation options. See meck_tests:cover_test_.
+
+-module(cover_test_module).
+-export([a/0, b/0, c/2]).
+
+%% a/0 is defined in include/cover_test.hrl. We don't put the full
+%% path here, since it should be provided as a compiler option.
+-include("cover_test.hrl").
+
+%% Also, make that this module was compiled with -DTEST.
+-ifdef(TEST).
+b() ->
+ b.
+-endif.
+
+c(A, B) ->
+ {A, B}.
diff --git a/deps/meck/test/include/cover_test.hrl b/deps/meck/test/include/cover_test.hrl
new file mode 100644
index 00000000..0e770862
--- /dev/null
+++ b/deps/meck/test/include/cover_test.hrl
@@ -0,0 +1 @@
+a() -> a.
diff --git a/deps/meck/test/meck_performance_test.erl b/deps/meck/test/meck_performance_test.erl
new file mode 100644
index 00000000..71af107d
--- /dev/null
+++ b/deps/meck/test/meck_performance_test.erl
@@ -0,0 +1,65 @@
+%% @doc
+-module(meck_performance_test).
+
+%% Interface exports
+-export([run/1]).
+
+%%==============================================================================
+%% Interface exports
+%%==============================================================================
+
+run(N) ->
+ meck:new(test),
+ 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)),
+ io:format("expect/3+args\t~p\t~p\t~p\t~p~n",
+ test_avg(meck, expect, [test, normal_args,
+ fun(_, _) -> ok end], N)),
+ io:format("expect/4\t~p\t~p\t~p\t~p~n",
+ test_avg(meck, expect, [test, shortcut, 0, ok], N)),
+ io:format("expect/4+args\t~p\t~p\t~p\t~p~n",
+ test_avg(meck, expect, [test, shortcut_args, 2, ok], N)),
+
+ meck:expect(test, shortcut_opaque, 0, self()),
+
+ io:format("~n\t\tMin\tMax\tMed\tAvg~n"),
+ io:format("normal\t\t~p\t~p\t~p\t~p~n",
+ test_avg(test, normal, [], N)),
+ io:format("normal_args\t~p\t~p\t~p\t~p~n",
+ test_avg(test, normal_args, [a, b], N)),
+ io:format("shortcut\t~p\t~p\t~p\t~p~n",
+ test_avg(test, shortcut, [], N)),
+ io:format("shortcut_args\t~p\t~p\t~p\t~p~n",
+ test_avg(test, shortcut_args, [a, b], N)),
+ io:format("shortcut_opaque\t~p\t~p\t~p\t~p~n",
+ test_avg(test, shortcut_opaque, [], N)),
+ meck:unload(test),
+
+ meck:new(test),
+ meck:expect(test, func, 1, ok),
+ [test:func(I) || I <- lists:seq(1, 100)],
+ io:format("~n\t\tMin\tMax\tMed\tAvg~n"),
+ io:format("called\t\t~p\t~p\t~p\t~p~n",
+ test_avg(meck, called, [test, func, 50], N)),
+ meck:unload(test),
+ ok.
+
+%%==============================================================================
+%% Internal functions
+%%==============================================================================
+
+test_avg(M, F, A, N) when N > 0 ->
+ L = test_loop(M, F, A, N, []),
+ Length = length(L),
+ Min = lists:min(L),
+ Max = lists:max(L),
+ Med = lists:nth(round((Length / 2)), lists:sort(L)),
+ Avg = round(lists:foldl(fun(X, Sum) -> X + Sum end, 0, L) / Length),
+ [Min, Max, Med, Avg].
+
+test_loop(_M, _F, _A, 0, List) ->
+ List;
+test_loop(M, F, A, N, List) ->
+ {T, _Result} = timer:tc(M, F, A),
+ test_loop(M, F, A, N - 1, [T|List]).
diff --git a/deps/meck/test/meck_test_module.erl b/deps/meck/test/meck_test_module.erl
new file mode 100644
index 00000000..6dee52be
--- /dev/null
+++ b/deps/meck/test/meck_test_module.erl
@@ -0,0 +1,8 @@
+-module(meck_test_module).
+-export([a/0, b/0, c/2]).
+
+a() -> a.
+b() -> b.
+
+c(A, B) ->
+ {A, B}.
diff --git a/deps/meck/test/meck_test_parametrized_module.erl b/deps/meck/test/meck_test_parametrized_module.erl
new file mode 100644
index 00000000..19126797
--- /dev/null
+++ b/deps/meck/test/meck_test_parametrized_module.erl
@@ -0,0 +1,7 @@
+-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/deps/meck/test/meck_tests.erl b/deps/meck/test/meck_tests.erl
new file mode 100644
index 00000000..9e019521
--- /dev/null
+++ b/deps/meck/test/meck_tests.erl
@@ -0,0 +1,890 @@
+%%==============================================================================
+%% 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_tests).
+
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+
+meck_test_() ->
+ {foreach, fun setup/0, fun teardown/1,
+ [{with, [T]} || T <- [fun ?MODULE:new_/1,
+ fun ?MODULE:unload_/1,
+ fun ?MODULE:double_new_/1,
+ fun ?MODULE:validate_/1,
+ fun ?MODULE:expect_/1,
+ fun ?MODULE:exports_/1,
+ fun ?MODULE:call_return_value_/1,
+ fun ?MODULE:call_argument_/1,
+ fun ?MODULE:call_undef_/1,
+ fun ?MODULE:call_function_clause_/1,
+ fun ?MODULE:validate_unexpected_error_/1,
+ fun ?MODULE:validate_expected_error_/1,
+ fun ?MODULE:validate_chained_/1,
+ fun ?MODULE:stacktrace_/1,
+ fun ?MODULE:stacktrace_function_clause_/1,
+ fun ?MODULE:change_func_/1,
+ fun ?MODULE:caller_does_not_crash_on_reload_/1,
+ fun ?MODULE:call_original_undef_/1,
+ fun ?MODULE:history_empty_/1,
+ fun ?MODULE:history_call_/1,
+ fun ?MODULE:history_throw_/1,
+ fun ?MODULE:history_throw_fun_/1,
+ fun ?MODULE:history_exit_/1,
+ fun ?MODULE:history_error_/1,
+ fun ?MODULE:history_error_args_/1,
+ fun ?MODULE:history_meck_throw_/1,
+ fun ?MODULE:history_meck_throw_fun_/1,
+ fun ?MODULE:history_meck_exit_/1,
+ fun ?MODULE:history_meck_error_/1,
+ fun ?MODULE:history_by_pid_/1,
+ fun ?MODULE:shortcut_expect_/1,
+ fun ?MODULE:shortcut_expect_negative_arity_/1,
+ fun ?MODULE:shortcut_call_return_value_/1,
+ fun ?MODULE:shortcut_call_argument_/1,
+ fun ?MODULE:shortcut_re_add_/1,
+ fun ?MODULE:shortcut_opaque_/1,
+ fun ?MODULE:delete_/1,
+ fun ?MODULE:called_false_no_args_/1,
+ fun ?MODULE:called_true_no_args_/1,
+ fun ?MODULE:called_true_two_functions_/1,
+ fun ?MODULE:called_false_one_arg_/1,
+ fun ?MODULE:called_true_one_arg_/1,
+ fun ?MODULE:called_false_few_args_/1,
+ fun ?MODULE:called_true_few_args_/1,
+ fun ?MODULE:called_false_error_/1,
+ fun ?MODULE:called_true_error_/1,
+ fun ?MODULE:called_with_pid_no_args_/1,
+ fun ?MODULE:num_calls_/1,
+ fun ?MODULE:num_calls_error_/1,
+ fun ?MODULE:num_calls_with_pid_no_args_/1,
+ fun ?MODULE:called_wildcard_/1,
+ fun ?MODULE:sequence_/1,
+ fun ?MODULE:sequence_multi_/1,
+ fun ?MODULE:loop_/1,
+ fun ?MODULE:loop_multi_/1
+ ]]}.
+
+setup() ->
+ % Uncomment to run tests with dbg:
+ % dbg:tracer(),
+ % dbg:p(all, call),
+ % dbg:tpl(meck, []),
+ ok = meck:new(mymod),
+ mymod.
+
+teardown(Module) ->
+ catch meck:unload(Module).
+
+%% --- Tests using setup and teardown ------------------------------------------
+
+new_(Mod) ->
+ Info = Mod:module_info(),
+ ?assert(is_list(Info)).
+
+unload_(Mod) ->
+ ok = meck:unload(Mod),
+ ?assertEqual(false, code:is_loaded(Mod)).
+
+double_new_(Mod) ->
+ ?assertError({already_started, _}, meck:new(Mod)).
+
+validate_(Mod) ->
+ ?assertEqual(true, meck:validate(Mod)).
+
+expect_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> ok end),
+ ?assertEqual(true, meck:validate(Mod)).
+
+exports_(Mod) ->
+ ok = meck:expect(Mod, test1, fun() -> ok end),
+ ok = meck:expect(Mod, test2, fun(_) -> ok end),
+ ok = meck:expect(Mod, test3, fun(_, _) -> ok end),
+ ?assertEqual(0, proplists:get_value(test1, Mod:module_info(exports))),
+ ?assertEqual(1, proplists:get_value(test2, Mod:module_info(exports))),
+ ?assertEqual(2, proplists:get_value(test3, Mod:module_info(exports))),
+ ?assertEqual(true, meck:validate(Mod)).
+
+call_return_value_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> apa end),
+ ?assertEqual(apa, Mod:test()),
+ ?assertEqual(true, meck:validate(Mod)).
+
+call_argument_(Mod) ->
+ ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
+ ?assertEqual(apa, Mod:test(hest, 1)),
+ ?assertEqual(true, meck:validate(Mod)).
+
+call_function_clause_(Mod) ->
+ ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
+ ?assertError(function_clause, Mod:test(hest, 2)),
+ ?assertEqual(false, meck:validate(Mod)).
+
+validate_unexpected_error_(Mod) ->
+ ok = meck:expect(Mod, test, fun(hest, 1) -> erlang:error(timeout) end),
+ ?assertError(timeout, Mod:test(hest, 1)),
+ ?assertEqual(false, meck:validate(Mod)).
+
+validate_expected_error_(Mod) ->
+ ok = meck:expect(Mod, test, fun(hest, 1) ->
+ meck:exception(error, timeout)
+ end),
+ ?assertError(timeout, Mod:test(hest, 1)),
+ ?assertEqual(true, meck:validate(Mod)).
+
+validate_chained_(Mod) ->
+ ok = meck:new(mymod2),
+ ok = meck:expect(mymod2, test, fun() ->
+ meck:exception(error, test_error)
+ end),
+ ok = meck:expect(Mod, test, fun() ->
+ mymod2:test()
+ end),
+ ?assertError(test_error, Mod:test()),
+ ?assertEqual(false, meck:validate(Mod)),
+ ?assertEqual(true, meck:validate(mymod2)),
+ ok = meck:unload(mymod2).
+
+stacktrace_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> erlang:error(test_error) end),
+ try
+ Mod:test(),
+ throw(failed)
+ catch
+ error:test_error ->
+ ?assert(lists:any(fun({M, test, []}) when M == Mod -> true;
+ ({M, test, [],[]}) when M == Mod -> true;
+ (_) -> false
+ end, erlang:get_stacktrace()))
+ end.
+
+stacktrace_function_clause_(Mod) ->
+ ok = meck:expect(Mod, test, fun(1) -> ok end),
+ try
+ Mod:test(error),
+ throw(failed)
+ 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))
+ end.
+
+
+call_undef_(Mod) ->
+ ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
+ ?assertError(undef, Mod:test(hest)).
+
+caller_does_not_crash_on_reload_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> timer:sleep(infinity) end),
+ Pid = spawn(fun() -> Mod:test() end),
+ ok = meck:expect(Mod, new1, fun() -> ok end),
+ ok = meck:expect(Mod, new2, fun() -> ok end),
+ ok = meck:expect(Mod, new3, fun() -> ok end),
+ ?assertEqual(true, is_process_alive(Pid)).
+
+change_func_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> 1 end),
+ ?assertEqual(1, Mod:test()),
+ ok = meck:expect(Mod, test, fun() -> 2 end),
+ ?assertEqual(2, Mod:test()).
+
+call_original_undef_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> meck:passthrough([]) end),
+ ?assertError(undef, Mod:test()).
+
+history_empty_(Mod) ->
+ ?assertEqual([], meck:history(Mod)).
+
+history_call_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> ok end),
+ ok = meck:expect(Mod, test2, fun(_, _) -> result end),
+ ok = meck:expect(Mod, test3, 0, 3),
+ Mod:test(),
+ Mod:test2(a, b),
+ Mod:test3(),
+ ?assertEqual([{self(), {Mod, test, []}, ok},
+ {self(), {Mod, test2, [a, b]}, result},
+ {self(), {Mod, test3, []}, 3}], meck:history(Mod)).
+
+history_throw_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> throw(test_exception) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, throw, test_exception, _Stacktrace}],
+ meck:history(Mod)).
+
+history_throw_fun_(Mod) ->
+ Fun = fun() -> exception_fun end,
+ ok = meck:expect(Mod, test, fun() -> throw(Fun) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, throw, Fun, _Stacktrace}],
+ meck:history(Mod)).
+
+history_exit_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> exit(test_exit) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, exit, test_exit, _Stacktrace}],
+ meck:history(Mod)).
+
+history_error_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> erlang:error(test_error) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}],
+ meck:history(Mod)).
+
+history_error_args_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> erlang:error(test_error, [fake_args]) end),
+ catch Mod:test(),
+ History = meck:history(Mod),
+ ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}],
+ meck:history(Mod)),
+ [{_Pid, _MFA, error, test_error, Stacktrace}] = History,
+ ?assert(lists:any(fun({_M, _F, [fake_args]}) -> true;
+ ({_M, _F, [fake_args], [{file,_},{line,_}]}) -> true;
+ (_) -> false end, Stacktrace)).
+
+history_meck_throw_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> meck:exception(throw, test_exception) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, throw, test_exception, _Stacktrace}],
+ meck:history(Mod)).
+
+history_meck_throw_fun_(Mod) ->
+ Fun = fun() -> exception_fun end,
+ ok = meck:expect(Mod, test, fun() -> meck:exception(throw, Fun) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, throw, Fun, _Stacktrace}],
+ meck:history(Mod)).
+
+history_meck_exit_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> meck:exception(exit, test_exit) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, exit, test_exit, _Stacktrace}],
+ meck:history(Mod)).
+
+history_meck_error_(Mod) ->
+ ok = meck:expect(Mod, test, fun() -> meck:exception(error, test_error) end),
+ catch Mod:test(),
+ ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}],
+ meck:history(Mod)).
+
+history_by_pid_(Mod) ->
+ ok = meck:expect(Mod, test1, fun() -> ok end),
+ ok = meck:expect(Mod, test2, fun() -> ok end),
+
+ TestPid = self(),
+ Fun = fun() ->
+ Mod:test1(),
+ TestPid ! {self(), done}
+ end,
+ Pid = spawn(Fun),
+ Mod:test1(),
+ Mod:test2(),
+ receive {Pid, done} -> ok end,
+ ?assertEqual([{Pid, {Mod, test1, []}, ok}], meck:history(Mod, Pid)),
+ ?assertEqual([{TestPid, {Mod, test1, []}, ok},
+ {TestPid, {Mod, test2, []}, ok}], meck:history(Mod, TestPid)),
+ ?assertEqual(meck:history(Mod), meck:history(Mod, '_')).
+
+shortcut_expect_(Mod) ->
+ ok = meck:expect(Mod, test, 0, ok),
+ ?assertEqual(true, meck:validate(Mod)).
+
+shortcut_expect_negative_arity_(Mod) ->
+ ?assertError(function_clause, meck:expect(Mod, test, -1, ok)).
+
+shortcut_call_return_value_(Mod) ->
+ ok = meck:expect(Mod, test, 0, apa),
+ ?assertEqual(apa, Mod:test()),
+ ?assertEqual(true, meck:validate(Mod)).
+
+shortcut_call_argument_(Mod) ->
+ ok = meck:expect(Mod, test, 2, apa),
+ ?assertEqual(apa, Mod:test(hest, 1)),
+ ?assertEqual(true, meck:validate(Mod)).
+
+shortcut_re_add_(Mod) ->
+ ok = meck:expect(Mod, test, 2, apa),
+ ?assertEqual(apa, Mod:test(hest, 1)),
+ ok = meck:expect(Mod, test, 2, new),
+ ?assertEqual(new, Mod:test(hest, 1)),
+ ?assertEqual(true, meck:validate(Mod)).
+
+shortcut_opaque_(Mod) ->
+ ok = meck:expect(Mod, test, 0, {test, [a, self()]}),
+ ?assertMatch({test, [a, P]} when P == self(), Mod:test()).
+
+delete_(Mod) ->
+ ok = meck:expect(Mod, test, 2, ok),
+ ?assertEqual(ok, meck:delete(Mod, test, 2)),
+ ?assertError(undef, Mod:test(a, b)),
+ ?assert(meck:validate(Mod)).
+
+called_false_no_args_(Mod) ->
+ Args = [],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ assert_called(Mod, test, Args, false),
+ ok.
+
+called_true_no_args_(Mod) ->
+ Args = [],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ ok = apply(Mod, test, Args),
+ assert_called(Mod, test, Args, true),
+ ok.
+
+called_true_two_functions_(Mod) ->
+ Args = [],
+ ok = meck:expect(Mod, test1, length(Args), ok),
+ ok = meck:expect(Mod, test2, length(Args), ok),
+ ok = apply(Mod, test1, Args),
+ ok = apply(Mod, test2, Args),
+ assert_called(Mod, test2, Args, true),
+ ok.
+
+called_false_one_arg_(Mod) ->
+ Args = ["hello"],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ assert_called(Mod, test, Args, false),
+ ok.
+
+called_true_one_arg_(Mod) ->
+ Args = ["hello"],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ ok = apply(Mod, test, Args),
+ assert_called(Mod, test, Args, true),
+ ok.
+
+called_false_few_args_(Mod) ->
+ Args = [one, 2, {three, 3}, "four"],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ assert_called(Mod, test, Args, false),
+ ok.
+
+called_true_few_args_(Mod) ->
+ Args = [one, 2, {three, 3}, "four"],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ ok = apply(Mod, test, Args),
+ assert_called(Mod, test, Args, true),
+ ok.
+
+called_false_error_(Mod) ->
+ Args = [one, "two", {3, 3}],
+ TestFun = fun (_, _, _) -> meck:exception(error, my_error) end,
+ ok = meck:expect(Mod, test, TestFun),
+ assert_called(Mod, test, Args, false),
+ ok.
+
+called_true_error_(Mod) ->
+ Args = [one, "two", {3, 3}],
+ expect_catch_apply(Mod, test, Args),
+ assert_called(Mod, test, Args, true),
+ ok.
+
+called_with_pid_no_args_(Mod) ->
+ Args = [],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ Pid = spawn_caller_and_sync(Mod, test, Args),
+ assert_called(Mod, test, Args, self(), false),
+ assert_called(Mod, test, Args, Pid, true),
+ ok = apply(Mod, test, Args),
+ assert_called(Mod, test, Args, self(), true),
+ ?assertEqual(meck:called(Mod, test, Args, '_'),
+ meck:called(Mod, test, Args)).
+
+spawn_caller_and_sync(Mod, Func, Args) ->
+ TestPid = self(),
+ Fun = fun() ->
+ catch apply(Mod, Func, Args),
+ TestPid ! {self(), done}
+ end,
+ Pid = spawn(Fun),
+ receive {Pid, done} -> ok end, % sync with the spawned process
+ Pid.
+
+num_calls_(Mod) ->
+ Args = [],
+ IncorrectArgs = [foo],
+ ok = meck:expect(Mod, test1, length(Args), ok),
+ ?assertEqual(0, meck:num_calls(Mod, test1, Args)),
+ ok = apply(Mod, test1, Args),
+ ?assertEqual(1, meck:num_calls(Mod, test1, Args)),
+ ?assertEqual(0, meck:num_calls(Mod, test1, IncorrectArgs)).
+
+num_calls_error_(Mod) ->
+ Args = [one, "two", {3, 3}],
+ expect_catch_apply(Mod, test, Args),
+ ?assertEqual(1, meck:num_calls(Mod, test, Args)).
+
+num_calls_with_pid_no_args_(Mod) ->
+ Args = [],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ Pid = spawn_caller_and_sync(Mod, test, Args),
+ ?assertEqual(0, meck:num_calls(Mod, test, Args, self())),
+ ?assertEqual(1, meck:num_calls(Mod, test, Args, Pid)),
+ ok = apply(Mod, test, Args),
+ ?assertEqual(1, meck:num_calls(Mod, test, Args, self())),
+ ?assertEqual(meck:num_calls(Mod, test, Args, '_'),
+ meck:num_calls(Mod, test, Args)).
+
+expect_apply(Mod, Func, Args) ->
+ ok = meck:expect(Mod, Func, length(Args), ok),
+ ok = apply(Mod, Func, Args).
+
+expect_catch_apply(Mod, Func, Args) ->
+ TestFun = fun (_, _, _) -> meck:exception(error, my_error) end,
+ ok = meck:expect(Mod, Func, TestFun),
+ catch apply(Mod, Func, Args).
+
+called_wildcard_(Mod) ->
+ Args = [one, 2, {three, 3}, "four"],
+ ok = meck:expect(Mod, test, length(Args), ok),
+ ok = apply(Mod, test, Args),
+ assert_called(Mod, test, [one, '_', {three, '_'}, "four"], true),
+ ok.
+
+sequence_(Mod) ->
+ Sequence = [a, b, c, d, e],
+ ?assertEqual(ok, meck:sequence(Mod, s, 2, Sequence)),
+ ?assertEqual(Sequence,
+ [Mod:s(a, b) || _ <- lists:seq(1, length(Sequence))]),
+ ?assertEqual([e, e, e, e, e],
+ [Mod:s(a, b) || _ <- lists:seq(1, 5)]),
+ ?assert(meck:validate(Mod)).
+
+sequence_multi_(Mod) ->
+ meck:new(mymod2),
+ Mods = [Mod, mymod2],
+ Sequence = [a, b, c, d, e],
+ ?assertEqual(ok, meck:sequence(Mods, s, 2, Sequence)),
+ ?assertEqual(Sequence,
+ [Mod:s(a, b) || _ <- lists:seq(1, length(Sequence))]),
+ ?assertEqual([e, e, e, e, e],
+ [Mod:s(a, b) || _ <- lists:seq(1, 5)]),
+ ?assertEqual(Sequence,
+ [mymod2:s(a, b) || _ <- lists:seq(1, length(Sequence))]),
+ ?assertEqual([e, e, e, e, e],
+ [mymod2:s(a, b) || _ <- lists:seq(1, 5)]),
+ ?assert(meck:validate(Mods)).
+
+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)).
+
+loop_multi_(Mod) ->
+ meck:new(mymod2),
+ Mods = [Mod, mymod2],
+ Loop = [a, b, c, d, e],
+ ?assertEqual(ok, meck:loop(Mods, l, 2, Loop)),
+ [[?assertEqual(V, M:l(a, b)) || _ <- lists:seq(1, length(Loop)), V <- Loop]
+ || M <- Mods],
+ ?assert(meck:validate(Mods)).
+
+%% --- Tests with own setup ----------------------------------------------------
+
+call_original_test() ->
+ false = code:purge(meck_test_module),
+ ?assertEqual({module, meck_test_module}, code:load_file(meck_test_module)),
+ ok = meck:new(meck_test_module, [no_passthrough_cover]),
+ ?assertEqual({file, ""}, code:is_loaded(meck_test_module_meck_original)),
+ ok = meck:expect(meck_test_module, a, fun() -> c end),
+ ok = meck:expect(meck_test_module, b, fun() -> meck:passthrough([]) end),
+ ?assertEqual(c, meck_test_module:a()),
+ ?assertEqual(b, meck_test_module:b()),
+ ok = meck:unload(meck_test_module).
+
+unload_renamed_original_test() ->
+ ok = meck:new(meck_test_module),
+ ok = meck:unload(meck_test_module),
+ ?assertEqual(false, code:is_loaded(meck_test_module_meck_original)).
+
+unload_all_test() ->
+ Mods = [test_a, test_b, test_c, test_d, test_e],
+ ok = meck:new(Mods),
+ ?assertEqual(lists:sort(Mods), lists:sort(meck:unload())),
+ [?assertEqual(false, code:is_loaded(M)) || M <- Mods].
+
+original_no_file_test() ->
+ {ok, Mod, Beam} = compile:forms([{attribute, 1, module, meck_not_on_disk}]),
+ {module, Mod} = code:load_binary(Mod, "", Beam),
+ ?assertEqual(ok, meck:new(meck_not_on_disk)),
+ ok = meck:unload(meck_not_on_disk).
+
+original_has_no_object_code_test() ->
+ {ok, Mod, Beam} = compile:forms([{attribute, 1, module, meck_on_disk}]),
+ ok = file:write_file("meck_on_disk.beam", Beam),
+ {module, Mod} = code:load_binary(Mod, "meck_on_disk.beam", Beam),
+ ?assertEqual(ok, meck:new(meck_on_disk)),
+ ok = file:delete("meck_on_disk.beam"),
+ ok = meck:unload(meck_on_disk).
+
+passthrough_nonexisting_module_test() ->
+ ok = meck:new(mymod, [passthrough]),
+ ok = meck:expect(mymod, test, fun() -> ok end),
+ ?assertEqual(ok, mymod:test()),
+ ok = meck:unload(mymod).
+
+passthrough_test() ->
+ passthrough_test([]).
+
+passthrough_test(Opts) ->
+ ok = meck:new(meck_test_module, [passthrough|Opts]),
+ ok = meck:expect(meck_test_module, a, fun() -> c end),
+ ?assertEqual(c, meck_test_module:a()),
+ ?assertEqual(b, meck_test_module:b()),
+ ?assertEqual({1, 2}, meck_test_module:c(1, 2)),
+ ok = meck:unload(meck_test_module).
+
+passthrough_different_arg_test() ->
+ ok = meck:new(meck_test_module),
+ ok = meck:expect(meck_test_module, c,
+ fun(_, _) -> meck:passthrough([x, y]) end),
+ ?assertEqual({x, y}, meck_test_module:c(1, 2)),
+ ok = meck:unload(meck_test_module).
+
+passthrough_bif_test() ->
+ ?assertEqual(ok, meck:new(file, [unstick, passthrough])),
+ ?assertEqual(ok, meck:unload(file)).
+
+cover_test() ->
+ {ok, _} = cover:compile("../test/meck_test_module.erl"),
+ a = meck_test_module:a(),
+ b = meck_test_module:b(),
+ {1, 2} = meck_test_module:c(1, 2),
+ {ok, {meck_test_module, {3,0}}} = cover:analyze(meck_test_module, module),
+ run_mock_no_cover_file(meck_test_module),
+ {ok, {meck_test_module, {3,0}}} = cover:analyze(meck_test_module, module).
+
+cover_options_test_() ->
+ {foreach, fun compile_options_setup/0, fun compile_options_teardown/1,
+ [{with, [T]} || T <- [fun ?MODULE:cover_options_/1,
+ fun ?MODULE:cover_options_fail_/1
+ ]]}.
+
+compile_options_setup() ->
+ Module = cover_test_module,
+ % Our test module won't compile without compiler options that
+ % rebar won't give it, thus the rename dance.
+ Src = join("../test/", Module, ".erl"),
+ ok = file:rename(join("../test/", Module, ".dontcompile"), Src),
+ OldPath = code:get_path(),
+ code:add_path("../test"),
+ {OldPath, Src, Module}.
+
+compile_options_teardown({OldPath, Src, Module}) ->
+ file:rename(Src, join("../test/", Module, ".dontcompile")),
+ code:purge(Module),
+ code:delete(Module),
+ code:set_path(OldPath).
+
+cover_options_({_OldPath, Src, Module}) ->
+ % Test that compilation options (include paths and preprocessor
+ % definitions) are used when un-mecking previously cover compiled
+ % modules.
+ CompilerOptions = [{i, "../test/include"}, {d, 'TEST', true}],
+ % The option recover feature depends on having the BEAM file
+ % available.
+ {ok, _} = compile:file(Src, [{outdir, "../test"}|CompilerOptions]),
+ {ok, _} = cover:compile(Src, CompilerOptions),
+ a = Module:a(),
+ b = Module:b(),
+ {1, 2} = Module:c(1, 2),
+ % We get 2 instead of 3 as expected. Maybe because cover doesn't
+ % count include files?
+ ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)),
+ run_mock_no_cover_file(Module),
+ % 2 instead of 3, as above
+ ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)).
+
+cover_options_fail_({_OldPath, Src, Module}) ->
+ %% This may look like the test above but there is a subtle
+ %% difference. When `cover:compile_beam' is called it squashes
+ %% compile options. This test verifies that function `b/0', which
+ %% relies on the `TEST' directive being set can still be called
+ %% after the module is meck'ed.
+ CompilerOptions = [{i, "../test/include"}, {d, 'TEST', true},
+ {outdir, "../test"}, debug_info],
+ {ok, _} = compile:file(Src, CompilerOptions),
+ ?assertEqual(CompilerOptions, meck_mod:compile_options(Module)),
+ {ok, _} = cover:compile_beam(Module),
+ ?assertEqual([], meck_mod:compile_options(Module)),
+ a = Module:a(),
+ b = Module:b(),
+ {1, 2} = Module:c(1, 2),
+ ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)),
+ ok = meck:new(Module, [passthrough]),
+ ok = meck:expect(Module, a, fun () -> c end),
+ ?assertEqual(c, Module:a()),
+ ?assertEqual(b, Module:b()),
+ ?assertEqual({1, 2}, Module:c(1, 2)),
+ ok = meck:unload(Module),
+ %% Verify passthru calls went to cover
+ ?assertEqual({ok, {Module, 4}}, cover:analyze(Module, calls, module)).
+
+join(Path, Module, Ext) -> filename:join(Path, atom_to_list(Module) ++ Ext).
+
+run_mock_no_cover_file(Module) ->
+ ok = meck:new(Module),
+ ok = meck:expect(Module, a, fun () -> c end),
+ ?assertEqual(c, Module:a()),
+ ok = meck:unload(Module),
+ ?assert(not filelib:is_file(atom_to_list(Module) ++ ".coverdata")).
+
+%% @doc Verify that passthrough calls _don't_ appear in cover
+%% analysis.
+no_cover_passthrough_test() ->
+ {ok, _} = cover:compile("../test/meck_test_module.erl"),
+ {ok, {meck_test_module, {0,3}}} = cover:analyze(meck_test_module, module),
+ passthrough_test([no_passthrough_cover]),
+ {ok, {meck_test_module, {0,3}}} = cover:analyze(meck_test_module, module).
+
+%% @doc Verify that passthrough calls appear in cover analysis.
+cover_passthrough_test() ->
+ {ok, _} = cover:compile("../test/meck_test_module.erl"),
+ ?assertEqual({ok, {meck_test_module, {0,3}}},
+ cover:analyze(meck_test_module, module)),
+ passthrough_test([]),
+ ?assertEqual({ok, {meck_test_module, {2,1}}},
+ cover:analyze(meck_test_module, module)).
+
+% @doc The mocked module is unloaded if the meck process crashes.
+unload_when_crashed_test() ->
+ ok = meck:new(mymod),
+ ?assertMatch({file, _}, code:is_loaded(mymod)),
+ SaltedName = mymod_meck,
+ Pid = whereis(SaltedName),
+ ?assertEqual(true, is_pid(Pid)),
+ unlink(Pid),
+ exit(Pid, expected_test_exit),
+ timer:sleep(100),
+ ?assertEqual(undefined, whereis(SaltedName)),
+ ?assertEqual(false, code:is_loaded(mymod)).
+
+% @doc The mocked module is unloaded if the meck process crashes.
+unlink_test() ->
+ ok = meck:new(mymod, [no_link]),
+ 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.
+expect_without_new_test() ->
+ ?assertError({not_mocked, othermod},
+ meck:expect(othermod, test, fun() -> ok end)).
+
+history_passthrough_test() ->
+ ok = meck:new(meck_test_module, [passthrough]),
+ ok = meck:expect(meck_test_module, a, fun() -> c end),
+ c = meck_test_module:a(),
+ b = meck_test_module:b(),
+ ?assertEqual([{self(), {meck_test_module, a, []}, c},
+ {self(), {meck_test_module, b, []}, b}],
+ meck:history(meck_test_module)),
+ ok = meck:unload(meck_test_module).
+
+multi_test() ->
+ Mods = [mod1, mod2, mod3],
+ ok = meck:new(Mods),
+ ok = meck:expect(Mods, test, fun() -> ok end),
+ ok = meck:expect(Mods, test2, 0, ok),
+ [?assertEqual(ok, M:test()) || M <- Mods],
+ ?assert(meck:validate(Mods)),
+ ok = meck:unload(Mods).
+
+multi_invalid_test() ->
+ Mods = [mod1, mod2, mod3],
+ ok = meck:new(Mods),
+ ok = meck:expect(Mods, test, fun(1) -> ok end),
+ ?assertError(function_clause, mod2:test(2)),
+ ?assert(not meck:validate(Mods)),
+ ok = meck:unload(Mods).
+
+multi_option_test() ->
+ Mods = [mod1, mod2, mod3],
+ ok = meck:new(Mods, [passthrough]),
+ ok = meck:expect(Mods, test, fun() -> ok end),
+ [?assertEqual(ok, M:test()) || M <- Mods],
+ ?assert(meck:validate(Mods)),
+ ok = meck:unload(Mods).
+
+multi_shortcut_test() ->
+ Mods = [mod1, mod2, mod3],
+ ok = meck:new(Mods),
+ ok = meck:expect(Mods, test, 0, ok),
+ [?assertEqual(ok, M:test()) || M <- Mods],
+ ?assert(meck:validate(Mods)),
+ ok = meck:unload(Mods).
+
+multi_delete_test() ->
+ Mods = [mod1, mod2, mod3],
+ ok = meck:new(Mods),
+ 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).
+
+handle_cast_unmodified_state_test() ->
+ S = dummy_state,
+ ?assertEqual({noreply, S}, meck:handle_cast(dummy_msg, S)).
+
+code_change_unmodified_state_test() ->
+ S = dummy_state,
+ ?assertEqual({ok, S}, meck:code_change(old_version, S, [])).
+
+remote_meck_test_() ->
+ {foreach, fun remote_setup/0, fun remote_teardown/1,
+ [{with, [T]} || T <- [fun remote_meck_/1,
+ fun remote_meck_cover_/1]]}.
+
+remote_setup() ->
+ [] = os:cmd("epmd -daemon"),
+ Hostname = "localhost",
+ Myself = list_to_atom("meck_eunit_test@" ++ Hostname),
+ net_kernel:start([Myself, shortnames]),
+ {ok, Node} = slave:start_link(list_to_atom(Hostname), meck_remote_test,
+ "-pa test"),
+ {Mod, Bin, File} = code:get_object_code(meck),
+ {module, Mod} = rpc:call(Node, code, load_binary, [Mod, File, Bin]),
+ {module, meck_test_module} =
+ rpc:call(Node, code, load_file, [meck_test_module]),
+ {Node, meck_test_module}.
+
+remote_teardown({Node, _Mod}) ->
+ ok = slave:stop(Node).
+
+remote_meck_({Node, Mod}) ->
+ ?assertEqual(ok, rpc:call(Node, meck, new, [Mod, [no_link]])),
+ ?assertEqual(ok, rpc:call(Node, meck, expect, [Mod, test, 0, true])),
+ ?assertEqual(true, rpc:call(Node, Mod, test, [])).
+
+remote_meck_cover_({Node, Mod}) ->
+ {ok, Mod} = cover:compile(Mod),
+ {ok, _Nodes} = cover:start([Node]),
+ ?assertEqual(ok, rpc:call(Node, meck, new, [Mod])).
+
+can_mock_sticky_modules_test() ->
+ code:stick_mod(meck_test_module),
+ meck:new(meck_test_module, [unstick]),
+ ?assertNot(code:is_sticky(meck_test_module)),
+ meck:unload(meck_test_module),
+ ?assert(code:is_sticky(meck_test_module)),
+ code:unstick_mod(meck_test_module).
+
+
+sticky_directory_test_() ->
+ {foreach, fun sticky_setup/0, fun sticky_teardown/1,
+ [{with, [T]}
+ || T <- [fun ?MODULE:can_mock_sticky_module_not_yet_loaded_/1,
+ fun ?MODULE:cannot_mock_sticky_module_without_unstick_/1]]}.
+
+sticky_setup() ->
+ % Find out where the beam file is (purge because it is cover compiled)
+ Module = meck_test_module,
+ false = code:purge(Module),
+ {module, Module} = code:load_file(Module),
+ Beam = code:which(Module),
+
+ % Unload module so it's not loaded when running meck
+ false = code:purge(Module),
+ true = code:delete(Module),
+
+ % Create new sticky dir and copy beam file
+ Dir = "sticky_test",
+ ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
+ Dest = filename:join(Dir, filename:basename(Beam)),
+ {ok, _BytesCopied} = file:copy(Beam, Dest),
+ true = code:add_patha(Dir),
+ ok = code:stick_dir(Dir),
+ code:load_file(Module),
+
+ {Module, {Dir, Dest}}.
+
+sticky_teardown({Module, {Dir, Dest}}) ->
+ % Clean up
+ ok = code:unstick_dir(Dir),
+ false = code:purge(Module),
+ true = code:del_path(Dir),
+ ok = file:delete(Dest),
+ ok = file:del_dir(Dir).
+
+can_mock_sticky_module_not_yet_loaded_({Mod, _}) ->
+ ?assertEqual(ok, meck:new(Mod, [unstick])),
+ ?assertNot(code:is_sticky(Mod)),
+ ?assertEqual(ok, meck:unload(Mod)),
+ ?assert(code:is_sticky(Mod)).
+
+cannot_mock_sticky_module_without_unstick_({Mod, _}) ->
+ ?assertError(module_is_sticky, meck:new(Mod, [no_link])).
+
+can_mock_non_sticky_module_test() ->
+ ?assertNot(code:is_sticky(meck_test_module)),
+ ?assertEqual(ok, meck:new(meck_test_module, [unstick])),
+ ?assertNot(code:is_sticky(meck_test_module)),
+ ?assertEqual(ok, meck:unload(meck_test_module)),
+ ?assertNot(code:is_sticky(meck_test_module)).
+
+cannot_expect_bif_or_autogenerated_test() ->
+ ?assertEqual(ok, meck:new(unicode, [unstick, passthrough])),
+ ?assertError({cannot_mock_builtin, {unicode, characters_to_binary, 2}},
+ meck:expect(unicode, characters_to_binary, 2, doh)),
+ ?assertError({cannot_mock_autogenerated, {unicode, module_info, 0}},
+ meck:expect(unicode, module_info, 0, doh)),
+ ?assertEqual(ok, meck:unload(unicode)).
+
+meck_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_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)).
+
+%%==============================================================================
+%% Internal Functions
+%%==============================================================================
+
+assert_called(Mod, Function, Args, WasCalled) ->
+ ?assertEqual(WasCalled, meck:called(Mod, Function, Args)),
+ ?assert(meck:validate(Mod)).
+
+assert_called(Mod, Function, Args, Pid, WasCalled) ->
+ ?assertEqual(WasCalled, meck:called(Mod, Function, Args, Pid)),
+ ?assert(meck:validate(Mod)).