PropertyDamage.Executor (PropertyDamage v0.2.0)
View SourceExecutes command sequences against the System Under Test.
The Executor is the core engine that runs command sequences, resolves placeholders, updates projections, runs checks, and collects events from both the adapter and injector adapters.
Sequence Execution
The Executor handles both linear and branching sequences:
Linear Sequences
Commands are executed sequentially:
%Sequence{prefix: [cmd1, cmd2, cmd3], branches: nil, suffix: []}Execution: cmd1 → cmd2 → cmd3
Branching Sequences
Commands with parallel branches:
%Sequence{
prefix: [cmd1],
branches: [[cmd2a, cmd3a], [cmd2b]],
suffix: [cmd4]
}Execution:
- Execute prefix: cmd1
- Fork state at branch point
- Execute branch A: cmd2a → cmd3a
- Execute branch B: cmd2b
- Verify linearizability of branch execution
- Merge state, execute suffix: cmd4
Execution Flow
For each command in the sequence:
- Resolve placeholders to concrete values
- Execute via adapter (command → events)
- Capture external() values produced by this command from its events
- Update projections (command first, then events)
- Drain injector events and process them
- Run triggered checks
- Record events in event log
Placeholder Resolution
Commands may contain placeholders for server-generated values (declared via
external/0 on producer events). Before execution, these are replaced with
the concrete values captured from the producer's events. If a placeholder
hasn't been resolved yet (its producer hasn't run), execution fails.
Event Log
All events are recorded in the event log with metadata:
- Command events: source = :command, command_index set
- Injector events: source = :injector, injector_adapter set
- Branch ID for parallel execution tracking
Results
Returns a result struct containing:
:success- Boolean indicating if all checks passed:event_log- Complete event log:projections- Final projection states:failed_at_index- Index where check failed (nil if success):failure_reason- Check failure reason (nil if success):linearization- Selected linearization (for branching sequences)
Summary
Types
Assertion mode controls whether and how assertion failures are handled.
Result of executing a command sequence.
Functions
Execute a command sequence without model projections or assertions.
Execute a command sequence with pre-established contexts.
Execute a command sequence using the given model and adapter.
Types
@type assertion_mode() :: :disabled | :halt | :record | :log
Assertion mode controls whether and how assertion failures are handled.
:disabled- Skip all assertions (useful for load testing focused on throughput):halt(default) - Stop execution at first failure, return failure:record- Record failures and continue, return all failures at end:log- Log failures as warnings and continue
@type result() :: %{ success: boolean(), event_log: [PropertyDamage.EventLog.Entry.t()], projections: %{required(module()) => any()}, failed_at_index: non_neg_integer() | nil, failure_reason: term() | nil, linearization: [struct()] | nil, assertion_failures: [map()] | nil }
Result of executing a command sequence.
Functions
@spec execute_raw(PropertyDamage.Sequence.t() | list(), module(), map()) :: {:ok, [PropertyDamage.EventLog.Entry.t()]} | {:error, term()}
Execute a command sequence without model projections or assertions.
This is a simplified execution path for static regression tests where you want to run a fixed command sequence and assert on the raw event log directly, without using model-defined projections or checks.
Parameters
sequence- Sequence struct or list of commands to executeadapter- Adapter module for SUT interactioncontext- Execution context map containing::adapter_context- Pre-established adapter context from adapter.setup/1:event_queue- EventQueue pid for injector events (optional)
Returns
{:ok, event_log}- List of EventLog.Entry structs{:error, {:adapter_error, reason, partial_events}}- Adapter failed
Example
{:ok, adapter_ctx} = MyAdapter.setup(%{})
{:ok, event_queue} = EventQueue.start_link()
context = %{
adapter_context: adapter_ctx,
event_queue: event_queue
}
{:ok, events} = Executor.execute_raw(commands, MyAdapter, context)
@spec execute_sequence( PropertyDamage.Sequence.t() | list(), module(), module(), map(), pid() | nil, PropertyDamage.Stutter.Config.t() | nil, pid() | nil, assertion_mode(), [atom()] ) :: result()
Execute a command sequence with pre-established contexts.
Lower-level API when you've already set up the adapter. Useful for shrinking where you want to reuse contexts across multiple execution attempts.
Parameters
sequence- Sequence struct to executemodel- Model moduleadapter- Adapter moduleadapter_context- Pre-established adapter contextevent_queue- EventQueue pid (optional)stutter_config- Stutter.Config for idempotency testing (optional)mock_registry- MockServiceRegistry pid (optional)assertion_mode- How to handle assertion failures (optional, default::halt)
Returns
Result struct directly (no wrapping tuple).
@spec run(PropertyDamage.Sequence.t() | list(), module(), module(), keyword()) :: {:ok, result()} | {:error, term()}
Execute a command sequence using the given model and adapter.
This is the main entry point for execution. It handles the full lifecycle: adapter setup, command execution, injector event draining, and cleanup.
Parameters
sequence- Sequence struct to execute (linear or branching)model- Model module defining projections and checksadapter- Adapter module for SUT interactionopts- Options (see below)
Options
:adapter_config- Config passed to adapter.setup/1:event_queue- EventQueue pid for injector events (optional):injector_adapters- List of injector adapter modules (optional):stutter_config- Stutter.Config for idempotency testing (optional):mock_registry- MockServiceRegistry pid for mock service support (optional):assertion_mode- How to handle assertions (:disabled,:halt,:record,:log). Default::halt
Returns
{:ok, result}- Execution completed (check result.success for pass/fail){:error, reason}- Setup or execution infrastructure failed