Merge tag 'upstream/0.8.4+dfsg' into upstream
Philipp Huebner
2 years ago
40 | 40 | |
41 | 41 | $CHANGELOG" |
42 | 42 | git push && git push --tags |
43 | MIX_EXS mix hex.publish | |
43 | MIX_EXS=package.exs mix hex.publish | |
44 | 44 |
185 | 185 | ```erlang |
186 | 186 | {deps, [ |
187 | 187 | {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"}}} | |
189 | 189 | ]}. |
190 | 190 | ``` |
191 | 191 | |
211 | 211 | * `os` |
212 | 212 | * `crypto` |
213 | 213 | * `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 | ``` | |
214 | 246 | |
215 | 247 | Contribute |
216 | 248 | ---------- |
223 | 255 | Should you find yourself using Meck and have issues, comments or |
224 | 256 | feedback please [create an issue here on GitHub][4]. |
225 | 257 | |
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! | |
250 | 264 | |
251 | 265 | [1]: https://github.com/eproxus/meck/wiki/0.8-Release-Notes |
252 | 266 | "0.8 Release Notes" |
253 | 267 | [2]: https://github.com/hyperthunk/hamcrest-erlang "Hamcrest for Erlang" |
254 | 268 | [3]: https://github.com/basho/rebar "Rebar - A build tool for Erlang" |
255 | 269 | [4]: http://github.com/eproxus/meck/issues "Meck issues" |
270 |
15 | 15 | |
16 | 16 | @author Adam Lindberg <eproxus@gmail.com> |
17 | 17 | @copyright 2011, Adam Lindberg & Erlang Solutions Ltd |
18 | @version 0.8.3 | |
18 | @version 0.8.4 | |
19 | 19 | @title meck, a Mocking Library for Erlang |
20 | 20 | |
21 | 21 | @doc |
3 | 3 | def project do |
4 | 4 | [ |
5 | 5 | app: :meck, |
6 | version: "0.8.3", | |
6 | version: "0.8.4", | |
7 | 7 | description: description, |
8 | 8 | package: package, |
9 | 9 | deps: [], |
0 | 0 | %% -*- mode: erlang; -*- |
1 | 1 | {application, meck, |
2 | 2 | [{description, "A mocking framework for Erlang"}, |
3 | {vsn, "0.8.3"}, | |
3 | {vsn, "0.8.4"}, | |
4 | 4 | {modules, []}, |
5 | 5 | {registered, []}, |
6 | {applications, [kernel, stdlib]}, | |
6 | {applications, [kernel, stdlib, tools]}, | |
7 | 7 | {env, []}]}. |
181 | 181 | %% passed in. It is possible to specify this option as just `stub_all' |
182 | 182 | %% then stubs will return atom `ok'. If used along with `passthrough' |
183 | 183 | %% 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> | |
184 | 191 | %% </dl> |
185 | 192 | -spec new(Mods, Options) -> ok when |
186 | 193 | Mods :: Mod | [Mod], |
62 | 62 | try |
63 | 63 | [?attribute(Key, Val) || {Key, Val} <- |
64 | 64 | proplists:get_value(attributes, Mod:module_info(), []), |
65 | Key =/= vsn, Key =/= deprecated] | |
65 | Key =/= vsn, Key =/= deprecated, Key =/= optional_callbacks] | |
66 | 66 | catch |
67 | 67 | error:undef -> [] |
68 | 68 | end. |
143 | 143 | -spec exec(CallerPid::pid(), Mod::atom(), Func::atom(), Args::[any()]) -> |
144 | 144 | Result::any(). |
145 | 145 | 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 | |
147 | 147 | undefined -> |
148 | 148 | meck_proc:invalidate(Mod), |
149 | 149 | raise(Pid, Mod, Func, Args, error, function_clause); |
159 | 159 | after |
160 | 160 | erase(?CURRENT_CALL) |
161 | 161 | end |
162 | catch | |
163 | error:{not_mocked, Mod} -> | |
164 | apply(Mod, Func, Args) | |
162 | 165 | end. |
163 | 166 | |
164 | 167 | -spec handle_exception(CallerPid::pid(), Mod::atom(), Func::atom(), |
59 | 59 | history = [] :: meck_history:history() | undefined, |
60 | 60 | original :: term(), |
61 | 61 | was_sticky = false :: boolean(), |
62 | merge_expects = false :: boolean(), | |
62 | 63 | reload :: {Compiler::pid(), {From::pid(), Tag::any()}} | |
63 | 64 | undefined, |
64 | 65 | trackers = [] :: [tracker()]}). |
178 | 179 | |
179 | 180 | -spec invalidate(Mod::atom()) -> ok. |
180 | 181 | invalidate(Mod) -> |
181 | gen_server(call, Mod, invalidate). | |
182 | gen_server(cast, Mod, invalidate). | |
182 | 183 | |
183 | 184 | -spec stop(Mod::atom()) -> ok. |
184 | 185 | stop(Mod) -> |
197 | 198 | _ -> false |
198 | 199 | end, |
199 | 200 | NoPassCover = proplists:get_bool(no_passthrough_cover, Options), |
201 | MergeExpects = proplists:get_bool(merge_expects, Options), | |
200 | 202 | EnableOnLoad = proplists:get_bool(enable_on_load, Options), |
201 | 203 | Original = backup_original(Mod, NoPassCover, EnableOnLoad), |
202 | 204 | NoHistory = proplists:get_bool(no_history, Options), |
212 | 214 | expects = Expects, |
213 | 215 | original = Original, |
214 | 216 | was_sticky = WasSticky, |
217 | merge_expects = MergeExpects, | |
215 | 218 | history = History}} |
216 | 219 | catch |
217 | 220 | exit:{error_loading_module, Mod, sticky_directory} -> |
223 | 226 | {ResultSpec, NewExpects} = do_get_result_spec(S#state.expects, Func, Args), |
224 | 227 | {reply, ResultSpec, S#state{expects = NewExpects}}; |
225 | 228 | handle_call({set_expect, Expect}, From, |
226 | S = #state{mod = Mod, expects = Expects}) -> | |
229 | S = #state{mod = Mod, expects = Expects, merge_expects = MergeExpects}) -> | |
227 | 230 | check_if_being_reloaded(S), |
228 | 231 | FuncAri = {Func, Ari} = meck_expect:func_ari(Expect), |
229 | 232 | case validate_expect(Mod, Func, Ari, S#state.can_expect) of |
230 | 233 | ok -> |
231 | 234 | {NewExpects, CompilerPid} = store_expect(Mod, FuncAri, Expect, |
232 | Expects), | |
235 | Expects, MergeExpects), | |
233 | 236 | {noreply, S#state{expects = NewExpects, |
234 | 237 | reload = {CompilerPid, From}}}; |
235 | 238 | {error, Reason} -> |
263 | 266 | end; |
264 | 267 | handle_call(reset, _From, S) -> |
265 | 268 | {reply, ok, S#state{history = []}}; |
266 | handle_call(invalidate, _From, S) -> | |
267 | {reply, ok, S#state{valid = false}}; | |
268 | 269 | handle_call(validate, _From, S) -> |
269 | 270 | {reply, S#state.valid, S}; |
270 | 271 | handle_call(stop, _From, S) -> |
271 | 272 | {stop, normal, ok, S}. |
272 | 273 | |
273 | 274 | %% @hidden |
275 | handle_cast(invalidate, S) -> | |
276 | {noreply, S#state{valid = false}}; | |
274 | 277 | handle_cast({add_history, HistoryRecord}, S = #state{history = undefined, |
275 | 278 | trackers = Trackers}) -> |
276 | 279 | UpdTracker = update_trackers(HistoryRecord, Trackers), |
484 | 487 | end. |
485 | 488 | |
486 | 489 | -spec store_expect(Mod::atom(), meck_expect:func_ari(), |
487 | meck_expect:expect(), Expects::meck_dict()) -> | |
490 | meck_expect:expect(), Expects::meck_dict(), boolean()) -> | |
488 | 491 | {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), | |
491 | 503 | compile_expects(Mod, NewExpects). |
492 | 504 | |
493 | 505 | -spec do_delete_expect(Mod::atom(), meck_expect:func_ari(), Expects::meck_dict()) -> |
802 | 802 | |
803 | 803 | %% --- Tests with own setup ---------------------------------------------------- |
804 | 804 | |
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 | ||
805 | 841 | undefined_module_test() -> |
806 | 842 | %% When/Then |
807 | 843 | ?assertError({{undefined_module, blah}, _}, meck:new(blah, [no_link])). |
1081 | 1117 | ?assert(not lists:member(self(), Links)), |
1082 | 1118 | ok = meck:unload(mymod). |
1083 | 1119 | |
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 | ||
1084 | 1144 | %% @doc Exception is thrown when you run expect on a non-existing (and not yet |
1085 | 1145 | %% mocked) module. |
1086 | 1146 | expect_without_new_test() -> |
1438 | 1498 | assert_called(Mod, Function, Args, Pid, WasCalled) -> |
1439 | 1499 | ?assertEqual(WasCalled, meck:called(Mod, Function, Args, Pid)), |
1440 | 1500 | ?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. |