Plushie.Test.SessionPool (Plushie v0.6.0)

Copy Markdown View Source

Session manager for concurrent test renderer sessions.

For :mock and :headless, owns a single renderer Port and multiplexes multiple logical sessions over it. For :windowed, each logical session gets its own renderer process because the real iced renderer only supports one live app session per process.

Usage

Start the pool once (typically in test_helper.exs or setup_all):

{:ok, pool} = SessionPool.start_link(mode: :mock, max_sessions: 8)

The Runtime test backend connects to the pool via a PoolAdapter:

session = Session.start(MyApp, backend: Backend.Runtime, pool: pool)

The pool assigns session IDs, sends messages to the right renderer, and forwards replies back to the caller.

Summary

Types

t()

Pool state for the test renderer session manager.

Functions

Returns a specification to start this module under a supervisor.

Register a new session. Returns a unique session ID.

Send an interact message that may produce intermediate steps.

Send a message to the renderer for the given session.

Start the pool, spawning a renderer process.

Unregister a session. Sends Reset to the renderer to free resources.

Unregister a session without waiting for the renderer reset.

Types

mode()

@type mode() :: :mock | :headless | :windowed

session_id()

@type session_id() :: String.t()

t()

@type t() :: %Plushie.Test.SessionPool{
  closing_windowed: %{
    required(session_id()) =>
      {Plushie.Test.SessionPool.Windowed.close_kind(), port()}
  },
  env: [{String.t(), String.t()}],
  format: Plushie.Test.SessionPool.Transport.format(),
  max_sessions: pos_integer(),
  mode: mode(),
  multiplexed: Plushie.Test.SessionPool.Multiplexed.t() | nil,
  next_id: pos_integer(),
  next_session: pos_integer(),
  renderer: String.t(),
  sessions: %{required(session_id()) => Plushie.Test.SessionPool.Windowed.t()}
}

Pool state for the test renderer session manager.

The top-level struct keeps only pool-wide configuration and counters. Mode- specific runtime state lives in Multiplexed or Windowed structs.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

register(pool)

@spec register(pool :: GenServer.server()) :: session_id()

Register a new session. Returns a unique session ID.

send_interact(pool, session_id, msg)

@spec send_interact(
  pool :: GenServer.server(),
  session_id(),
  msg :: map()
) :: String.t()

Send an interact message that may produce intermediate steps.

Unlike send_message/4, this does NOT block for the response. Instead, interact_step and interact_response messages are forwarded to the session owner via {:plushie_pool_event, ...}. The caller is responsible for handling them in handle_info.

Returns the request ID assigned to this interact.

send_message(pool, session_id, msg, expect_response \\ nil)

@spec send_message(
  pool :: GenServer.server(),
  session_id(),
  msg :: map(),
  expect_response :: String.t() | nil
) :: {:ok, map()} | {:error, term()} | :ok

Send a message to the renderer for the given session.

The session field is injected automatically. Synchronous -- waits for the matching response if expect_response is a response type string (e.g. "query_response"). For fire-and-forget messages (snapshot, patch, subscription), pass nil.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Start the pool, spawning a renderer process.

Options

  • :renderer -- path to the plushie binary (required)
  • :mode -- :mock (default), :headless, or :windowed
  • :format -- :msgpack (default) or :json
  • :max_sessions -- maximum concurrent sessions (default 8)
  • :name -- optional registered name for the pool

unregister(pool, session_id)

@spec unregister(pool :: GenServer.server(), session_id()) :: :ok

Unregister a session. Sends Reset to the renderer to free resources.

unregister_async(pool, session_id)

@spec unregister_async(pool :: GenServer.server(), session_id()) :: :ok

Unregister a session without waiting for the renderer reset.

The session is removed from the active map immediately. The reset is still sent to the renderer, but the caller doesn't block on the response. Used by terminate/2 to avoid blocking on slow renderers.