1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Imperative property-based testing library with no dependencies, no
//! macros, no unsafe, and no implicit I/O.
//!
//! Properties are plain
//! `Fn(&mut TestCaseContext) -> Result<(), Box<dyn Error>>` closures
//! that use ordinary Rust control flow — `if` / `match` / `for` — to
//! draw values and check them, so a test reads like the code it
//! exercises. Every `noprop::sample_*` call records the produced
//! value at its source location, so a failure surfaces the actual
//! generated input without extra plumbing.
//!
//! # Quick start
//!
//! ```
//! # fn body() -> noprop::TestResult {
//! noprop::Runner::new(0xDEAD_BEEF).run(256, |ctx| {
//! let a = noprop::sample_u32(ctx);
//! let b = noprop::sample_u32(ctx);
//! assert_eq!(a.wrapping_add(b), b.wrapping_add(a));
//! Ok(())
//! })?;
//! # Ok(())
//! # }
//! # body().unwrap();
//! ```
//!
//! The seed is always caller-supplied; noprop never reads the system
//! clock or environment on its own. For a `#[test]` that stays
//! reproducible from a failure report, read the seed from an
//! environment variable and fall back to the clock:
//!
//! ```
//! # fn body() -> noprop::TestResult {
//! let seed = noprop::seed_from_env_or_time("MYAPP_SEED")?;
//! noprop::Runner::new(seed).run(256, |_ctx| Ok(()))?;
//! # Ok(())
//! # }
//! # body().unwrap();
//! ```
//!
//! Pick a project-specific variable name — the hex seed printed by a
//! failure report can be pasted into it verbatim.
//!
//! # Choosing a runner
//!
//! - [`Runner::run`] samples inputs uniformly. This is the default;
//! use it unless a property has a rare region that uniform sampling
//! would only reach by luck.
//! - [`Runner::run_feedback_guided`] steers the search toward inputs
//! that report new semantic features via
//! [`TestCaseContext::event`], [`bucket`](TestCaseContext::bucket),
//! and [`transition`](TestCaseContext::transition). The property
//! closure has the same shape as `run`, and the feedback methods are
//! allocation-free no-ops under `Runner::run`, so the same property
//! can be exercised under both entry points. The design is
//! documented in [`docs::feedback_guided_search`].
//!
//! # 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:
//!
//! ```text
//! reproduce with: noprop::Runner::new(0x...).run(N, |ctx| ...)
//! ```
//!
//! Copy the seed into the runner (or into the env variable read by
//! `seed_from_env_or_time`) and rerun with the same case budget and
//! the same property closure — the run reproduces the identical
//! failure case index. The [`examples/reproduce.rs`][reproduce] file
//! walks through the full workflow with a deliberately failing
//! property.
//!
//! [reproduce]: https://github.com/sile/noprop/blob/main/examples/reproduce.rs
//!
//! # Requirements and constraints
//!
//! - **`panic=unwind`.** noprop catches property panics and uses
//! panic-based unwinding for
//! [`TestCaseContext::reject_case`], so the crate does not work
//! under `panic=abort`.
//! - **No automatic shrinking in v0.1.** 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]`. See
//! [`docs::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.
//! - **Static argument errors panic, external input errors return
//! `Result`.** Generator misuse
//! (`sample_with_rejection(ctx, 0, _)`, `Ratio::new(2, 1)` called
//! on an unchecked input) is a caller bug and panics with a
//! `#[track_caller]` message. Environment or config errors that a
//! user can hit at runtime (a mistyped `MYAPP_SEED`) return
//! `Result`.
//! - **`sample_with_rejection` attempt budgets are explicit.** Every
//! call names its own `max_attempts`; there is no library-wide
//! default.
//!
//! noprop is imperative-first and does not replace a general PBT
//! library where automatic shrinking or seed persistence is the
//! priority. It fits well when the property is naturally sequential
//! (dependent generation, stateful commands, protocol traces) and
//! reads more clearly as plain Rust than as a combinator DSL.
//!
//! # Where to look next
//!
//! - [`docs::recipes`] — task-oriented recipes for common property
//! shapes (sampling, collections, rejection scopes, stateful,
//! feedback-guided, reproduction).
//! - [`docs::generator_design`] — the small design decisions every
//! `sample_*` generator has to make (support, distribution,
//! termination, rejection scope, valid-by-construction).
//! - [`docs::feedback_guided_search`] — the design of
//! [`Runner::run_feedback_guided`], the corpus admission and
//! eviction rules, and how the feature registry is bounded.
//! - The [`examples/`][examples] directory ships runnable end-to-end
//! demos: `basics.rs`, `stateful.rs`, `feedback_guided.rs`,
//! `rejection.rs`, `reproduce.rs`. Each runs with
//! `cargo run --example <name>`.
//!
//! [examples]: https://github.com/sile/noprop/tree/main/examples
pub use seed_from_env_or_time;
pub use ;
pub use ;
pub use ;
pub use ;