Skip to main content

hegel/generators/
compose.rs

1use super::{Generator, TestCase};
2use std::marker::PhantomData;
3
4/// A generator built from imperative code. Created by [`compose!`](crate::compose).
5#[doc(hidden)]
6pub struct ComposedGenerator<T, F> {
7    f: F,
8    _phantom: PhantomData<fn() -> T>,
9}
10
11impl<T, F> ComposedGenerator<T, F>
12where
13    F: Fn(TestCase) -> T,
14{
15    /// Create a composed generator from a closure that receives a [`TestCase`].
16    pub fn new(f: F) -> Self {
17        ComposedGenerator {
18            f,
19            _phantom: PhantomData,
20        }
21    }
22}
23
24impl<T, F> Generator<T> for ComposedGenerator<T, F>
25where
26    F: Fn(TestCase) -> T + Send + Sync,
27{
28    fn do_draw(&self, tc: &TestCase) -> T {
29        (self.f)(tc.clone())
30    }
31}
32
33/// Compile-time FNV-1a hash of a byte slice, producing a u64 label.
34#[doc(hidden)]
35pub const fn fnv1a_hash(bytes: &[u8]) -> u64 {
36    const FNV_OFFSET: u64 = 0xcbf29ce484222325;
37    const FNV_PRIME: u64 = 0x100000001b3;
38    let mut hash = FNV_OFFSET;
39    let mut i = 0;
40    while i < bytes.len() {
41        hash ^= bytes[i] as u64;
42        hash = hash.wrapping_mul(FNV_PRIME);
43        i += 1;
44    }
45    hash
46}
47
48/// Create a generator from imperative code that draws from other generators.
49///
50/// This is analogous to Hypothesis's `@composite` decorator. The closure
51/// receives a `TestCase` parameter. Use `tc.draw()` to draw values from
52/// other generators within the compose block.
53///
54/// # Example
55///
56/// ```no_run
57/// use hegel::generators as gs;
58///
59/// #[hegel::test]
60/// fn my_test(tc: hegel::TestCase) {
61///     let value = tc.draw(hegel::compose!(|tc| {
62///         let x = tc.draw(gs::integers::<i32>().min_value(0).max_value(10));
63///         let y = tc.draw(gs::integers::<i32>().min_value(x).max_value(100));
64///         (x, y)
65///     }));
66/// }
67/// ```
68#[macro_export]
69macro_rules! compose {
70    (|$tc:ident| { $($body:tt)* }) => {{
71        const LABEL: u64 = $crate::generators::fnv1a_hash(stringify!($($body)*).as_bytes());
72        $crate::generators::ComposedGenerator::new(move |$tc: $crate::TestCase| {
73            $tc.start_span(LABEL);
74            let __result = { $($body)* };
75            $tc.stop_span(false);
76            __result
77        })
78    }};
79}