checkito 5.0.0

A safe, efficient and simple QuickCheck-inspired library to generate shrinkable random data mainly oriented towards generative/property/exploratory testing.
Documentation
pub mod common;
use common::*;

mod range {
    use super::*;

    macro_rules! tests {
        ($type:ident [$($macro: ident)?]) => {
            mod $type {
                use super::*;

                $($macro!($type);)?

                #[test]
                fn has_sample() {
                    for i in 1..100 {
                        <$type>::generator().samples(i).next().unwrap();
                    }
                }

                #[test]
                fn sample_has_count() {
                    for i in 0..100 {
                        assert_eq!(<$type>::generator().samples(i).len(), i);
                    }
                }

                #[cfg(feature = "check")]
                mod check {
                    use super::*;

                    #[check(1usize..=500)]
                    fn has_sample_for_arbitrary_count(count: usize) {
                        <$type>::generator().samples(count).next().unwrap();
                    }

                    #[check(0usize..=500)]
                    fn sample_has_exact_count(count: usize) {
                        assert_eq!(<$type>::generator().samples(count).len(), count);
                    }

                    #[check(1usize..=2000)]
                    fn shrinks_to_zero_for_arbitrary_sample_count(count: usize) {
                        for mut outer in shrinker(number::<$type>()).samples(count) {
                            while let Some(inner) = outer.shrink() {
                                outer = inner;
                            }
                            assert_eq!(0 as $type, outer.item());
                        }
                    }
                }

                #[test]
                fn empty_range() {
                    assert!(number::<$type>().flat_map(|value| value..value).check(|_| true).is_none());
                }

                #[test]
                fn is_same() {
                    assert!(number::<$type>()
                        .flat_map(|value| (value, same(value)))
                        .check(|(left, right)| left == right)
                        .is_none());
                }

                #[test]
                fn has_extremes() {
                    let samples = $type::generator().samples(5_000).collect::<Vec<_>>();
                    assert!(samples.contains(&$type::MIN));
                    assert!(samples.contains(&$type::MAX));
                    assert!(samples.contains(&(0 as $type)));
                }

                #[test]
                fn is_same_range() {
                    assert!(number::<$type>()
                        .flat_map(|value| (value, value..=value))
                        .check(|(left, right)| assert_eq!(left, right))
                        .is_none());
                }

                #[test]
                fn is_in_range() {
                    assert!((number::<$type>(), number::<$type>())
                        .map(|(low, high)| (low.min($type::MAX - $type::MAX / 100 as $type), high.min($type::MAX - $type::MAX / 100 as $type)))
                        .map(|(low, high)| (low.min(high), low.max(high) + $type::MAX / 100 as $type))
                        .flat_map(|(low, high)| (low..high, low, high))
                        .check(|(value, low, high)| value >= low && value < high)
                        .is_none());
                }

                #[test]
                fn is_in_range_inclusive() {
                    assert!((number::<$type>(), number::<$type>())
                        .map(|(low, high)| (low.min(high), low.max(high)))
                        .flat_map(|(low, high)| (low..=high, low, high))
                        .check(|(value, low, high)| value >= low && value <= high)
                        .is_none());
                }

                #[test]
                fn is_in_range_from() {
                    assert!(number::<$type>().flat_map(|low| (low, low..)).check(|(low, high)| low <= high)
                    .is_none());
                }

                #[test]
                fn is_in_range_to() {
                    assert!(number::<$type>()
                        .map(|high| high.max($type::MIN + $type::MAX / 100 as $type))
                        .flat_map(|high| (..high, high))
                        .check(|(low, high)| low < high)
                        .is_none());
                }

                #[test]
                fn is_in_range_to_inclusive() {
                    assert!(number::<$type>()
                        .flat_map(|high| (..=high, high))
                        .check(|(low, high)| low <= high)
                        .ok_or(true)
                        .unwrap_err());
                }

                #[test]
                fn is_positive() {
                    assert!(positive::<$type>()
                        .check(|value| value >= 0 as $type)
                        .is_none());
                }

                #[test]
                fn keeps_value() {
                    let fail = number::<$type>().keep().check(|value| value < 100 as $type).unwrap();
                    assert_eq!(fail.shrinks, 0);
                }

                #[test]
                fn shrinks_to_zero() {
                    for mut outer in shrinker(number::<$type>()).samples(1000) {
                        while let Some(inner) = outer.shrink() {
                            outer = inner;
                        }
                        assert_eq!(0 as $type, outer.item());
                    }
                }

                #[test]
                fn shrinks_to_low_or_high() {
                    assert!(number::<$type>()
                        .flat_map(|value| {
                            if value < 0 as $type {
                                (value..=value, value..=0 as $type)
                            } else {
                                (0 as $type..=value, value..=value)
                            }
                        })
                        .flat_map(|(low, high)| (low, high, shrinker(low..=high)))
                        .check(|(low, high, mut outer)| {
                            while let Some(inner) = outer.shrink() {
                                outer = inner;
                            }
                            if low >= 0 as $type {
                                assert_eq!(low, outer.item())
                            } else {
                                assert_eq!(high, outer.item())
                            }
                        })
                        .is_none());
                }

                #[test]
                fn is_negative() {
                    assert!(negative::<$type>().check(|value| value <= 0 as $type).is_none());
                }

                #[test]
                fn negative_full_range_no_overflow() {
                    // Regression test for issue #13: avoid overflow in a debug-only
                    // `end - value` subtraction when generating from the $type::MIN..=0
                    // range at full size (for unsigned types this degenerates to 0..=0).
                    assert!(
                        ($type::MIN..=0 as $type)
                            .check(|value| value >= $type::MIN && value <= 0 as $type)
                            .is_none()
                    );
                }

                #[test]
                fn check_finds_maximum() {
                    let fail = (negative::<$type>(), negative::<$type>().keep())
                        .check(|(left, right)| left > right)
                        .unwrap();
                    assert_eq!(fail.item.0, fail.item.1);
                }

                #[test]
                fn check_finds_minimum() {
                    let fail = (positive::<$type>(), positive::<$type>().keep())
                        .check(|(left, right)| left < right)
                        .unwrap();
                    assert_eq!(fail.item.0, fail.item.1);
                }

                #[test]
                fn check_shrinks_irrelevant_items() {
                    let fail = (positive::<$type>(), positive::<$type>().keep(), number::<$type>())
                        .check(|(left, right, _)| left < right)
                        .unwrap();
                    assert_eq!(fail.item.2, 0 as $type);
                }

                #[test]
                fn check_shrink_converges_to_zero() {
                    let mut count = 100usize;
                    let fail = number::<$type>()
                        .check(|_| {
                            count = count.saturating_sub(1);
                            count > 0
                        })
                        .unwrap();
                    assert_eq!(0 as $type, fail.item);
                }
            }
        };
        ($($type:ident),+) => { $(tests!($type []);)* };
    }

    macro_rules! rational {
        ($type: ident) => {
            #[test]
            fn has_special() {
                let samples = $type::generator().samples(5_000).collect::<Vec<_>>();
                assert!(samples.contains(&(0 as $type)));
                assert!(samples.contains(&$type::MIN));
                assert!(samples.contains(&$type::MAX));
                assert!(samples.contains(&$type::NEG_INFINITY));
                assert!(samples.contains(&$type::INFINITY));
                assert!(samples.contains(&$type::EPSILON));
                assert!(samples.contains(&$type::MIN_POSITIVE));
                assert!(samples.iter().copied().any($type::is_finite));
                assert!(samples.iter().copied().any($type::is_normal));
                assert!(samples.iter().copied().any($type::is_subnormal));
                assert!(samples.iter().copied().any($type::is_infinite));
                assert!(samples.iter().copied().any($type::is_sign_negative));
                assert!(samples.iter().copied().any($type::is_sign_positive));
                assert!(samples.iter().copied().any($type::is_nan));
            }
        };
    }

    tests!(
        i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
    );
    tests!(f32[rational]);
    tests!(f64[rational]);
}

#[cfg(feature = "check")]
mod check {
    use super::*;
    use checkito::primitive::Number;
    use core::sync::atomic::{AtomicUsize, Ordering};

    #[check(positive::<u8>(), 0u8)]
    #[check(positive::<u16>(), 0u16)]
    #[check(positive::<u32>(), 0u32)]
    #[check(positive::<u64>(), 0u64)]
    #[check(positive::<u128>(), 0u128)]
    #[check(positive::<usize>(), 0usize)]
    #[check(positive::<i8>(), 0i8)]
    #[check(positive::<i16>(), 0i16)]
    #[check(positive::<i32>(), 0i32)]
    #[check(positive::<i64>(), 0i64)]
    #[check(positive::<i128>(), 0i128)]
    #[check(positive::<isize>(), 0isize)]
    #[check(positive::<f32>(), 0f32)]
    #[check(positive::<f64>(), 0f64)]
    fn is_positive<T: PartialOrd>(value: T, zero: T) {
        assert!(value >= zero);
    }

    #[check(negative::<u8>(), 0u8)]
    #[check(negative::<u16>(), 0u16)]
    #[check(negative::<u32>(), 0u32)]
    #[check(negative::<u64>(), 0u64)]
    #[check(negative::<u128>(), 0u128)]
    #[check(negative::<usize>(), 0usize)]
    #[check(negative::<i8>(), 0i8)]
    #[check(negative::<i16>(), 0i16)]
    #[check(negative::<i32>(), 0i32)]
    #[check(negative::<i64>(), 0i64)]
    #[check(negative::<i128>(), 0i128)]
    #[check(negative::<isize>(), 0isize)]
    #[check(negative::<f32>(), 0f32)]
    #[check(negative::<f64>(), 0f64)]
    fn is_negative<T: PartialOrd>(value: T, zero: T) {
        assert!(value <= zero);
    }

    #[check(negative::<u8>())]
    #[check(negative::<u16>())]
    #[check(negative::<u32>())]
    #[check(negative::<u64>())]
    #[check(negative::<u128>())]
    #[check(negative::<usize>())]
    fn positive_negative_runs_once_per_type<T: Number>(value: T) {
        static RUNS: AtomicUsize = AtomicUsize::new(0);
        assert_eq!(value, T::ZERO);
        assert!(RUNS.fetch_add(1, Ordering::Relaxed) <= 6);
    }
}