Supertester.SupervisorHelpers (Supertester v0.6.0)

Copy Markdown View Source

Specialized testing utilities for supervision trees.

This module provides helpers for testing supervisor behavior, restart strategies, and supervision tree structures.

Key Features

  • Test restart strategies (one_for_one, one_for_all, rest_for_one)
  • Trace supervision events
  • Verify supervision tree structure
  • Wait for supervisor stabilization

Usage

import Supertester.SupervisorHelpers

test "supervisor restart strategy" do
  {:ok, supervisor} = setup_isolated_supervisor(MySupervisor)

  result = test_restart_strategy(supervisor, :one_for_one, {:kill_child, :worker_1})

  assert result.restarted == [:worker_1]
  assert result.not_restarted == [:worker_2, :worker_3]
end

Summary

Functions

Asserts supervision tree matches expected structure.

Gets the count of active children in a supervisor.

Tests supervisor restart strategies with various failure scenarios.

Traces all supervision events for verification.

Waits until supervisor has all children running and stable.

Types

child_structure()

@type child_structure() ::
  {child_id :: term(), module :: module()}
  | {child_id :: term(), tree_structure()}

restart_scenario()

@type restart_scenario() ::
  {:kill_child, child_id :: term()}
  | {:kill_children, [child_id :: term()]}
  | {:cascade_failure, start_child_id :: term()}

supervision_event()

@type supervision_event() ::
  {:child_started, child_id :: term(), pid()}
  | {:child_terminated, child_id :: term(), pid(), reason :: term()}
  | {:child_restarted, child_id :: term(), old_pid :: pid(), new_pid :: pid()}

test_result()

@type test_result() :: %{
  restarted: [term()],
  not_restarted: [term()],
  supervisor_alive: boolean()
}

tree_structure()

@type tree_structure() :: %{
  supervisor: module(),
  strategy: atom(),
  children: [child_structure()]
}

Functions

assert_supervision_tree_structure(supervisor, expected)

@spec assert_supervision_tree_structure(Supervisor.supervisor(), tree_structure()) ::
  :ok

Asserts supervision tree matches expected structure.

Parameters

  • supervisor - The supervisor PID
  • expected - Expected tree structure

Examples

test "supervision tree structure" do
  {:ok, root} = setup_isolated_supervisor(RootSupervisor)

  assert_supervision_tree_structure(root, %{
    supervisor: RootSupervisor,
    strategy: :one_for_one,
    children: [
      {:cache, CacheServer},
      {:workers, %{
        supervisor: WorkerSupervisor,
        strategy: :one_for_all,
        children: [
          {:worker_1, Worker},
          {:worker_2, Worker}
        ]
      }}
    ]
  })
end

get_active_child_count(supervisor)

@spec get_active_child_count(Supervisor.supervisor()) :: non_neg_integer()

Gets the count of active children in a supervisor.

Parameters

  • supervisor - The supervisor PID

Returns

Number of active children

Examples

count = get_active_child_count(supervisor)
assert count == 3

test_restart_strategy(supervisor, expected_strategy, scenario)

@spec test_restart_strategy(Supervisor.supervisor(), atom(), restart_scenario()) ::
  test_result()

Tests supervisor restart strategies with various failure scenarios.

Parameters

  • supervisor - The supervisor PID or name
  • strategy - Expected strategy (:one_for_one, :one_for_all, :rest_for_one)
  • scenario - The failure scenario to test

Examples

test "one_for_one restarts only failed child" do
  {:ok, supervisor} = setup_isolated_supervisor(MySupervisor)

  result = test_restart_strategy(supervisor, :one_for_one, {:kill_child, :worker_1})

  assert result.restarted == [:worker_1]
  assert result.not_restarted == [:worker_2, :worker_3]
end

trace_supervision_events(supervisor, opts \\ [])

@spec trace_supervision_events(
  Supervisor.supervisor(),
  keyword()
) :: {:ok, (-> [supervision_event()])}

Traces all supervision events for verification.

Parameters

  • supervisor - The supervisor PID to trace
  • opts - Options (currently unused)

Returns

{:ok, stop_fn} where stop_fn returns the list of traced events

Examples

test "supervisor restart behavior" do
  {:ok, supervisor} = setup_isolated_supervisor(MySupervisor)
  {:ok, stop_trace} = trace_supervision_events(supervisor)

  # Cause some failures
  children = Supervisor.which_children(supervisor)
  Enum.each(children, fn {_id, pid, _type, _mods} ->
    Process.exit(pid, :kill)
  end)

  # Get events
  events = stop_trace.()

  # Verify restart sequence
  assert length(events) >= 6  # 3 terminates + 3 restarts
end

wait_for_supervisor_stabilization(supervisor, timeout \\ 5000)

@spec wait_for_supervisor_stabilization(Supervisor.supervisor(), timeout()) ::
  :ok | {:error, :timeout}

Waits until supervisor has all children running and stable.

Parameters

  • supervisor - The supervisor PID
  • timeout - Timeout in milliseconds (default: 5000)

Examples

test "supervisor recovery" do
  {:ok, supervisor} = setup_isolated_supervisor(MySupervisor)

  # Cause chaos
  children = Supervisor.which_children(supervisor)
  Enum.each(children, fn {_id, pid, _type, _mods} ->
    Process.exit(pid, :kill)
  end)

  # Wait for stabilization
  assert :ok = wait_for_supervisor_stabilization(supervisor)

  # All should be alive
  assert_all_children_alive(supervisor)
end