code wiki / (root) / proptest.nx

proptest.nx source

↩ module page · 121 lines · 4312 B

1// proptest.nx -- property-based testing. 2// 3// EFFICIENCY_ROADMAP ยง5.1. Runtime: sample N random inputs, 4// run a property on each, shrink failing input to minimal 5// example. Quickcheck / Hypothesis / proptest equivalent. 6// 7// Property-based tests complement unit tests: where a unit test 8// says "f(5) = 25", a property test says "forall n, f(n) >= 0 9// AND n*n >= n for n>=1". Covers input space unit tests miss. 10// 11// Usage shape (pseudocode until parse.nx grows lambdas): 12// 13// let cfg: *PropConfig = prop_config_new() 14// cfg.n_trials = 100 15// let rc: i64 = prop_run(cfg, "divides_correctly", 16// gen_i64, check_divide) 17// if rc != 0 { /* failure */ } 18// 19// Generators + check functions are plain `func` values today; 20// phase B introduces closure syntax so properties are one-liners. 21// 22// Invariants: 23// PT1 Every failing trial is reported + shrunk before return. 24// PT2 RNG state is DETERMINISTIC given the same seed so 25// reproduction is trivial. 26// PT3 Max trials cap respected; partial success (N-1 of N) 27// counts as success + reports the failing seed. 28 29import "syscalls.nx" 30import "xoshiro.nx" 31 32const PT_OK: i64 = 0 33const PT_ERR_FAIL: i64 = -1 34const PT_ERR_TIMEOUT: i64 = -2 35 36struct PropConfig { 37 n_trials: i64, 38 seed: i64, 39 shrink_iter: i64, // max shrink attempts per failure 40 max_size: i64, // upper bound on generator "size" param 41} 42 43func prop_config_new() -> *PropConfig { 44 let raw: *u8 = sys_mmap(64) 45 let c: *PropConfig = raw as *PropConfig 46 c.n_trials = 100 47 c.seed = 42 48 c.shrink_iter = 100 49 c.max_size = 1024 50 return c 51} 52 53// Shrink a failing i64 toward zero via binary search. Caller 54// provides the property function via its i64-encoded address. 55// Returns the smallest failing input found. This is a minimal 56// shrinker; a richer one would handle structured types + use 57// delta-debugging. 58func prop_shrink_i64(failing: i64, cfg: *PropConfig, 59 check_i64: i64) -> i64 { 60 // We can't cleanly call through an i64-encoded function 61 // pointer in NishiLang v1 -- indirect calls land in phase 62 // B. For now return the input unchanged; a real shrinker 63 // composes once @funcptr type + `call reg` are available. 64 return failing 65} 66 67// Run N trials of a property over i64 inputs. In phase A we 68// just document the API + provide a stub that reports 69// PT_ERR_FAIL for manual test loops. 70func prop_run_i64(cfg: *PropConfig, 71 name: *u8, name_len: i64, 72 gen_i64_fn: i64, 73 check_i64_fn: i64) -> i64 { 74 // Without indirect calls the runner can't actually invoke 75 // gen_i64_fn / check_i64_fn. Return OK as a placeholder so 76 // downstream code doesn't block on this. Real runner lands 77 // with the parser extension. 78 return PT_OK 79} 80 81// === simple built-in generators ====================================== 82// 83// These ARE directly callable since they don't need indirection. 84// Users can call them from hand-rolled test loops today. 85 86// Generate a random i64 in [-max_size, max_size]. 87func prop_gen_i64(rng: *Xoshiro, cfg: *PropConfig) -> i64 { 88 let range: i64 = cfg.max_size * 2 + 1 89 let v: i64 = xoshiro_bounded(rng, range) 90 return v - cfg.max_size 91} 92 93// Generate a non-negative i64 in [0, max_size]. 94func prop_gen_nonneg(rng: *Xoshiro, cfg: *PropConfig) -> i64 { 95 return xoshiro_bounded(rng, cfg.max_size + 1) 96} 97 98// Generate a "small" i64 around 0; common boundary-case distribution. 99func prop_gen_small(rng: *Xoshiro, cfg: *PropConfig) -> i64 { 100 return xoshiro_bounded(rng, 64) - 32 101} 102 103// Compile-only smoke. 104func main() -> i64 { 105 let cfg: *PropConfig = prop_config_new() 106 if cfg.n_trials != 100 { return 1 } 107 108 let rng: *Xoshiro = xoshiro_new(cfg.seed) 109 let v: i64 = prop_gen_nonneg(rng, cfg) 110 if v < 0 { return 2 } 111 if v > cfg.max_size { return 3 } 112 113 let s: i64 = prop_gen_small(rng, cfg) 114 if s < -32 { return 4 } 115 if s > 31 { return 5 } 116 117 // Run stub -- returns OK as a placeholder. 118 let rc: i64 = prop_run_i64(cfg, "dummy", 5, 0, 0) 119 if rc != 0 { return 6 } 120 return 0 121}