Hey Alchemists,
I am writing a unit test against a GenServer that verifies that the state is maintained when a monitored client crashes. (This is code snippet from the Little Elixir & OTP Guidebook).
In this snippet, I setup the Pooly
GenServer by invoking Pooly.start_pool
with some configuration. Next, I create two client processes. Each process checks out a worker and waits for a message. The Server.checkout/0
function monitors all clients who ask for a worker. The test asserts that when the client crashes, the state of the server is maintained. This implies that handle_info
has been invoked. This code works as expected via iex
. However in a test, the code finishes running before the handle_info
message can be received.
I have hacked in a :timer.sleep/1
call to wait for this code to work, but I am wondering if there is a more idiomatic way to get the test to wait. Any thoughts?
Thanks
Steve
test "persists pool state when client crashes" do
pool_config = [
mfa: {SampleWorker, :start_link, []},
size: 2
]
Pooly.start_pool(pool_config)
pid_1 = spawn(&client/0)
_pid_2 = spawn(&client/0)
assert {2, 0} == Pooly.status()
Process.exit(pid_1, :kill)
assert {1, 1} == Pooly.status()
end
def client do
Pooly.checkout()
receive do
:stop ->
:ok
end
end
# Server module
def handle_call(:checkout, {from_pid, _ref}, %{workers: workers, monitors: monitors} = state) do
case workers do
[worker | rest] ->
ref = Process.monitor(from_pid)
true = :ets.insert(monitors, {worker, ref})
{:reply, worker, %{state | workers: rest }}
[] ->
{:reply, :noproc, state}
end
end
def handle_info({:DOWN, ref, _, _, _}, state = %{monitors: monitors, workers: workers}) do
case :ets.match(monitors, {:"$1", ref}) do
[[pid]] ->
true = :ets.delete(monitors, pid)
new_state = %{state | workers: [pid | workers]}
{:noreply, new_state}
[[]] ->
{:noreply, state}
end
end