Остановить GenServer после каждого теста

Фон

У меня есть набор тестов, для которых требуется запуск GenServer. Как правило, я понимаю, что рекомендуется выполнять очистку после каждого теста, поэтому я также хочу останавливать GenServer после каждого теста.

Проблема

Проблема здесь в том, что я не знаю, как остановить GenServer после завершения теста. Я всегда сталкиваюсь с проблемой параллелизма.

defmodule MyModuleTest do
  use ExUnit.Case

  alias MyModule

  setup do
    MyModule.Server.start_link(nil)
    context_info = 1
    more_info = 2
    %{context_info: context_info, more_info: more_info}
  end

  describe "some tests" do
    test "returns {:ok, order_id} if order was deleted correctly", context do
      # do test here that uses created server  and passed context
      assert actual == expected

      #clean up?
    end
  end
end

Теперь я попробовал on_exit/2 следующим образом:

  setup do
    {:ok, server} = MyModule.Server.start_link(nil)
    context_info = 1
    more_info = 2
    
    on_exit(fn -> GenServer.stop(server) end)

    %{context_info: context_info, more_info: more_info}   
  end

Но я получаю эту ошибку:

** (exit) exited in: GenServer.stop(#PID<0.296.0>, :normal, :infinity)
         ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started

У меня такое чувство, что это заканчивается слишком рано.

Я также пытался использовать start_supervised, однако, поскольку мой GenServer имеет длительную инициализацию в handle_continue тесты запускаются до того, как сервер будет готов.

Вопрос

Как я могу это исправить?


person Flame_Phoenix    schedule 08.01.2021    source источник


Ответы (1)


Я наконец понял, что происходит.

Оказывается, start_supervised работает, как и ожидалось, и на самом деле ждет завершения работы GenServer handle_continue (ну, он не точно ждет, он все еще отправляет сообщения, и они помещаются в очередь, ожидающая подходящего времени для выполнения).

Проблема здесь заключалась в том, что я не сделал полную очистку от всего, что я инициировал в моем handle_continue. Оказывается, некоторые соединения и процессы, которые я запустил, остались даже после того, как исходный GenServer умер.

Решение было двояким:

  • поймать все стоп-сигналы
  • как только сигнал остановки обнаружен, сделайте изящное отключение

В коде это переводится как:

def init(_) do
    Process.flag(:trap_exit, true) # trap all exits!
    {:ok, %{}, {:continue, :setup_queue}}
end

def handle_continue(:setup_queue, state) do
   # do heavy lifting
    {:noreply, state}
end

def terminate(_reason, state), do:
    # undo heavy lifting

С этим и start_supervised внутри моего блока setup все работает прекрасно.

person Flame_Phoenix    schedule 08.01.2021
comment
Интересный! Какие соединения и процессы пережили смерть GenServer? - person Segfault; 09.01.2021
comment
В моем случае я использовал библиотеку под названием :jobs, github.com/uwiger/jobs. Созданные очереди не будут удалены после того, как процесс, создавший их, умрет. Поэтому мне пришлось делать это вручную. - person Flame_Phoenix; 11.01.2021