camber 0.4.2

Opinionated async Rust for IO-bound services on top of Tokio
Documentation
use camber::RuntimeError;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;

struct DropSignal(Arc<AtomicBool>);

impl Drop for DropSignal {
    fn drop(&mut self) {
        self.0.store(true, Ordering::Release);
    }
}

#[camber::test]
async fn race_returns_first_to_complete() {
    let (fast_tx, fast_rx) = tokio::sync::oneshot::channel();
    let fast = async move {
        fast_rx.await.unwrap();
        "fast"
    };
    let slow = std::future::pending::<&str>();
    fast_tx.send(()).unwrap();

    assert_eq!(camber::task::race(fast, slow).await, "fast");
}

#[camber::test]
async fn race_all_returns_first_from_vec() {
    let (ready_tx, mut ready_rx) = tokio::sync::mpsc::unbounded_channel();
    let (winner_tx, winner_rx) = tokio::sync::oneshot::channel();
    let winner_tx = std::sync::Mutex::new(Some(winner_tx));
    let mut releases = Vec::new();
    let futures = (0..5)
        .map(|index| {
            let (release_tx, release_rx) = tokio::sync::oneshot::channel();
            releases.push(Some(release_tx));
            let task_ready = ready_tx.clone();
            let task_winner = &winner_tx;
            async move {
                task_ready.send(index).unwrap();
                release_rx.await.unwrap();
                match index {
                    2 => task_winner
                        .lock()
                        .unwrap()
                        .take()
                        .unwrap()
                        .send(())
                        .unwrap(),
                    _ => {}
                }
                index
            }
        })
        .collect::<Vec<_>>();
    drop(ready_tx);

    let controller = tokio::spawn(async move {
        let mut observed = Vec::new();
        while let Some(index) = ready_rx.recv().await {
            observed.push(index);
            match observed.len() {
                5 => break,
                _ => {}
            }
        }
        releases[2].take().unwrap().send(()).unwrap();
        winner_rx.await.unwrap();
    });

    assert_eq!(camber::task::race_all(futures).await.unwrap(), 2);
    controller.await.unwrap();
}

#[camber::test]
async fn race_cancels_loser() {
    let dropped = Arc::new(AtomicBool::new(false));
    let drop_signal = DropSignal(Arc::clone(&dropped));
    let slow = async move {
        let drop_signal_guard = drop_signal;
        std::hint::black_box(&drop_signal_guard);
        std::future::pending::<&str>().await
    };

    assert_eq!(
        camber::task::race(std::future::ready("done"), slow).await,
        "done"
    );
    assert!(dropped.load(Ordering::Acquire));
}

#[camber::test]
async fn race_propagates_error_from_first() {
    let fast = std::future::ready(Err::<&str, RuntimeError>(RuntimeError::Timeout));
    let slow = std::future::pending::<Result<&str, RuntimeError>>();

    assert!(matches!(
        camber::task::race(fast, slow).await,
        Err(RuntimeError::Timeout)
    ));
}

#[camber::test]
async fn race_all_empty_returns_error() {
    let futures = Vec::<std::future::Ready<()>>::new();
    assert!(matches!(
        camber::task::race_all(futures).await,
        Err(RuntimeError::InvalidArgument(_))
    ));
}

#[camber::test]
async fn timeout_returns_error_on_expiry() {
    let result = camber::timeout(Duration::from_millis(50), std::future::pending::<()>()).await;
    assert!(matches!(result, Err(RuntimeError::Timeout)));
}

#[camber::test]
async fn timeout_returns_value_on_success() {
    assert_eq!(
        camber::timeout(Duration::from_secs(1), std::future::ready(42))
            .await
            .unwrap(),
        42
    );
}