В моем приложении у меня есть GenServer. Он создает резервные копии данных, необходимых для повторного запуска в агенте. Я хочу проверить, правильно ли мой GenServer выполняет резервное копирование и восстановление, поэтому я хотел запустить агент резервного копирования, затем перезапустить GenServer и посмотреть, работает ли он (помнит конфигурацию перед перезапуском).
Прямо сейчас я настроил и запустил GenServer (с start_supervised!
) в тестовой настройке. Мне нужно как-то перезапустить этот GenServer.
Есть ли хороший способ сделать это? Должен ли я делать это совершенно по-другому? Есть ли другой, правильный способ тестирования поведения перезапуска?
Супервизор решает, когда перезапустить процесс, находящийся под его наблюдением, через child_spec
этого дочернего процесса. По умолчанию, когда вы определяете свой модуль GenServer и используете use GenServer
на модуле, по умолчанию (значения перезапуска) будет :permanent
, что означает, что всегда перезапускайте этот процесс, если он завершается.
Учитывая это, должно быть достаточно отправить ему сигнал выхода с помощью Process.exit(your_gen_server_pid, :kill)
(:kill
гарантирует, что даже если процесс перехватывает выходы, он будет убит), а затем супервизор должен снова запустить процесс, и вы можете затем делать свои утверждения .
Вам понадобится способ обращения к «новому» процессу genserver, так как он будет убит, при перезапуске его pid
не будет таким же, как было изначально, обычно вы делаете это, указав имя при его запуске.
Если ваш генератор загружает состояние как часть своего init
, вам не обязательно контролировать его, чтобы проверить поведение резервного копирования, вы можете просто запустить его отдельно, убить его, а затем запустить снова.
Могут быть крайние случаи в зависимости от того, как вы создаете резервную копию и т. д., Но обычно этого будет достаточно.
Обновлено:
Чтобы справиться как с завершением процесса, так и с его повторным запуском, вы можете написать 2 вспомогательные функции, специально предназначенные для этого.
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
Вы могли бы вместо этого взять name
и сделать GenServer.whereis
для получения pid, но идея та же.
Чтобы убедиться, что он жив:
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
Основная идея такова. Не уверен, что уже есть помощники для подобных вещей.
Затем вы просто используете это (вы можете написать 3-го помощника, который берет живой pid, имя и делает все это за один «шаг»), или написать:
:ok = ensure_exited(pid)
true = is_back_up?(name)
@ VOID404 да, я добавлю еще несколько деталей по вашим первым вопросам. Я не использовал ExUnit экстенсивно, поэтому для этого уже могут быть некоторые помощники, но легко написать 2, чтобы делать именно такие вещи. Что касается части setup
, я не понимаю, что вы имеете в виду.
Я запускаю все, что контролируется, в обратном вызове установки ExUnit, поэтому ручное управление (как вы предложили - без присмотра) означало бы, что мне придется пропустить обратный вызов установки для одного теста.
Вы также можете просто оставить его с настройкой, которую я себе представляю - это было просто примечание относительно тестирования резервной копии, теоретически ее не нужно контролировать, но если это так, она должна работать так же.
Кроме того, обратите внимание, что проблема, о которой вы упомянули, также показывает «проблему», которую тест не покрывает, то есть, если при запуске фактической программы сервер завершает работу между «взаимодействиями» с ней, это приведет к сбою, поэтому вам нужно как-то учитывать и такую возможность.
У меня почему-то не перезапускается процесс... Я начинаю так: start_supervised!(Bot.Heart, restart: :permanent)
, а останавливаю так: Process.exit(heart, :kill)
. У меня есть функция, которая ждет смерти, и функция, которая ждет, пока не найдет PID для заданного имени, и Process.alive?(pid)
. Последний работает вечно.
Ой. Ты прав. Я делаю всю эту «актерскую штучку» неправильно? Не могли бы вы порекомендовать некоторые ресурсы для самообразования?
Особо об эликсире я ничего не читал. Документация по Elixir и Erlang и множество тем в Интернете. В качестве книги я бы предложил «Программирование на Erlang» Джо Армстронга, она написана на языке erlang, но должна дать вам хорошее представление о семантике. У Sasã Juric также есть Эликсир в действии, который явно находится в Эликсире (у меня он есть, но я еще не читал), и я слышал о нем хорошие отзывы. Вы также можете проверить темы на elixirforum, там есть несколько тем о книгах и обзорах.
Это решило бы мою проблему, за исключением того, что ни убийство, ни перезапуск не происходят мгновенно, поэтому мне нужен способ дождаться завершения перезапуска. С другой стороны, PID не проблема, потому что все начинается с имени модуля. В жизни моего приложения нет момента, когда у актера было бы 2 экземпляра. Кроме того, у меня есть актеры, начинающиеся с
setup
(перед каждым тестом) сstart_supervised
, так что есть ли способ пропустить настройку для одного теста, чтобы я мог делать все вручную?