keep : Outcome
Mark a generated value as useful after the property has held.
:= []
Define typed, deterministic inputs and the pure property that roc-fuzz tests.
Most targets use target, return keep for useful
inputs, and build their input generator from u8, u64, str, bytes,
and list. Generators for record fields compose with Roc's .Fuzz record
builder.
Input := { bytes : List(U8), radix : U8 }.{
generator_for = |_| {
{ bytes: Fuzz.bytes, radix: Fuzz.u8_in(2, 36) }.Fuzz
}
}
target = Fuzz.target({
name: "decode",
test: |input| if input.bytes.is_empty() Fuzz.reject else Fuzz.keep,
show: Str.inspect,
})
Start with these typed combinators. target_with,
from_bytes, and the Arbitrary module support advanced or
migration use cases.
keep : Outcome
Mark a generated value as useful after the property has held.
alloc_count! : () => U64
Total allocations the platform has served this process so far.
Read it before and after a region of code and subtract to count that
region's allocations. Counts every roc_alloc and roc_realloc; a
reallocation counts as one allocation.
The absolute value is meaningless across runs -- the counter is process-wide and the fuzzer reuses the process for millions of inputs -- so always assert on a difference, never on the raw number.
live_alloc_count! : () => U64
Allocations served but not yet freed.
A region that frees everything it allocates leaves this unchanged. Use
it to assert that a property does not leak; see
expect_no_leaks!.
measure_allocs! : ({} => a) => { value : a, allocations : U64 }
Run body! and report how many allocations it performed.
{ value: dict, allocations } = Fuzz.measure_allocs!(|{}| build_dict(pairs))
expect_allocs_at_most! : U64, ({} => a) => a
Crash unless body! performs at most limit allocations.
This is the allocation analogue of a property assertion: it turns a silent performance regression, such as a builtin that stops mutating a uniquely owned value in place, into a fuzz failure.
expect_allocs_at_least! : U64, ({} => a) => a
Crash unless body! performs at least limit allocations.
This is useful for copy-on-write invariants: an operation on shared storage must allocate rather than mutate an alias in place.
expect_no_leaks! : ({} => a) => {}
Crash unless body! frees everything it allocated.
The result is dropped before the balance is read, so a value that is still alive does not read as a leak.
reject : Outcome
Discard a generated value that is outside the property's valid domain.
Prefer generators that produce valid inputs directly. Use reject for
preconditions that are awkward or expensive to encode in a generator.
target : { name : Str, test : a -> Outcome, show : a -> Str } -> Target where [a.generator_for : FuzzEncoding -> Generator(a)]
Build a fuzz target using the input type's statically dispatched generator.
The input type must define generator_for : FuzzEncoding -> Generator(a).
For each fuzzer input, target generates one value, calls test, and uses
show when a person asks the runner to render that saved input.
target_with : { name : Str, generator : Generator(a), test : a -> Outcome, show : a -> Str } -> Target
Build a target from an explicit generator.
This is useful for a one-off structural input that does not need a nominal
generator_for method. Prefer target when the input type owns
its generation policy.
target_with! : { name : Str, generator : Generator(a), test! : a => Outcome, show : a -> Str } -> Target
Effectful sibling of target_with.
Use this when the property itself needs the platform's allocation counters, for example to assert that a builtin mutates a uniquely owned value in place instead of reallocating.
from_bytes! : { name : Str, test! : List(U8) => U8 } -> Target
Effectful sibling of from_bytes.
from_bytes : { name : Str, test : List(U8) -> U8 } -> Target
Adapt an existing List(U8) -> U8 quality target.
The returned byte is ignored; a crash or failed expect still reports a
failure. This is a migration bridge for existing byte-oriented targets.
New targets should prefer target and typed generators.
constant : a -> Generator(a)
Generate the same value without consuming any input bytes.
map : Generator(a), (a -> b) -> Generator(b)
Transform the value produced by a generator while preserving its state.
map2 : Generator(a), Generator(b), (a, b -> c) -> Generator(c)
Generate two values in sequence and combine them.
Roc's { first: gen_a, second: gen_b }.Fuzz record-builder syntax lowers
to this combinator and chains it for larger records. Calling map2
directly is useful for tuples and custom constructors.
Generate any U8 value.
Generate a U8 in the inclusive range from low through high.
The generator crashes if low is greater than high.
Generate any U64 value.
Generate a U64 in the inclusive range from low through high.
The generator crashes if low is greater than high.
Generate an F32, biased toward the values that break float code.
About half of all generated values come from a fixed table of boundary
values: quiet, negative, and signalling NaN; positive and negative
infinity; positive and negative zero; the smallest and largest subnormals;
the smallest normal; 1.0 and -1.0; the machine epsilon; F32.highest
and F32.lowest; and 2^24, the first magnitude at which consecutive
integers stop being representable. The other half are arbitrary IEEE 754
bit patterns, which are almost always huge or tiny magnitudes.
The generator always consumes five bytes, so composing it into a record
does not shift the input bytes that later fields decode. Exhausted input
produces 0.0. Use f32_in when the target needs a finite
value within known bounds.
Generate a finite F32 in the inclusive range from low through high.
Both endpoints are reachable. Unlike f32, this generator
never produces NaN or an infinity. It crashes if either bound is not
finite or if low is greater than high.
Generate an F64, biased toward the values that break float code.
About half of all generated values come from a fixed table of boundary
values: quiet, negative, and signalling NaN; positive and negative
infinity; positive and negative zero; the smallest and largest subnormals;
the smallest normal; 1.0 and -1.0; the machine epsilon; F64.highest
and F64.lowest; and 2^53, the first magnitude at which consecutive
integers stop being representable. The other half are arbitrary IEEE 754
bit patterns, which are almost always huge or tiny magnitudes.
The generator always consumes nine bytes, so composing it into a record
does not shift the input bytes that later fields decode. Exhausted input
produces 0.0. Use f64_in when the target needs a finite
value within known bounds.
Generate a finite F64 in the inclusive range from low through high.
Both endpoints are reachable. Unlike f64, this generator
never produces NaN or an infinity. It crashes if either bound is not
finite or if low is greater than high.
Generate a byte list with a length and allocation shape chosen from input.
Use raw_bytes when the target must receive every fuzzer
byte unchanged.
Generate the complete fuzzer input as one byte list without decoding it.
This is mainly useful for migration and byte-format targets. Typed targets usually get better mutations and clearer properties from smaller generators composed into their real input shape.
Generate a valid UTF-8 Str with varied length and allocation shape.
Generate a list containing at most max_len values.
Lengths range from zero through max_len, inclusive. max_len limits the
decoded list length; the runner's maximum raw input size is configured
separately.
:= [Keep, Reject]
Whether a generated value belongs to the useful input domain.
Return Keep after the property holds. Return Reject when a generated
value does not satisfy a precondition. A crash or failed expect is a
fuzz failure; Reject is not.
Generator : Arbitrary -> { value : a, state : Arbitrary }
A deterministic decoder from fuzzer bytes to a typed Roc value.
Each generator returns the generated value and the unconsumed state, so
combinators can decode several values from one input. Most applications do
not need to inspect Arbitrary directly.
:= [Default]
Selects the generator used by a nominal type's generator_for method.
Default is currently the only encoding. The marker gives static dispatch
the same shape as APIs such as Json.parser_for and leaves room for future
generation policies.