In my app I have a GenServer. It backs up data needed to start again in an Agent. I want to test if my GenServer backs up and restores correctly, so I wanted to start backup agent, then restart GenServer and see if it works (remembers config from before restart).
Right now I have GenServer configured and started (with start_supervised!
) in test setup. I need to somehow restart that GenServer.
Is there a good way to do it? Should I be doing it completely differently? Is there a different, correct way of testing restart behavior?
A Supervisor decides when to restart a process under its supervision through the child_spec
of that child process. By default, when you define your GenServer module, and use use GenServer
on the module, the default (restart values) will be :permanent
, which means, always restart this process if it exits.
Given this, it should be enough to send it an exit signal, with Process.exit(your_gen_server_pid, :kill)
(:kill
will ensure that even if the process is trapping exits it will be killed), and the supervisor should then start the process again and you can then do your assertions.
You'll need a way to address the "new" genserver process, since it will be killed, when restarted its pid
won't be the same as was originally, usually you do that by providing a name when starting it.
If your genserver loads the state as part of its init
you don't necessarily need to supervise it to test the backup behaviour, you could just start it individually, kill it, and then start it again.
There might be edge-cases depending on how you establish the backup, etc, but normally that would be enough.
UPDATE:
To address both the process exiting and being up again, you could write 2 helper functions to deal specifically with that.
def ensure_exited(pid, timeout \\ 1_000) do
true = Process.alive?(pid)
ref = Process.monitor(pid)
Process.exit(pid, :kill)
receive do
{:DOWN, ^ref, :process, ^pid, _reason} -> :ok
after
timeout -> :timeout
end
end
You could make it take instead a name
and do GenServer.whereis
to retrieve the pid, but the idea is the same.
To make sure it's alive:
def is_back_up?(name, max \\ 200, tries \\ 0) when tries <= max do
case GenServer.whereis(name) do
nil ->
Process.sleep(5)
is_back_up?(name, max, tries + 1)
pid -> true
end
end
def is_back_up?(_, _, _), do: false
The basic idea is that. Not sure if there's already some helpers to do this sort of thing.
Then you just use that (you could write a 3rd helper that takes the live pid, the name, and does it all in one "step"), or write:
:ok = ensure_exited(pid)
true = is_back_up?(name)