NWave nw-pbt-erlang-elixir
Erlang/Elixir property-based testing with PropEr, PropCheck, and StreamData frameworks
install
source · Clone the upstream repo
git clone https://github.com/nWave-ai/nWave
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/nWave-ai/nWave "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/nw/skills/nw-pbt-erlang-elixir" ~/.claude/skills/nwave-ai-nwave-nw-pbt-erlang-elixir-8e34c9 && rm -rf "$T"
manifest:
plugins/nw/skills/nw-pbt-erlang-elixir/SKILL.mdsource content
PBT Erlang/Elixir -- PropEr, PropCheck, StreamData
Framework Selection
| Framework | Language | Stateful | Parallel | Choose When |
|---|---|---|---|---|
| PropEr | Erlang | Yes | Yes (linearizability) | Erlang projects needing full stateful/parallel testing |
| PropCheck | Elixir | Yes (via PropEr) | Yes (via PropEr) | Elixir projects needing stateful/parallel testing |
| StreamData | Elixir | No | No | Elixir projects needing only stateless PBT |
PropCheck wraps PropEr with Elixir syntax. StreamData is pure Elixir but lacks stateful testing.
Quick Start
%% PropEr (Erlang) -include_lib("proper/include/proper.hrl"). prop_sort_preserves_length() -> ?FORALL(List, list(integer()), length(lists:sort(List)) =:= length(List)). %% Run: proper:quickcheck(my_module:prop_sort_preserves_length()).
Syntax Differences
| Concept | PropEr (Erlang) | PropCheck (Elixir) | StreamData (Elixir) |
|---|---|---|---|
| Import | | | |
| Property | | | |
| Integer | | | |
| List | | | |
| Run | | | |
Generator Cheat Sheet (PropEr)
Primitives
integer() % any integer integer(0, 100) % bounded float() binary() % binary data boolean() atom()
Collections
list(integer()) % list of integers non_empty(list(integer())) % non-empty list vector(5, integer()) % fixed-length list {integer(), binary()} % tuple (direct syntax)
Combinators
oneof([integer(), binary()]) % union elements([a, b, c]) % pick from list frequency([{80, integer()}, {20, atom()}]) % weighted %% ?LET (map/transform) even() -> ?LET(N, integer(), N * 2). %% ?SUCHTHAT (filter -- use sparingly) non_empty_list() -> ?SUCHTHAT(L, list(integer()), L =/= []). %% Nested ?LET (dependent generation) list_and_element() -> ?LET(List, non_empty(list(integer())), ?LET(Elem, elements(List), {List, Elem})).
Recursive
tree(Type) -> ?SIZED(Size, tree(Size, Type)). tree(0, Type) -> {leaf, Type}; tree(Size, Type) -> frequency([ {1, {leaf, Type}}, {5, ?LAZY({node, Type, tree(Size div 2, Type), tree(Size div 2, Type)})} ]).
Shrinking
year() -> ?SHRINK(integer(0, 9999), [integer(1970, 2000)]). date() -> ?LETSHRINK([Y, M, D], [integer(1, 9999), integer(1, 12), integer(1, 31)], {Y, M, D}).
Stateful Testing (proper_statem)
-behaviour(proper_statem). initial_state() -> #{items => #{}}. command(#{items := Items}) -> oneof([ {call, ?MODULE, put, [key(), value()]}, {call, ?MODULE, get, [elements(maps:keys(Items))]} || maps:size(Items) > 0 ]). precondition(#{items := Items}, {call, _, get, [Key]}) -> maps:is_key(Key, Items); precondition(_, _) -> true. postcondition(#{items := Items}, {call, _, get, [Key]}, Result) -> Result =:= maps:get(Key, Items); postcondition(_, _, _) -> true. next_state(State = #{items := Items}, _Var, {call, _, put, [Key, Val]}) -> State#{items := Items#{Key => Val}}; next_state(State, _, _) -> State. prop_store() -> ?FORALL(Cmds, commands(?MODULE), begin {History, State, Result} = run_commands(?MODULE, Cmds), cleanup(), Result =:= ok end).
StreamData: No stateful testing. PropCheck: Same proper_statem callbacks with Elixir syntax.
Parallel Testing (Linearizability)
prop_store_parallel() -> ?FORALL(Cmds, parallel_commands(?MODULE), begin {Sequential, Parallel, Result} = run_parallel_commands(?MODULE, Cmds), cleanup(), Result =:= ok end).
Swap
commands for parallel_commands, run_commands for run_parallel_commands. Framework generates sequential prefix + parallel branches and checks all linearizations.
Test Runner Integration
%% PropEr: {deps, [{proper, "1.4.0"}]}. Run: rebar3 proper %% PropCheck: {:propcheck, "~> 1.4", only: :test}. Run: mix test %% StreamData: {:stream_data, "~> 1.0", only: :test}. Run: mix test
Unique Features
- proper_fsm: Finite state machine testing module (distinct from generic state machines)
- PULSE integration: Controlled scheduling for parallel test race detection
- Symbolic references: First-class support with two-phase (abstract + concrete) execution
- collect/aggregate: Built-in distribution analysis for generator quality verification
- Linearizability checking: One of the most mature implementations in any PBT framework