I’ve got some GenServers that use async functions GenServer.cast.
Right now in my unit tests, I’m treating the async calls like synchronous calls. That works like 90% of the time, especially when the system is lightly loaded.
test "an async function" do
MyModule.async_call_to_update_genserver_state(<newstate>)
assert MyModule.get_state == <newstate>
end
Can someone suggest an ExUnit strategy for testing async calls that will work 100% of the time??
If MyModule.get_state is reading the data from the GenServer, the previous async call does not matter because the synchronous get_state will still be processed after the async call. Unless you have the GenServer doing the writes and reading the data from elsewhere. In this case, it would be nice to have more information.
Thanks for your response - I just realized that GenServer messages are processed in order, so sending a synchronous call (like MyModule.get_state) should flush all the pending async actions from the message queue. I’ll report back after I have studied this a bit more…
I believe I understand this timing issue. My code has a named supervisor with workers. The workers invoke an add_child method on the supervisor. If the supervisor is dead, the worker will fail.
Originally, I started the supervisor in a setup block: setup :start_supervisor which looked like
defp start_supervisor(_)
MySupervisor.start_link
:ok
end
The problem was:
MySupervisor.start_link would fail intermittently (approx 1 in 50 times)
The error message was {:error, {:already_started, PID}}
I suspect the ‘already started’ PID was left over from the previous test run…
Immediately after the setup, the supervisor PID would die
Without a supervisor, the test would fail
My solution was to restart the supervisor if the error condition was detected:
defp start_supervisor(_) do
case MySupervisor.start_link do
{:ok, pid} -> pid
{:error, elem} ->
IO.inspect(START_SUP_ERROR_A: elem)
start_supervisor(:restart)
end
:ok
end
Perhaps the lingering supervisor is an ExUnit problem - perhaps not. I’m new with Elixir - I’m not sure.
AndyL - thank you for your question. I am having a similar issue as you are having.
My understanding is that each test runs in its own processes, and I believe that the same process that runs the test also invokes the setup callback. When the test process dies, any processes created by the test process will also die, including the processes created in the setup callback and in the test itself. (This works much the same way as how all processes supervised by a supervisor will die if the supervisor dies).
If you run your tests concurrently, and some processes are named, then we are going to run into conflicts, as multiple processes try to register with the same name. However, if we are not running the tests concurrently, then, ideally, test 1 should finish, that process and all processes it started should die, and test 2 should start.
Like you, I’m experiencing this “lingering process” issues, and my tests are trying to register a process with a name that already exists.
All you need to do is just explicitly GenServer.stop the process that you create in the test. Process death via links is async, so the process may still be around when the next case starts. GenServer.stop will force the process to wait until your named process is down.