I have been able to test socket disconnection by using a separate process. The idea is the following.
First, in the decorated init/1
you store the PID of the monitor in the socket assigns:
def init(state) do
{:ok, {state, socket}} = super(state)
monitor_pid = monitor(socket)
{:ok, {state, assign(socket, :monitor_pid, monitor_pid)}}
end
Then, in the test you execute the connect/2
call in a separate process, so killing it does not kill the actual test (because, as I explained above, the transport PID is the current process PID).
We need to block until the socket monitor has processed the :DOWN
message and exited, to be able to test what should happen in a disconnection, so this needs some coordination:
test "we log driver disconnections" do
parent = self()
child = spawn(fn ->
{:ok, socket} = connect(Subject, @params)
send parent, {self(), socket, :connected}
Process.sleep(:infinity)
end)
receive do
{^child, socket, :connected} ->
kill_socket_and_wait_for_its_monitor = fn ->
ref = Process.monitor(socket.assigns.monitor_pid)
Process.exit(socket.transport_pid, :kill)
receive do
{:DOWN, ^ref, :process, _monitored_pid, _reason} ->
# Just block until the socket monitor has processed :DOWN and do
# nothing.
nil
end
end
assert_log :info,
kill_socket_and_wait_for_its_monitor,
"Driver #{socket.assigns.driver_id} disconnected"
end
There, assert_log/3
is a wrapper around capture_log/2
, not relevant for the technique.
I find the test a bit ugly, but will do for 1:55 AM. Will probably polish it after some sleep.