/// @module std::core::utils::property_testing
/// Property-Based Testing
///
/// Generates random inputs to test invariants (properties) that should
/// hold for all inputs. Inspired by QuickCheck/Hypothesis.
/// Run a property test with random inputs.
///
/// @param name - Test name for reporting
/// @param n_trials - Number of random inputs to try
/// @param gen_fn - Generator function () => input value
/// @param prop_fn - Property function (input) => bool
/// @returns { passed, name, trials, counterexample }
pub fn property(name, n_trials, gen_fn, prop_fn) {
var counterexample = None;
var passed = true;
for i in range(0, n_trials) {
let input = gen_fn();
let result = prop_fn(input);
if !result {
counterexample = input;
passed = false;
break;
}
}
{
passed: passed,
name: name,
trials: n_trials,
counterexample: counterexample
}
}
/// Run multiple property tests and return a summary.
///
/// @param tests - Array of { name, trials, gen, prop } objects
/// @returns { passed, failed, results }
///
/// Note: `Array<int>` is the strict-typing-required concrete element type
/// for the empty-array accumulator (Cluster C of R8 W6 audit). The real
/// payload is the TypedObject returned by `property()`, but the strict
/// typing pass rejects anonymous typed-object and named typed-object
/// empty-array bindings at module-load time; only primitive element
/// annotations are honored. End-to-end behaviour for this function is
/// blocked by Cluster B (generic-arithmetic inference loss in `property`
/// callees) and is tracked alongside the broader pure-shape stdlib
/// inference fix follow-up.
pub fn run_properties(tests) {
let mut results: Array<int> = [];
var passed_count = 0;
var failed_count = 0;
for test in tests {
let result = property(test.name, test.trials, test.gen, test.prop);
results.push(result);
if result.passed {
passed_count = passed_count + 1;
} else {
failed_count = failed_count + 1;
}
}
{
passed: passed_count,
failed: failed_count,
total: tests.len(),
results: results
}
}
// ===== Built-in Generators =====
/// Generate random integer in [lo, hi]
pub fn gen_int(lo: number, hi: number) {
|| __intrinsic_random_int(lo, hi)
}
/// Generate random float in [lo, hi)
pub fn gen_float(lo: number, hi: number) {
|| {
let r: number = __intrinsic_random();
let span: number = hi - lo;
lo + r * span
}
}
/// Generate random boolean
pub fn gen_bool() {
|| {
let r: number = __intrinsic_random();
r < 0.5
}
}
/// Generate random string of given length from ascii letters
pub fn gen_string(max_len: number) {
|| {
let chars: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let n: number = __intrinsic_random_int(0, max_len);
var s: string = "";
for i in range(0, n) {
let idx: number = __intrinsic_random_int(0, 61);
let ch: string = chars[idx];
s = s + ch;
}
s
}
}
/// Generate random array of given max length using element generator.
///
/// Note: strict typing requires a concrete element type for the empty-array
/// accumulator. `Array<int>` is chosen as the representative concrete element
/// (matches `gen_int`); generators returning other types are v0.3-gating until
/// generic empty-array element-type inference (Cluster C of R8 W6 audit, with
/// the broader generic-arithmetic inference loss in Cluster B) is implemented.
pub fn gen_array(max_len: number, elem_gen) {
|| {
let n = __intrinsic_random_int(0, max_len);
let mut arr: Array<int> = [];
for i in range(0, n) {
arr.push(elem_gen());
}
arr
}
}
/// Generate a value picked uniformly from a list of choices
pub fn gen_one_of(choices: Array<int>) {
|| {
let idx = __intrinsic_random_int(0, choices.len() - 1);
choices[idx]
}
}