proptest_async 0.2.1

proptest macro supporting async tests
Documentation
#[cfg(all(feature = "tokio", feature = "async_std"))]
compile_error!("proptest_async: multiple async executors are selected");

#[cfg(feature = "tokio")]
#[allow(dead_code)]
#[inline]
fn block_on<F>(f: F) -> F::Output
where
    F: core::future::Future,
{
    let rt = tokio::runtime::Builder::new_current_thread().enable_time().build().unwrap();

    rt.block_on(f)
}

#[cfg(feature = "async_std")]
#[allow(dead_code)]
#[inline]
fn block_on<F>(f: F) -> F::Output
where
    F: core::future::Future,
{
    async_std::task::block_on(f)
}

/// Adjustment of proptest's macro to enable async tests via async_std.
#[macro_export]
macro_rules! proptest {
    (#![proptest_config($config:expr)]
     $(
        $(#[$meta:meta])*
        fn $test_name:ident($($parm:pat in $strategy:expr),+ $(,)?) $body:block
    )*) => {
        $(
            $(#[$meta])*
            fn $test_name() {
                let mut config = ::proptest::test_runner::contextualize_config($config.clone());
                config.test_name = Some(
                    concat!(module_path!(), "::", stringify!($test_name)));
                ::proptest::proptest_helper!(@_BODY config ($($parm in $strategy),+) [] $body);
            }
        )*
    };
    (#![proptest_config($config:expr)]
     $(
        $(#[$meta:meta])*
        fn $test_name:ident($($arg:tt)+) $body:block
    )*) => {
        $(
            $(#[$meta])*
            fn $test_name() {
                let mut config = ::proptest::test_runner::contextualize_config($config.clone());
                config.test_name = Some(
                    concat!(module_path!(), "::", stringify!($test_name)));
                ::proptest::proptest_helper!(@_BODY2 config ($($arg)+) [] $body);
            }
        )*
    };

    ($(
        $(#[$meta:meta])*
        fn $test_name:ident($($parm:pat in $strategy:expr),+ $(,)?) $body:block
    )*) => { $crate::proptest! {
        #![proptest_config(::proptest::test_runner::Config::default())]
        $($(#[$meta])*
          fn $test_name($($parm in $strategy),+) $body)*
    } };

    ($(
        $(#[$meta:meta])*
        fn $test_name:ident($($arg:tt)+) $body:block
    )*) => { $crate::proptest! {
        #![proptest_config(::proptest::test_runner::Config::default())]
        $($(#[$meta])*
        fn $test_name($($arg)+) $body)*
    } };



    (#![proptest_config($config:expr)]
     $(
        $(#[$meta:meta])*
       async fn $test_name:ident($($parm:pat in $strategy:expr),+ $(,)?) $body:block
    )*) => {
        $(
            $(#[$meta])*
            fn $test_name() {
                let mut config = ::proptest::test_runner::contextualize_config($config.clone());
                config.test_name = Some(
                    concat!(module_path!(), "::", stringify!($test_name)));
                ::proptest::proptest_helper!(@_BODY config ($($parm in $strategy),+) [] $crate::block_on(async move $body));
            }
        )*
    };

    (#![proptest_config($config:expr)]
     $(
        $(#[$meta:meta])*
        async fn $test_name:ident($($arg:tt)+) $body:block
    )*) => {
        $(
            $(#[$meta])*
            fn $test_name() {
                let mut config = ::proptest::test_runner::contextualize_config($config.clone());
                config.test_name = Some(
                    concat!(module_path!(), "::", stringify!($test_name)));
                ::proptest::proptest_helper!(@_BODY2 config ($($arg)+) [] $crate::block_on(async move $body));
            }
        )*
    };

    ($(
        $(#[$meta:meta])*
        async fn $test_name:ident($($parm:pat in $strategy:expr),+ $(,)?) $body:block
    )*) => { $crate::proptest! {
        #![proptest_config(::proptest::test_runner::Config::default())]
        $($(#[$meta])*
        async fn $test_name($($parm in $strategy),+) $body)*
    } };

    ($(
        $(#[$meta:meta])*
        async fn $test_name:ident($($arg:tt)+) $body:block
    )*) => { $crate::proptest! {
        #![proptest_config(::proptest::test_runner::Config::default())]
        $($(#[$meta])*
        async fn $test_name($($arg)+) $body)*
    } };

    (|($($parm:pat in $strategy:expr),+ $(,)?)| $body:expr) => {
        $crate::proptest!(
            proptest::test_runner::Config::default(),
            |($($parm in $strategy),+)| $body)
    };

    (move |($($parm:pat in $strategy:expr),+ $(,)?)| $body:expr) => {
        $crate::proptest!(
            ::proptest::test_runner::Config::default(),
            move |($($parm in $strategy),+)| $body)
    };

    (|($($arg:tt)+)| $body:expr) => {
        $crate::proptest!(
            ::proptest::test_runner::Config::default(),
            |($($arg)+)| $body)
    };

    (move |($($arg:tt)+)| $body:expr) => {
        $crate::proptest!(
            ::proptest::test_runner::Config::default(),
            move |($($arg)+)| $body)
    };

    ($config:expr, |($($parm:pat in $strategy:expr),+ $(,)?)| $body:expr) => { {
        let mut config = ::proptest::test_runner::contextualize_config($config.__sugar_to_owned());
        ::proptest::sugar::force_no_fork(&mut config);
        ::proptest::proptest_helper!(@_BODY config ($($parm in $strategy),+) [] $body)
    } };

    ($config:expr, move |($($parm:pat in $strategy:expr),+ $(,)?)| $body:expr) => { {
        let mut config = ::proptest::test_runner::contextualize_config($config.__sugar_to_owned());
        ::proptest::sugar::force_no_fork(&mut config);
        ::proptest::proptest_helper!(@_BODY config ($($parm in $strategy),+) [move] $body)
    } };

    ($config:expr, |($($arg:tt)+)| $body:expr) => { {
        let mut config = ::proptest::test_runner::contextualize_config($config.__sugar_to_owned());
        ::proptest::sugar::force_no_fork(&mut config);
        ::proptest::proptest_helper!(@_BODY2 config ($($arg)+) [] $body);
    } };

    ($config:expr, move |($($arg:tt)+)| $body:expr) => { {
        let mut config = $crate::test_runner::contextualize_config($config.__sugar_to_owned());
        ::proptest::sugar::force_no_fork(&mut config);
        ::proptest::proptest_helper!(@_BODY2 config ($($arg)+) [move] $body);
    } };
}

#[cfg(test)]
mod test {
    extern crate std;

    use core::time::Duration;

    #[cfg(feature = "async_std")]
    #[inline]
    fn wait(dur: Duration) -> impl core::future::Future<Output = ()> {
        async_std::task::sleep(dur)
    }

    #[cfg(not(feature = "async_std"))]
    #[inline]
    fn wait(dur: Duration) -> impl core::future::Future<Output = ()> {
        tokio::time::sleep(dur)
    }

    crate::proptest! {
        #[test]
        async fn test(x in 0..10) {
            let start = std::time::Instant::now();

            wait(Duration::from_millis(x as _)).await;
            let wait_dur = std::time::Instant::now().duration_since(start);

            std::println!("{x} / {wait_dur:?}");
            assert!(Duration::from_millis(x as _) <= wait_dur);
        }
    }
}