Expand description
Imperative property-based testing with no dependencies, macros, unsafe code, or implicit I/O.
A property is a plain Rust closure that samples values and asserts
on the results. Functions, if, match, and for compose simple
inputs, dependent inputs, and stateful operation sequences without
a combinator DSL or a separate stateful-testing framework. Every
sample_* call records its value at the caller’s source location,
so a failure includes the generated inputs without extra plumbing.
§Quick start
let seed = noprop::seed_from_env_or_time("MYAPP_PROPTEST_SEED")?;
noprop::Runner::new(seed).run(1024, |ctx| {
let a = noprop::sample_u32(ctx);
let b = noprop::sample_u32(ctx);
assert_eq!(a.wrapping_add(b), b.wrapping_add(a));
Ok(())
})?;The seed is caller-supplied. seed_from_env_or_time is an
explicit convenience for the common test setup: an environment
variable overrides a clock-derived fallback. A reported hex seed
can be pasted into that variable and replayed.
§Designing a property
The search space is the set of inputs and operation sequences that the property can generate. A bug-triggering case must be in that set and receive enough probability to occur within the case budget.
Draw dependent values in order. For example, draw a variant before
fields whose valid range depends on that variant, or inspect the
current model state before selecting the next valid command. Use
sample_with_boundaries to assign exact probability to domain
boundaries and sample_weighted_index to keep branch or command
weights visible in the property.
For a stateful property, create a fresh model and system under test inside each case, drive both through the same bounded command loop, and compare results and state after every meaningful transition. The stateful example demonstrates state-dependent command selection and non-mutating per-transition observations.
Runner::run accepts Fn, not FnMut, so ordinary mutable
captures cannot accidentally carry state between cases. When a
cross-case observation is intentional, such as a coverage gate,
use interior mutability (Cell, RefCell, or an atomic). Increment
the gate only after the relevant check passes, then assert after the
run that the gate was reached. The search-space example
shows the complete pattern.
§Reproducing a failure
A property failure (panic, returned Err, or too-many-rejections
exit) becomes a RunError carrying the seed, the case index, the
recorded value trace, and observability Stats. Both Debug and
Display include a reproduce hint that reuses the original case
budget:
reproduce with: noprop::Runner::new(0x...).run(N, |ctx| ...)Copy the seed into the runner (or its environment variable), then
rerun with the same case budget, property closure, and relevant
external configuration. The run reaches the same failure case
index. See Runner::run’s “Reproducibility” note for the full
determinism contract and the reproduction example
for a deliberately failing walkthrough.
§Requirements and constraints
- No automatic shrinking. Failures are reproduced from
the seed and the case budget; to freeze a specific case as a
regression test, simplify it by hand from the value trace and
inline the witness as a regular
#[test]. Seedocs::recipes(“Turn a trace into a hand-written regression test”) for the pattern. - No file-based failure persistence. The caller manages the seed and case budget; there is no on-disk seed corpus.
- No type-derived generation. noprop does not generate values for user-defined structs or enums from their definitions. Write an ordinary Rust sampling function so dependencies, boundaries, and distributions remain explicit.
- No macros or combinator DSL. Properties and reusable samplers are ordinary Rust closures and functions.
If automatically minimizing a failing input is a requirement, consider a property-testing library that provides shrinking.
§Where to look next
docs::recipes— task-oriented recipes for common property shapes (sampling, collections, rejection scopes, stateful, coverage gates, reproduction).docs::generator_design— the small design decisions everysample_*generator has to make (support, distribution, termination, rejection scope, valid-by-construction).docs::generator_authoring— how-to guide for writingsample_*helpers: composing primitives, bounded rejection,NonZerorecipes, and the finite-by-default float samplers.- Runnable end-to-end demos:
basics.rsfor the minimal shape,search_space.rsfor explicit search-space design,stateful.rsfor model-based command loops, andreproduce.rsfor failure replay. Each runs withcargo run --example <name>;reproducefails on purpose. - The failure-diagnostics workflow explains how to reproduce, diagnose, reduce, and freeze a failure.
Modules§
- docs
- Supplemental documentation for noprop’s design.
Structs§
- Generated
Value - A single entry in the generated-value trace collected during a case.
- Ratio
- An exact rational probability
numerator / denominator. - RunError
- Failure information from a
Runner::runinvocation. - Runner
- A property-based test runner.
- Stats
- Observability data from a
Runner::runinvocation. - Test
Case Context - Per-case state threaded through every generator and property closure.
Enums§
- RunError
Kind - The kind of a
RunError, for type-safe dispatch on the failure mode.
Functions§
- sample_
ascii_ char - Uniformly-distributed ASCII
char(0x00..=0x7F, including control characters). - sample_
ascii_ printable_ char - Uniformly-distributed printable ASCII
char(0x20..=0x7E, space through~, excluding control characters and DEL). - sample_
ascii_ printable_ string - Uniformly-distributed printable-ASCII
Stringof exactlylencode points (0x20..=0x7E, space through~). Each character is produced by callingsample_ascii_printable_charonce. - sample_
ascii_ string - Uniformly-distributed ASCII
Stringof exactlylencode points (0x00..=0x7F, including control characters). Each character is produced by callingsample_ascii_charonce. - sample_
bool - Uniformly-distributed
bool. - sample_
bytes - Uniformly-distributed fixed-size byte array.
- sample_
bytes_ vec - Uniformly-distributed
Vec<u8>of lengthlen. - sample_
char - Uniformly-distributed
charover the valid Unicode scalar values (0..=0x10FFFF, excluding the surrogate range0xD800..=0xDFFF). - sample_
choice - Pick one element from
choicesuniformly at random. - sample_
f32 - Uniformly-distributed finite
f32over the full finite domain (excludesNaNand±∞; includes normals, subnormals, and both signed zeros). - sample_
f64 - Uniformly-distributed finite
f64over the full finite domain. Same conventions and rationale assample_f32; the rejection rate overu64bit patterns is even lower (~2⁻¹¹ of patterns are non-finite). - sample_
f32_ in - Uniformly-distributed
f32in[min, max). - sample_
f64_ in - Uniformly-distributed
f64in[min, max). - sample_
i8 - Uniformly-distributed
i8. - sample_
i8_ in - Uniformly-distributed
i8insiderange. - sample_
i16 - Uniformly-distributed
i16. - sample_
i32 - Uniformly-distributed
i32. - sample_
i64 - Uniformly-distributed
i64. - sample_
i16_ in - Uniformly-distributed
i16insiderange. - sample_
i32_ in - Uniformly-distributed
i32insiderange. - sample_
i128 - Uniformly-distributed
i128. - sample_
isize - Uniformly-distributed
isize. - sample_
ratio - Return
truewith probabilityratio. - sample_
string - Uniformly-distributed
Stringof exactlylenUnicode scalar values. Each code point is produced by callingsample_charonce. - sample_
u8 - Uniformly-distributed
u8. - sample_
u8_ in - Uniformly-distributed
u8insiderange, with the result type the caller needs so no cast appears at the call site. - sample_
u16 - Uniformly-distributed
u16. - sample_
u32 - Uniformly-distributed
u32. - sample_
u64 - Uniformly-distributed
u64. - sample_
u16_ in - Uniformly-distributed
u16insiderange, with the result type the caller needs so no cast appears at the call site. - sample_
u32_ in - Uniformly-distributed
u32insiderange, with the result type the caller needs so no cast appears at the call site. - sample_
u64_ in - Uniformly-distributed
u64insiderange. - sample_
u128 - Uniformly-distributed
u128. - sample_
usize - Uniformly-distributed
usize. - sample_
usize_ in - Uniformly-distributed
usizeinsiderange. - sample_
weighted_ index - Pick an index from
weightswith probability proportional to each weight. - sample_
with_ boundaries - Return a value from
boundarieswith probabilityratio, otherwise draw fromsample. - sample_
with_ rejection - Repeatedly invoke
attemptup tomax_attemptstimes until it returnsSome, then return that value. On exhaustion (all attempts returnedNone) this callsTestCaseContext::reject_case, which unwinds out to the enclosingRunner::runand marks the current iteration as rejected — so this function’s return type isT, notOption<T>. - seed_
from_ env_ or_ time - Read
varas au64seed, or derive one from the current time when it is not set.
Type Aliases§
- RunResult
- Result alias for
Runner::run. - Test
Result - Result alias for
#[test]functions and property closures.