async-lazy 0.1.2

A value which is initialized on the first access, with an async function.
Documentation
#![warn(rust_2018_idioms)]

use core::panic;
use std::ops::Drop;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;

use futures::{pin_mut, poll};
use tokio::runtime;
use tokio::time;

use async_lazy::Lazy;

#[test]
fn drop_lazy() {
    let rt = runtime::Builder::new_current_thread().build().unwrap();

    static NUM_DROPS: AtomicU32 = AtomicU32::new(0);

    rt.block_on(async {
        struct Foo {}

        let fooer = Foo {};

        impl Drop for Foo {
            fn drop(&mut self) {
                NUM_DROPS.fetch_add(1, Ordering::Release);
            }
        }

        {
            let lazy = Lazy::new(|| Box::pin(async { fooer }));
            lazy.force().await;
        }
        assert!(NUM_DROPS.load(Ordering::Acquire) == 1);
    });
}

#[test]
fn force() {
    let rt = runtime::Builder::new_current_thread()
        .enable_time()
        .start_paused(true)
        .build()
        .unwrap();

    static LAZY: Lazy<u32> = Lazy::new(|| Box::pin(async { 5 }));

    rt.block_on(async {
        let handle1 = rt.spawn(async { *LAZY.force().await });
        let handle2 = rt.spawn(async {
            time::sleep(Duration::from_millis(1)).await;
            *LAZY.force().await
        });

        time::advance(Duration::from_millis(1)).await;
        time::resume();

        let result1 = handle1.await.unwrap();
        let result2 = handle2.await.unwrap();

        assert_eq!(result1, 5);
        assert_eq!(result2, 5);
    });
}

#[test]
fn force_cancel() {
    let rt = runtime::Builder::new_current_thread()
        .enable_time()
        .start_paused(true)
        .build()
        .unwrap();

    static LAZY: Lazy<u32> = Lazy::new(|| Box::pin(async { 5 }));

    rt.block_on(async {
        let handle1 = rt.spawn(async {
            let fut = LAZY.force();
            pin_mut!(fut);
            let _ = poll!(fut);
        });
        let handle2 = rt.spawn(async {
            time::sleep(Duration::from_millis(1)).await;
            *LAZY.force().await
        });

        time::advance(Duration::from_millis(1)).await;
        time::resume();

        handle1.await.unwrap();
        let result2 = handle2.await.unwrap();

        assert_eq!(result2, 5);
    });
}

#[test]
#[cfg(panic = "unwind")]
fn force_panic() {
    let rt = runtime::Builder::new_current_thread()
        .enable_time()
        .start_paused(true)
        .build()
        .unwrap();

    static LAZY: Lazy<u32> = Lazy::new(|| Box::pin(async { panic!() }));

    rt.block_on(async {
        let handle1 = rt.spawn(async { *LAZY.force().await });
        let handle2 = rt.spawn(async {
            time::sleep(Duration::from_millis(1)).await;
            *LAZY.force().await
        });

        time::advance(Duration::from_millis(1)).await;
        time::resume();

        handle1.await.unwrap_err();
        handle2.await.unwrap_err();

        assert_eq!(LAZY.get(), None);
    });
}

#[test]
fn force_and_get() {
    let rt = runtime::Builder::new_current_thread().build().unwrap();

    static LAZY: Lazy<u32> = Lazy::new(|| Box::pin(async { 5 }));

    rt.block_on(async {
        let _ = rt.spawn(async { LAZY.force().await }).await;
        let value = *LAZY.get().unwrap();
        assert_eq!(value, 5);
    });
}

#[test]
fn get_uninit() {
    static LAZY: Lazy<u32> = Lazy::new(|| Box::pin(async { panic!() }));
    let uninit = LAZY.get();
    assert!(uninit.is_none());
}

#[cfg(feature = "nightly")]
#[test]
fn test_nightly() {
    let rt = runtime::Builder::new_current_thread().build().unwrap();

    static LAZY: Lazy<u32> = Lazy::new(|| Box::pin(async { 42 }));

    rt.block_on(async {
        let _ = rt.spawn(async { (&LAZY).await }).await;
        let value = *LAZY.get().unwrap();
        assert_eq!(value, 42);
    });
}