test
property-based e fuzzing
Adições ao
test(§21), no mesmo modelo: decorator de core declara, asserções são funções, comparador e semântica fixos (anti-p-hacking). Tier 1 sobre otest.
@property: teste baseado em propriedade (com shrinking)
Seção intitulada “@property: teste baseado em propriedade (com shrinking)”Gera N casos, e ao falhar encolhe até o contra-exemplo mínimo:
@property(runs: 1000)fn reverse_reverse_e_identidade(xs: List[int]) { test.expect_eq(xs.reverse().reverse(), xs) // o runner gera xs; encolhe no menor que falha}Duas superfícies coexistem. Dentro de um corpo @property você tira VALORES direto (gen.int(), gen.bool(), …);
para uma distribuição custom você compõe um OBJETO Gen[T] e entrega ao runner.
Um Gen[T] é uma INTERFACE (um valor fat-pointer que pode ser guardado), não um valor-função armazenado — o §14
proíbe o segundo, então um gerador não pode ser um campo fn() -> T. Pelo mesmo motivo gen.map recebe uma
interface Mapper[A, B], não um fn(A) -> B. Essa é a forma nativa-da-linguagem da superfície de combinadores.
// samplers de valor (retornam um valor; use direto num corpo @property)fn gen.int() -> intfn gen.int_range(lo: int, hi: int) -> intfn gen.float() -> floatfn gen.bool() -> bool
// combinadores Gen[T] (retornam um OBJETO gerador composável)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] // deriva um gerador de outrofn 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 // valor aleatório AUTO por reflect(T) (§10), structs/enums
pub decl Gen[T] { fn sample() -> T }pub decl Mapper[A, B] { fn apply(x: A) -> B } // o substituto compatível-com-§14 de `fn(A) -> B`Um string aleatório vem de gen.derived[string]() (a reflexão cobre o kind String); a superfície de combinadores
ainda não tem um gen.strings() dedicado.
@property com parâmetros usa gen.derived[T]() por default (o mesmo reflect que deriva Serializable/
hash); você passa geradores explícitos quando quiser distribuição custom.
@fuzz: fuzzing contínuo
Seção intitulada “@fuzz: fuzzing contínuo”Harness estilo go test -fuzz: alimenta entradas mutadas (coverage-guided) procurando crash ou violação:
@fuzzfn fuzz_parser(data: []byte) { _ := parse(data) // o runner muta 'data'; falha = crash/panic capturado}fn fuzz_roundtrip(data: []byte) { match decode(data) { Err(_) => {} // entrada inválida: ok, ignora Ok(v) => test.expect_eq(decode(encode(v)).or_panic(), v) // válida: round-trip tem que fechar }}O corpus inicial vai em tests/fuzz/<nome>/; o runner persiste os casos que aumentam cobertura (e o que
crashou vira caso de regressão permanente).
Curadoria
Seção intitulada “Curadoria”@propertye@fuzzsão adição aotest: mesmo modelo (decorator de core declara, asserções são funções detest, isolação por processo). Não é runtime de teste novo.- Shrinking é a feature: um contra-exemplo de 500 elementos é inútil; o runner encolhe ao mínimo que
ainda falha. Auto pros tipos gerados; override via
Shrinker[T]. - Geradores derivados por reflexão:
gen.derived[T]()reusa oreflect(T)que já derivaSerializable/hash; você só escreve gerador à mão para distribuição custom. - Comparador e semântica fixos (como
@bench): anti-p-hacking; você controla o esforço (runs/tempo), não a régua de falha.