hegeltest 0.32.0

Property-based testing for Rust, built on Hypothesis
Documentation
use super::{BoxedGenerator, Generator, TestCase, integers, labels};
use crate::test_case::invalid_argument;
use std::borrow::Cow;
use std::marker::PhantomData;

/// Generator that picks from a fixed list of values. Created by [`sampled_from()`].
pub struct SampledFromGenerator<'a, T: Clone> {
    elements: Cow<'a, [T]>,
}

impl<'a, T: Clone + Send + Sync + 'a> Generator<T> for SampledFromGenerator<'a, T> {
    fn do_draw(&self, tc: &TestCase) -> T {
        let indices = integers::<usize>()
            .min_value(0)
            .max_value(self.elements.len() - 1);
        let index = indices.do_draw(tc);
        self.elements[index].clone()
    }

    fn enumerate_values(&self) -> Option<Vec<T>> {
        Some(self.elements.to_vec())
    }
}

/// Pick from a fixed list of values.
///
/// Accepts anything convertible into `Cow<[T]>`, including:
/// - `Vec<T>` (consumed without re-allocation)
/// - `&[T]` where `T: Clone` (borrowed, zero allocation)
/// - `&Vec<T>` or `&[T; N]` (via coercion to `&[T]`)
///
/// Panics if `elements` is empty.
pub fn sampled_from<'a, T, S>(elements: S) -> SampledFromGenerator<'a, T>
where
    T: Clone + Send + Sync,
    S: Into<Cow<'a, [T]>>,
{
    let elements = elements.into();
    if elements.is_empty() {
        invalid_argument!("Collection passed to sampled_from cannot be empty");
    }
    SampledFromGenerator { elements }
}

/// Generator that chooses from a runtime collection of boxed generators.
/// Created by [`one_of()`]; the [`one_of!`](crate::one_of) macro instead
/// builds an arity-specific generator that keeps its components unboxed.
///
/// Generic over the stored generator type `B`, defaulting to
/// [`BoxedGenerator`](super::BoxedGenerator) — the type-erased form a
/// runtime-sized collection of alternatives needs.
pub struct OneOfGenerator<'a, T, B = BoxedGenerator<'a, T>> {
    generators: Vec<B>,
    _phantom: PhantomData<&'a fn() -> T>,
}

fn draw_one_of<T>(tc: &TestCase, max_index: usize, pick: impl FnOnce(usize) -> T) -> T {
    tc.start_span(labels::ONE_OF);
    let index = integers::<usize>()
        .min_value(0)
        .max_value(max_index)
        .do_draw(tc);
    let result = pick(index);
    tc.stop_span(false);
    result
}

impl<'a, T, B: Generator<T>> Generator<T> for OneOfGenerator<'a, T, B> {
    fn do_draw(&self, tc: &TestCase) -> T {
        draw_one_of(tc, self.generators.len() - 1, |index| {
            self.generators[index].do_draw(tc)
        })
    }

    fn enumerate_values(&self) -> Option<Vec<T>> {
        let mut all = Vec::new();
        for g in &self.generators {
            all.extend(g.enumerate_values()?);
        }
        Some(all)
    }
}

/// Choose from multiple generators of the same type.
///
/// Accepts any iterable of generators of one type — typically
/// `Vec<BoxedGenerator<T>>`. For a more convenient syntax, use the
/// `one_of!` macro instead.
pub fn one_of<'a, T, B, I>(generators: I) -> OneOfGenerator<'a, T, B>
where
    B: Generator<T>,
    I: IntoIterator<Item = B>,
{
    let generators: Vec<B> = generators.into_iter().collect();
    if generators.is_empty() {
        invalid_argument!("one_of requires at least one generator");
    }
    OneOfGenerator {
        generators,
        _phantom: PhantomData,
    }
}

/// Choose from 1–12 generators of the same type.
///
/// The component generators keep their concrete types (no boxing), so the
/// result is a nameable, arity-specific generator type mirroring
/// [`tuples!`](crate::tuples): [`OneOf1Generator`](crate::generators::OneOf1Generator)
/// through [`OneOf12Generator`](crate::generators::OneOf12Generator). For
/// more than 12 alternatives, or a number not known at compile time, box
/// the generators and call [`one_of`] directly.
///
/// # Example
///
/// ```no_run
/// use hegel::generators as gs;
///
/// #[hegel::test]
/// fn my_test(tc: hegel::TestCase) {
///     let value: i32 = tc.draw(hegel::one_of!(
///         gs::integers::<i32>().min_value(0).max_value(10),
///         gs::integers::<i32>().min_value(100).max_value(110),
///     ));
/// }
/// ```
#[macro_export]
macro_rules! one_of {
    ($g1:expr $(,)?) => {
        $crate::generators::one_of1($g1)
    };
    ($g1:expr, $g2:expr $(,)?) => {
        $crate::generators::one_of2($g1, $g2)
    };
    ($g1:expr, $g2:expr, $g3:expr $(,)?) => {
        $crate::generators::one_of3($g1, $g2, $g3)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr $(,)?) => {
        $crate::generators::one_of4($g1, $g2, $g3, $g4)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr $(,)?) => {
        $crate::generators::one_of5($g1, $g2, $g3, $g4, $g5)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr $(,)?) => {
        $crate::generators::one_of6($g1, $g2, $g3, $g4, $g5, $g6)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr $(,)?) => {
        $crate::generators::one_of7($g1, $g2, $g3, $g4, $g5, $g6, $g7)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr, $g8:expr $(,)?) => {
        $crate::generators::one_of8($g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr, $g8:expr, $g9:expr $(,)?) => {
        $crate::generators::one_of9($g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr, $g8:expr, $g9:expr, $g10:expr $(,)?) => {
        $crate::generators::one_of10($g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr, $g8:expr, $g9:expr, $g10:expr, $g11:expr $(,)?) => {
        $crate::generators::one_of11($g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10, $g11)
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr, $g8:expr, $g9:expr, $g10:expr, $g11:expr, $g12:expr $(,)?) => {
        $crate::generators::one_of12(
            $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10, $g11, $g12,
        )
    };
    ($g1:expr, $g2:expr, $g3:expr, $g4:expr, $g5:expr, $g6:expr, $g7:expr, $g8:expr, $g9:expr, $g10:expr, $g11:expr, $g12:expr, $($rest:tt)+) => {
        compile_error!(
            "one_of! supports at most 12 generators; for more, box them and call \
             hegel::generators::one_of directly, e.g. \
             one_of(vec![g1.boxed(), g2.boxed(), ...]) with the \
             hegel::generators::Generator trait in scope for .boxed()"
        )
    };
}

macro_rules! impl_one_of {
    ($name:ident, $fn_name:ident, $arity:literal,
     $(($idx:tt, $field:ident, $G:ident)),* ; ($last_field:ident, $last_G:ident)) => {
        #[doc = concat!(
            "The ", $arity, "-alternative generator created by [`one_of!`](crate::one_of)."
        )]
        pub struct $name<$($G,)* $last_G, T> {
            $($field: $G,)*
            $last_field: $last_G,
            _phantom: PhantomData<fn(T)>,
        }

        impl<T, $($G,)* $last_G> Generator<T> for $name<$($G,)* $last_G, T>
        where
            $($G: Generator<T>,)*
            $last_G: Generator<T>,
        {
            fn do_draw(&self, tc: &TestCase) -> T {
                draw_one_of(tc, $arity - 1, |index| match index {
                    $($idx => self.$field.do_draw(tc),)*
                    _ => self.$last_field.do_draw(tc),
                })
            }

            fn enumerate_values(&self) -> Option<Vec<T>> {
                let mut all = Vec::new();
                $(all.extend(self.$field.enumerate_values()?);)*
                all.extend(self.$last_field.enumerate_values()?);
                Some(all)
            }
        }

        #[doc(hidden)]
        #[allow(clippy::too_many_arguments)]
        pub fn $fn_name<T, $($G: Generator<T>,)* $last_G: Generator<T>>(
            $($field: $G,)* $last_field: $last_G,
        ) -> $name<$($G,)* $last_G, T> {
            $name {
                $($field,)*
                $last_field,
                _phantom: PhantomData,
            }
        }
    };
}

impl_one_of!(OneOf1Generator, one_of1, 1, ; (gen1, G1));
impl_one_of!(OneOf2Generator, one_of2, 2, (0, gen1, G1); (gen2, G2));
impl_one_of!(
    OneOf3Generator,
    one_of3,
    3,
    (0, gen1, G1),
    (1, gen2, G2);
    (gen3, G3)
);
impl_one_of!(
    OneOf4Generator,
    one_of4,
    4,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3);
    (gen4, G4)
);
impl_one_of!(
    OneOf5Generator,
    one_of5,
    5,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4);
    (gen5, G5)
);
impl_one_of!(
    OneOf6Generator,
    one_of6,
    6,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5);
    (gen6, G6)
);
impl_one_of!(
    OneOf7Generator,
    one_of7,
    7,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5),
    (5, gen6, G6);
    (gen7, G7)
);
impl_one_of!(
    OneOf8Generator,
    one_of8,
    8,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5),
    (5, gen6, G6),
    (6, gen7, G7);
    (gen8, G8)
);
impl_one_of!(
    OneOf9Generator,
    one_of9,
    9,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5),
    (5, gen6, G6),
    (6, gen7, G7),
    (7, gen8, G8);
    (gen9, G9)
);
impl_one_of!(
    OneOf10Generator,
    one_of10,
    10,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5),
    (5, gen6, G6),
    (6, gen7, G7),
    (7, gen8, G8),
    (8, gen9, G9);
    (gen10, G10)
);
impl_one_of!(
    OneOf11Generator,
    one_of11,
    11,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5),
    (5, gen6, G6),
    (6, gen7, G7),
    (7, gen8, G8),
    (8, gen9, G9),
    (9, gen10, G10);
    (gen11, G11)
);
impl_one_of!(
    OneOf12Generator,
    one_of12,
    12,
    (0, gen1, G1),
    (1, gen2, G2),
    (2, gen3, G3),
    (3, gen4, G4),
    (4, gen5, G5),
    (5, gen6, G6),
    (6, gen7, G7),
    (7, gen8, G8),
    (8, gen9, G9),
    (9, gen10, G10),
    (10, gen11, G11);
    (gen12, G12)
);

/// Generator that produces `Some(value)` or `None`. Created by [`optional()`].
pub struct OptionalGenerator<G, T> {
    inner: G,
    _phantom: PhantomData<fn(T)>,
}

impl<T, G> Generator<Option<T>> for OptionalGenerator<G, T>
where
    G: Generator<T>,
{
    fn do_draw(&self, tc: &TestCase) -> Option<T> {
        tc.start_span(labels::OPTIONAL);
        let result = if tc.generate_boolean(0.5) {
            Some(self.inner.do_draw(tc))
        } else {
            None
        };
        tc.stop_span(false);
        result
    }
}

/// Generate `Option<T>` values: either `Some(value)` from the inner generator, or `None`.
pub fn optional<T, G: Generator<T>>(inner: G) -> OptionalGenerator<G, T> {
    OptionalGenerator {
        inner,
        _phantom: PhantomData,
    }
}