noprop
An imperative property-based testing library for Rust.
- Expressive with a small imperative API
- A property samples values and checks results directly with ordinary Rust functions, control flow, state, and assertions. The same building blocks scale from simple properties to stateful tests.
- No combinator DSL, derive macros, or separate stateful framework is required.
- Explicit control over the search space
- A well-designed search space is essential: bug-triggering cases must be reachable and likely enough to occur.
- noprop keeps search-space decisions in the property code: what can be generated, how often each path is chosen, and how later choices depend on earlier draws or the current state.
- With the imperative API, a coverage gate is an ordinary Rust check. It verifies that the run exercised an important region of the search space, failing the test if no case reaches the relevant assertion.
- No dependencies
Quick start
On failure, copy the reported seed into MYAPP_PROPTEST_SEED and rerun
the same test with the same case budget to reproduce the failing case.
When noprop fits
noprop is especially direct when a property has one or more of these shapes:
- The next value depends on an earlier length, variant, setting, or the current state
- A command sequence compares a model with the system under test after every transition
- History and operation order affect a protocol, parser, or streaming API
- Empty, singleton, maximum, or other domain boundaries need explicit probability
- A coverage gate must prevent success when an important invariant never runs
Designing the search space
First make every relevant input and operation sequence reachable. Then
shape how often they occur: use sample_with_boundaries for domain
boundaries, sample_weighted_index for branches or commands, and ordinary
control flow for values that depend on earlier draws or the current state.
A property can pass vacuously when no case reaches an important assertion.
Count evidence only after the important check succeeds, then assert after
Runner::run that the count is non-zero. This post-run assertion is a
coverage gate.
Adjust support, boundary probabilities, and branch weights before increasing the case budget. See Recipes for concrete patterns and Generator design for support, distribution, termination, and rejection scope.
Reproducing failures and constraints
A failure report contains the seed, case index, generated-value trace, and the original case budget in a reproduce hint. Reuse the same seed, case budget, property closure, and relevant external configuration. The trace and semantic assertion message identify the failing inputs and transition.
noprop deliberately has a small contract:
- Failing inputs are not automatically shrunk; simplify a reproduced witness by hand and freeze it as a regular regression test
- Failure seeds are not persisted to files; the caller owns the seed and case budget
- Values for user-defined structs and enums are not generated from their type definitions; write samplers as ordinary Rust functions so their dependencies, boundaries, and distributions stay explicit
- Macros and a combinator DSL are not provided
If automatically minimizing a failing input is a requirement, consider a property-testing library that provides shrinking.
Where to look next
- Recipes — task-oriented patterns for seed and run scaffolding, sampling, rejection, stateful properties, coverage gates, and failure diagnosis
- Generator design — how to choose a generator's support, distribution, termination, and rejection scope
- Generator authoring — how to write
reusable
sample_*functions from noprop's primitives - API reference — every public function and type
- Runnable examples — end-to-end properties:
basics.rs— the minimal property shape and an environment-controlled seedsearch_space.rs— dependent draws, boundary probabilities, branch weights, and coverage gatesstateful.rs— state-dependent commands and per-transition model / SUT checksreproduce.rs— replaying a failure from its seed and report; this example fails on purpose
- noprop skill — search-space design and API
guidance for supported AI coding agents; install it with
gh skill install sile/noprop noprop
The Markdown guides also render as docs::* modules on docs.rs.