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
150
151
152
153
154
155
156
157
158
//! Property-based test runner.
use AssertUnwindSafe;
use crate::;
/// A property-based test runner.
///
/// [`Runner`] is a small config struct with public fields. Construct it
/// with a struct literal and call [`run`](Runner::run):
///
/// ```
/// let _: noprop::Result<()> = noprop::Runner { seed: 0xDEAD_BEEF, iterations: 16 }.run(|rng| {
/// let x = noprop::gen_u32(rng);
/// assert_eq!(x, x);
/// Ok(())
/// });
/// ```
///
/// Named-field construction avoids the two-numeric-args swap risk of a
/// positional `new(seed, iterations)`.
///
/// Other PBT libraries call the same count `cases` (proptest),
/// `examples` (Hypothesis), or `tests` (QuickCheck). noprop uses
/// `iterations` for a direct match with the Rust `Iterator` /
/// benchmark vocabulary and to avoid visual confusion with `#[test]`.
///
/// # Configuring seed and iterations
///
/// [`Runner`] takes `seed` and `iterations` as required fields and does
/// not prescribe how to obtain them. A common setup reads both from
/// project-specific environment variables so that failures are
/// reproducible from a failure report (via the seed) and the iteration
/// count can differ between local and CI runs:
///
/// ```
/// fn seed() -> u64 {
/// std::env::var("MYAPP_SEED")
/// .ok()
/// .and_then(|s| s.parse().ok())
/// .unwrap_or_else(|| {
/// std::time::SystemTime::now()
/// .duration_since(std::time::UNIX_EPOCH)
/// .map(|d| d.as_nanos() as u64)
/// .unwrap_or(0)
/// })
/// }
///
/// fn iterations() -> usize {
/// std::env::var("MYAPP_ITERATIONS")
/// .ok()
/// .and_then(|s| s.parse().ok())
/// .unwrap_or(256)
/// }
///
/// let _: noprop::Result<()> = noprop::Runner { seed: seed(), iterations: iterations() }
/// .run(|_rng| {
/// // property
/// Ok(())
/// });
/// ```
///
/// The env var names shown above are project-specific placeholders;
/// pick names that fit the calling project.
///
/// # Failing a case via `Err` or panic
///
/// The property closure signals success by returning `Ok(())`. A
/// failure can be signalled either by returning `Err` or by panicking
/// (typically via `assert!` / `assert_eq!`); both are captured into the
/// resulting [`Error`] uniformly.
///
/// The `Err` variant is `Box<dyn std::error::Error>`, so the `?`
/// operator works for any error type that implements [`Error`]:
///
/// ```
/// let _: noprop::Result<()> = noprop::Runner { seed: 0, iterations: 1 }.run(|_rng| {
/// let _n: u32 = "42".parse()?; // ParseIntError -> Box<dyn Error>
/// Ok(())
/// });
/// ```
///
/// Ad-hoc messages work via `Into`:
///
/// ```
/// let _: noprop::Result<()> = noprop::Runner { seed: 0, iterations: 1 }.run(|_rng| {
/// if false { return Err("something bad".into()); }
/// Ok(())
/// });
/// ```
///
/// [`Error`]: std::error::Error
/// Extract a human-readable message from a `catch_unwind` payload.
///
/// Panic payloads come in two common shapes (`&'static str` and `String`).
/// Anything else collapses to a placeholder — this is not a typical case,
/// but we don't want to swallow the failure entirely.