test
property-based and fuzzing
Additions to
test(§21), in the same model: a core decorator declares, assertions are functions, comparator and semantics fixed (anti-p-hacking). Tier 1 on top oftest.
@property: property-based testing (with shrinking)
Section titled “@property: property-based testing (with shrinking)”Generates N cases, and on failure shrinks down to the minimal counterexample:
@property(runs: 1000)fn reverse_reverse_e_identidade(xs: List[int]) { test.expect_eq(xs.reverse().reverse(), xs) // the runner generates xs; shrinks to the smallest that fails}Two surfaces coexist. Inside a @property body you can draw VALUES directly (gen.int(), gen.bool(), …); for a
custom distribution you compose a Gen[T] OBJECT and hand it to the runner.
A Gen[T] is an INTERFACE (a holdable fat-pointer value), not a stored function value — §14 forbids the latter, so
a generator cannot be a fn() -> T field. For the same reason gen.map takes a Mapper[A, B] interface, not a
fn(A) -> B. This is the language-native shape of the combinator surface.
// value samplers (return a value; use directly in a @property body)fn gen.int() -> intfn gen.int_range(lo: int, hi: int) -> intfn gen.float() -> floatfn gen.bool() -> bool
// Gen[T] combinators (return a composable generator OBJECT)fn gen.ints() -> Gen[int]fn gen.ints_between(lo: int, hi: int) -> Gen[int]fn gen.floats() -> Gen[float]fn gen.bools() -> Gen[bool]fn gen.constant[T](v: T) -> Gen[T]fn gen.map[A, B](src: Gen[A], f: Mapper[A, B]) -> Gen[B] // derives one generator from anotherfn gen.list_of[T](elem: Gen[T], size: int) -> Gen[List[T]]fn gen.optionals[T](inner: Gen[T]) -> Gen[Optional[T]]fn gen.one_of[T](opts: List[Gen[T]]) -> Gen[T]fn gen.derived[T]() -> T // AUTO random value via reflect(T) (§10), structs/enums
pub decl Gen[T] { fn sample() -> T }pub decl Mapper[A, B] { fn apply(x: A) -> B } // the §14-compatible stand-in for `fn(A) -> B`A random string value comes from gen.derived[string]() (reflection covers the String kind); the combinator
surface has no dedicated gen.strings() yet.
@property with parameters uses gen.derived[T]() by default (the same reflect that derives Serializable/
hash); you pass explicit generators when you want a custom distribution.
@fuzz: continuous fuzzing
Section titled “@fuzz: continuous fuzzing”Harness in the style of go test -fuzz: feeds mutated inputs (coverage-guided) looking for a crash or violation:
@fuzzfn fuzz_parser(data: []byte) { _ := parse(data) // the runner mutates 'data'; failure = captured crash/panic}fn fuzz_roundtrip(data: []byte) { match decode(data) { Err(_) => {} // invalid input: ok, ignore Ok(v) => test.expect_eq(decode(encode(v)).or_panic(), v) // valid: round-trip must close }}The initial corpus goes in tests/fuzz/<name>/; the runner persists the cases that increase coverage (and whatever
crashed becomes a permanent regression case).
Curation
Section titled “Curation”@propertyand@fuzzare an addition totest: same model (a core decorator declares, assertions are functions oftest, isolation per process). It is not a new test runtime.- Shrinking is the feature: a 500-element counterexample is useless; the runner shrinks to the minimum that
still fails. Auto for the generated types; override via
Shrinker[T]. - Reflection-derived generators:
gen.derived[T]()reuses thereflect(T)that already derivesSerializable/hash; you only write a generator by hand for a custom distribution. - Comparator and semantics fixed (like
@bench): anti-p-hacking; you control the effort (runs/time), not the failure yardstick.