1#![doc(
2 issue_tracker_base_url = "https://github.com/jockbert/monkey_test/issues/"
3)]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/jockbert/monkey_test/main/assets/doc/logo-256.png"
6)]
7#![doc(
8 html_favicon_url = "https://raw.githubusercontent.com/jockbert/monkey_test/main/assets/doc/logo.ico"
9)]
10#![warn(missing_docs)]
11#![doc = include_str!("../DOCUMENTATION.md")]
12
13mod config;
14pub mod gens;
15mod runner;
16pub mod shrinks;
17
18#[cfg(test)]
19mod testing;
20
21pub use config::*;
23
24pub fn monkey_test() -> Conf {
36 Conf::default()
37}
38
39pub type BoxIter<E> = Box<dyn Iterator<Item = E>>;
41
42pub type BoxShrink<E> = Box<dyn Shrink<E>>;
44
45pub type BoxGen<E> = Box<dyn Gen<E>>;
47
48pub type Property<E> = fn(E) -> bool;
50
51#[doc(hidden)]
53pub trait CloneGen<E> {
54 fn clone_box(&self) -> BoxGen<E>;
55}
56
57impl<E: Clone + 'static, T> CloneGen<E> for T
58where
59 T: Gen<E> + Clone + 'static,
60{
61 fn clone_box(&self) -> BoxGen<E> {
62 Box::new(self.clone())
63 }
64}
65
66impl<E: Clone> Clone for BoxGen<E> {
67 fn clone(&self) -> Self {
68 self.clone_box()
69 }
70}
71
72#[doc(hidden)]
74pub trait CloneShrink<E> {
75 fn clone_box(&self) -> BoxShrink<E>;
76}
77
78impl<E: Clone + 'static, T> CloneShrink<E> for T
79where
80 T: Shrink<E> + Clone + 'static,
81{
82 fn clone_box(&self) -> BoxShrink<E> {
83 Box::new(self.clone())
84 }
85}
86
87impl<E: Clone> Clone for BoxShrink<E> {
88 fn clone(&self) -> Self {
89 self.clone_box()
90 }
91}
92
93impl<E> core::fmt::Debug for dyn Gen<E> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.write_str(std::any::type_name::<Self>())
96 }
97}
98
99pub trait Gen<E: Clone + 'static>: CloneGen<E> {
101 fn examples(&self, seed: u64) -> BoxIter<E>;
104
105 fn shrinker(&self) -> BoxShrink<E>;
115
116 fn with_shrinker(&self, other_shrink: BoxShrink<E>) -> BoxGen<E> {
118 gens::other_shrinker(self.clone_box(), other_shrink)
119 }
120
121 fn chain(&self, other_gen: BoxGen<E>) -> BoxGen<E> {
123 gens::chain(self.clone_box(), other_gen)
124 }
125}
126
127pub trait ZipWithGen<E0>
133where
134 E0: Clone + 'static,
135{
136 fn zip<E1>(&self, other_gen: BoxGen<E1>) -> BoxGen<(E0, E1)>
138 where
139 E1: Clone + 'static;
140
141 fn zip_3<E1, E2>(
143 &self,
144 gen1: BoxGen<E1>,
145 gen2: BoxGen<E2>,
146 ) -> BoxGen<(E0, E1, E2)>
147 where
148 E1: Clone + 'static,
149 E2: Clone + 'static;
150
151 fn zip_4<E1, E2, E3>(
153 &self,
154 gen1: BoxGen<E1>,
155 gen2: BoxGen<E2>,
156 gen3: BoxGen<E3>,
157 ) -> BoxGen<(E0, E1, E2, E3)>
158 where
159 E1: Clone + 'static,
160 E2: Clone + 'static,
161 E3: Clone + 'static;
162
163 fn zip_5<E1, E2, E3, E4>(
165 &self,
166 gen1: BoxGen<E1>,
167 gen2: BoxGen<E2>,
168 gen3: BoxGen<E3>,
169 gen4: BoxGen<E4>,
170 ) -> BoxGen<(E0, E1, E2, E3, E4)>
171 where
172 E1: Clone + 'static,
173 E2: Clone + 'static,
174 E3: Clone + 'static,
175 E4: Clone + 'static;
176
177 fn zip_6<E1, E2, E3, E4, E5>(
179 &self,
180 gen1: BoxGen<E1>,
181 gen2: BoxGen<E2>,
182 gen3: BoxGen<E3>,
183 gen4: BoxGen<E4>,
184 gen5: BoxGen<E5>,
185 ) -> BoxGen<(E0, E1, E2, E3, E4, E5)>
186 where
187 E1: Clone + 'static,
188 E2: Clone + 'static,
189 E3: Clone + 'static,
190 E4: Clone + 'static,
191 E5: Clone + 'static;
192}
193
194impl<E0: Clone + 'static> ZipWithGen<E0> for dyn Gen<E0> {
195 fn zip<E1>(&self, other_gen: BoxGen<E1>) -> BoxGen<(E0, E1)>
196 where
197 E1: Clone + 'static,
198 {
199 gens::zip(self.clone_box(), other_gen)
200 }
201
202 fn zip_3<E1, E2>(
203 &self,
204 gen1: BoxGen<E1>,
205 gen2: BoxGen<E2>,
206 ) -> BoxGen<(E0, E1, E2)>
207 where
208 E1: Clone + 'static,
209 E2: Clone + 'static,
210 {
211 gens::zip(gens::zip(self.clone_box(), gen1), gen2)
212 .map(|((e0, e1), e2)| (e0, e1, e2), |(e0, e1, e2)| ((e0, e1), e2))
213 }
214
215 fn zip_4<E1, E2, E3>(
216 &self,
217 gen1: BoxGen<E1>,
218 gen2: BoxGen<E2>,
219 gen3: BoxGen<E3>,
220 ) -> BoxGen<(E0, E1, E2, E3)>
221 where
222 E1: Clone + 'static,
223 E2: Clone + 'static,
224 E3: Clone + 'static,
225 {
226 gens::zip(gens::zip(self.clone_box(), gen1), gens::zip(gen2, gen3)).map(
227 |((e0, e1), (e2, e3))| (e0, e1, e2, e3),
228 |(e0, e1, e2, e3)| ((e0, e1), (e2, e3)),
229 )
230 }
231
232 fn zip_5<E1, E2, E3, E4>(
233 &self,
234 gen1: BoxGen<E1>,
235 gen2: BoxGen<E2>,
236 gen3: BoxGen<E3>,
237 gen4: BoxGen<E4>,
238 ) -> BoxGen<(E0, E1, E2, E3, E4)>
239 where
240 E1: Clone + 'static,
241 E2: Clone + 'static,
242 E3: Clone + 'static,
243 E4: Clone + 'static,
244 {
245 gens::zip(gens::zip(self.clone_box(), gen1), gen2.zip_3(gen3, gen4))
246 .map(
247 |((e0, e1), (e2, e3, e4))| (e0, e1, e2, e3, e4),
248 |(e0, e1, e2, e3, e4)| ((e0, e1), (e2, e3, e4)),
249 )
250 }
251
252 fn zip_6<E1, E2, E3, E4, E5>(
253 &self,
254 gen1: BoxGen<E1>,
255 gen2: BoxGen<E2>,
256 gen3: BoxGen<E3>,
257 gen4: BoxGen<E4>,
258 gen5: BoxGen<E5>,
259 ) -> BoxGen<(E0, E1, E2, E3, E4, E5)>
260 where
261 E1: Clone + 'static,
262 E2: Clone + 'static,
263 E3: Clone + 'static,
264 E4: Clone + 'static,
265 E5: Clone + 'static,
266 {
267 gens::zip(self.zip_3(gen1, gen2), gen3.zip_3(gen4, gen5)).map(
268 |((e0, e1, e2), (e3, e4, e5))| (e0, e1, e2, e3, e4, e5),
269 |(e0, e1, e2, e3, e4, e5)| ((e0, e1, e2), (e3, e4, e5)),
270 )
271 }
272}
273
274pub trait MapWithGen<E0>
276where
277 E0: Clone + 'static,
278{
279 fn map<E1>(
281 &self,
282 map_fn: fn(E0) -> E1,
283 unmap_fn: fn(E1) -> E0,
284 ) -> BoxGen<E1>
285 where
286 E1: Clone + 'static;
287}
288
289impl<E0: Clone + 'static> MapWithGen<E0> for dyn Gen<E0> {
290 fn map<E1>(
291 &self,
292 map_fn: fn(E0) -> E1,
293 unmap_fn: fn(E1) -> E0,
294 ) -> BoxGen<E1>
295 where
296 E1: Clone + 'static,
297 {
298 gens::map(self.clone_box(), map_fn, unmap_fn)
299 }
300}
301
302pub trait FilterWithGen<E>
304where
305 E: Clone + 'static,
306{
307 fn filter<P>(&self, predicate: P) -> BoxGen<E>
309 where
310 P: Fn(&E) -> bool + Clone + 'static;
311}
312
313impl<E: Clone + 'static> FilterWithGen<E> for dyn Gen<E> {
314 fn filter<P>(&self, predicate: P) -> BoxGen<E>
315 where
316 P: Fn(&E) -> bool + Clone + 'static,
317 {
318 gens::filter(self.clone_box(), predicate)
319 }
320}
321
322pub trait Shrink<E>: CloneShrink<E>
327where
328 E: Clone,
329{
330 fn candidates(&self, original: E) -> BoxIter<E>;
332}
333
334pub trait ZipWithShrink<E0>
336where
337 E0: Clone + 'static,
338{
339 fn zip<E1>(&self, other_shrink: BoxShrink<E1>) -> BoxShrink<(E0, E1)>
341 where
342 E1: Clone + 'static;
343}
344
345impl<E0: Clone + 'static> ZipWithShrink<E0> for dyn Shrink<E0> {
346 fn zip<E1>(&self, other_gen: BoxShrink<E1>) -> BoxShrink<(E0, E1)>
347 where
348 E1: Clone + 'static,
349 {
350 shrinks::zip(self.clone_box(), other_gen)
351 }
352}
353
354pub trait MapWithShrink<E0>
356where
357 E0: Clone + 'static,
358{
359 fn map<E1>(
361 &self,
362 map_fn: fn(E0) -> E1,
363 unmap_fn: fn(E1) -> E0,
364 ) -> BoxShrink<E1>
365 where
366 E1: Clone + 'static;
367}
368
369impl<E0: Clone + 'static> MapWithShrink<E0> for dyn Shrink<E0> {
370 fn map<E1>(
371 &self,
372 map_fn: fn(E0) -> E1,
373 unmap_fn: fn(E1) -> E0,
374 ) -> BoxShrink<E1>
375 where
376 E1: Clone + 'static,
377 {
378 shrinks::map(self.clone_box(), map_fn, unmap_fn)
379 }
380}
381
382pub trait FilterWithShrink<E>
384where
385 E: Clone + 'static,
386{
387 fn filter<P>(&self, predicate: P) -> BoxShrink<E>
389 where
390 P: Fn(&E) -> bool + Clone + 'static;
391}
392
393impl<E: Clone + 'static> FilterWithShrink<E> for dyn Shrink<E> {
394 fn filter<P>(&self, predicate: P) -> BoxShrink<E>
395 where
396 P: Fn(&E) -> bool + Clone + 'static,
397 {
398 shrinks::filter(self.clone_box(), predicate)
399 }
400}
401
402#[doc = include_str!("../README.md")]
404#[cfg(doctest)]
405pub struct ReadmeDoctests;