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 gen;
15mod runner;
16pub mod shrink;
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 gen::other_shrinker(self.clone_box(), other_shrink)
119 }
120
121 fn chain(&self, other_gen: BoxGen<E>) -> BoxGen<E> {
123 gen::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 gen::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 gen::zip(gen::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 gen::zip(gen::zip(self.clone_box(), gen1), gen::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 gen::zip(gen::zip(self.clone_box(), gen1), gen2.zip_3(gen3, gen4)).map(
246 |((e0, e1), (e2, e3, e4))| (e0, e1, e2, e3, e4),
247 |(e0, e1, e2, e3, e4)| ((e0, e1), (e2, e3, e4)),
248 )
249 }
250
251 fn zip_6<E1, E2, E3, E4, E5>(
252 &self,
253 gen1: BoxGen<E1>,
254 gen2: BoxGen<E2>,
255 gen3: BoxGen<E3>,
256 gen4: BoxGen<E4>,
257 gen5: BoxGen<E5>,
258 ) -> BoxGen<(E0, E1, E2, E3, E4, E5)>
259 where
260 E1: Clone + 'static,
261 E2: Clone + 'static,
262 E3: Clone + 'static,
263 E4: Clone + 'static,
264 E5: Clone + 'static,
265 {
266 gen::zip(self.zip_3(gen1, gen2), gen3.zip_3(gen4, gen5)).map(
267 |((e0, e1, e2), (e3, e4, e5))| (e0, e1, e2, e3, e4, e5),
268 |(e0, e1, e2, e3, e4, e5)| ((e0, e1, e2), (e3, e4, e5)),
269 )
270 }
271}
272
273pub trait MapWithGen<E0>
275where
276 E0: Clone + 'static,
277{
278 fn map<E1>(
280 &self,
281 map_fn: fn(E0) -> E1,
282 unmap_fn: fn(E1) -> E0,
283 ) -> BoxGen<E1>
284 where
285 E1: Clone + 'static;
286}
287
288impl<E0: Clone + 'static> MapWithGen<E0> for dyn Gen<E0> {
289 fn map<E1>(
290 &self,
291 map_fn: fn(E0) -> E1,
292 unmap_fn: fn(E1) -> E0,
293 ) -> BoxGen<E1>
294 where
295 E1: Clone + 'static,
296 {
297 gen::map(self.clone_box(), map_fn, unmap_fn)
298 }
299}
300
301pub trait FilterWithGen<E>
303where
304 E: Clone + 'static,
305{
306 fn filter<P>(&self, predicate: P) -> BoxGen<E>
308 where
309 P: Fn(&E) -> bool + Clone + 'static;
310}
311
312impl<E: Clone + 'static> FilterWithGen<E> for dyn Gen<E> {
313 fn filter<P>(&self, predicate: P) -> BoxGen<E>
314 where
315 P: Fn(&E) -> bool + Clone + 'static,
316 {
317 gen::filter(self.clone_box(), predicate)
318 }
319}
320
321pub trait Shrink<E>: CloneShrink<E>
326where
327 E: Clone,
328{
329 fn candidates(&self, original: E) -> BoxIter<E>;
331}
332
333pub trait ZipWithShrink<E0>
335where
336 E0: Clone + 'static,
337{
338 fn zip<E1>(&self, other_shrink: BoxShrink<E1>) -> BoxShrink<(E0, E1)>
340 where
341 E1: Clone + 'static;
342}
343
344impl<E0: Clone + 'static> ZipWithShrink<E0> for dyn Shrink<E0> {
345 fn zip<E1>(&self, other_gen: BoxShrink<E1>) -> BoxShrink<(E0, E1)>
346 where
347 E1: Clone + 'static,
348 {
349 shrink::zip(self.clone_box(), other_gen)
350 }
351}
352
353pub trait MapWithShrink<E0>
355where
356 E0: Clone + 'static,
357{
358 fn map<E1>(
360 &self,
361 map_fn: fn(E0) -> E1,
362 unmap_fn: fn(E1) -> E0,
363 ) -> BoxShrink<E1>
364 where
365 E1: Clone + 'static;
366}
367
368impl<E0: Clone + 'static> MapWithShrink<E0> for dyn Shrink<E0> {
369 fn map<E1>(
370 &self,
371 map_fn: fn(E0) -> E1,
372 unmap_fn: fn(E1) -> E0,
373 ) -> BoxShrink<E1>
374 where
375 E1: Clone + 'static,
376 {
377 shrink::map(self.clone_box(), map_fn, unmap_fn)
378 }
379}
380
381pub trait FilterWithShrink<E>
383where
384 E: Clone + 'static,
385{
386 fn filter<P>(&self, predicate: P) -> BoxShrink<E>
388 where
389 P: Fn(&E) -> bool + Clone + 'static;
390}
391
392impl<E: Clone + 'static> FilterWithShrink<E> for dyn Shrink<E> {
393 fn filter<P>(&self, predicate: P) -> BoxShrink<E>
394 where
395 P: Fn(&E) -> bool + Clone + 'static,
396 {
397 shrink::filter(self.clone_box(), predicate)
398 }
399}
400
401#[doc = include_str!("../README.md")]
403#[cfg(doctest)]
404pub struct ReadmeDoctests;