To sum up:
I wanted to know if this is reliable:
test "callback does NOT run" do
fun_that_should_not_run_callback(fn ->
send(self(), :cb_run)
end)
refute_received(:cb_run)
end
As noted by @hst337 , it’s not, because another process could have sent :cb_run
by the time refute_received()
run.
And also, depending of what fun_that_should_not_run_callback
does, the callback function could be transferred to another process. send(self(), {:cb_run, ref})
would then run in that other process and be received by that other process, and so this test proves nothing.
This would be a better test:
test "callback does NOT run" do
ref = make_ref()
test_process = self()
fun_that_should_not_run_callback(fn ->
send(test_process, {:cb_run, ref})
end)
refute_received({:cb_run, ^ref})
end
But it was not the point of my question.
In fact, I wanted to know if a possible message sent by send(self(), {:cb_run, ref})
would necessarily be received by refute_received()
, or if it could be delayed on the way and arrive too late.
Put another way:
fn ->
send(self(), :hello)
receive do
:hello -> :ok
after
0 -> :error
end
end
Is this function guaranteed to never return :error ?
Turns out that it works this way in the current BEAM implementation, but it is an implementation detail and there is no such guarantee.
An option is to use refute_receive/3 instead of refute_received/2:
test "callback does NOT run" do
ref = make_ref()
test_process = self()
fun_that_should_not_run_callback(fn ->
send(test_process, {:cb_run, ref})
end)
refute_receive({:cb_run, ^ref}, 100)
end
This is reliable because refute_receive will wait for {:cb_run, ^ref}
for 100
ms, the disadvantage being that the test will last at least 100
ms.
Another option is to rely on the only guarantee we have, which is that the message order is guaranteed for a given pair of processes:
test "callback does NOT run" do
ref = make_ref()
test_process = self()
fun_that_should_not_run_callback(fn ->
assert test_process == self()
send(test_process, {:cb_run, ref})
end)
ref_2 = make_ref()
send(self(), ref_2)
assert_receive(^ref_2, 100)
refute_received({:cb_run, ^ref})
end
It will be faster, because assert_receive
will not systematically wait 100
ms, but only until the ref_2
message arrives. Then, the {:cb_run, ref}
message has necessarily arrived if it has been sent.
The disadvantage of this option is that you have to make sure that the function is not executed from another process, hence the assert test_process == self()
line. Depending of what fun_that_should_not_run_callback
does, it is applicable or not.