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 }.{
parser_for : _
encoder_for : _
generator_for = |_| {
{ bytes: Fuzz.bytes, radix: Fuzz.u8_in(2, 36) }.Fuzz
}
}
target = Fuzz.target({
examples: [],
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 application allocations the platform has served this process so far. Private measurement ownership containers are excluded from all counters.
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!.
realloc_count! : () => U64
roc_realloc calls the platform has served this process so far.
Like alloc_count!, it is process-wide and never
reset, so only a difference across a region is meaningful.
dealloc_count! : () => U64
roc_dealloc calls the platform has served this process so far.
A reallocation frees the block it replaces without counting a deallocation. Only a difference across a region is meaningful.
requested_bytes! : () => U64
Bytes requested from the platform's allocator this process so far.
An allocation adds its requested length and a reallocation adds the full new length it asks for, whether it grows or shrinks the block. A deallocation adds nothing, and a zero-length request adds zero. This is allocator traffic: it is not the heap still in use, the process's resident memory, or the bytes a reallocation copies. A request the allocator cannot serve ends the process, so every request a completed region made is counted. Arithmetic overflow fails explicitly rather than wrapping or saturating. Only a difference across a region is meaningful.
scope_start! : Str => U64
Native scope engine entry point. Prefer start_measure! or measure!.
scope_finish! : U64 => Stats
Native scope engine completion. Invalid or out-of-order scopes fail.
prepare_return_box! : () => {}
Internal ownership fence preparation. The following return container is platform bookkeeping and is excluded from every allocation observation.
scope_finish_return! : U64, Box(a) => { value : Box(a), stats : Stats }
Complete a region while the opaque return container owns every returned value. Transfers that container back unchanged; applications use measure!.
start_measure! : Str => Measurement
Start a manual window using the same engine as measure!.
Run body! once and automatically record its named allocation statistics. Nested windows are inclusive; the returned value is alive at the boundary.
Record a direct event in the current logical trace region.
Require both named events and every first occurrence before every second. Register before any direct event or completed child region occurs.
Require exactly count occurrences in the current region. Register first.
forbidden! : Str => {}
Forbid an event in the current region. Register before events begin.
trace_rules! : List(TraceRule) => {}
Register all rules before emitting events. Validation is independent of the bounded diagnostic trace, so truncation cannot hide a violation.
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.
Declare a source-authored typed example. Use a zero-argument maker such
as make: || Input.{ bytes: [], radix: 10 } to avoid retaining fixtures.
Declare a retained witness using its original bytes and recorded form. Logical rendering never replaces entropy or storage-sensitive artifacts.
Encode a logical value as a mutable corpus seed using platform format v1.
Float encoding preserves signed zero, infinities and NaN equivalence; Roc
canonicalizes NaN payloads. Use integer bit inputs for payload assertions.
On nightly-2026-10-27-de79a0f, derived Dict keys containing records or
tuples require --specialize=no because the default compiler backend
crashes while specializing those codecs. Scalar and tag keys work with
the default backend.
Frame generator entropy while preserving its storage-shape decisions.
target : { name : Str, examples : List(Example(a)), test : a -> Outcome, show : a -> Str } -> Target where [a.generator_for : FuzzEncoding -> Generator(a), a.Encodable, a.Parseable(errs)]
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, examples : List(Example(a)), generator : Generator(a), test : a -> Outcome, show : a -> Str } -> Target where [a.Encodable, a.Parseable(errs)]
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, examples : List(Example(a)), generator : Generator(a), test! : a => Outcome, show : a -> Str } -> Target where [a.Encodable, a.Parseable(errs)]
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.
Effectful sibling of from_bytes.
Adapt an existing List(U8) -> U8 quality target.
Zero keeps the input; a nonzero byte rejects it. A crash or failed expect remains a failure. New targets usually prefer typed inputs and examples.
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.
Stats : { allocs : U64, reallocs : U64, deallocs : U64, requested_bytes : U64, entry_live_allocs : U64, entry_live_bytes : U64, exit_live_allocs : U64, exit_live_bytes : U64, peak_live_bytes : U64 }
Inclusive allocator observations for one named region. Only Roc allocator hooks count; private measurement containers and host bookkeeping are excluded. Peaks include entry-live memory and the actual old/new overlap during reallocations.
:= { id : U64 }
An open named measurement and logical trace region. Finish exactly once.
finish! : Measurement => Stats
Complete this measurement, retaining values alive at this boundary.
:= [Before(Str, Str), Count(Str, U64), Forbidden(Str)]
A source-declared rule over direct marks and completed child regions.
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.
InputFormat : Format
Type-only name for the platform's binary codec marker. Roc resolves this through encoder_for/parser_for derivation; applications need no codec value.
InputState : State
Type-only name for the bounded binary parser cursor. Normal inputs opt in with parser_for : _ rather than constructing or manipulating a cursor.
Requirement for types that can become source-authored corpus seeds.
Parseable : a where [a.parser_for : Format -> (State -> Try({ value : a, rest : State }, [MalformedInput, BudgetExceeded, ..errs]))]
Requirement for types that can decode mutated source-authored seeds.
:= [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.
:= [Typed({ name : Str, revision : Str, make : () -> a }), Saved({ name : Str, revision : Str, bytes : List(U8) })]
A named input that always runs during checking, regardless of coverage. Makers are called only while preparing corpus bytes; temporary typed values are released before the property executes in a fresh process.