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}