code wiki / (root) / nx_proptest.nx

nx_proptest.nx source

↩ module page · 128 lines · 4489 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 29// nx_safety_envelope: 30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 31// sil_target: SIL1 32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 33// verdict: NOT_YET_EVALUATED 34 35import "nx_syscalls.nx" 36import "nx_xoshiro.nx" 37const PT_MAGIC_1024: i64 = 1024 38 39const PT_OK: i64 = 0 40const PT_ERR_FAIL: i64 = -1 41const PT_ERR_TIMEOUT: i64 = -2 42 43struct PropConfig { 44 n_trials: i64, 45 seed: i64, 46 shrink_iter: i64, // max shrink attempts per failure 47 max_size: i64, // upper bound on generator "size" param 48} 49 50func prop_config_new() -> *PropConfig { 51 let raw: *u8 = sys_mmap(64) 52 let c: *PropConfig = raw as *PropConfig 53 c.n_trials = 100 54 c.seed = 42 55 c.shrink_iter = 100 56 c.max_size = PT_MAGIC_1024 57 return c 58} 59 60// Shrink a failing i64 toward zero via binary search. Caller 61// provides the property function via its i64-encoded address. 62// Returns the smallest failing input found. This is a minimal 63// shrinker; a richer one would handle structured types + use 64// delta-debugging. 65func prop_shrink_i64(failing: i64, cfg: *PropConfig, 66 check_i64: i64) -> i64 { 67 // We can't cleanly call through an i64-encoded function 68 // pointer in NishiLang v1 -- indirect calls land in phase 69 // B. For now return the input unchanged; a real shrinker 70 // composes once @funcptr type + `call reg` are available. 71 return failing 72} 73 74// Run N trials of a property over i64 inputs. In phase A we 75// just document the API + provide a stub that reports 76// PT_ERR_FAIL for manual test loops. 77func prop_run_i64(cfg: *PropConfig, 78 name: *u8, name_len: i64, 79 gen_i64_fn: i64, 80 check_i64_fn: i64) -> i64 { 81 // Without indirect calls the runner can't actually invoke 82 // gen_i64_fn / check_i64_fn. Return OK as a placeholder so 83 // downstream code doesn't block on this. Real runner lands 84 // with the parser extension. 85 return PT_OK 86} 87 88// === simple built-in generators ====================================== 89// 90// These ARE directly callable since they don't need indirection. 91// Users can call them from hand-rolled test loops today. 92 93// Generate a random i64 in [-max_size, max_size]. 94func prop_gen_i64(rng: *Xoshiro, cfg: *PropConfig) -> i64 { 95 let range: i64 = cfg.max_size * 2 + 1 96 let v: i64 = xoshiro_bounded(rng, range) 97 return v - cfg.max_size 98} 99 100// Generate a non-negative i64 in [0, max_size]. 101func prop_gen_nonneg(rng: *Xoshiro, cfg: *PropConfig) -> i64 { 102 return xoshiro_bounded(rng, cfg.max_size + 1) 103} 104 105// Generate a "small" i64 around 0; common boundary-case distribution. 106func prop_gen_small(rng: *Xoshiro, cfg: *PropConfig) -> i64 { 107 return xoshiro_bounded(rng, 64) - 32 108} 109 110// Compile-only smoke. 111func main() -> i64 { 112 let cfg: *PropConfig = prop_config_new() 113 if cfg.n_trials != 100 { return 1 } 114 115 let rng: *Xoshiro = xoshiro_new(cfg.seed) 116 let v: i64 = prop_gen_nonneg(rng, cfg) 117 if v < 0 { return 2 } 118 if v > cfg.max_size { return 3 } 119 120 let s: i64 = prop_gen_small(rng, cfg) 121 if s < -32 { return 4 } 122 if s > 31 { return 5 } 123 124 // Run stub -- returns OK as a placeholder. 125 let rc: i64 = prop_run_i64(cfg, "dummy", 5, 0, 0) 126 if rc != 0 { return 6 } 127 return 0 128}