metamon/generator
Generators: deterministic, size-aware samplers that produce values alongside their lazy shrink alternatives.
A Generator(a) is a pair of:
run: takes a seed and a size and returns aTree(a)edges: a finite list of must-try boundary values
Combinators (map, bind, map2, tuple2, one_of, …) preserve
integrated shrinking via the underlying Tree(a) and propagate edges
in a way appropriate for each combinator (see § 4.4 of the spec).
Types
Values
pub fn add_edges(g: Generator(a), more: List(a)) -> Generator(a)
Append more edges to an existing generator. Symmetric to
with_examples but reads better for library authors.
pub fn ascii_alphanumeric() -> Generator(String)
A single ASCII alphanumeric character.
pub fn ascii_letter() -> Generator(String)
A single ASCII letter generator (a-zA-Z).
pub fn ascii_lower() -> Generator(String)
A single ASCII lowercase letter generator (a-z).
pub fn ascii_printable() -> Generator(String)
A single ASCII printable character (space..~).
pub fn ascii_upper() -> Generator(String)
A single ASCII uppercase letter generator (A-Z).
pub fn bind(
g: Generator(a),
k: fn(a) -> Generator(b),
) -> Generator(b)
Monadic bind. Edges are taken from the outer generator only (the inner edges depend on the value, which is not safe to enumerate at composition time without running the inner generator with a fixed seed).
pub fn bit_array(byte_len: range.Range) -> Generator(BitArray)
Bit array whose total length in bytes lies inside byte_len.
Each byte is generated uniformly. Output is always byte-aligned
(no sub-byte tail).
pub fn bit_array_printable(
byte_len: range.Range,
) -> Generator(BitArray)
Bit array whose every byte is a printable ASCII codepoint
(0x20..0x7E). byte_len is, like bit_array, the number of
bytes in the resulting array. Useful for fuzzing parsers that
take BitArray but expect printable input (HTTP headers, MIME
types, etc.).
pub fn bit_array_unaligned(
bit_len: range.Range,
) -> Generator(BitArray)
Bit array of arbitrary bit length, possibly NOT byte-aligned.
bit_len is the total number of bits in the result.
Use this generator to fuzz code paths that take a BitArray whose
total length is not necessarily a multiple of 8: codec internals,
length-prefixed framing, bignum bit walks, and any parser that
must reject (or handle) sub-byte tails. Reach for bit_array when
byte-aligned input is sufficient — it is faster and the size
metric matches byte-oriented properties more naturally.
pub fn bit_array_utf8(
codepoint_len: range.Range,
) -> Generator(BitArray)
Bit array that is guaranteed to be valid UTF-8. codepoint_len is
the number of Unicode codepoints; the resulting byte length will
be larger when the random string contains multi-byte codepoints.
pub fn dict_of(
key: Generator(k),
value: Generator(v),
len: range.Range,
) -> Generator(dict.Dict(k, v))
Generate a dict.Dict(k, v) of size in len. Duplicate keys are
resolved by last-write-wins (the standard dict.from_list
semantics).
pub fn edges_of(g: Generator(a)) -> List(a)
Inspect the must-try edge values associated with a generator.
pub fn element_of(values: List(a)) -> Generator(a)
Pick uniformly from a non-empty list of values. Equivalent to
one_of(list.map(values, return)) and inherits the same edge
behaviour: every value is an edge.
pub fn filter(
g: Generator(a),
predicate: fn(a) -> Bool,
) -> Generator(a)
Reject values that fail predicate. The implementation retries
internally up to filter_retry_limit times before panicking; this
is the canonical “filter is too strict” failure and is preferable
to silently misreporting the property.
Edges are filtered by predicate so user-supplied edges that fail
the predicate are silently dropped (they would never be tried
anyway).
pub fn float(lo: Float, hi: Float) -> Generator(Float)
Generates finite Float values uniformly in the closed interval
[lo, hi]. Shrinking moves toward the closest endpoint (no fancy
mantissa shrinking yet).
This generator does not emit NaN, ±Infinity, or denormal
values. Use float_special (or splice float_special_edges() via
with_examples) when codec correctness depends on those edges —
f64.to_string(NaN) formats as "NaN" but a parser may not accept
the token, -0.0 round-trips differently than 0.0 through some
encoders, etc.
pub fn float_special() -> Generator(Float)
IEEE 754 special-value generator: emits values that the regular
float generator never produces — NaN, +Infinity, -Infinity,
the smallest positive denormal, the largest finite double, plus the
“ordinary” anchors 0.0, -0.0, 1.0. Every value is also an
edge, so the runner tries each one before falling back to random.
Use this when codec / serialisation correctness depends on IEEE
edges (f64.to_string(NaN) formats as "NaN", but a parser may not
accept that token; -0.0 round-trips differently than 0.0 through
some encoders, etc.). The values pass through metamon’s edge pipeline
unchanged because the runner does not perform arithmetic on edge
values — but downstream user code that does arithmetic on the
generated value will raise badarith on the BEAM, exactly as
IEEE 754 expects.
Target asymmetry: on JavaScript, the non-finite slots return
genuine NaN / ±Infinity. On the BEAM, they return finite
sentinels (largest finite double for NaN / +Infinity, the
negation thereof for -Infinity) because the BEAM has no portable
way to construct a non-finite double from pure Erlang. Properties
that strictly require genuine non-finite values must run on the
JavaScript target.
pub fn float_special_edges() -> List(Float)
The list of values emitted by float_special. Exposed so callers
can splice them into a custom range generator via
with_examples(my_float_gen, generator.float_special_edges()).
pub fn frequency(
weighted: List(#(Int, Generator(a))),
) -> Generator(a)
Weighted choice over a non-empty list of (weight, generator) pairs.
Weights must be >= 1. A weight of 0 or a negative weight is a
programming error (a “disabled” branch silently re-enabled by the
previous coercion to 1, or a weight = max(0, computed)
defensive pattern losing its safety net) and panics with a
structured message naming the offending position. Pass at least
1 for any branch you want to keep, or remove the entry entirely
to opt it out.
pub fn generate(
g: Generator(a),
s: seed.Seed,
size: Int,
) -> tree.Tree(a)
Run the generator at the given seed and size. Used by the runner; most user code should not call this directly.
pub fn int(range_value: range.Range) -> Generator(Int)
Integer in the given range, with binary shrinking toward the range
origin. Standard edges (0, 1, -1, bounds) are populated within
the constant interval.
pub fn list_of(
element: Generator(a),
len: range.Range,
) -> Generator(List(a))
A list of values whose length is drawn from len. Shrinking removes
elements via shrink/list_drops and shrinks each remaining element.
pub fn map(g: Generator(a), f: fn(a) -> b) -> Generator(b)
Functor map. Edges are mapped element-wise.
pub fn map2(
g1: Generator(a),
g2: Generator(b),
f: fn(a, b) -> c,
) -> Generator(c)
Applicative map over two independent generators. Shrinking holds one
component fixed while shrinking the other (via tree.zip).
pub fn map3(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
f: fn(a, b, c) -> d,
) -> Generator(d)
Three-way applicative map.
pub fn map4(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
f: fn(a, b, c, d) -> e,
) -> Generator(e)
Four-way applicative map.
pub fn map5(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
f: fn(a, b, c, d, e) -> g,
) -> Generator(g)
Five-way applicative map.
pub fn map6(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
g6: Generator(g),
f: fn(a, b, c, d, e, g) -> h,
) -> Generator(h)
Six-way applicative map.
pub fn map7(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
g6: Generator(g),
g7: Generator(h),
f: fn(a, b, c, d, e, g, h) -> i,
) -> Generator(i)
Seven-way applicative map. Like map6 but accepts a seventh
generator. Use this for record types with seven fields when you
want to keep integrated shrinking on every component (the bind-
based workaround in the README’s Limitations section shrinks
shallowly).
pub fn map8(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
g6: Generator(g),
g7: Generator(h),
g8: Generator(i),
f: fn(a, b, c, d, e, g, h, i) -> j,
) -> Generator(j)
Eight-way applicative map. Like map7 but accepts an eighth
generator. Stops at eight because nine is comfortably above every
record arity in the audited workload; reach for nested map2 /
bind (and accept the shallow-shrinking caveat) if you genuinely
need more.
pub fn negative_int() -> Generator(Int)
Integer in [-1_000_000, -1]. Shrinks toward -1.
pub fn no_edges(g: Generator(a)) -> Generator(a)
Strip the edges from a generator. Useful when composing into a product type that has its own edge story.
pub fn non_empty_list_of(
element: Generator(a),
len: range.Range,
) -> Generator(List(a))
Non-empty list. Wraps list_of and rejects empty lists from edges.
pub fn non_negative_int() -> Generator(Int)
Integer in [0, 1_000_000]. Linear scaling so small values come
first; shrinks toward 0.
pub fn one_of(generators: List(Generator(a))) -> Generator(a)
Pick uniformly from a non-empty list of generators. Edges are the concatenation of all branch edges.
pub fn option_of(g: Generator(a)) -> Generator(option.Option(a))
Some / None of an inner generator.
pub fn positive_int() -> Generator(Int)
Integer in [1, 1_000_000]. Shrinks toward 1.
pub fn recursive(
base: Generator(a),
step: fn(Generator(a)) -> Generator(a),
) -> Generator(a)
Recursive generator. At size = 0 only base is used; at higher
sizes the recursive call is the result of step applied to a copy
of itself with halved size.
pub fn resize(g: Generator(a), new_size: Int) -> Generator(a)
Override the size used by a generator.
pub fn result_of(
ok: Generator(a),
err: Generator(e),
) -> Generator(Result(a, e))
Ok / Error of two inner generators.
pub fn return(value: a) -> Generator(a)
A constant generator that always returns value and has no shrinks.
pub fn sample(g: Generator(a), n: Int) -> List(a)
Pull n plain sample values, ignoring shrink trees. Useful at the
REPL or in test diagnostics.
pub fn scale(g: Generator(a), f: fn(Int) -> Int) -> Generator(a)
Transform the size given to a generator.
pub fn set_of(
element: Generator(a),
len: range.Range,
) -> Generator(set.Set(a))
Generate a set.Set(a) of size in len.
pub fn sized(builder: fn(Int) -> Generator(a)) -> Generator(a)
Construct a generator whose strategy depends on the current size.
pub fn statistics(
g: Generator(a),
n: Int,
classify: fn(a) -> String,
) -> dict.Dict(String, Int)
Bucket n generated values via classify for distribution sanity
checks.
pub fn string(
char_gen: Generator(String),
len: range.Range,
) -> Generator(String)
A string built from char_gen characters with length in len.
pub fn string_alpha(len: range.Range) -> Generator(String)
A string of ASCII letters (a-zA-Z) with length in len.
pub fn string_alphanumeric(len: range.Range) -> Generator(String)
A string of ASCII alphanumerics (a-zA-Z0-9) with length in len.
pub fn string_ascii(len: range.Range) -> Generator(String)
An ASCII string with length in len.
Random sampling spans the full ASCII range (0x00..0x7F),
including control characters (0x00..0x1F, 0x7F). Reach for
string_printable_ascii if your property cannot tolerate control
bytes; this function is for fuzzing parsers / serialisers that
must handle the whole 7-bit space.
Curated edges include both control bytes (\t, \n) and
printable boundaries (" ", "~").
pub fn string_digit(len: range.Range) -> Generator(String)
A string of ASCII digits (0-9) with length in len.
pub fn string_printable_ascii(
len: range.Range,
) -> Generator(String)
A string of printable ASCII codepoints (0x20..0x7E) with length
in len. Differs from string_ascii by skipping the curated edge
cases that include control characters (\t, \n).
pub fn string_unicode(len: range.Range) -> Generator(String)
A Unicode-aware string (includes BiDi/emoji/NUL via edges).
Produces valid UTF-8 scalar values only — see
unicode_codepoint for the codepoint set. Lone surrogates and
other malformed UTF-8 byte sequences are not reachable through
this generator because they cannot exist inside a Gleam String.
To fuzz parsers that must accept or reject malformed UTF-8 input,
use bit_array(byte_len) and operate at the byte level.
No bias toward Unicode normalisation boundaries (NFC vs NFD) is
built in: equivalent forms like "é" (U+00E9) and
"e\u{0301}" (U+0065 U+0301) are sampled independently. Pre-compose
any equivalence-class edges via with_examples if your property
depends on them.
pub fn tuple2(
g1: Generator(a),
g2: Generator(b),
) -> Generator(#(a, b))
Pair two independent generators.
pub fn tuple3(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
) -> Generator(#(a, b, c))
Triple of independent generators.
pub fn tuple4(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
) -> Generator(#(a, b, c, d))
Quadruple of independent generators.
pub fn tuple5(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
) -> Generator(#(a, b, c, d, e))
Quintuple of independent generators.
pub fn tuple6(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
g6: Generator(g),
) -> Generator(#(a, b, c, d, e, g))
Sextuple of independent generators.
pub fn tuple7(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
g6: Generator(g),
g7: Generator(h),
) -> Generator(#(a, b, c, d, e, g, h))
Septuple of independent generators.
pub fn tuple8(
g1: Generator(a),
g2: Generator(b),
g3: Generator(c),
g4: Generator(d),
g5: Generator(e),
g6: Generator(g),
g7: Generator(h),
g8: Generator(i),
) -> Generator(#(a, b, c, d, e, g, h, i))
Octuple of independent generators.
pub fn unicode_codepoint() -> Generator(Int)
A Unicode scalar value (excluding surrogates).
Draws from [0, 0xD7FF] ∪ [0xE000, 0x10FFFF]. The surrogate range
[0xD800, 0xDFFF] is intentionally excluded because Gleam strings
are UTF-8: lone surrogates would not survive
string.from_utf_codepoints.
To fuzz codecs / parsers that must handle malformed UTF-8 byte
sequences (lone surrogates, overlong encodings, truncated
continuation bytes, …), drop down to bit_array(byte_len) and
exercise the parser at the byte level instead — those inputs
cannot exist inside a Gleam String and so cannot reach a
property whose generator is string_unicode.