%%=============================================================================
%% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%=============================================================================

-module(meck_tests).

-include_lib("eunit/include/eunit.hrl").
-include("../src/meck.hrl").

-define(assertTerminated(MonitorRef, Reason, Timeout),
        (fun() ->
                receive
                    {'DOWN', MonitorRef, process, _Pid, Reason} ->
                         ok;
                    {'DOWN', MonitorRef, process, _Pid, AnotherReason} ->
                        erlang:error({dead_for_another_reason, AnotherReason})
                after
                    Timeout ->
                        erlang:error(still_alive)
                end
         end)()).

meck_test_() ->
    {foreach, fun setup/0, fun teardown/1,
     [{with, [T]} || T <- [fun new_/1,
                           fun unload_/1,
                           fun double_new_/1,
                           fun validate_/1,
                           fun expect_/1,
                           fun exports_/1,
                           fun call_return_value_/1,
                           fun call_return_value_improper_list_/1,
                           fun call_argument_/1,
                           fun call_undef_/1,
                           fun call_function_clause_/1,
                           fun validate_unexpected_error_/1,
                           fun validate_expected_error_/1,
                           fun validate_chained_/1,
                           fun stacktrace_/1,
                           fun stacktrace_function_clause_/1,
                           fun change_func_/1,
                           fun caller_does_not_crash_on_reload_/1,
                           fun call_original_undef_/1,
                           fun history_empty_/1,
                           fun history_call_/1,
                           fun history_throw_/1,
                           fun history_throw_fun_/1,
                           fun history_exit_/1,
                           fun history_error_/1,
                           fun history_error_args_/1,
                           fun history_meck_throw_/1,
                           fun history_meck_throw_fun_/1,
                           fun history_meck_exit_/1,
                           fun history_meck_error_/1,
                           fun history_by_pid_/1,
                           fun reset_/1,
                           fun shortcut_expect_/1,
                           fun shortcut_expect_negative_arity_/1,
                           fun shortcut_call_return_value_/1,
                           fun shortcut_call_argument_/1,
                           fun shortcut_re_add_/1,
                           fun shortcut_opaque_/1,
                           fun shortcut_stacktrace_/1,
                           fun delete_/1,
                           fun expects_/1,
                           fun called_false_no_args_/1,
                           fun called_true_no_args_/1,
                           fun called_true_two_functions_/1,
                           fun called_false_one_arg_/1,
                           fun called_true_one_arg_/1,
                           fun called_false_few_args_/1,
                           fun called_true_few_args_/1,
                           fun called_few_args_matchers_/1,
                           fun called_false_error_/1,
                           fun called_true_error_/1,
                           fun called_with_pid_no_args_/1,
                           fun num_calls_/1,
                           fun num_calls_error_/1,
                           fun num_calls_with_pid_no_args_/1,
                           fun called_wildcard_/1,
                           fun sequence_/1,
                           fun sequence_fun_/1,
                           fun expect_args_sequence_/1,
                           fun expect_arity_sequence_/1,
                           fun expect_complex_sequence_/1,
                           fun sequence_multi_/1,
                           fun loop_/1,
                           fun expect_empty_clause_list_/1,
                           fun expect_args_value_/1,
                           fun expect_args_invalid_call_/1,
                           fun expect_arity_value_/1,
                           fun expect_args_loop_/1,
                           fun expect_arity_loop_/1,
                           fun expect_complex_loop_/1,
                           fun expect_loop_in_seq_/1,
                           fun expect_args_exception_/1,
                           fun expect_arity_exception_/1,
                           fun expect_arity_clause_/1,
                           fun loop_multi_/1,
                           fun expect_args_pattern_override_/1,
                           fun expect_args_pattern_shadow_/1,
                           fun expect_args_pattern_missing_/1,
                           fun expect_args_pattern_invalid_/1,
                           fun expect_args_matchers_/1,
                           fun expect_ret_specs_/1
                          ]]}.

setup() ->
    % Uncomment to run tests with dbg:
    % dbg:tracer(),
    % dbg:p(all, call),
    % dbg:tpl(meck, []),
    ok = meck:new(mymod, [non_strict]),
    mymod.

teardown(Module) ->
    try meck:unload(Module)
    catch
        _:_ ->
            ok
    end.

%% --- Tests using setup and teardown -----------------------------------------

new_(Mod) ->
    Info = Mod:module_info(),
    ?assert(is_list(Info)).

unload_(Mod) ->
    ok = meck:unload(Mod),
    ?assertEqual(false, code:is_loaded(Mod)).

double_new_(Mod) ->
    ?assertError({already_started, _}, meck:new(Mod)).

validate_(Mod) ->
    ?assertEqual(true, meck:validate(Mod)).

expect_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> ok end),
    ?assertEqual(true, meck:validate(Mod)).

exports_(Mod) ->
    ok = meck:expect(Mod, test1, fun() -> ok end),
    ok = meck:expect(Mod, test2, fun(_) -> ok end),
    ok = meck:expect(Mod, test3, fun(_, _) -> ok end),
    ?assertEqual(0, proplists:get_value(test1, Mod:module_info(exports))),
    ?assertEqual(1, proplists:get_value(test2, Mod:module_info(exports))),
    ?assertEqual(2, proplists:get_value(test3, Mod:module_info(exports))),
    ?assertEqual(true, meck:validate(Mod)).

call_return_value_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> apa end),
    ?assertEqual(apa, Mod:test()),
    ?assertEqual(true, meck:validate(Mod)).

call_return_value_improper_list_(Mod) ->
    Dict = dict:store(hest, apa, dict:new()),
    ok = meck:expect(Mod, test, 0, Dict),
    ?assertEqual(Dict, Mod:test()),
    ?assertEqual(true, meck:validate(Mod)).

call_argument_(Mod) ->
    ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
    ?assertEqual(apa, Mod:test(hest, 1)),
    ?assertEqual(true, meck:validate(Mod)).

call_function_clause_(Mod) ->
    ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
    ?assertError(function_clause, Mod:test(hest, 2)),
    ?assertEqual(false, meck:validate(Mod)).

validate_unexpected_error_(Mod) ->
    ok = meck:expect(Mod, test, fun(hest, 1) -> erlang:error(timeout) end),
    ?assertError(timeout, Mod:test(hest, 1)),
    ?assertEqual(false, meck:validate(Mod)).

validate_expected_error_(Mod) ->
    ok = meck:expect(Mod, test, fun(hest, 1) ->
                                     meck:exception(error, timeout)
                             end),
    ?assertError(timeout, Mod:test(hest, 1)),
    ?assertEqual(true, meck:validate(Mod)).

validate_chained_(Mod) ->
    ok = meck:new(mymod2, [non_strict]),
    ok = meck:expect(mymod2, test, fun() ->
                                      meck:exception(error, test_error)
                              end),
    ok = meck:expect(Mod, test, fun() ->
                                     mymod2:test()
                             end),
    ?assertError(test_error, Mod:test()),
    ?assertEqual(false, meck:validate(Mod)),
    ?assertEqual(true, meck:validate(mymod2)),
    ok = meck:unload(mymod2).

stacktrace_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> erlang:error(test_error) end),
    try
        Mod:test(),
        throw(failed)
    catch
        ?_exception_(error, test_error, StackToken) ->
            ?assert(lists:any(fun({M, test, []}) when M == Mod    -> true;
                                 ({M, test, [],[]}) when M == Mod -> true;
                                 (_)                              -> false
                              end, ?_get_stacktrace_(StackToken)))
    end.

stacktrace_function_clause_(Mod) ->
    ok = meck:expect(Mod, test, fun(1) -> ok end),
    try
        Mod:test(error),
        throw(failed)
    catch
        ?_exception_(error, function_clause, StackToken) ->
            ?assert(lists:any(
                fun ({M, test, [error]}) when M == Mod     -> true;
                    ({M, test, [error], []}) when M == Mod -> true;
                    (_)                                    -> false
                end, ?_get_stacktrace_(StackToken)))
    end.

call_undef_(Mod) ->
    ok = meck:expect(Mod, test, fun(hest, 1) -> apa end),
    ?assertError(undef, Mod:test(hest)).

caller_does_not_crash_on_reload_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> timer:sleep(infinity) end),
    Pid = spawn(fun() -> Mod:test() end),
    ok = meck:expect(Mod, new1, fun() -> ok end),
    ok = meck:expect(Mod, new2, fun() -> ok end),
    ok = meck:expect(Mod, new3, fun() -> ok end),
    ?assertEqual(true, is_process_alive(Pid)).

change_func_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> 1 end),
    ?assertEqual(1, Mod:test()),
    ok = meck:expect(Mod, test, fun() -> 2 end),
    ?assertEqual(2, Mod:test()).

call_original_undef_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> meck:passthrough([]) end),
    ?assertError(undef, Mod:test()),
    ?assert(not meck:validate(Mod)),
    ?assertEqual(undefined, get('$meck_call')).

history_empty_(Mod) ->
    ?assertEqual([], meck:history(Mod)).

history_call_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> ok end),
    ok = meck:expect(Mod, test2, fun(_, _) -> result end),
    ok = meck:expect(Mod, test3, 0, 3),
    ok = meck:expect(Mod, test4, 0, {1,2,3}),
    Mod:test(),
    Mod:test2(a, b),
    Mod:test3(),
    Mod:test4(),
    ?assertEqual([{self(), {Mod, test,  []},     ok},
                  {self(), {Mod, test2, [a, b]}, result},
                  {self(), {Mod, test3, []},     3},
                  {self(), {Mod, test4, []},     {1,2,3}}], meck:history(Mod)).

history_throw_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> throw(test_exception) end),
    try Mod:test() catch _:_ -> ok end,
    ?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),
    try Mod:test() catch _:_ -> ok end,
    ?assertMatch([{_Pid, {Mod, test, []}, throw, Fun, _Stacktrace}],
                 meck:history(Mod)).

history_exit_(Mod) ->
    ok = meck:expect(Mod, test, fun() -> exit(test_exit) end),
    try Mod:test() catch _:_ -> ok end,
    ?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),
    try Mod:test() catch _:_ -> ok end,
    ?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),
    try Mod:test() catch _:_ -> ok end,
    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),
    try Mod:test() catch _:_ -> ok end,
    ?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),
    try Mod:test() catch _:_ -> ok end,
    ?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),
    try Mod:test() catch _:_ -> ok end,
    ?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),
    try Mod:test() catch _:_ -> ok end,
    ?assertMatch([{_Pid, {Mod, test, []}, error, test_error, _Stacktrace}],
                 meck:history(Mod)).

history_by_pid_(Mod) ->
    ok = meck:expect(Mod, test1, fun() -> ok end),
    ok = meck:expect(Mod, test2, fun() -> ok end),

    TestPid = self(),
    Fun = fun() ->
                  Mod:test1(),
                  TestPid ! {self(), done}
          end,
    Pid = spawn(Fun),
    Mod:test1(),
    Mod:test2(),
    receive {Pid, done} -> ok end,
    ?assertEqual([{Pid, {Mod, test1, []}, ok}], meck:history(Mod, Pid)),
    ?assertEqual([{TestPid, {Mod, test1, []}, ok},
                  {TestPid, {Mod, test2, []}, ok}],
                 meck:history(Mod, TestPid)),
    ?assertEqual(meck:history(Mod), meck:history(Mod, '_')).

reset_(Mod) ->
    % Given
    meck:expect(Mod, test1, fun() -> ok end),
    meck:expect(Mod, test2, fun() -> ok end),
    Mod:test1(),
    Mod:test2(),
    % When
    meck:reset(Mod),
    Mod:test1(),
    % Then
    ?assertMatch([{_Pid, {Mod, test1, []}, ok}], meck:history(Mod)).

shortcut_expect_(Mod) ->
    ok = meck:expect(Mod, test, 0, ok),
    ?assertEqual(true, meck:validate(Mod)).

shortcut_expect_negative_arity_(Mod) ->
    ?assertError(function_clause, meck:expect(Mod, test, -1, ok)).

shortcut_call_return_value_(Mod) ->
    ok = meck:expect(Mod, test, 0, apa),
    ?assertEqual(apa, Mod:test()),
    ?assertEqual(true, meck:validate(Mod)).

shortcut_call_argument_(Mod) ->
    ok = meck:expect(Mod, test, 2, apa),
    ?assertEqual(apa, Mod:test(hest, 1)),
    ?assertEqual(true, meck:validate(Mod)).

shortcut_re_add_(Mod) ->
    ok = meck:expect(Mod, test, 2, apa),
    ?assertEqual(apa, Mod:test(hest, 1)),
    ok = meck:expect(Mod, test, 2, new),
    ?assertEqual(new, Mod:test(hest, 1)),
    ?assertEqual(true, meck:validate(Mod)).

shortcut_opaque_(Mod) ->
    Ref = make_ref(),
    ok = meck:expect(Mod, test, 0, {test, [a, self()], Ref}),
    ?assertMatch({test, [a, P], Ref} when P == self(), Mod:test()).

shortcut_stacktrace_(Mod) ->
    ok = meck:expect(Mod, test, [true], ok),
    ?assertEqual(
        {error, function_clause, [{mymod, test, [false], []}]},
        try Mod:test(false)
        catch
            Class:Error:Stacktrace ->
                {Class, Error, Stacktrace}
        end
    ).

delete_(Mod) ->
    ok = meck:expect(Mod, test, 2, ok),
    ?assertEqual(ok, meck:delete(Mod, test, 2)),
    ?assertError(undef, Mod:test(a, b)),
    ?assert(meck:validate(Mod)).

expects_(Mod) ->
    ?assertEqual([], meck:expects(Mod)),
    ok = meck:expect(Mod, test, 2, ok),
    ?assertEqual([{Mod, test, 2}], meck:expects(Mod)),
    ok = meck:expect(Mod, test2, 0, ok),
    ?assertEqual([{Mod, test, 2}, {Mod, test2, 0}], lists:sort(meck:expects(Mod))).

called_false_no_args_(Mod) ->
    Args = [],
    ok = meck:expect(Mod, test, length(Args), ok),
    assert_called(Mod, test, Args, false),
    ok.

called_true_no_args_(Mod) ->
    Args = [],
    ok = meck:expect(Mod, test, length(Args), ok),
    ok = apply(Mod, test, Args),
    assert_called(Mod, test, Args, true),
    ok.

called_true_two_functions_(Mod) ->
    Args = [],
    ok = meck:expect(Mod, test1, length(Args), ok),
    ok = meck:expect(Mod, test2, length(Args), ok),
    ok = apply(Mod, test1, Args),
    ok = apply(Mod, test2, Args),
    assert_called(Mod, test2, Args, true),
    ok.

called_false_one_arg_(Mod) ->
    Args = ["hello"],
    ok = meck:expect(Mod, test, length(Args), ok),
    assert_called(Mod, test, Args, false),
    ok.

called_true_one_arg_(Mod) ->
    Args = ["hello"],
    ok = meck:expect(Mod, test, length(Args), ok),
    ok = apply(Mod, test, Args),
    assert_called(Mod, test, Args, true),
    ok.

called_false_few_args_(Mod) ->
    Args = [one, 2, {three, 3}, "four"],
    ok = meck:expect(Mod, test, length(Args), ok),
    assert_called(Mod, test, Args, false),
    ok.

called_true_few_args_(Mod) ->
    Args = [one, 2, {three, 3}, "four"],
    ok = meck:expect(Mod, test, length(Args), ok),
    ok = apply(Mod, test, Args),
    assert_called(Mod, test, Args, true),
    ok.

called_few_args_matchers_(Mod) ->
    Args = [one, 2, {three, 3}, "four"],
    ok = meck:expect(Mod, test, length(Args), ok),
    ok = apply(Mod, test, Args),
    assert_called(Mod, test, ['_', meck:is(fun(X) -> X == 2 end), {'_', 3}, "four"], true),
    assert_called(Mod, test, ['_', meck:is(fun(X) -> X == 3 end), {'_', 3}, "four"], false),
    ok.

called_false_error_(Mod) ->
    Args = [one, "two", {3, 3}],
    TestFun = fun (_, _, _) -> meck:exception(error, my_error) end,
    ok = meck:expect(Mod, test, TestFun),
    assert_called(Mod, test, Args, false),
    ok.

called_true_error_(Mod) ->
    Args = [one, "two", {3, 3}],
    expect_catch_apply(Mod, test, Args),
    assert_called(Mod, test, Args, true),
    ok.

called_with_pid_no_args_(Mod) ->
    Args = [],
    ok = meck:expect(Mod, test, length(Args), ok),
    Pid = spawn_caller_and_sync(Mod, test, Args),
    assert_called(Mod, test, Args, self(), false),
    assert_called(Mod, test, Args, Pid, true),
    ok = apply(Mod, test, Args),
    assert_called(Mod, test, Args, self(), true),
    ?assertEqual(meck:called(Mod, test, Args, '_'),
                 meck:called(Mod, test, Args)).

spawn_caller_and_sync(Mod, Func, Args) ->
    TestPid = self(),
    Fun = fun() ->
                  try apply(Mod, Func, Args) catch _:_ -> ok end,
                  TestPid ! {self(), done}
          end,
    Pid = spawn(Fun),
    receive {Pid, done} -> ok end, % sync with the spawned process
    Pid.

num_calls_(Mod) ->
    Args = [],
    IncorrectArgs = [foo],
    ok = meck:expect(Mod, test1, length(Args), ok),
    ?assertEqual(0, meck:num_calls(Mod, test1, Args)),
    ok = apply(Mod, test1, Args),
    ?assertEqual(1, meck:num_calls(Mod, test1, Args)),
    ?assertEqual(0, meck:num_calls(Mod, test1, IncorrectArgs)).

num_calls_error_(Mod) ->
    Args = [one, "two", {3, 3}],
    expect_catch_apply(Mod, test, Args),
    ?assertEqual(1, meck:num_calls(Mod, test, Args)).

num_calls_with_pid_no_args_(Mod) ->
    Args = [],
    ok = meck:expect(Mod, test, length(Args), ok),
    Pid = spawn_caller_and_sync(Mod, test, Args),
    ?assertEqual(0, meck:num_calls(Mod, test, Args, self())),
    ?assertEqual(1, meck:num_calls(Mod, test, Args, Pid)),
    ok = apply(Mod, test, Args),
    ?assertEqual(1, meck:num_calls(Mod, test, Args, self())),
    ?assertEqual(meck:num_calls(Mod, test, Args, '_'),
                 meck:num_calls(Mod, test, Args)).

expect_catch_apply(Mod, Func, Args) ->
    TestFun = fun (_, _, _) -> meck:exception(error, my_error) end,
    ok = meck:expect(Mod, Func, TestFun),
    try apply(Mod, Func, Args) catch _:_ -> ok end.

called_wildcard_(Mod) ->
    Args = [one, 2, {three, 3}, "four"],
    ok = meck:expect(Mod, test, length(Args), ok),
    ok = apply(Mod, test, Args),
    assert_called(Mod, test, [one, '_', {three, '_'}, "four"], true),
    ok.

sequence_(Mod) ->
    Sequence = [a, b, c, d, e],
    ?assertEqual(ok, meck:sequence(Mod, s, 2, Sequence)),
    ?assertEqual(Sequence,
                 [Mod:s(a, b) || _ <- lists:seq(1, length(Sequence))]),
    ?assertEqual([e, e, e, e, e],
                 [Mod:s(a, b) || _ <- lists:seq(1, 5)]),
    ?assert(meck:validate(Mod)).

sequence_fun_(Mod) ->
    Sequence = [fun(A) -> A + 1 end, fun(A) -> A + 2 end, fun(_A) -> 1 end],
    ?assertEqual(ok, meck:sequence(Mod, s, 1, Sequence)),
    ?assertEqual([2,4,1],
                 [Mod:s(N) || N <- lists:seq(1, length(Sequence))]),
    ?assertEqual([1,1,1,1],
                 [Mod:s(N) || N <- lists:seq(1, 4)]),
    ?assert(meck:validate(Mod)).

sequence_multi_(Mod) ->
    meck:new(mymod2, [non_strict]),
    Mods = [Mod, mymod2],
    Sequence = [a, b, c, d, e],
    ?assertEqual(ok, meck:sequence(Mods, s, 2, Sequence)),
    ?assertEqual(Sequence,
                 [Mod:s(a, b) || _ <- lists:seq(1, length(Sequence))]),
    ?assertEqual([e, e, e, e, e],
                 [Mod:s(a, b) || _ <- lists:seq(1, 5)]),
    ?assertEqual(Sequence,
                 [mymod2:s(a, b) || _ <- lists:seq(1, length(Sequence))]),
    ?assertEqual([e, e, e, e, e],
                 [mymod2:s(a, b) || _ <- lists:seq(1, 5)]),
    ?assert(meck:validate(Mods)).

expect_empty_clause_list_(Mod) ->
    ?assertError(empty_clause_list, meck:expect(Mod, dummy, [])).

expect_args_value_(Mod) ->
    %% When
    meck:expect(Mod, val, [1001], meck:val(a)),
    %% Then
    ?assertEqual(a, Mod:val(1001)),
    ?assertEqual(a, Mod:val(1001)).

expect_args_invalid_call_(Mod) ->
    %% When
    meck:expect(Mod, val, [1001], meck:val(a)),
    %% Then
    ?assertError(function_clause, Mod:val(1002)).

expect_arity_value_(Mod) ->
    %% When
    meck:expect(Mod, val, 1, meck:val(a)),
    %% Then
    ?assertEqual(a, Mod:val(1001)),
    ?assertEqual(a, Mod:val(1001)).

expect_args_sequence_(Mod) ->
    %% When
    meck:expect(Mod, seq, [1001], meck:seq([a, b, c])),
    %% Then
    ?assertEqual(a, Mod:seq(1001)),
    ?assertEqual(b, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)).

expect_arity_sequence_(Mod) ->
    %% When
    meck:expect(Mod, seq, 1, meck:seq([a, b, c])),
    %% Then
    ?assertEqual(a, Mod:seq(1001)),
    ?assertEqual(b, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)).

expect_complex_sequence_(Mod) ->
    %% When
    meck:expect(Mod, seq, 1, meck:seq([meck:val(a),
                                       meck:seq([b, c]),
                                       meck:seq([meck:raise(error, d),
                                                 meck:seq([e, f, g]),
                                                 h,
                                                 meck:val(i)])])),
    %% Then
    ?assertEqual(a, Mod:seq(1001)),
    ?assertEqual(b, Mod:seq(1001)),
    ?assertEqual(c, Mod:seq(1001)),
    ?assertException(error, d, Mod:seq(1001)),
    ?assertEqual(e, Mod:seq(1001)),
    ?assertEqual(f, Mod:seq(1001)),
    ?assertEqual(g, Mod:seq(1001)),
    ?assertEqual(h, Mod:seq(1001)),
    ?assertEqual(i, Mod:seq(1001)),
    ?assertEqual(i, Mod:seq(1001)),
    ?assertEqual(i, Mod:seq(1001)).

loop_(Mod) ->
    Loop = [a, b, c, d, e],
    ?assertEqual(ok, meck:loop(Mod, l, 2, Loop)),
    [?assertEqual(V, Mod:l(a, b)) || _ <- lists:seq(1, length(Loop)), V <- Loop],
    ?assert(meck:validate(Mod)).

expect_args_loop_(Mod) ->
    %% When
    meck:expect(Mod, loop, [1001], meck:loop([a, b, c])),
    %% Then
    ?assertEqual(a, Mod:loop(1001)),
    ?assertEqual(b, Mod:loop(1001)),
    ?assertEqual(c, Mod:loop(1001)),
    ?assertEqual(a, Mod:loop(1001)),
    ?assertEqual(b, Mod:loop(1001)),
    ?assertEqual(c, Mod:loop(1001)),
    ?assertEqual(a, Mod:loop(1001)).

expect_arity_loop_(Mod) ->
    %% When
    meck:expect(Mod, loop, 1, meck:loop([a, b, c])),
    %% Then
    ?assertEqual(a, Mod:loop(1001)),
    ?assertEqual(b, Mod:loop(1001)),
    ?assertEqual(c, Mod:loop(1001)),
    ?assertEqual(a, Mod:loop(1001)),
    ?assertEqual(b, Mod:loop(1001)),
    ?assertEqual(c, Mod:loop(1001)),
    ?assertEqual(a, Mod:loop(1001)).

expect_complex_loop_(Mod) ->
    %% When
    meck:expect(Mod, loop, 1, meck:loop([meck:val(a),
                                         meck:seq([b, c]),
                                         meck:seq([meck:raise(error, d),
                                                   meck:seq([e, f, g]),
                                                   h,
                                                   meck:val(i)])])),
    %% Then
    ?assertEqual(a, Mod:loop(1001)),
    ?assertEqual(b, Mod:loop(1001)),
    ?assertEqual(c, Mod:loop(1001)),
    ?assertException(error, d, Mod:loop(1001)),
    ?assertEqual(e, Mod:loop(1001)),
    ?assertEqual(f, Mod:loop(1001)),
    ?assertEqual(g, Mod:loop(1001)),
    ?assertEqual(h, Mod:loop(1001)),
    ?assertEqual(i, Mod:loop(1001)),
    %% The second round
    ?assertEqual(a, Mod:loop(1001)),
    ?assertEqual(b, Mod:loop(1001)),
    ?assertEqual(c, Mod:loop(1001)),
    ?assertException(error, d, Mod:loop(1001)),
    ?assertEqual(e, Mod:loop(1001)),
    ?assertEqual(f, Mod:loop(1001)),
    ?assertEqual(g, Mod:loop(1001)),
    ?assertEqual(h, Mod:loop(1001)),
    ?assertEqual(i, Mod:loop(1001)),
    %% The third round
    ?assertEqual(a, Mod:loop(1001)).

expect_loop_in_seq_(Mod) ->
    %% When
    meck:expect(Mod, seq, 1, meck:seq([meck:val(a),
                                       meck:loop([b,
                                                  meck:raise(throw, c),
                                                  d]),
                                       meck:val(e), % Never returned
                                       meck:raise(exit, f)])),
    %% Then
    ?assertEqual(a, Mod:seq(1001)),
    ?assertEqual(b, Mod:seq(1001)),
    ?assertException(throw, c, Mod:seq(1001)),
    ?assertEqual(d, Mod:seq(1001)),
    %% The second round
    ?assertEqual(b, Mod:seq(1001)),
    ?assertException(throw, c, Mod:seq(1001)),
    ?assertEqual(d, Mod:seq(1001)),
    %% The third round
    ?assertEqual(b, Mod:seq(1001)).

expect_args_exception_(Mod) ->
    %% Given
    meck:expect(Mod, f, [{[1001], meck:raise(error, a)},
                         {[1002], meck:raise(throw, b)},
                         {[1003], meck:raise(exit, c)},
                         {[1004], meck:val(d)}]),
    %% When/Then
    ?assertException(error, a, Mod:f(1001)),
    ?assertException(throw, b, Mod:f(1002)),
    ?assertException(exit, c, Mod:f(1003)),
    ?assertMatch(d, Mod:f(1004)).

expect_arity_exception_(Mod) ->
    %% Given
    meck:expect(Mod, f, 1, meck:raise(error, a)),
    %% When/Then
    ?assertError(a, Mod:f(1001)).

expect_arity_clause_(Mod) ->
    %% Given
    meck:expect(Mod, foo, [{2, blah}]),
    %% When/Then
    ?assertMatch(blah, Mod:foo(1, 2)),
    ?assertError(_, Mod:foo(1, 2, 3)).

loop_multi_(Mod) ->
    meck:new(mymod2, [non_strict]),
    Mods = [Mod, mymod2],
    Loop = [a, b, c, d, e],
    ?assertEqual(ok, meck:loop(Mods, l, 2, Loop)),
    [[?assertEqual(V, M:l(a, b)) || _ <- lists:seq(1, length(Loop)), V <- Loop]
     || M <- Mods],
    ?assert(meck:validate(Mods)).

expect_args_pattern_override_(Mod) ->
    %% When
    meck:expect(Mod, f, [{[1, 1],     a},
                         {[1, '_'],   b},
                         {['_', '_'], c}]),
    %% Then
    ?assertEqual(a, Mod:f(1, 1)),
    ?assertEqual(b, Mod:f(1, 2)),
    ?assertEqual(c, Mod:f(2, 2)).

expect_args_pattern_shadow_(Mod) ->
    %% When
    meck:expect(Mod, f, [{[1, 1],     a},
                         {['_', '_'], c},
                         {[1, '_'],   b}]),
    %% Then
    ?assertEqual(a, Mod:f(1, 1)),
    ?assertEqual(c, Mod:f(1, 2)),
    ?assertEqual(c, Mod:f(2, 2)).

expect_args_pattern_missing_(Mod) ->
    %% When
    meck:expect(Mod, f, [{[1, 1],   a},
                         {[1, '_'], b}]),
    %% Then
    ?assertError(function_clause, Mod:f(2, 2)),
    ?assertEqual(a, Mod:f(1, 1)),
    ?assertEqual(b, Mod:f(1, 2)).

expect_args_pattern_invalid_(Mod) ->
    %% When/Then
    ?assertError({invalid_arity, {{expected, 2},
                                  {actual, 3},
                                  {clause, {[1, 2, 3], b}}}},
                 meck:expect(Mod, f, [{[1, 2],    a},
                                      {[1, 2, 3], b}])).

expect_args_matchers_(Mod) ->
    %% When
    meck:expect(Mod, f, [{[1, meck:is(fun(X) -> X == 1 end)], a},
                         {[1, meck:is(fun(X) -> X < 3 end)],  b},
                         {['_', '_'],                         c}]),
    %% Then
    ?assertEqual(a, Mod:f(1, 1)),
    ?assertEqual(b, Mod:f(1, 2)),
    ?assertEqual(c, Mod:f(2, 2)).

expect_ret_specs_(Mod) ->
    %% When
    meck:expect(Mod, f, [{[1, 1],     meck:seq([a, b, c])},
                         {[1, '_'],   meck:loop([d, e])},
                         {['_', '_'], meck:val(f)}]),
    %% Then
    ?assertEqual(d, Mod:f(1, 2)),
    ?assertEqual(f, Mod:f(2, 2)),
    ?assertEqual(e, Mod:f(1, 2)),
    ?assertEqual(a, Mod:f(1, 1)),
    ?assertEqual(d, Mod:f(1, 2)),
    ?assertEqual(b, Mod:f(1, 1)),
    ?assertEqual(c, Mod:f(1, 1)),
    ?assertEqual(f, Mod:f(2, 2)),
    ?assertEqual(c, Mod:f(1, 1)),
    ?assertEqual(e, Mod:f(1, 2)),
    ?assertEqual(c, Mod:f(1, 1)).

%% --- Tests with own setup ----------------------------------------------------

validate_options_test() ->
    Mod = validate_options,
    try
        meck:new(Mod, passthrough),
        throw(failed)
    catch
        error:function_clause -> ok
    end.

merge_expects_module_test() ->
    Mod = merge_mod,
    meck:new(Mod, [non_strict, merge_expects]),
    %% Given
    meck:expect(Mod, f, [2001], meck:raise(error, a)),
    meck:expect(Mod, f, [2002], meck:raise(throw, b)),
    meck:expect(Mod, f, [2003], meck:raise(exit, c)),
    meck:expect(Mod, f, [2004], meck:val(d)),
    %% When/Then
    ?assertException(error, a, Mod:f(2001)),
    ?assertException(throw, b, Mod:f(2002)),
    ?assertException(exit, c, Mod:f(2003)),
    ?assertMatch(d, Mod:f(2004)),
    meck:unload(Mod).

merge_expects_ret_specs_test() ->
    Mod = merge_mod,
    meck:new(Mod, [non_strict, merge_expects]),
    %% When
    meck:expect(Mod, f, [1, 1],   meck:seq([a, b, c])),
    meck:expect(Mod, f, [1, '_'], meck:loop([d, e])),
    meck:expect(Mod, f, ['_', '_'], meck:val(f)),
    %% Then
    ?assertEqual(d, Mod:f(1, 2)),
    ?assertEqual(f, Mod:f(2, 2)),
    ?assertEqual(e, Mod:f(1, 2)),
    ?assertEqual(a, Mod:f(1, 1)),
    ?assertEqual(d, Mod:f(1, 2)),
    ?assertEqual(b, Mod:f(1, 1)),
    ?assertEqual(c, Mod:f(1, 1)),
    ?assertEqual(f, Mod:f(2, 2)),
    ?assertEqual(c, Mod:f(1, 1)),
    ?assertEqual(e, Mod:f(1, 2)),
    ?assertEqual(c, Mod:f(1, 1)),
    meck:unload(Mod).

merge_expects_passthrough_test() ->
  meck:new(meck_test_module, [passthrough, merge_expects]),
  %% When
  meck:expect(meck_test_module, c, [1, 1], meck:seq([a, b, c])),
  %% Then
  ?assertEqual({a, b}, meck_test_module:c(a, b)),
  ?assertEqual(a, meck_test_module:c(1, 1)),
  ?assertEqual(b, meck_test_module:c(1, 1)),
  ?assertEqual(c, meck_test_module:c(1, 1)),

  %% When
  meck:expect(meck_test_module, c, [1, '_'], meck:loop([d, e])),
  %% Then
  ?assertEqual({a, b}, meck_test_module:c(a, b)),
  ?assertEqual(d, meck_test_module:c(1, 2)),
  ?assertEqual(e, meck_test_module:c(1, 2)),
  ?assertEqual(d, meck_test_module:c(1, 2)),
  ?assertEqual(e, meck_test_module:c(1, 2)),
  %% And
  ?assertEqual(c, meck_test_module:c(1, 1)),

  %% When
  meck:expect(meck_test_module, c, ['_', '_'], meck:val(f)),
  %% Then
  ?assertEqual(f, meck_test_module:c(a, b)),
  %% And
  ?assertEqual(d, meck_test_module:c(1, 2)),
  ?assertEqual(e, meck_test_module:c(1, 2)),
  ?assertEqual(d, meck_test_module:c(1, 2)),
  ?assertEqual(e, meck_test_module:c(1, 2)),
  ?assertEqual(c, meck_test_module:c(1, 1)),

  meck:delete(meck_test_module, c, 2),
  ?assertEqual({1, 1}, meck_test_module:c(1, 1)),

  meck:unload(meck_test_module).

undefined_module_test() ->
    %% When/Then
    ?assertError({{undefined_module, blah}, _}, meck:new(blah, [no_link])).

undefined_function_test() ->
    %% Given
    meck:new(meck_test_module),
    %% When/Then
    meck:expect(meck_test_module, b, 0, ok),
    ?assertError({undefined_function, {meck_test_module, b, 1}},
                 meck:expect(meck_test_module, b, 1, ok)),
    meck:unload(meck_test_module).

call_original_test() ->
    false = code:purge(meck_test_module),
    ?assertEqual({module, meck_test_module}, code:load_file(meck_test_module)),
    ok = meck:new(meck_test_module, [no_passthrough_cover]),
    ?assertEqual({file, ""}, code:is_loaded(meck_test_module_meck_original)),
    ok = meck:expect(meck_test_module, a, fun() -> c end),
    ok = meck:expect(meck_test_module, b, fun() -> meck:passthrough([]) end),
    ?assertEqual(c, meck_test_module:a()),
    ?assertEqual(b, meck_test_module:b()),
    ok = meck:unload(meck_test_module).

unload_renamed_original_test() ->
    ok = meck:new(meck_test_module),
    ok = meck:unload(meck_test_module),
    ?assertEqual(false, code:is_loaded(meck_test_module_meck_original)).

unload_all_test() ->
    Mods = [test_a, test_b, test_c, test_d, test_e],
    ok = meck:new(Mods, [non_strict]),
    ?assertEqual(lists:sort(Mods), lists:sort(meck:unload())),
    [?assertEqual(false, code:is_loaded(M)) || M <- Mods].

reload_module_test() ->
    false = code:purge(meck_test_module),
    ?assertEqual({module, meck_test_module}, code:load_file(meck_test_module)),
    ok = meck:new(meck_test_module),
    ?assertEqual(ok, meck:unload(meck_test_module)),
    ?assertMatch({file, _}, code:is_loaded(meck_test_module)),
    false = code:purge(meck_test_module),
    true = code:delete(meck_test_module),
    ?assertEqual(false, code:is_loaded(meck_test_module)),
    ok = meck:new(meck_test_module),
    ?assertMatch({file, _}, code:is_loaded(meck_test_module)),
    ?assertEqual(ok, meck:unload(meck_test_module)),
    ?assertEqual(false, code:is_loaded(meck_test_module)).

original_no_file_test() ->
    {ok, Mod, Beam} = compile:forms([{attribute, 1, module, meck_not_on_disk}]),
    {module, Mod} = code:load_binary(Mod, "", Beam),
    ?assertEqual(ok, meck:new(meck_not_on_disk)),
    ok = meck:unload(meck_not_on_disk).

original_has_no_object_code_test() ->
    {ok, Mod, Beam} = compile:forms([{attribute, 1, module, meck_on_disk}]),
    ok = file:write_file("meck_on_disk.beam", Beam),
    {module, Mod} = code:load_binary(Mod, "meck_on_disk.beam", Beam),
    ?assertEqual(ok, meck:new(meck_on_disk)),
    ok = file:delete("meck_on_disk.beam"),
    ok = meck:unload(meck_on_disk).

passthrough_with_no_object_code_test() ->
    {ok, Mod, Beam} = compile:forms([{attribute, 1, module, no_abstract_code}]),
    ok = file:write_file("no_abstract_code.beam", Beam),
    {module, Mod} = code:load_binary(Mod, "no_abstract_code.beam", Beam),
    ?assertError(
        {abstract_code_not_found, Mod},
        meck:new(no_abstract_code, [passthrough, no_link])
    ),
    ok = file:delete("no_abstract_code.beam").

passthrough_nonexisting_module_test() ->
    ok = meck:new(mymod, [passthrough, non_strict]),
    ok = meck:expect(mymod, test, fun() -> ok end),
    ?assertEqual(ok, mymod:test()),
    ok = meck:unload(mymod).

passthrough_test() ->
    passthrough_test([]).

passthrough_test(Opts) ->
    ok = meck:new(meck_test_module, [passthrough|Opts]),
    ok = meck:expect(meck_test_module, a, fun() -> c end),
    ?assertEqual(c, meck_test_module:a()),
    ?assertEqual(b, meck_test_module:b()),
    ?assertEqual({1, 2}, meck_test_module:c(1, 2)),
    ok = meck:unload(meck_test_module).

passthrough_different_arg_test() ->
    ok = meck:new(meck_test_module),
    ok = meck:expect(meck_test_module, c,
                     fun(_, _) -> meck:passthrough([x, y]) end),
    ?assertEqual({x, y}, meck_test_module:c(1, 2)),
    ok = meck:unload(meck_test_module).

passthrough_bif_test() ->
    ?assertEqual(ok, meck:new(file, [unstick, passthrough])),
    ?assertEqual(ok, meck:unload(file)).

stub_all_test() ->
    ok = meck:new(meck_test_module, [{stub_all, meck:seq([a, b])}]),
    ok = meck:expect(meck_test_module, a, [], c),
    ?assertEqual(c, meck_test_module:a()),
    ?assertEqual(a, meck_test_module:b()),
    ?assertEqual(b, meck_test_module:b()),
    ?assertEqual(b, meck_test_module:b()),
    ?assertEqual(a, meck_test_module:c(1, 2)),
    ?assertEqual(b, meck_test_module:c(1, 2)),
    ?assertEqual(b, meck_test_module:c(1, 2)),
    ok = meck:unload(meck_test_module).

stub_all_default_test() ->
    ok = meck:new(meck_test_module, [stub_all]),
    ?assertEqual(ok, meck_test_module:c(1, 2)),
    ok = meck:unload(meck_test_module).

stub_all_undefined_test() ->
    ok = meck:new(meck_test_module, [{stub_all, undefined}]),
    ?assertEqual(undefined, meck_test_module:c(1, 2)),
    ok = meck:unload(meck_test_module).

stub_all_true_test() ->
    ok = meck:new(meck_test_module, [{stub_all, true}]),
    ?assertEqual(true, meck_test_module:c(1, 2)),
    ok = meck:unload(meck_test_module).

stub_all_overridden_by_passthrough_test() ->
    ok = meck:new(meck_test_module, [stub_all, passthrough]),
    ?assertEqual(a, meck_test_module:a()),
    ok = meck:unload(meck_test_module).

mock_file_existing_test() ->
    %% Given
    ExistingFile = test_file(?MODULE, ".erl"),
    {ok, ExistsInfo} = file:read_file_info(ExistingFile),
    meck:new(file, [unstick, passthrough]),
    %% When
    meck:expect(file, read_file_info, fun(Path) -> meck:passthrough([Path]) end),
    %% Then
    ?assertEqual({ok, ExistsInfo}, file:read_file_info(ExistingFile)),
    %% Cleanup
    meck:unload(file).

mock_file_missing_test() ->
    %% Given
    MissingFile = "blah.erl",
    {error, enoent} = file:read_file_info(MissingFile),
    meck:new(file, [unstick, passthrough]),
    %% When
    meck:expect(file, read_file_info, 1, {ok, no_info}),
    %% Then
    ?assertEqual({ok, no_info}, file:read_file_info(MissingFile)),
    %% Cleanup
    meck:unload(file).

cover_test() ->
    {ok, _} = cover:compile(test_file(meck_test_module, ".erl")),
    a = meck_test_module:a(),
    b = meck_test_module:b(),
    {1, 2} = meck_test_module:c(1, 2),
    {ok, {meck_test_module, {3,0}}} = cover:analyze(meck_test_module, module),
    run_mock_no_cover_file(meck_test_module),
    {ok, {meck_test_module, {3,0}}} = cover:analyze(meck_test_module, module).

cover_options_test_() ->
    {foreach, fun compile_options_setup/0, fun compile_options_teardown/1,
     [{with, [T]} || T <- [fun cover_options_/1,
                           fun cover_options_fail_/1
                          ]]}.

compile_options_setup() ->
    Module = cover_test_module,
    % Our test module won't compile without compiler options that
    % rebar won't give it, thus the rename dance.
    Src = test_file(Module, ".erl"),
    ok = file:rename(test_file(Module, ".dontcompile"), Src),
    OldPath = code:get_path(),
    code:add_path(test_dir()),
    {OldPath, Src, Module}.

compile_options_teardown({OldPath, Src, Module}) ->
    file:rename(Src, test_file(Module, ".dontcompile")),
    code:purge(Module),
    code:delete(Module),
    code:set_path(OldPath).

cover_options_({_OldPath, Src, Module}) ->
    % Test that compilation options (include paths and preprocessor
    % definitions) are used when un-mecking previously cover compiled
    % modules.
    CompilerOptions = [{i, test_include()}, {d, 'TEST', true}],
    % The option recover feature depends on having the BEAM file
    % available.
    {ok, _} = compile:file(Src, [{outdir, test_dir()}|CompilerOptions]),
    {ok, _} = cover:compile(Src, CompilerOptions),
    a      = Module:a(),
    b      = Module:b(),
    {1, 2} = Module:c(1, 2),
    % We get 2 instead of 3 as expected.  Maybe because cover doesn't
    % count include files?
    ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)),
    run_mock_no_cover_file(Module),
    % 2 instead of 3, as above
    ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)).

cover_options_fail_({_OldPath, Src, Module}) ->
    %% This may look like the test above but there is a subtle
    %% difference.  When `cover:compile_beam' is called it squashes
    %% compile options.  This test verifies that function `b/0', which
    %% relies on the `TEST' directive being set can still be called
    %% after the module is meck'ed.
    CompilerOptions = [
        debug_info,
        {i, test_include()},
        {outdir, test_dir()},
        {d, 'TEST', true}
    ],
    {ok, _} = compile:file(Src, CompilerOptions),
    ?assertEqual(
        proplists:delete(outdir, lists:sort(CompilerOptions)),
        proplists:delete(outdir, lists:sort(meck_code:compile_options(Module)))
    ),
    {ok, _} = cover:compile_beam(Module),
    ActualCompileOpts = meck_code:compile_options(Module),
    ?assertEqual({i, test_include()}, lists:keyfind(i, 1, ActualCompileOpts)),
    ?assertEqual({d, 'TEST', true}, lists:keyfind(d, 1, ActualCompileOpts)),
    a      = Module:a(),
    b      = Module:b(),
    {1, 2} = Module:c(1, 2),
    ?assertEqual({ok, {Module, {2,0}}}, cover:analyze(Module, module)),
    ok = meck:new(Module, [passthrough]),
    ok = meck:expect(Module, a, fun () -> c end),
    ?assertEqual(c, Module:a()),
    ?assertEqual(b, Module:b()),
    ?assertEqual({1, 2}, Module:c(1, 2)),
    ok = meck:unload(Module),
    %% Verify passthru calls went to cover
    ?assertEqual({ok, {Module, 4}}, cover:analyze(Module, calls, module)).

test_file(Module, Ext) ->
    filename:join(test_dir(), atom_to_list(Module) ++ Ext).

test_dir() ->
    case code:which(?MODULE) of
        Filename when is_list(Filename) ->
            filename:dirname(Filename);
        Atom when is_atom(Atom) ->
            error({test_dir_not_found, ?MODULE, Atom})
    end.

test_include() -> filename:join(test_dir(), "include").

run_mock_no_cover_file(Module) ->
    ok = meck:new(Module),
    ok = meck:expect(Module, a, fun () -> c end),
    ?assertEqual(c, Module:a()),
    ok = meck:unload(Module),
    ?assert(not filelib:is_file(atom_to_list(Module) ++ ".coverdata")).

%% @doc Verify that passthrough calls _don't_ appear in cover
%% analysis.
no_cover_passthrough_test() ->
    {ok, _} = cover:compile("test/meck_test_module.erl"),
    {ok, {meck_test_module, {0,3}}} = cover:analyze(meck_test_module, module),
    passthrough_test([no_passthrough_cover]),
    {ok, {meck_test_module, {0,3}}} = cover:analyze(meck_test_module, module).

%% @doc Verify that passthrough calls appear in cover analysis.
cover_passthrough_test() ->
    {ok, _} = cover:compile("test/meck_test_module.erl"),
    ?assertEqual({ok, {meck_test_module, {0,3}}},
                 cover:analyze(meck_test_module, module)),
    passthrough_test([]),
    ?assertEqual({ok, {meck_test_module, {2,1}}},
                 cover:analyze(meck_test_module, module)).

cover_no_meck_original_in_cover_export_test() ->
    {ok, _} = cover:compile("test/meck_test_module.erl"),

    passthrough_test([]),

    Filename =
        lists:flatten(
            io_lib:format(
                "tmp_~b_~p.coverdata",
                [rand:uniform(100_000), meck_util:original_name(meck_test_module)]
            )
        ),
    try
        ok = cover:export(Filename),
        ok = cover:import(Filename)
    after
        _ = file:delete(Filename)
    end,

    ?assertNot(
        lists:member(meck_util:original_name(meck_test_module), cover:imported_modules()),
        "the meck generated module should not be in the exported cover data"
    ).

cover_path_test() ->
    {ok, _} = cover:compile("test/meck_test_module.erl"),
    ?assertEqual({ok, {meck_test_module, {0,3}}},
                 cover:analyze(meck_test_module, module)),
    ok = meck:new(meck_test_module, [passthrough]),
    ok = meck:expect(meck_test_module, a, fun() -> c end),
    ?assertEqual(c, meck_test_module:a()),
    ?assertEqual(b, meck_test_module:b()),
    ?assertEqual({1, 2}, meck_test_module:c(1, 2)),
    {ok, CWD} = file:get_cwd(),
    try
        ok = file:set_cwd("/tmp"),
        ok = meck:unload(meck_test_module),
        ?assertEqual({ok, {meck_test_module, {2,1}}},
                     cover:analyze(meck_test_module, module))
    after
        ok = file:set_cwd(CWD)
    end.

% @doc The mocked module is unloaded if the meck process crashes.
unload_when_crashed_test() ->
    ok = meck:new(mymod, [non_strict]),
    ?assertMatch({file, _}, code:is_loaded(mymod)),
    SaltedName = mymod_meck,
    Pid = whereis(SaltedName),
    ?assertEqual(true, is_pid(Pid)),
    unlink(Pid),
    error_logger:tty(false),
    exit(Pid, expected_test_exit),
    timer:sleep(100),
    error_logger:tty(true),
    ?assertEqual(undefined, whereis(SaltedName)),
    ?assertEqual(false, code:is_loaded(mymod)).

% @doc The mocked module is unloaded if the meck process crashes.
unlink_test() ->
    ok = meck:new(mymod, [no_link, non_strict]),
    SaltedName = mymod_meck,
    {links, Links} = process_info(whereis(SaltedName), links),
    ?assert(not lists:member(self(), Links)),
    ok = meck:unload(mymod).

%% @doc A concurrent process calling into the mocked module while it's
%% being unloaded gets either the mocked response or the original
%% response, but won't crash.
atomic_unload_test() ->
    ok = meck:new(meck_test_module),
    ok = meck:expect(meck_test_module, a, fun () -> c end),

    %% Suspend the meck_proc in order to ensure all messages are in
    %% its inbox in the correct order before it would process them
    Proc = meck_util:proc_name(meck_test_module),
    sys:suspend(Proc),
    StopReq = concurrent_req(
                Proc,
                fun () -> ?assertEqual(ok, meck:unload(meck_test_module)) end),
    SpecReq = concurrent_req(
                Proc,
                fun () -> ?assertMatch(V when V =:= a orelse V =:= c,
                                       meck_test_module:a())
                end),
    sys:resume(Proc),

    ?assertEqual(normal, wait_concurrent_req(StopReq)),
    ?assertEqual(normal, wait_concurrent_req(SpecReq)).

%% @doc Exception is thrown when you run expect on a non-existing (and not yet
%% mocked) module.
expect_without_new_test() ->
    ?assertError({not_mocked, othermod},
                 meck:expect(othermod, test, fun() -> ok end)).

history_passthrough_test() ->
    ok = meck:new(meck_test_module, [passthrough]),
    ok = meck:expect(meck_test_module, a, fun() -> c end),
    c = meck_test_module:a(),
    b = meck_test_module:b(),
    ?assertEqual([{self(), {meck_test_module, a, []}, c},
                  {self(), {meck_test_module, b, []}, b}],
                 meck:history(meck_test_module)),
    ok = meck:unload(meck_test_module).

multi_test() ->
    Mods = [mod1, mod2, mod3],
    ok = meck:new(Mods, [non_strict]),
    ok = meck:expect(Mods, test, fun() -> ok end),
    ok = meck:expect(Mods, test2, 0, ok),
    [?assertEqual(ok, M:test()) || M <- Mods],
    ?assert(meck:validate(Mods)),
    ok = meck:unload(Mods).

multi_invalid_test() ->
    Mods = [mod1, mod2, mod3],
    ok = meck:new(Mods, [non_strict]),
    ok = meck:expect(Mods, test, fun(1) -> ok end),
    ?assertError(function_clause, mod2:test(2)),
    ?assert(not meck:validate(Mods)),
    ok = meck:unload(Mods).

multi_option_test() ->
    Mods = [mod1, mod2, mod3],
    ok = meck:new(Mods, [passthrough, non_strict]),
    ok = meck:expect(Mods, test, fun() -> ok end),
    [?assertEqual(ok, M:test()) || M <- Mods],
    ?assert(meck:validate(Mods)),
    ok = meck:unload(Mods).

multi_shortcut_test() ->
    Mods = [mod1, mod2, mod3],
    ok = meck:new(Mods, [non_strict]),
    ok = meck:expect(Mods, test, 0, ok),
    [?assertEqual(ok, M:test()) || M <- Mods],
    ?assert(meck:validate(Mods)),
    ok = meck:unload(Mods).

multi_delete_test() ->
    Mods = [mod1, mod2, mod3],
    ok = meck:new(Mods, [non_strict]),
    ok = meck:expect(Mods, test, 0, ok),
    ?assertEqual(ok, meck:delete(Mods, test, 0)),
    [?assertError(undef, M:test()) || M <- Mods],
    ?assert(meck:validate(Mods)),
    ok = meck:unload(Mods).

multi_expects_test() ->
    Mods = [mod1, mod2, mod3],
    ok = meck:new(Mods, [non_strict]),
    ok = meck:expect(Mods, test, 0, ok),
    ?assertEqual([{mod1, test, 0}, {mod2, test, 0}, {mod3, test, 0}],
                 lists:sort(meck:expects(Mods))),
    ok = meck:unload(Mods).

multi_reset_test() ->
    % Given
    Mods = [mod1, mod2, mod3],
    meck:new(Mods, [non_strict]),
    meck:expect(Mods, test1, 0, ok),
    meck:expect(Mods, test2, 0, ok),
    mod1:test1(),
    mod1:test2(),
    mod2:test1(),
    mod3:test2(),
    % When
    meck:reset(Mods),
    mod1:test1(),
    mod1:test1(),
    % Then
    ?assertMatch([{_, {mod1, test1, []}, ok},
                  {_, {mod1, test1, []}, ok}], meck:history(mod1)),
    ?assertMatch([], meck:history(mod2)),
    ?assertMatch([], meck:history(mod3)).

handle_cast_unmodified_state_test() ->
    S = dummy_state,
    ?assertEqual({noreply, S}, meck_proc:handle_cast(dummy_msg, S)).

code_change_unmodified_state_test() ->
    S = dummy_state,
    ?assertEqual({ok, S}, meck_proc:code_change(old_version, S, [])).

remote_meck_test_() ->
    {foreach, fun remote_setup/0, fun remote_teardown/1,
     [{with, [T]} || T <- [fun remote_meck_/1,
                           fun remote_meck_cover_/1]]}.

remote_setup() ->
    [] = os:cmd("epmd -daemon"),
    net_kernel:start([meck_eunit_test, shortnames]),
    {ok, Pid, Node} = peer:start_link(#{
        name => meck_remote_test,
        args => ["-pa", test_dir()]
    }),
    {Mod, Bin, File} = code:get_object_code(meck),
    true = rpc:call(Node, code, add_path, [filename:dirname(File)]),
    {module, Mod} = rpc:call(Node, code, load_binary, [Mod, File, Bin]),
    {module, meck_test_module} =
        rpc:call(Node, code, load_file, [meck_test_module]),
    {Pid, Node, meck_test_module}.

remote_teardown({Pid, _Node, _Mod}) ->
    ok = peer:stop(Pid),
    ok = net_kernel:stop().

remote_meck_({_Pid, Node, Mod}) ->
    ?assertEqual(ok, rpc:call(Node, meck, new, [Mod, [no_link, non_strict]])),
    ?assertEqual(ok, rpc:call(Node, meck, expect, [Mod, test, 0, true])),
    ?assertEqual(true, rpc:call(Node, Mod, test, [])).

remote_meck_cover_({_Pid, Node, Mod}) ->
    {ok, Mod} = cover:compile(test_file(Mod, ".erl")),
    {ok, _Nodes} = cover:start([Node]),
    ?assertEqual(ok, rpc:call(Node, meck, new, [Mod])).

can_mock_sticky_modules_test() ->
    code:stick_mod(meck_test_module),
    meck:new(meck_test_module, [unstick]),
    ?assertNot(code:is_sticky(meck_test_module)),
    meck:unload(meck_test_module),
    ?assert(code:is_sticky(meck_test_module)),
    code:unstick_mod(meck_test_module).

meck_reentrant_test() ->
    meck:new(string, [unstick, passthrough]),
    meck:expect(string, strip, 
        fun(String) -> meck:passthrough([string:reverse(String)]) end),
    ?assertEqual(string:strip("  ABC  "), "CBA"),
    meck:unload(string).

sticky_directory_test_() ->
    {foreach, fun sticky_setup/0, fun sticky_teardown/1,
     [{with, [T]}
      || T <- [fun can_mock_sticky_module_not_yet_loaded_/1,
               fun cannot_mock_sticky_module_without_unstick_/1]]}.

sticky_setup() ->
    % Find out where the beam file is (purge because it is cover compiled)
    Module = meck_test_module,
    false = code:purge(Module),
    {module, Module} = code:load_file(Module),
    Beam = code:which(Module),

    % Unload module so it's not loaded when running meck
    false = code:purge(Module),
    true = code:delete(Module),

    % Create new sticky dir and copy beam file
    Dir = "sticky_test",
    ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
    Dest = filename:join(Dir, filename:basename(Beam)),
    {ok, _BytesCopied} = file:copy(Beam, Dest),
    true = code:add_patha(Dir),
    ok = code:stick_dir(Dir),
    code:load_file(Module),

    {Module, {Dir, Dest}}.

sticky_teardown({Module, {Dir, Dest}}) ->
    % Clean up
    ok = code:unstick_dir(Dir),
    false = code:purge(Module),
    true = code:del_path(Dir),
    ok = file:delete(Dest),
    ok = file:del_dir(Dir).

can_mock_sticky_module_not_yet_loaded_({Mod, _}) ->
    ?assertEqual(ok, meck:new(Mod, [unstick])),
    ?assertNot(code:is_sticky(Mod)),
    ?assertEqual(ok, meck:unload(Mod)),
    ?assert(code:is_sticky(Mod)).

cannot_mock_sticky_module_without_unstick_({Mod, _}) ->
    error_logger:tty(false),
    ?assertError({module_is_sticky, Mod}, meck:new(Mod, [no_link])),
    error_logger:tty(true).

can_mock_non_sticky_module_test() ->
    ?assertNot(code:is_sticky(meck_test_module)),
    ?assertEqual(ok, meck:new(meck_test_module, [unstick])),
    ?assertNot(code:is_sticky(meck_test_module)),
    ?assertEqual(ok, meck:unload(meck_test_module)),
    ?assertNot(code:is_sticky(meck_test_module)).

cannot_expect_bif_or_autogenerated_test() ->
    ?assertEqual(ok, meck:new(unicode, [unstick, passthrough])),
    ?assertError({cannot_mock_builtin, {unicode, characters_to_binary, 2}},
                 meck:expect(unicode, characters_to_binary, 2, doh)),
    ?assertError({cannot_mock_autogenerated, {unicode, module_info, 0}},
                 meck:expect(unicode, module_info, 0, doh)),
    ?assertEqual(ok, meck:unload(unicode)).

meck_module_attributes_test() ->
    ?assertEqual(ok, meck:new(meck_test_module)),
    ?assertEqual([foobar], proplists:get_value(tag,
                                proplists:get_value(attributes,
                                    meck_test_module:module_info()))),
    ?assertEqual(ok, meck:unload(meck_test_module)).

meck_implicit_new_test()->
    meck:expect(meck_test_module, c, [{[1, 1], foo},
                                      {['_', '_'], bar}]),
    ?assertMatch(foo, meck_test_module:c(1, 1)),
    meck:unload().

wait_for_zero_calls_test() ->
    %% Given
    meck:new(test, [non_strict]),
    %% When/Then
    ?assertMatch(ok, meck:wait(0, test, foo, [1, '_'], 100)),
    %% Clean
    meck:unload().

wait_already_called_test() ->
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    %% When
    test:foo(1, 2),
    test:foo(1, 2),
    %% Then
    ?assertMatch(ok, meck:wait(2, test, foo, [1, '_'], 100)),
    %% Clean
    meck:unload().

wait_not_called_zero_timeout_test() ->
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    %% When
    test:foo(1, 2),
    test:foo(1, 2),
    %% Then
    ?assertError(timeout, meck:wait(3, test, foo, [1, '_'], 0)),
    %% Clean
    meck:unload().

wait_not_called_another_proc_test() ->
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    %% When
    test:foo(1, 2), % Called, but not by the expected proc.
    Pid = erlang:spawn(fun() ->
                              test:foo(2, 2) % Unexpected first argument
                       end),
    %% Then
    ?assertError(timeout, meck:wait(1, test, foo, [1, '_'], Pid, 100)),
    %% Clean
    meck:unload().

wait_called_another_proc_test() ->
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    %% When
    Pid = erlang:spawn(fun() ->
                              timer:sleep(50),
                              test:foo(1, 2),
                              test:foo(2, 2), % Unexpected first argument
                              test:foo(1, 2)
                       end),
    %% Then
    ?assertMatch(ok, meck:wait(2, test, foo, [1, '_'], Pid, 500)),
    %% Clean
    meck:unload().

wait_timeout_test() ->
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    %% When
    test:foo(1, 2),
    %% Then
    ?assertError(timeout, meck:wait(2, test, foo, [1, '_'], '_', 10)),
    %% Clean
    meck:unload().

wait_for_the_same_pattern_on_different_processes_test() ->
    error_logger:tty(false),
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    Pid1 = erlang:spawn(fun() ->
                               ?assertMatch(ok,
                                            meck:wait(2, test, foo,
                                                      [1, 2], 100))
                         end),
    MonitorRef1 = erlang:monitor(process, Pid1),
    Pid2 = erlang:spawn(fun() ->
                               ?assertMatch(ok,
                                            meck:wait(3, test, foo,
                                                      [1, 2], 100))
                        end),
    MonitorRef2 = erlang:monitor(process, Pid2),
    %% When
    timer:sleep(50),
    test:foo(1, 2),
    test:foo(1, 2),
    %% Then
    ?assertTerminated(MonitorRef1, normal, 300),
    ?assertTerminated(MonitorRef2, {timeout, _}, 300),
    %% Clean
    meck:unload(),
    error_logger:tty(true).

wait_for_different_patterns_on_different_processes_test() ->
    error_logger:tty(false),
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 1, ok),
    meck:expect(test, bar, 2, ok),
    Pid1 = erlang:spawn(fun() ->
                               ?assertMatch(ok,
                                            meck:wait(2, test, foo,
                                                      [1], 100))
                        end),
    MonitorRef1 = erlang:monitor(process, Pid1),
    Pid2 = erlang:spawn(fun() ->
                               ?assertMatch(ok,
                                            meck:wait(3, test, bar,
                                                      [1, 2], 100))
                        end),
    MonitorRef2 = erlang:monitor(process, Pid2),
    %% When
    timer:sleep(50),
    test:bar(1, 2),
    test:foo(1),
    test:bar(1, 2),
    test:bar(1, 2),
    %% Then
    ?assertTerminated(MonitorRef1, {timeout, _}, 300),
    ?assertTerminated(MonitorRef2, normal, 300),
    %% Clean
    meck:unload(),
    error_logger:tty(true).

wait_purge_expired_tracker_test() ->
    %% Given
    meck:new(test, [non_strict]),
    meck:expect(test, foo, 2, ok),
    ?assertError(timeout, meck:wait(1, test, foo, [1, '_'], 1)),
    %% When
    timer:sleep(50),
    % Makes expired tracker be purged. There is no way to check that from the
    % code only in the coverage report. But at least we exercise this code path
    % here.
    test:foo(1, 2),
    %% Clean
    meck:unload().

mocked_test() ->
  %% At start, no modules should be mocked:
  [] = meck:mocked(),

  %% Mocking a new module adds it to the list of mocked modules:
  meck:new(mocked_test, [non_strict]),
  [mocked_test] = meck:mocked(),

  %% Mocking with implicit `meck:new` also adds to the list of mocked modules:
  meck:expect(meck_test_module, c, 2, ok),
  [meck_test_module, mocked_test] = lists:sort(meck:mocked()),

  meck:new([foo, bar], [non_strict]),
  [bar, foo, meck_test_module, mocked_test] = lists:sort(meck:mocked()),

  ok = meck:unload([foo, meck_test_module]),
  [bar, mocked_test] = lists:sort(meck:mocked()),

  meck:unload(),
  %% After unload all, no modules should be mocked again
  [] = meck:mocked().

meck_passthrough_test_() ->
    {foreach, fun setup_passthrough/0, fun teardown/1,
     [{with, [T]} || T <- [
                           fun delete_passthrough_/1,
                           fun delete_passthrough_force_/1,
                           fun expects_passthrough_/1
                          ]]}.

setup_passthrough() ->
    % Uncomment to run tests with dbg:
    % dbg:tracer(),
    % dbg:p(all, call),
    % dbg:tpl(meck, []),
    ok = meck:new(meck_test_module, [passthrough, non_strict]),
    meck_test_module.

delete_passthrough_(Mod) ->
    ok = meck:expect(Mod, c, 2, {c, d}),
    ?assertMatch({c, d}, Mod:c(a, b)),
    ?assertEqual(ok, meck:delete(Mod, c, 2)),
    ?assertMatch({a, b}, Mod:c(a, b)),
    ?assert(meck:validate(Mod)).

delete_passthrough_force_(Mod) ->
    ok = meck:expect(Mod, c, 2, ok),
    ?assertEqual(ok, meck:delete(Mod, c, 2, true)),
    ?assertError(undef, Mod:test(a, b)),
    ?assert(meck:validate(Mod)).

expects_passthrough_(Mod) ->
    ok = meck:expect(Mod, test, 2, ok),
    ?assertEqual([{Mod, a, 0}, {Mod, b, 0}, {Mod, c, 2}, {Mod, test, 2}],
                 lists:sort(meck:expects(Mod, false))),
    ?assertEqual([{Mod, test, 2}], meck:expects(Mod, true)).


%%=============================================================================
%% Internal Functions
%%=============================================================================

assert_called(Mod, Function, Args, WasCalled) ->
    ?assertEqual(WasCalled, meck:called(Mod, Function, Args)),
    ?assert(meck:validate(Mod)).

assert_called(Mod, Function, Args, Pid, WasCalled) ->
    ?assertEqual(WasCalled, meck:called(Mod, Function, Args, Pid)),
    ?assert(meck:validate(Mod)).

%% @doc Spawn a new process to concurrently call `Fun'. `Fun' is
%% expected to send a request to the specified process, and this
%% function will wait for this message to arrive. (Therefore the
%% process should be suspended and not consuming its message queue.)
%%
%% The returned request handle can be used later in in {@link
%% wait_concurrent_req/1} to wait for the concurrent process to
%% terminate.
concurrent_req(Name, Fun) when is_atom(Name) ->
    case whereis(Name) of
        Pid when is_pid(Pid) ->
            concurrent_req(Pid, Fun);
        undefined ->
            exit(noproc)
    end;
concurrent_req(Pid, Fun) when is_pid(Pid) ->
    {message_queue_len, Msgs} = process_info(Pid, message_queue_len),
    Req = spawn_monitor(Fun),
    wait_message(Pid, Msgs + 1, 100),
    Req.

%% @doc Wait for a concurrent request started with {@link
%% concurrent_req/2} to terminate. The return value is the exit reason
%% of the process.
wait_concurrent_req(Req = {Pid, Monitor}) ->
    receive
        {'DOWN', Monitor, process, Pid, Reason} ->
            Reason
    after
        1000 ->
            exit(Pid, kill),
            wait_concurrent_req(Req)
    end.

wait_message(Pid, _ExpMsgs, Retries) when Retries < 0 ->
    exit(Pid, kill),
    exit(wait_message_timeout);
wait_message(Pid, ExpMsgs, Retries) ->
    {message_queue_len, Msgs} = process_info(Pid, message_queue_len),
    if Msgs >= ExpMsgs ->
            ok;
       true ->
            timer:sleep(1),
            wait_message(Pid, ExpMsgs, Retries - 1)
    end.
