proptest_arbitrary_adapter/lib.rs
1//! Provides the necessary glue to reuse an implementation of
2//! [`arbitrary::Arbitrary`] as a [`proptest::strategy::Strategy`].
3//!
4//! # Usage
5//!
6//! Assuming you use [`test-strategy`](https://crates.io/crates/test-strategy)
7//! (which you should), using a strategy for a type that implements
8//! [`arbitrary::Arbitrary`] is as simple as:
9//!
10//! ```rust
11//! # use test_strategy::proptest;
12//! # #[derive(Debug, Clone, arbitrary::Arbitrary)]
13//! # struct MyType(u8);
14//! #
15//! #[proptest]
16//! fn my_test(#[strategy(arb())] my_type: MyType) {
17//! // …
18//! }
19//! ```
20//!
21//! # Origin
22//!
23//! This code is a copy of the unmaintained crate
24//! [`proptest-arbitrary-interop`][origin], with some additional improvements
25//! from open pull requests of the original's repository.
26//!
27//! [origin]: https://crates.io/crates/proptest-arbitrary-interop
28//!
29//! # Caveats
30//!
31//! It only works with types that implement [`arbitrary::Arbitrary`] in a
32//! particular fashion: those conforming to the requirements of [`ArbInterop`].
33//! These are roughly "types that, when randomly-generated, don't retain
34//! pointers into the random-data buffer wrapped by the
35//! [`arbitrary::Unstructured`] they are generated from". Many implementations
36//! of [`arbitrary::Arbitrary`] will fit the bill, but certain kinds of
37//! "zero-copy" implementations of [`arbitrary::Arbitrary`] will not work. This
38//! requirement appears to be a necessary part of the semantic model of
39//! [`proptest`] – generated values have to own their pointer graph, no
40//! borrows. Patches welcome if you can figure out a way to not require it.
41
42use core::fmt::Debug;
43use std::marker::PhantomData;
44
45use proptest::prelude::RngCore;
46use proptest::test_runner::TestRunner;
47
48/// The subset of possible [`arbitrary::Arbitrary`] implementations that this
49/// crate works with. The main concern here is the `for<'a> Arbitrary<'a>`
50/// business, which (in practice) decouples the generated `Arbitrary` value from
51/// the lifetime of the random buffer it's fed; I can't actually explain how,
52/// because Rust's type system is way over my head.
53pub trait ArbInterop: for<'a> arbitrary::Arbitrary<'a> + 'static + Debug + Clone {}
54impl<A> ArbInterop for A where A: for<'a> arbitrary::Arbitrary<'a> + 'static + Debug + Clone {}
55
56#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
57pub struct ArbStrategy<A: ArbInterop> {
58 size: usize,
59 _ph: PhantomData<A>,
60}
61
62#[derive(Debug)]
63pub struct ArbValueTree<A: Debug> {
64 bytes: Vec<u8>,
65 curr: A,
66 prev: Option<A>,
67 next: usize,
68}
69
70impl<A: ArbInterop> proptest::strategy::ValueTree for ArbValueTree<A> {
71 type Value = A;
72
73 fn current(&self) -> Self::Value {
74 self.curr.clone()
75 }
76
77 fn simplify(&mut self) -> bool {
78 if self.next == 0 {
79 return false;
80 }
81 self.next -= 1;
82 let Ok(simpler) = Self::gen_one_with_size(&self.bytes, self.next) else {
83 return false;
84 };
85
86 // Throw away the previous value and set the current value as prev.
87 // Advance the iterator and set the current value to the next one.
88 self.prev = Some(core::mem::replace(&mut self.curr, simpler));
89
90 true
91 }
92
93 fn complicate(&mut self) -> bool {
94 // We can only complicate if we previously simplified. Complicating
95 // twice in a row without interleaved simplification is guaranteed to
96 // always yield false for the second call.
97 let Some(prev) = self.prev.take() else {
98 return false;
99 };
100
101 // Throw away the current value!
102 self.curr = prev;
103
104 true
105 }
106}
107
108impl<A: ArbInterop> ArbStrategy<A> {
109 pub fn new(size: usize) -> Self {
110 Self {
111 size,
112 _ph: PhantomData,
113 }
114 }
115}
116
117impl<A: ArbInterop> ArbValueTree<A> {
118 fn gen_one_with_size(bytes: &[u8], size: usize) -> Result<A, arbitrary::Error> {
119 A::arbitrary(&mut arbitrary::Unstructured::new(&bytes[0..size]))
120 }
121
122 pub fn new(bytes: Vec<u8>) -> Result<Self, arbitrary::Error> {
123 let next = bytes.len();
124 let curr = Self::gen_one_with_size(&bytes, next)?;
125
126 Ok(Self {
127 bytes,
128 prev: None,
129 curr,
130 next,
131 })
132 }
133}
134
135impl<A: ArbInterop> proptest::strategy::Strategy for ArbStrategy<A> {
136 type Tree = ArbValueTree<A>;
137 type Value = A;
138
139 fn new_tree(&self, run: &mut TestRunner) -> proptest::strategy::NewTree<Self> {
140 loop {
141 let mut bytes = vec![0; self.size];
142 run.rng().fill_bytes(&mut bytes);
143 match ArbValueTree::new(bytes) {
144 Ok(v) => return Ok(v),
145
146 // If the Arbitrary impl cannot construct a value from the given
147 // bytes, try again.
148 Err(e @ arbitrary::Error::IncorrectFormat) => run.reject_local(format!("{e}"))?,
149 Err(e) => return Err(format!("{e}").into()),
150 }
151 }
152 }
153}
154
155/// Constructs a [`proptest::strategy::Strategy`] for a given
156/// [`arbitrary::Arbitrary`] type, generating `size` bytes of random data as
157/// input to the [`arbitrary::Arbitrary`] type.
158pub fn arb_sized<A: ArbInterop>(size: usize) -> ArbStrategy<A> {
159 ArbStrategy::new(size)
160}
161
162/// Constructs a [`proptest::strategy::Strategy`] for a given
163/// [`arbitrary::Arbitrary`] type.
164///
165/// Calls [`arb_sized`] with a best-effort guess for the size, generating `size`
166/// bytes of random data as input to the [`arbitrary::Arbitrary`] type.
167///
168/// In particular, if `A`'s [`size_hint`](arbitrary::Arbitrary::size_hint) is
169/// useful, the hint is used; otherwise, a default size of 256 is used.
170pub fn arb<A: ArbInterop>() -> ArbStrategy<A> {
171 let (low, opt_high) = A::size_hint(0);
172 let Some(high) = opt_high else {
173 let size_hint = (2 * low).max(256);
174 return arb_sized(size_hint);
175 };
176
177 arb_sized(high)
178}
179
180#[cfg(test)]
181#[cfg_attr(coverage_nightly, feature(coverage_attribute))]
182mod tests {
183 use arbitrary::Arbitrary;
184 use proptest::prelude::*;
185 use test_strategy::proptest;
186
187 use super::*;
188
189 #[derive(Debug, Clone, Arbitrary)]
190 struct Test(u8);
191
192 #[proptest(cases = 1)]
193 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
194 fn type_can_be_generated(#[strategy(arb())] test: Test) {
195 let Test(_t) = test;
196 }
197
198 // As far as I know, `wasm_bindgen_test` does not support the
199 // `#[should_panic]` attribute:
200 // https://github.com/wasm-bindgen/wasm-bindgen/issues/2286
201 #[should_panic]
202 #[proptest(cases = 1)]
203 fn type_can_shrink(#[strategy(arb())] _test: Test) {
204 Err(TestCaseError::Fail("always".into()))?;
205 }
206}