Codebase list erlang-meck / c41cff2
Merge tag 'upstream/0.8.4+dfsg' into upstream Philipp Huebner 2 years ago
9 changed file(s) with 183 addition(s) and 40 deletion(s). Raw diff Collapse all Expand all
4040
4141 $CHANGELOG"
4242 git push && git push --tags
43 MIX_EXS mix hex.publish
43 MIX_EXS=package.exs mix hex.publish
4444
185185 ```erlang
186186 {deps, [
187187 {meck, ".*",
188 {git, "https://github.com/eproxus/meck.git", {tag, "0.8.2"}}}
188 {git, "https://github.com/eproxus/meck.git", {tag, "0.8.3"}}}
189189 ]}.
190190 ```
191191
211211 * `os`
212212 * `crypto`
213213 * `compile`
214 * `global`
215 * `timer` (possible to mock, but used by some test frameworks, like Elixir's ExUnit)
216
217 Also, a meck expectation set up for a function _f_ does not apply to the module-local invocation of _f_ within the mocked module.
218 Consider the following module:
219 ```
220 -module(test).
221 -export([a/0, b/0, c/0]).
222
223 a() ->
224 c().
225
226 b() ->
227 ?MODULE:c().
228
229 c() ->
230 original.
231 ```
232 Note how the module-local call to `c/0` in `a/0` stays unchanged even though the expectation changes the externally visible behaviour of `c/0`:
233
234 ```
235 3> meck:new(test, [passthrough]).
236 ok
237 4> meck:expect(test,c,0,changed).
238 ok
239 5> test:a().
240 original
241 6> test:b().
242 changed
243 6> test:c().
244 changed
245 ```
214246
215247 Contribute
216248 ----------
223255 Should you find yourself using Meck and have issues, comments or
224256 feedback please [create an issue here on GitHub][4].
225257
226 Contributors:
227
228 - Maxim Vladimirsky (@horkhe)
229 - Ryan Zezeski (@rzezeski)
230 - David Haglund (@daha)
231 - Magnus Henoch (@legoscia)
232 - Susan Potter (@mbbx6spp)
233 - Andreas Amsenius (@adbl)
234 - Anthony Molinaro (@djnym)
235 - Matt Campbell (@xenolinguist)
236 - Martynas Pumputis (@brb)
237 - Shunichi Shinohara (@shino)
238 - Miƫtek Bak
239 - Henry Nystrom
240 - Ward Bekker (@wardbekker)
241 - Damon Richardson
242 - Christopher Meiklejohn
243 - Joseph Wayne Norton (@norton)
244 - Erkan Yilmaz (@Erkan-Yilmaz)
245 - Joe Williams (@joewilliams)
246 - Russell Brown
247 - Michael Klishin (@michaelklishin)
248 - Magnus Klaar
249
258 Meck has been greatly improved by [many contributors]
259 (https://github.com/eproxus/meck/graphs/contributors)!
260
261 ### Donations
262
263 If you or your company use Meck and find it useful, Bitcoin donations to the address `1M7pLbBpjkwxffT7kZPKhxiPGKf4eHDqtz` are greatly appreciated!
250264
251265 [1]: https://github.com/eproxus/meck/wiki/0.8-Release-Notes
252266 "0.8 Release Notes"
253267 [2]: https://github.com/hyperthunk/hamcrest-erlang "Hamcrest for Erlang"
254268 [3]: https://github.com/basho/rebar "Rebar - A build tool for Erlang"
255269 [4]: http://github.com/eproxus/meck/issues "Meck issues"
270
1515
1616 @author Adam Lindberg <eproxus@gmail.com>
1717 @copyright 2011, Adam Lindberg & Erlang Solutions Ltd
18 @version 0.8.3
18 @version 0.8.4
1919 @title meck, a Mocking Library for Erlang
2020
2121 @doc
33 def project do
44 [
55 app: :meck,
6 version: "0.8.3",
6 version: "0.8.4",
77 description: description,
88 package: package,
99 deps: [],
00 %% -*- mode: erlang; -*-
11 {application, meck,
22 [{description, "A mocking framework for Erlang"},
3 {vsn, "0.8.3"},
3 {vsn, "0.8.4"},
44 {modules, []},
55 {registered, []},
6 {applications, [kernel, stdlib]},
6 {applications, [kernel, stdlib, tools]},
77 {env, []}]}.
181181 %% passed in. It is possible to specify this option as just `stub_all'
182182 %% then stubs will return atom `ok'. If used along with `passthrough'
183183 %% then `stub_all' is ignored. </dd>
184 %%
185 %% <dt>`merge_expects'</dt>
186 %% <dd>The expectations for the function/arity signature are merged with
187 %% existing ones instead of replacing all of them each time an
188 %% expectation is added. Expectations are added to the end of the
189 %% function clause list, meaning that pattern matching will be performed
190 %% in the order the expectations were added.</dd>
184191 %% </dl>
185192 -spec new(Mods, Options) -> ok when
186193 Mods :: Mod | [Mod],
6262 try
6363 [?attribute(Key, Val) || {Key, Val} <-
6464 proplists:get_value(attributes, Mod:module_info(), []),
65 Key =/= vsn, Key =/= deprecated]
65 Key =/= vsn, Key =/= deprecated, Key =/= optional_callbacks]
6666 catch
6767 error:undef -> []
6868 end.
143143 -spec exec(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()]) ->
144144 Result::any().
145145 exec(Pid, Mod, Func, Args) ->
146 case meck_proc:get_result_spec(Mod, Func, Args) of
146 try meck_proc:get_result_spec(Mod, Func, Args) of
147147 undefined ->
148148 meck_proc:invalidate(Mod),
149149 raise(Pid, Mod, Func, Args, error, function_clause);
159159 after
160160 erase(?CURRENT_CALL)
161161 end
162 catch
163 error:{not_mocked, Mod} ->
164 apply(Mod, Func, Args)
162165 end.
163166
164167 -spec handle_exception(CallerPid::pid(), Mod::atom(), Func::atom(),
5959 history = [] :: meck_history:history() | undefined,
6060 original :: term(),
6161 was_sticky = false :: boolean(),
62 merge_expects = false :: boolean(),
6263 reload :: {Compiler::pid(), {From::pid(), Tag::any()}} |
6364 undefined,
6465 trackers = [] :: [tracker()]}).
178179
179180 -spec invalidate(Mod::atom()) -> ok.
180181 invalidate(Mod) ->
181 gen_server(call, Mod, invalidate).
182 gen_server(cast, Mod, invalidate).
182183
183184 -spec stop(Mod::atom()) -> ok.
184185 stop(Mod) ->
197198 _ -> false
198199 end,
199200 NoPassCover = proplists:get_bool(no_passthrough_cover, Options),
201 MergeExpects = proplists:get_bool(merge_expects, Options),
200202 EnableOnLoad = proplists:get_bool(enable_on_load, Options),
201203 Original = backup_original(Mod, NoPassCover, EnableOnLoad),
202204 NoHistory = proplists:get_bool(no_history, Options),
212214 expects = Expects,
213215 original = Original,
214216 was_sticky = WasSticky,
217 merge_expects = MergeExpects,
215218 history = History}}
216219 catch
217220 exit:{error_loading_module, Mod, sticky_directory} ->
223226 {ResultSpec, NewExpects} = do_get_result_spec(S#state.expects, Func, Args),
224227 {reply, ResultSpec, S#state{expects = NewExpects}};
225228 handle_call({set_expect, Expect}, From,
226 S = #state{mod = Mod, expects = Expects}) ->
229 S = #state{mod = Mod, expects = Expects, merge_expects = MergeExpects}) ->
227230 check_if_being_reloaded(S),
228231 FuncAri = {Func, Ari} = meck_expect:func_ari(Expect),
229232 case validate_expect(Mod, Func, Ari, S#state.can_expect) of
230233 ok ->
231234 {NewExpects, CompilerPid} = store_expect(Mod, FuncAri, Expect,
232 Expects),
235 Expects, MergeExpects),
233236 {noreply, S#state{expects = NewExpects,
234237 reload = {CompilerPid, From}}};
235238 {error, Reason} ->
263266 end;
264267 handle_call(reset, _From, S) ->
265268 {reply, ok, S#state{history = []}};
266 handle_call(invalidate, _From, S) ->
267 {reply, ok, S#state{valid = false}};
268269 handle_call(validate, _From, S) ->
269270 {reply, S#state.valid, S};
270271 handle_call(stop, _From, S) ->
271272 {stop, normal, ok, S}.
272273
273274 %% @hidden
275 handle_cast(invalidate, S) ->
276 {noreply, S#state{valid = false}};
274277 handle_cast({add_history, HistoryRecord}, S = #state{history = undefined,
275278 trackers = Trackers}) ->
276279 UpdTracker = update_trackers(HistoryRecord, Trackers),
484487 end.
485488
486489 -spec store_expect(Mod::atom(), meck_expect:func_ari(),
487 meck_expect:expect(), Expects::meck_dict()) ->
490 meck_expect:expect(), Expects::meck_dict(), boolean()) ->
488491 {NewExpects::meck_dict(), CompilerPid::pid()}.
489 store_expect(Mod, FuncAri, Expect, Expects) ->
490 NewExpects = dict:store(FuncAri, Expect, Expects),
492 store_expect(Mod, FuncAri, Expect, Expects, true) ->
493 NewExpects = case dict:is_key(FuncAri, Expects) of
494 true ->
495 {FuncAri, ExistingClauses} = dict:fetch(FuncAri, Expects),
496 {FuncAri, NewClauses} = Expect,
497 dict:store(FuncAri, {FuncAri, ExistingClauses ++ NewClauses}, Expects);
498 false -> dict:store(FuncAri, Expect, Expects)
499 end,
500 compile_expects(Mod, NewExpects);
501 store_expect(Mod, FuncAri, Expect, Expects, false) ->
502 NewExpects = dict:store(FuncAri, Expect, Expects),
491503 compile_expects(Mod, NewExpects).
492504
493505 -spec do_delete_expect(Mod::atom(), meck_expect:func_ari(), Expects::meck_dict()) ->
802802
803803 %% --- Tests with own setup ----------------------------------------------------
804804
805 merge_expects_module_test() ->
806 Mod = merge_mod,
807 meck:new(Mod, [non_strict, merge_expects]),
808 %% Given
809 meck:expect(Mod, f, [2001], meck:raise(error, a)),
810 meck:expect(Mod, f, [2002], meck:raise(throw, b)),
811 meck:expect(Mod, f, [2003], meck:raise(exit, c)),
812 meck:expect(Mod, f, [2004], meck:val(d)),
813 %% When/Then
814 ?assertException(error, a, Mod:f(2001)),
815 ?assertException(throw, b, Mod:f(2002)),
816 ?assertException(exit, c, Mod:f(2003)),
817 ?assertMatch(d, Mod:f(2004)),
818 meck:unload(Mod).
819
820 merge_expects_ret_specs_test() ->
821 Mod = merge_mod,
822 meck:new(Mod, [non_strict, merge_expects]),
823 %% When
824 meck:expect(Mod, f, [1, 1], meck:seq([a, b, c])),
825 meck:expect(Mod, f, [1, '_'], meck:loop([d, e])),
826 meck:expect(Mod, f, ['_', '_'], meck:val(f)),
827 %% Then
828 ?assertEqual(d, Mod:f(1, 2)),
829 ?assertEqual(f, Mod:f(2, 2)),
830 ?assertEqual(e, Mod:f(1, 2)),
831 ?assertEqual(a, Mod:f(1, 1)),
832 ?assertEqual(d, Mod:f(1, 2)),
833 ?assertEqual(b, Mod:f(1, 1)),
834 ?assertEqual(c, Mod:f(1, 1)),
835 ?assertEqual(f, Mod:f(2, 2)),
836 ?assertEqual(c, Mod:f(1, 1)),
837 ?assertEqual(e, Mod:f(1, 2)),
838 ?assertEqual(c, Mod:f(1, 1)),
839 meck:unload(Mod).
840
805841 undefined_module_test() ->
806842 %% When/Then
807843 ?assertError({{undefined_module, blah}, _}, meck:new(blah, [no_link])).
10811117 ?assert(not lists:member(self(), Links)),
10821118 ok = meck:unload(mymod).
10831119
1120 %% @doc A concurrent process calling into the mocked module while it's
1121 %% being unloaded gets either the mocked response or the original
1122 %% response, but won't crash.
1123 atomic_unload_test() ->
1124 ok = meck:new(meck_test_module),
1125 ok = meck:expect(meck_test_module, a, fun () -> c end),
1126
1127 %% Suspend the meck_proc in order to ensure all messages are in
1128 %% its inbox in the correct order before it would process them
1129 Proc = meck_util:proc_name(meck_test_module),
1130 sys:suspend(Proc),
1131 StopReq = concurrent_req(
1132 Proc,
1133 fun () -> ?assertEqual(ok, meck:unload(meck_test_module)) end),
1134 SpecReq = concurrent_req(
1135 Proc,
1136 fun () -> ?assertMatch(V when V =:= a orelse V =:= c,
1137 meck_test_module:a())
1138 end),
1139 sys:resume(Proc),
1140
1141 ?assertEqual(normal, wait_concurrent_req(StopReq)),
1142 ?assertEqual(normal, wait_concurrent_req(SpecReq)).
1143
10841144 %% @doc Exception is thrown when you run expect on a non-existing (and not yet
10851145 %% mocked) module.
10861146 expect_without_new_test() ->
14381498 assert_called(Mod, Function, Args, Pid, WasCalled) ->
14391499 ?assertEqual(WasCalled, meck:called(Mod, Function, Args, Pid)),
14401500 ?assert(meck:validate(Mod)).
1501
1502 %% @doc Spawn a new process to concurrently call `Fun'. `Fun' is
1503 %% expected to send a request to the specified process, and this
1504 %% function will wait for this message to arrive. (Therefore the
1505 %% process should be suspended and not consuming its message queue.)
1506 %%
1507 %% The returned request handle can be used later in in {@link
1508 %% wait_concurrent_req/1} to wait for the concurrent process to
1509 %% terminate.
1510 concurrent_req(Name, Fun) when is_atom(Name) ->
1511 case whereis(Name) of
1512 Pid when is_pid(Pid) ->
1513 concurrent_req(Pid, Fun);
1514 undefined ->
1515 exit(noproc)
1516 end;
1517 concurrent_req(Pid, Fun) when is_pid(Pid) ->
1518 {message_queue_len, Msgs} = process_info(Pid, message_queue_len),
1519 Req = spawn_monitor(Fun),
1520 wait_message(Pid, Msgs + 1, 100),
1521 Req.
1522
1523 %% @doc Wait for a concurrent request started with {@link
1524 %% concurrent_req/2} to terminate. The return value is the exit reason
1525 %% of the process.
1526 wait_concurrent_req(Req = {Pid, Monitor}) ->
1527 receive
1528 {'DOWN', Monitor, process, Pid, Reason} ->
1529 Reason
1530 after
1531 1000 ->
1532 exit(Pid, kill),
1533 wait_concurrent_req(Req)
1534 end.
1535
1536 wait_message(Pid, _ExpMsgs, Retries) when Retries < 0 ->
1537 exit(Pid, kill),
1538 exit(wait_message_timeout);
1539 wait_message(Pid, ExpMsgs, Retries) ->
1540 {message_queue_len, Msgs} = process_info(Pid, message_queue_len),
1541 if Msgs >= ExpMsgs ->
1542 ok;
1543 true ->
1544 timer:sleep(1),
1545 wait_message(Pid, ExpMsgs, Retries - 1)
1546 end.