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::*;
use std::{
    collections::{HashSet, LinkedList, VecDeque},
    rc::Rc,
    sync::Arc,
};

#[test]
fn empty_range() {
    assert!(
        char::generator()
            .flat_map(|value| value..value)
            .check(|_| true)
            .is_none()
    );
}

#[test]
fn is_same() {
    assert!(
        char::generator()
            .flat_map(|value| (value, same(value)))
            .check(|(left, right)| left == right)
            .is_none()
    );
}

#[test]
fn is_ascii() {
    assert!(ascii().check(|value| value.is_ascii()).is_none());
}

#[test]
fn is_digit() {
    assert!(digit().check(|value| value.is_ascii_digit()).is_none());
}

#[test]
fn is_alphabetic() {
    assert!(
        letter()
            .check(|value| value.is_ascii_alphabetic())
            .is_none()
    );
}

#[test]
fn full_does_not_panic() {
    assert!(char::generator().check(|_| true).is_none());
}

#[test]
fn exclusive_range_past_surrogate_start_skips_surrogates() {
    // '\u{D7FF}'..'z' has an exclusive lower bound of '\u{D7FF}'.
    // After applying $up to U+D7FF, start should be U+E000 (not U+FFFD).
    for c in ('\u{D7FF}'..'\u{E010}').samples(1000) {
        let code = c as u32;
        assert!(
            !(0xD800..0xE000).contains(&code),
            "surrogate U+{:04X} generated",
            code
        );
    }
}

#[test]
fn exclusive_range_end_at_e000_skips_surrogates() {
    // 'a'..'\u{E000}' should produce up to U+D7FF, not any surrogate.
    for c in ('a'..'\u{E000}').samples(1000) {
        let code = c as u32;
        assert!(
            code < 0xD800,
            "surrogate or post-surrogate U+{:04X} generated",
            code
        );
    }
}

#[test]
fn collects_exhaustively() {
    let range = 'a'..='Z';
    let set = range
        .clone()
        .checks(Ok::<_, ()>)
        .map(|result| result.into_item())
        .collect::<HashSet<_>>();
    for letter in range {
        assert!(set.contains(&letter));
    }
}

macro_rules! collection {
    ($m:ident, $t:ty, $i:ident) => {
        mod $m {
            use super::*;

            #[test]
            fn has_same_count() {
                assert!(
                    Generate::flat_map(0..100usize, |count| (
                        count,
                        char::generator().collect_with::<_, $t>(count)
                    ))
                    .check(|(count, value)| value.$i().count() == count)
                    .is_none()
                );
            }

            #[test]
            fn is_ascii() {
                assert!(
                    ascii()
                        .collect::<$t>()
                        .check(|value| value.$i().all(|value| value.is_ascii()))
                        .is_none()
                );
            }

            #[test]
            fn is_digit() {
                assert!(
                    digit()
                        .collect::<$t>()
                        .check(|value| value.$i().all(|value| value.is_ascii_digit()))
                        .is_none()
                );
            }

            #[test]
            fn is_alphabetic() {
                assert!(
                    letter()
                        .collect::<$t>()
                        .check(|value| value.$i().all(|value| value.is_ascii_alphabetic()))
                        .is_none()
                );
            }

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

                #[check(ascii().collect())]
                fn is_ascii(value: $t) {
                    assert!(value.$i().all(|value| value.is_ascii()));
                }

                #[check(digit().collect())]
                fn is_digit(value: $t) {
                    assert!(value.$i().all(|value| value.is_ascii_digit()));
                }

                #[check(letter().collect())]
                fn is_alphabetic(value: $t) {
                    assert!(value.$i().all(|value| value.is_ascii_alphabetic()));
                }
            }
        }
    };
}

collection!(string, String, chars);
collection!(vec_char, Vec<char>, iter);
collection!(vecdeque_char, VecDeque<char>, iter);
collection!(linked_list, LinkedList<char>, iter);
collection!(box_char, Box<[char]>, iter);
collection!(rc_char, Rc<[char]>, iter);
collection!(arc_char, Arc<[char]>, iter);

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

    #[check(char::generator().flat_map(|value| value..value))]
    fn empty_range(_: char) {}

    #[check(char::generator().flat_map(|value| (value, same(value))))]
    fn is_same(pair: (char, char)) {
        assert_eq!(pair.0, pair.1);
    }

    #[check(ascii())]
    fn is_ascii(value: char) {
        assert!(value.is_ascii());
    }

    #[check(digit())]
    fn is_digit(value: char) {
        assert!(value.is_ascii_digit());
    }

    #[check(letter())]
    fn is_alphabetic(value: char) {
        assert!(value.is_ascii_alphabetic());
    }

    #[check(_)]
    fn full_does_not_panic(_: char) {}

    #[check(10usize..=2000)]
    fn surrogate_start_skips_surrogates_for_arbitrary_count(count: usize) {
        for c in ('\u{D7FF}'..'\u{E010}').samples(count) {
            let code = c as u32;
            assert!(
                !(0xD800..0xE000).contains(&code),
                "surrogate U+{:04X} generated",
                code
            );
        }
    }

    #[check(10usize..=2000)]
    fn range_end_at_e000_skips_surrogates_for_arbitrary_count(count: usize) {
        for c in ('a'..'\u{E000}').samples(count) {
            let code = c as u32;
            assert!(
                code < 0xD800,
                "surrogate or post-surrogate U+{:04X} generated",
                code
            );
        }
    }

    #[check(0usize..=100)]
    fn collect_with_arbitrary_count_has_requested_length(count: usize) {
        let value = char::generator()
            .collect_with::<_, Vec<char>>(count)
            .sample(1.0);
        assert_eq!(value.len(), count);
    }
}