-
-
Notifications
You must be signed in to change notification settings - Fork 229
Add meck:wait_for/6 #253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add meck:wait_for/6 #253
Conversation
-record(tracker, {opt_func :: '_' | atom(), | ||
args_matcher :: meck_args_matcher:args_matcher(), | ||
opt_caller_pid :: '_' | pid(), | ||
countdown :: non_neg_integer(), | ||
awaiting :: {cond_fun(), cond_state()}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering whether 'awaiting' here should be 'condition'.
end, | ||
Cond = fun | ||
(_, T) when T =:= 0 -> | ||
{halt, ok}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
halt
and cont
are taken from Elixir's Enum.reduce_while
. The ability to return {halt, Reply}
is maybe overkill, but it means that you can return something other than ok
from meck:wait_for
.
wait_for(Mod, {Cond, Times - 1}, OptFunc, ArgsMatcher, OptCallerPid, Timeout). | ||
|
||
wait_for(Mod, {Cond, CondState}, OptFunc, ArgsMatcher, OptCallerPid, Timeout) -> | ||
EffectiveTimeout = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed that this got reformatted, making the diff bigger. I can revert just this bit, if that's wanted.
case Filter(HistoryRec) of | ||
true -> | ||
{_Pid, {_M, _F, Args}, _Result} = HistoryRec, | ||
Cond(Args, CondState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure whether just 'Args' was enough here, or whether also passing 'Result' would be useful. I couldn't pass the whole HistoryRec, because we don't see the whole thing when updating the tracker (unless I'm misreading that code).
end | ||
end; | ||
update_tracker(_Func, _Args, _CallerPid, Tracker) -> | ||
Tracker. | ||
|
||
-spec timeout_to_timestamp(Timeout::non_neg_integer()) -> erlang:timestamp(). | ||
-spec timeout_to_timestamp(Timeout :: non_neg_integer()) -> erlang:timestamp(). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, more reformatting noise. Can revert.
%% When | ||
Pid = erlang:spawn(fun() -> | ||
test:foo(1, 1), | ||
test:foo(1, 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It occurs that these tests don't actually address the "any order / grouping" motivation. I can add more tests if the general approach looks sensible.
In the back of my head, I also wonder whether this can be generalised with some combinators, so you could write something like this:
...where we expect a bunch of calls to eventually match all of the conditions. |
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'. I also succeeded in generalising 'times_called' to demonstrate its usefulness.
I like that last idea, making it more composable. We can probably make the API even smoother: meck:wait(my_mocked_module, meck:condition({calls, unordered}, [
meck:condition(args, MatchSpecA),
meck:condition(args, MatchSpecB)
])). (Not sure what the purpose of Then you could have different conditions, like |
It was just off the top of my head. The intent was to say: wait for this to be called with its second arg matching
Yeah; something like that. At this point, though, adding combinators and helpers is a bonus, and isn't required for this PR. We can add those later fairly easily, as long as we're happy with the shape of this now. In fact, I think we should hold off on adding combinators until we've got a better idea of which ones people want. |
Adding new APIs is tricky and should be done with care. I would rather add a generic API right now that supports very few combinations, rather than the specific one in this PR because then it has to be supported for a long time. |
Agreed. That's why I'm suggesting not adding any new APIs for combinators at this point. We can add To be clear:
My suggestion about combinators -- #253 (comment) was me thinking out loud; ignore it. We can come back to it later. |
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'.
I also succeeded in generalising 'times_called' to demonstrate its
usefulness.