PropertyDamage.Generator (PropertyDamage v0.2.0)
View SourceGenerates command sequences for stateful property-based testing.
This module produces PropertyDamage.Sequence structs that can be either:
- Linear sequences: Commands executed sequentially
- Branching sequences: Commands with parallel execution branches
Linear Sequence Generation
Generator.generate_sequence(MyModel, max_commands: 20)
# => StreamData producing %Sequence{prefix: [...], branches: nil}Branching Sequence Generation
Generator.generate_sequence(MyModel,
max_commands: 20,
branching: [
branch_probability: 0.3, # 30% chance to create branch point
max_branches: 3, # Up to 3 parallel branches
max_branch_length: 5 # Each branch max 5 commands
]
)
# => StreamData producing %Sequence{prefix: [...], branches: [[...], [...]], suffix: [...]}Ref Dependency Rules
When generating branching sequences, the generator enforces ref isolation:
- Refs created in
prefixcan be used in any branch - Refs created in one branch CANNOT be used in another branch
- Refs created in branches CAN be used in
suffix
Auto-Lifting
Raw values passed as overrides are automatically wrapped in StreamData.constant/1:
merge_overrides(base, %{currency: "USD"})
merge_overrides(base, %{currency: StreamData.member_of(["USD", "EUR"])})
Summary
Functions
List the external placeholders available in projection state.
A seeded generator that picks one external placeholder from state.
Generate a command sequence for a given model.
Deterministically realizes a single value from a StreamData generator.
Merge overrides into base generators, auto-lifting raw values.
Derives the effective seed for a given run number from the base seed.
Check if a value is a StreamData generator.
Types
@type command() :: struct()
@type state() :: map()
@type weighted_command() :: {pos_integer(), module()}
Functions
List the external placeholders available in projection state.
During generation, external() markers in simulated events become
%PropertyDamage.Placeholder{} structs embedded in projection state. This
surfaces them so a model's with: function can route one into a command that
consumes a server-generated value.
Options
:event_module- keep only placeholders produced by this event module:path- keep only placeholders at this field path (e.g.[:id])
Example
# In the model's command list:
{ViewOrder, with: fn state ->
%{order_id: PropertyDamage.Generator.external_from(state, path: [:id])}
end}
@spec external_from( map(), keyword() ) :: StreamData.t(struct() | nil)
A seeded generator that picks one external placeholder from state.
Returns StreamData.constant(nil) when no matching external is available, so
a with: function can guard on nil. Accepts the same options as
available_externals/2.
@spec generate_sequence( module(), keyword() ) :: StreamData.t(PropertyDamage.Sequence.t())
Generate a command sequence for a given model.
Parameters
model- Model module defining commands and state projectionopts- Options::max_commands- Maximum total commands per sequence (default: 50):branching- Keyword list for branching configuration (see below)
Note: the returned generator is pure; reproducibility comes from consuming
it with generate_value/3 and an explicit seed.
Branching Options
Pass branching: [...] to generate branching sequences:
:branch_probability- Probability of creating a branch point (default: 0.2):max_branches- Maximum number of parallel branches (default: 3):max_branch_length- Maximum commands per branch (default: 5):min_prefix_length- Minimum commands before branching (default: 3)
If :branching is not provided, generates linear sequences.
Returns
A StreamData generator that produces PropertyDamage.Sequence structs.
Examples
# Linear sequence
Generator.generate_sequence(MyModel, max_commands: 20)
# Branching sequence with 30% branch probability
Generator.generate_sequence(MyModel,
max_commands: 30,
branching: [branch_probability: 0.3, max_branches: 2]
)
@spec generate_value(StreamData.t(val), integer(), keyword()) :: val when val: var
Deterministically realizes a single value from a StreamData generator.
Consuming a generator via Enum/Enumerable seeds from the wall clock
(see StreamData docs), which silently breaks seed reproducibility.
All framework code MUST realize generated values through this function.
Merge overrides into base generators, auto-lifting raw values.
Raw values are automatically wrapped in StreamData.constant/1.
StreamData generators are passed through unchanged.
Parameters
base- Map of field names to StreamData generatorsoverrides- Map of field names to values or generators to override
Returns
A map suitable for passing to StreamData.fixed_map/1.
Examples
iex> base = %{amount: StreamData.positive_integer(), currency: StreamData.constant("USD")}
iex> result = PropertyDamage.Generator.merge_overrides(base, %{currency: "EUR"})
iex> is_map(result)
true
@spec run_seed(integer(), non_neg_integer()) :: integer()
Derives the effective seed for a given run number from the base seed.
Run 0 uses the base seed unchanged, so re-running a failure's reported
seed with max_runs: 1 regenerates exactly the failing sequence.
Later runs get independent, well-mixed sub-seeds.
Check if a value is a StreamData generator.
Examples
iex> PropertyDamage.Generator.stream_data?(StreamData.integer())
true
iex> PropertyDamage.Generator.stream_data?(42)
false