Fuzz

:= []

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.

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.

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.

u8 : Generator(U8)

Generate any U8 value.

u8_in : U8, U8 -> Generator(U8)

Generate a U8 in the inclusive range from low through high.

The generator crashes if low is greater than high.

u64 : Generator(U64)

Generate any U64 value.

u64_in : U64, U64 -> Generator(U64)

Generate a U64 in the inclusive range from low through high.

The generator crashes if low is greater than high.

bytes : Generator(List(U8))

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.

raw_bytes : Generator(List(U8))

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.

str : Generator(Str)

Generate a valid UTF-8 Str with varied length and allocation shape.

list : Generator(a), U64 -> Generator(List(a))

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.

Outcome

:= [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.

FuzzEncoding

:= [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.