From 2d62c7db37608cc59bf03a150ca9eafea3f99dec Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 13:58:13 +0000 Subject: [PATCH 1/8] Fix eqwalizer warning --- src/meck_proc.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meck_proc.erl b/src/meck_proc.erl index 80bbf81..25c1f1a 100644 --- a/src/meck_proc.erl +++ b/src/meck_proc.erl @@ -68,7 +68,7 @@ args_matcher :: meck_args_matcher:args_matcher(), opt_caller_pid :: '_' | pid(), countdown :: non_neg_integer(), - reply_to :: {Caller::pid(), Tag::any()}, + reply_to :: gen_server:from(), expire_at :: erlang:timestamp()}). %%%============================================================================ From 526dee7bd30e18b8754cac4f8da4cbd65eeead64 Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 14:15:47 +0000 Subject: [PATCH 2/8] Allow meck:wait/{4,5,6} to be called with a condition When testing some non-deterministic code, I needed something that would wait for a function to be called with [1, 2, 3], but not necessarily all at once, and not necessarily in that order. That is: m:f([1]), m:f([2]), m:f([3]) is just as valid as m:f([1, 2]), m:f([3]), and as valid as m:f([2]), m:f([3, 1]), and so on. So: invent a way for meck to update what it's waiting for as the history is built. By accumulating state as the calls happen, we can check that the function is called with the correct arguments, whatever the order. This is called a 'condition'. Doc comment updates to follow in a later commit. --- src/meck.erl | 13 +++++- src/meck_proc.erl | 72 +++++++++++++++------------- test/meck_tests.erl | 111 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 162 insertions(+), 34 deletions(-) diff --git a/src/meck.erl b/src/meck.erl index 9a3f835..3e47465 100644 --- a/src/meck.erl +++ b/src/meck.erl @@ -599,7 +599,18 @@ wait(Times, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) when is_integer(Times) andalso Times > 0 andalso is_integer(Timeout) andalso Timeout >= 0 -> ArgsMatcher = meck_args_matcher:new(OptArgsSpec), - meck_proc:wait(Mod, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout). + Condition = { + fun + (_, T) when T =:= 0 -> {halt, ok}; + (_, T) -> {cont, T - 1} + end, + Times - 1 + }, + meck_proc:wait(Mod, Condition, OptFunc, ArgsMatcher, OptCallerPid, Timeout); +wait({CondFun, _} = Condition, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) + when is_function(CondFun, 2) -> + ArgsMatcher = meck_args_matcher:new(OptArgsSpec), + meck_proc:wait(Mod, Condition, OptFunc, ArgsMatcher, OptCallerPid, Timeout). %% @doc Erases the call history for a mocked module or a list of mocked modules. %% diff --git a/src/meck_proc.erl b/src/meck_proc.erl index 25c1f1a..74d0d79 100644 --- a/src/meck_proc.erl +++ b/src/meck_proc.erl @@ -48,6 +48,10 @@ %%% Definitions %%%============================================================================ +-type condition_state() :: term(). +-type condition_fun() :: fun((Args :: [term()], condition_state()) -> condition_state()). +-type condition() :: {condition_fun(), condition_state()}. + -type meck_dict() :: dict:dict(). -record(state, {mod :: atom(), @@ -67,7 +71,7 @@ -record(tracker, {opt_func :: '_' | atom(), args_matcher :: meck_args_matcher:args_matcher(), opt_caller_pid :: '_' | pid(), - countdown :: non_neg_integer(), + condition :: condition(), reply_to :: gen_server:from(), expire_at :: erlang:timestamp()}). @@ -151,13 +155,13 @@ get_history(Mod) -> gen_server(call, Mod, get_history). -spec wait(Mod::atom(), - Times::non_neg_integer(), + Condition::condition(), OptFunc::'_' | atom(), meck_args_matcher:args_matcher(), OptCallerPid::'_' | pid(), Timeout::non_neg_integer()) -> ok. -wait(Mod, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout) -> +wait(Mod, {_CondFun, _CondSt} = Condition, OptFunc, ArgsMatcher, OptCallerPid, Timeout) -> EffectiveTimeout = case Timeout of 0 -> infinity; @@ -165,7 +169,7 @@ wait(Mod, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout) -> Timeout end, Name = meck_util:proc_name(Mod), - try gen_server:call(Name, {wait, Times, OptFunc, ArgsMatcher, OptCallerPid, + try gen_server:call(Name, {wait, Condition, OptFunc, ArgsMatcher, OptCallerPid, Timeout}, EffectiveTimeout) of @@ -304,18 +308,35 @@ handle_call(get_history, _From, S = #state{history = undefined}) -> {reply, [], S}; handle_call(get_history, _From, S) -> {reply, lists:reverse(S#state.history), S}; -handle_call({wait, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout}, From, +handle_call({wait, {CondFun, CondState} = _Condition, OptFunc, ArgsMatcher, OptCallerPid, Timeout}, From, S = #state{history = History, trackers = Trackers}) -> - case times_called(OptFunc, ArgsMatcher, OptCallerPid, History) of - CalledSoFar when CalledSoFar >= Times -> + Filter = meck_history:new_filter(OptCallerPid, OptFunc, ArgsMatcher), + Result = lists:foldl( + fun + (HistoryRec, {cont, CondSt} = Acc) -> + case Filter(HistoryRec) of + true -> + {_Pid, {_M, _F, Args}, _Result} = HistoryRec, + CondFun(Args, CondSt); + false -> + Acc + end; + (_HistoryRec, {halt, _} = Acc) -> + Acc + end, + {cont, CondState}, + History + ), + case Result of + {halt, ok} -> {reply, ok, S}; - _CalledSoFar when Timeout =:= 0 -> + {cont, _} when Timeout =:= 0 -> {reply, {error, timeout}, S}; - CalledSoFar -> + {cont, CondState2} -> Tracker = #tracker{opt_func = OptFunc, args_matcher = ArgsMatcher, opt_caller_pid = OptCallerPid, - countdown = Times - CalledSoFar, + condition = {CondFun, CondState2}, reply_to = From, expire_at = timeout_to_timestamp(Timeout)}, {noreply, S#state{trackers = [Tracker | Trackers]}} @@ -687,28 +708,12 @@ cleanup(Mod) -> code:purge(meck_util:original_name(Mod)), Res = code:delete(meck_util:original_name(Mod)), - % `cover:export` might still export the meck generated module, + % `cover:export` might still export the meck generated module, % make sure that does not happen. _ = cover:reset(meck_util:original_name(Mod)), Res. --spec times_called(OptFunc::'_' | atom(), - meck_args_matcher:args_matcher(), - OptCallerPid::'_' | pid(), - meck_history:history()) -> - non_neg_integer(). -times_called(OptFunc, ArgsMatcher, OptCallerPid, History) -> - Filter = meck_history:new_filter(OptCallerPid, OptFunc, ArgsMatcher), - lists:foldl(fun(HistoryRec, Acc) -> - case Filter(HistoryRec) of - true -> - Acc + 1; - _Else -> - Acc - end - end, 0, History). - -spec update_trackers(meck_history:history_record(), [tracker()]) -> UpdTracker::[tracker()]. update_trackers(HistoryRecord, Trackers) -> @@ -738,7 +743,7 @@ update_tracker(Func, Args, CallerPid, #tracker{opt_func = OptFunc, args_matcher = ArgsMatcher, opt_caller_pid = OptCallerPid, - countdown = Countdown, + condition = {CondFun, CondState}, reply_to = ReplyTo, expire_at = ExpireAt} = Tracker) when (OptFunc =:= '_' orelse Func =:= OptFunc) andalso @@ -750,11 +755,14 @@ update_tracker(Func, Args, CallerPid, case is_expired(ExpireAt) of true -> expired; - false when Countdown == 1 -> - gen_server:reply(ReplyTo, ok), - expired; false -> - Tracker#tracker{countdown = Countdown - 1} + case CondFun(Args, CondState) of + {halt, ok} -> + gen_server:reply(ReplyTo, ok), + expired; + {cont, CondState2} -> + Tracker#tracker{condition = {CondFun, CondState2}} + end end end; update_tracker(_Func, _Args, _CallerPid, Tracker) -> diff --git a/test/meck_tests.erl b/test/meck_tests.erl index 0c33988..900df8e 100644 --- a/test/meck_tests.erl +++ b/test/meck_tests.erl @@ -1408,7 +1408,7 @@ can_mock_sticky_modules_test() -> meck_reentrant_test() -> meck:new(string, [unstick, passthrough]), - meck:expect(string, strip, + meck:expect(string, strip, fun(String) -> meck:passthrough([string:reverse(String)]) end), ?assertEqual(string:strip(" ABC "), "CBA"), meck:unload(string). @@ -1508,6 +1508,18 @@ wait_already_called_test() -> %% Clean meck:unload(). +wait_already_called_more_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(1, test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + wait_not_called_zero_timeout_test() -> %% Given meck:new(test, [non_strict]), @@ -1634,6 +1646,103 @@ wait_purge_expired_tracker_test() -> %% Clean meck:unload(). +%% These tests replicate the ones that exist for meck:wait(Times, ...), but they're explicit. +wait_times_called_condition_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(times_called(2), test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + +wait_times_called_condition_already_called_more_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(times_called(1), test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + +wait_times_called_condition_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(times_called(3), test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + +times_called(Times) -> + ConditionFun = fun + (_, T) when T =:= 0 -> {halt, ok}; + (_, T) when is_integer(T) -> {cont, T - 1} + end, + {ConditionFun, Times - 1}. + +%% These tests wait for a function to be called multiple times, in any order, with the expected arguments. +wait_for_all_condition_already_called_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 1), + test:foo(2, 2), + %% Then + ?assertMatch(ok, meck:wait(called_for_all([1, 2]), test, foo, ['_', '_'], 100)), + %% Clean + meck:unload(). + +wait_for_all_condition_not_called_zero_timeout_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 1), + test:foo(2, 2), + %% Then + ?assertError(timeout, meck:wait(called_for_all([1, 2, 3]), test, foo, ['_', '_'], 0)), + %% Clean + meck:unload(). + +wait_for_all_condition_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, 1), + test:foo(2, 2), % Unexpected first argument + test:foo(1, 2) + end), + %% Then + ?assertMatch(ok, meck:wait(called_for_all([1, 2]), test, foo, [1, '_'], Pid, 500)), + %% Clean + meck:unload(). + +called_for_all(Expected) -> + ConditionFun = + fun([_, X], State) -> + case State -- [X] of + [] -> + {halt, ok}; + State2 -> + {cont, State2} + end + end, + {ConditionFun, Expected}. + mocked_test() -> %% At start, no modules should be mocked: [] = meck:mocked(), From bffc6b66bd97dc4817cb0f758754e9c5207d45dc Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 14:49:52 +0000 Subject: [PATCH 3/8] Update doc comments to explain conditions --- src/meck.erl | 70 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/src/meck.erl b/src/meck.erl index 3e47465..0fc3db4 100644 --- a/src/meck.erl +++ b/src/meck.erl @@ -130,6 +130,11 @@ %% It is used in {@link expect/3} and {@link expect/4} to define a function %% clause of complex multi-clause expectations. +-type condition_state() :: term(). +-type condition_fun() :: fun((Args :: [term()], condition_state()) -> condition_state()). +-type condition() :: {condition_fun(), condition_state()}. +%% It is used in {@link wait/5} and {@link wait/6} to define a condition. + %%%============================================================================ %%% Interface exports %%%============================================================================ @@ -545,7 +550,7 @@ num_calls(Mod, OptFun, OptArgsSpec, OptPid) -> %% arguments matching `OptArgsSpec', or `Timeout' has elapsed. In the latter %% case the call fails with `error:timeout'. %% -%% The number of calls is counted starting from the most resent call to +%% The number of calls is counted starting from the most recent call to %% {@link reset/1} on the mock or from the mock creation, whichever occurred %% latter. If a matching call has already occurred, then the function returns %% `ok' immediately. @@ -559,35 +564,66 @@ num_calls(Mod, OptFun, OptArgsSpec, OptPid) -> wait(Mod, OptFunc, OptArgsSpec, Timeout) -> wait(1, Mod, OptFunc, OptArgsSpec, '_', Timeout). -%% @doc Blocks until either function `Mod:Func' is called at least `Times' with -%% arguments matching `OptArgsSpec', or `Timeout' has elapsed. In the latter -%% case the call fails with `error:timeout'. +%% @doc Blocks until function `Mod:Func' is called with arguments matching +%% `OptArgsSpec' and matching the `Condition`. +%% +%% Times out if instead `Timeout' has elapsed. In this case the call fails with +%% `error:timeout'. +%% +%% `Condition` is a tuple: `{CondFunc, CondState}`. +%% +%% On each call to `Mod:Func`, `CondFunc(Args, CondState)` is called. +%% +%% The condition function must return either `{halt, ok}` or +%% `{cont, NextCondState}`. +%% +%% In the former case, the condition is considered satisfied, and `wait` +%% returns. In the latter case, `wait` waits for further calls to `Mod:Func`. +%% +%% For backwards-compatibility, the condition can be specified as an integer. +%% This counts the number of calls to the function. %% -%% The number of calls is counted starting from the most resent call to +%% The number of calls is counted starting from the most recent call to %% {@link reset/1} on the mock or from the mock creation, whichever occurred %% latter. If `Times' number of matching calls has already occurred, then the %% function returns `ok' immediately. %% -%% @equiv wait(Times, Mod, OptFunc, OptArgsSpec, '_', Timeout) --spec wait(Times, Mod, OptFunc, OptArgsSpec, Timeout) -> ok when - Times :: pos_integer(), +%% @equiv wait(Condition, Mod, OptFunc, OptArgsSpec, '_', Timeout) +-spec wait(Condition, Mod, OptFunc, OptArgsSpec, Timeout) -> ok when + Condition :: condition(), Mod :: atom(), OptFunc :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), Timeout :: non_neg_integer(). -wait(Times, Mod, OptFunc, OptArgsSpec, Timeout) -> - wait(Times, Mod, OptFunc, OptArgsSpec, '_', Timeout). +wait(Condition, Mod, OptFunc, OptArgsSpec, Timeout) -> + wait(Condition, Mod, OptFunc, OptArgsSpec, '_', Timeout). -%% @doc Blocks until either function `Mod:Func' is called at least `Times' with -%% arguments matching `OptArgsSpec' by process `OptCallerPid', or `Timeout' has -%% elapsed. In the latter case the call fails with `error:timeout'. + +%% @doc Blocks until function `Mod:Func' is called with arguments matching +%% `OptArgsSpec' and matching the `Condition`. +%% +%% Times out if instead `Timeout' has elapsed. In this case the call fails with +%% `error:timeout'. +%% +%% `Condition` is a tuple: `{CondFunc, CondState}`. +%% +%% On each call to `Mod:Func`, `CondFunc(Args, CondState)` is called. %% -%% The number of calls is counted starting from the most resent call to +%% The condition function must return either `{halt, ok}` or +%% `{cont, NextCondState}`. +%% +%% In the former case, the condition is considered satisfied, and `wait` +%% returns. In the latter case, `wait` waits for further calls to `Mod:Func`. +%% +%% For backwards-compatibility, the condition can be specified as an integer. +%% This counts the number of calls to the function. +%% +%% The number of calls is counted starting from the most recent call to %% {@link reset/1} on the mock or from the mock creation, whichever occurred -%% latter. If `Times' number of matching call has already occurred, then the +%% latter. If `Times' number of matching calls has already occurred, then the %% function returns `ok' immediately. --spec wait(Times, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) -> ok when - Times :: pos_integer(), +-spec wait(Condition, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) -> ok when + Condition :: pos_integer() | condition(), Mod :: atom(), OptFunc :: '_' | atom(), OptArgsSpec :: '_' | args_spec(), From 2a2c7f026aba5dae3312ae63b8709746b1a133d5 Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 14:55:02 +0000 Subject: [PATCH 4/8] Add tests for meck:wait(Times, ...) with meck:reset --- test/meck_tests.erl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/meck_tests.erl b/test/meck_tests.erl index 900df8e..227d586 100644 --- a/test/meck_tests.erl +++ b/test/meck_tests.erl @@ -1562,6 +1562,34 @@ wait_called_another_proc_test() -> %% Clean meck:unload(). +wait_already_called_reset_more_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 2), + test:foo(1, 2), + meck:reset(test), + test:foo(1, 2), + test:foo(1, 2), + %% Then + ?assertMatch(ok, meck:wait(1, test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + +wait_already_called_reset_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 2), + test:foo(1, 2), + meck:reset(test), + %% Then + ?assertError(timeout, meck:wait(2, test, foo, [1, '_'], 0)), + %% Clean + meck:unload(). + wait_timeout_test() -> %% Given meck:new(test, [non_strict]), From 4d748233c1eb9a29ed8a2e5b4f76a4833e821e44 Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 14:55:10 +0000 Subject: [PATCH 5/8] Add tests for meck:wait(Cond, ...) with meck:reset --- test/meck_tests.erl | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/meck_tests.erl b/test/meck_tests.erl index 227d586..ba04029 100644 --- a/test/meck_tests.erl +++ b/test/meck_tests.erl @@ -1771,6 +1771,47 @@ called_for_all(Expected) -> end, {ConditionFun, Expected}. +wait_for_all_condition_already_called_reset_more_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 1), + test:foo(1, 2), + meck:reset(test), + test:foo(1, 1), + test:foo(1, 2), + %% Then + ?assertMatch(ok, meck:wait(called_for_all([1, 2]), test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + +wait_for_all_condition_already_called_reset_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 1), + test:foo(1, 2), + meck:reset(test), + %% Then + ?assertError(timeout, meck:wait(called_for_all([1, 2]), test, foo, [1, '_'], 0)), + %% Clean + meck:unload(). + +wait_for_all_condition_already_called_reset_2_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 1), + meck:reset(test), + test:foo(1, 2), + %% Then + ?assertError(timeout, meck:wait(called_for_all([1, 2]), test, foo, [1, '_'], 0)), + %% Clean + meck:unload(). + mocked_test() -> %% At start, no modules should be mocked: [] = meck:mocked(), From 930a159a0efc882fdf17f9de6bb35ee038f99906 Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 15:00:51 +0000 Subject: [PATCH 6/8] Add tests for meck:wait(Times, ...) with meck:reset --- test/meck_tests.erl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/meck_tests.erl b/test/meck_tests.erl index ba04029..b1b95e5 100644 --- a/test/meck_tests.erl +++ b/test/meck_tests.erl @@ -1590,6 +1590,42 @@ wait_already_called_reset_test() -> %% Clean meck:unload(). +wait_reset_on_different_processes_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + Fun = fun() -> + test:foo(1, 2) + end, + {_Pid1, MRef1} = erlang:spawn_monitor(Fun), + ?assertTerminated(MRef1, normal, 300), + meck:reset(test), + {_Pid2, MRef2} = erlang:spawn_monitor(Fun), + %% Then + ?assertMatch(ok, meck:wait(1, test, foo, [1, '_'], '_', 10)), + ?assertTerminated(MRef2, normal, 300), + %% Clean + meck:unload(). + +wait_reset_on_different_processes_2_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + Fun = fun() -> + test:foo(1, 2) + end, + {_Pid1, MRef1} = erlang:spawn_monitor(Fun), + ?assertTerminated(MRef1, normal, 300), + meck:reset(test), + {_Pid2, MRef2} = erlang:spawn_monitor(Fun), + %% Then + ?assertError(timeout, meck:wait(2, test, foo, [1, '_'], '_', 10)), + ?assertTerminated(MRef2, normal, 300), + %% Clean + meck:unload(). + wait_timeout_test() -> %% Given meck:new(test, [non_strict]), From 6487e910f18ef8eb4edf44c48acf46946eee845c Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Mon, 29 Dec 2025 15:19:07 +0000 Subject: [PATCH 7/8] Add tests for meck:wait(Cond, ...) with meck:reset --- test/meck_tests.erl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/meck_tests.erl b/test/meck_tests.erl index b1b95e5..942c74f 100644 --- a/test/meck_tests.erl +++ b/test/meck_tests.erl @@ -1848,6 +1848,24 @@ wait_for_all_condition_already_called_reset_2_test() -> %% Clean meck:unload(). +wait_for_all_condition_already_called_reset_2_another_process_test() -> + %% Given + meck:new(test, [non_strict]), + meck:expect(test, foo, 2, ok), + %% When + test:foo(1, 1), + meck:reset(test), + Fun = fun() -> + timer:sleep(50), + test:foo(1, 2) + end, + {_Pid1, MRef1} = erlang:spawn_monitor(Fun), + ?assertTerminated(MRef1, normal, 300), + %% Then + ?assertError(timeout, meck:wait(called_for_all([1, 2]), test, foo, [1, '_'], 100)), + %% Clean + meck:unload(). + mocked_test() -> %% At start, no modules should be mocked: [] = meck:mocked(), From 2c517feff80540cc857790c191ba12bd38f4a11b Mon Sep 17 00:00:00 2001 From: Roger Lipscombe Date: Thu, 22 Jan 2026 08:51:46 +0000 Subject: [PATCH 8/8] Fix typespec --- src/meck.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meck.erl b/src/meck.erl index 0fc3db4..1ec8ad1 100644 --- a/src/meck.erl +++ b/src/meck.erl @@ -590,7 +590,7 @@ wait(Mod, OptFunc, OptArgsSpec, Timeout) -> %% %% @equiv wait(Condition, Mod, OptFunc, OptArgsSpec, '_', Timeout) -spec wait(Condition, Mod, OptFunc, OptArgsSpec, Timeout) -> ok when - Condition :: condition(), + Condition :: pos_integer() | condition(), Mod :: atom(), OptFunc :: '_' | atom(), OptArgsSpec :: '_' | args_spec(),