test/support/horde_peer.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Test.HordePeer do
  @moduledoc """
  Starts the app's Horde topology on a `:peer` test node under a persistent
  keeper process.

  A naive `:erpc.call(node, Horde.Registry, :start_link, ...)` links the Horde
  process to the transient erpc handler, which exits the moment the call
  returns and takes Horde down with it. The keeper here is a long-lived process
  that owns (is linked to) both components and sleeps forever, so they live as
  long as the peer node does — and die with it when the node is stopped, which
  is exactly the node-loss signal the handoff test needs.
  """

  @registry OpenAgents.HordeRegistry
  @supervisor OpenAgents.HordeSupervisor

  @doc "Start `OpenAgents.HordeRegistry` + `OpenAgents.HordeSupervisor` on this node. Returns the keeper pid."
  def start! do
    parent = self()

    keeper =
      spawn(fn ->
        {:ok, _reg} =
          OpenAgents.Cluster.Registry.start_link(
            name: @registry,
            keys: :unique,
            members: :auto,
            delta_crdt_options: [sync_interval: 150]
          )

        {:ok, _sup} =
          OpenAgents.Cluster.DynamicSupervisor.start_link(
            name: @supervisor,
            strategy: :one_for_one,
            members: :auto,
            process_redistribution: :passive,
            delta_crdt_options: [sync_interval: 150]
          )

        send(parent, {:horde_started, self()})
        Process.sleep(:infinity)
      end)

    receive do
      {:horde_started, ^keeper} -> {:ok, keeper}
    after
      5_000 -> {:error, :timeout}
    end
  end

  @doc """
  A Horde child spec for a singleton stub Agent keyed by `key`.

  The Agent's value function is a *named* function in this module (which is
  compiled to ebin and loadable on peers), not an anonymous function in a test
  module (which is not) — so Horde can start the singleton on whichever node it
  routes to, including a peer.
  """
  def singleton_spec(key) do
    via = {:via, Horde.Registry, {@registry, key}}

    %{
      id: key,
      start: {Agent, :start_link, [&__MODULE__.value/0, [name: via]]},
      restart: :transient
    }
  end

  @doc false
  def value, do: :handoff_singleton
end