PropertyDamage.Replay (PropertyDamage v0.1.0)

View Source

Step-by-step replay of failure sequences for debugging.

Replay mode lets you execute a failing sequence one command at a time, inspecting the state after each step. This is invaluable for understanding exactly how the system reached the failure state.

Usage Modes

# Get all steps at once
{:ok, steps} = PropertyDamage.replay(failure)
for step <- steps do
  IO.puts("Command #{step.index}: #{step.command_name}")
  IO.inspect(step.projections, label: "State")
end

Interactive Mode (For LiveBook/IEx)

{:ok, session} = Replay.start(failure)
{:ok, session, step} = Replay.step(session)  # Execute first command
IO.inspect(step.projections)                  # Inspect state
{:ok, session, step} = Replay.step(session)  # Next command
# ...continue stepping...

Jump to Failure Point

{:ok, session} = Replay.start(failure)
{:ok, session, steps} = Replay.step_to(session, failure.failed_at_index)
# Now at the exact point where the failure occurred

Step Information

Each step returns:

  • index - Command index in sequence
  • command - The command struct
  • command_name - Short name for display
  • events - Events produced by this command
  • projections - Projection states after this command
  • refs - Ref resolution map
  • result - :ok, {:check_failed, ...}, or error

Summary

Functions

Get the current state of projections.

Format all steps for display.

Format a step for display.

Get all executed steps so far.

Get the command at a specific index (without executing).

Replay an entire failure sequence, returning all steps.

Start an interactive replay session.

Execute the next command in the sequence.

Execute commands up to (and including) the specified index.

Clean up session resources.

Types

step()

@type step() :: %{
  index: non_neg_integer(),
  command: struct(),
  command_name: String.t(),
  events: [struct()],
  projections: map(),
  projections_before: map(),
  refs: map(),
  result: :ok | {:check_failed, atom(), String.t()} | {:error, term()}
}

t()

@type t() :: %PropertyDamage.Replay{
  adapter: module(),
  adapter_config: map(),
  commands: [struct()],
  current_index: integer(),
  event_log: [term()],
  event_queue: pid(),
  failure: PropertyDamage.FailureReport.t(),
  model: module(),
  projections: map(),
  refs: map(),
  status: :ready | :in_progress | :completed | :failed,
  steps: [step()]
}

Functions

current_state(replay)

@spec current_state(t()) :: map()

Get the current state of projections.

format_history(steps)

@spec format_history(t() | [step()]) :: String.t()

Format all steps for display.

format_step(step)

@spec format_step(step()) :: String.t()

Format a step for display.

history(replay)

@spec history(t()) :: [step()]

Get all executed steps so far.

peek(replay, index)

@spec peek(t(), non_neg_integer()) :: {:ok, struct()} | {:error, :out_of_bounds}

Get the command at a specific index (without executing).

run(failure, opts \\ [])

@spec run(
  PropertyDamage.FailureReport.t(),
  keyword()
) :: {:ok, [step()]} | {:error, term()}

Replay an entire failure sequence, returning all steps.

This is the simplest way to replay - it executes all commands and returns structured information about each step.

Options

  • :adapter_config - Override adapter configuration
  • :stop_on_failure - Stop at first failure (default: true)
  • :include_projections - Include projection states (default: true)

Returns

{:ok, [step]} where each step contains command, events, and state info.

Example

{:ok, steps} = PropertyDamage.replay(failure)

# Find where things went wrong
Enum.each(steps, fn step ->
  IO.puts("[#{step.index}] #{step.command_name}")
  case step.result do
    :ok -> IO.puts("  -> OK")
    {:check_failed, check, msg} -> IO.puts("  -> FAILED: #{check} - #{msg}")
  end
end)

start(failure, opts \\ [])

@spec start(
  PropertyDamage.FailureReport.t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Start an interactive replay session.

Use step/1 to advance through commands one at a time.

Options

  • :adapter_config - Override adapter configuration

Example

{:ok, session} = Replay.start(failure)
{:ok, session, step1} = Replay.step(session)
{:ok, session, step2} = Replay.step(session)

step(session)

@spec step(t()) :: {:ok, t(), step()} | {:done, t()} | {:error, term()}

Execute the next command in the sequence.

Returns

  • {:ok, session, step} - Command executed, step contains results
  • {:done, session} - No more commands to execute
  • {:error, reason} - Execution failed

step_to(session, target_index)

@spec step_to(t(), non_neg_integer()) :: {:ok, t(), [step()]} | {:error, term()}

Execute commands up to (and including) the specified index.

Returns

  • {:ok, session, [step]} - Commands executed
  • {:error, reason} - Execution failed

stop(arg1)

@spec stop(t()) :: :ok

Clean up session resources.

Call this when done with an interactive session.