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
//! High-throughput property-based testing with `derive`, swarm-testing, precise sizing,
//! and full graph-theoretic type analysis over mutually inductive and uninstantiable types.
extern crate alloc;
mod arbitrary;
pub mod fields;
pub mod hash;
mod impls;
mod instantiability;
pub mod multiset;
pub mod panic;
mod persist;
pub mod reflection;
pub mod registration;
mod scc;
mod shrink;
mod size;
mod swarm;
mod unavoidability;
mod union_find;
pub use {
pbt_macros::{Pbt, pbt},
wyrand::WyRand,
};
/// The main property-based testing trait.
#[expect(
clippy::absolute_paths,
reason = "to avoid polluting the top-level namespace"
)]
pub trait Pbt: 'static + Clone + core::fmt::Debug {
/// Instantiate a specific variant of this type
/// by providing its index and its fields.
///
/// N.B.: Literal constructors, e.g. on `usize`,
/// should be instantiated using their built-in `generator` field,
/// not through this function, since they don't require fields.
fn construct<F>(parts: reflection::Parts<F>) -> Self
where
F: fields::Fields;
/// Deconstruct a value into its constructor index and its fields.
fn deconstruct(self) -> reflection::Parts<fields::Store>;
/// Enumerate the logical structure of all variants of this type.
///
/// This must *also* register all dependencies of this type.
/// For example, if this type contains fields of types
/// `A`, `B`, and `C`, we'd write the following:
/// ```rust
/// # extern crate alloc;
/// # type A = bool;
/// # type B = usize;
/// # type C = usize;
/// # fn f(registration: &mut pbt::registration::Registration<'_>) {
/// registration.register::<A>();
/// registration.register::<B>();
/// registration.register::<C>();
/// // ... return this type's variants ...
/// # }
/// ```
fn register(registration: &mut registration::Registration<'_>) -> reflection::Variants<Self>;
}
/// Check that deconstructing and then immediately reconstructing a value is a no-op.
#[inline]
pub fn check_eta_expansion<T>()
where
T: PartialEq + Pbt,
{
let mut prng = wyrand::WyRand::new(42);
let Ok(arbitrary) = arbitrary::arbitrary::<T>(&mut prng) else {
return;
};
for t in arbitrary.take(42) {
let parts = t.clone().deconstruct();
let reconstructed = T::construct(parts);
pretty_assertions::assert_eq!(
reconstructed,
t,
"\r\n\r\n{t:?} -> Parts {{ .. }} -> {reconstructed:?} =/= {t:?}",
);
}
}
/// Check that serializing and then immediately deserializing a value is a no-op.
#[inline]
pub fn check_serialization<T>()
where
T: PartialEq + Pbt,
{
let mut prng = wyrand::WyRand::new(42);
let Ok(arbitrary) = arbitrary::arbitrary::<T>(&mut prng) else {
return;
};
for t in arbitrary.take(16) {
let json = t.clone().deconstruct().serialize();
let reconstructed: Option<T> = reflection::Parts::deserialize(&json);
pretty_assertions::assert_eq!(
reconstructed,
Some(t.clone()),
"\r\n\r\n{t:?} -> {json:?} -> {reconstructed:?} =/= Some({t:?})",
);
}
}
/// Search for the smallest witness of an arbitrary property, if one exists.
///
/// If this fails, this does not mean that the property never holds;
/// instead, it simply means we didn't find a property in `cases` cases.
#[inline]
pub fn witness<T, Property, Proof>(
property: Property,
cases: usize,
prng: &mut wyrand::WyRand,
) -> Option<(T, Proof)>
where
Property: Fn(&T) -> Option<Proof>,
T: Pbt,
{
let arbitrary = arbitrary::arbitrary::<T>(prng).ok()?;
for t in arbitrary.take(cases) {
if let Some(proof) = property(&t) {
return Some(shrink::to_minimal_witness(&property, t, proof));
}
}
None
}
#[cfg(test)]
mod tests {
use {super::*, pretty_assertions::assert_eq, wyrand::WyRand};
#[test]
fn witness_at_least_42() {
let mut prng = WyRand::new(42);
assert_eq!(
witness(|i: &usize| i.checked_sub(42), 1_000, &mut prng),
Some((42, 0))
);
}
}