tokio-stream 0.1.19

Utilities to work with `Stream` and `tokio`.
Documentation
use futures_core::FusedStream;
use tokio_stream::StreamExt;

// Helper: a fused base stream built from a vec
fn fused_iter<T>(items: Vec<T>) -> impl FusedStream<Item = T> {
    tokio_stream::iter(items).fuse()
}

// ── map ──────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn map_not_terminated_before_done() {
    let stream = fused_iter(vec![1, 2]).map(|x| x * 2);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn map_terminated_after_inner_done() {
    let mut stream = fused_iter(vec![1]).map(|x| x * 2);
    assert_eq!(stream.next().await, Some(2));
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── filter ───────────────────────────────────────────────────────────────────

#[tokio::test]
async fn filter_not_terminated_before_done() {
    let stream = fused_iter(vec![1, 2]).filter(|x| *x > 0);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn filter_terminated_after_inner_done() {
    let mut stream = fused_iter(vec![1]).filter(|x| *x > 0);
    assert_eq!(stream.next().await, Some(1));
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── filter_map ───────────────────────────────────────────────────────────────

#[tokio::test]
async fn filter_map_not_terminated_before_done() {
    let stream = fused_iter(vec![1, 2]).filter_map(Some);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn filter_map_terminated_after_inner_done() {
    let mut stream = fused_iter(vec![1]).filter_map(|x| Some(x * 10));
    assert_eq!(stream.next().await, Some(10));
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── skip ─────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn skip_not_terminated_before_done() {
    let stream = fused_iter(vec![1, 2, 3]).skip(1);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn skip_terminated_after_inner_done() {
    let mut stream = fused_iter(vec![1, 2]).skip(1);
    assert_eq!(stream.next().await, Some(2));
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── skip_while ───────────────────────────────────────────────────────────────

#[tokio::test]
async fn skip_while_not_terminated_before_done() {
    let stream = fused_iter(vec![1, 2, 3]).skip_while(|x| *x < 2);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn skip_while_terminated_after_inner_done() {
    let mut stream = fused_iter(vec![1, 2]).skip_while(|x| *x < 2);
    assert_eq!(stream.next().await, Some(2));
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── take ─────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn take_not_terminated_before_limit() {
    let stream = tokio_stream::iter(vec![1, 2, 3]).take(2);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn take_terminated_when_remaining_zero() {
    let mut stream = tokio_stream::iter(vec![1, 2]).take(2);
    assert_eq!(stream.next().await, Some(1));
    assert!(!stream.is_terminated());
    assert_eq!(stream.next().await, Some(2));
    // remaining hits 0 after getting the second item
    assert!(stream.is_terminated());
    assert_eq!(stream.next().await, None);
}

#[tokio::test]
async fn take_zero_is_immediately_terminated() {
    let stream = tokio_stream::iter(vec![1, 2]).take(0);
    assert!(stream.is_terminated());
}

// ── take_while ───────────────────────────────────────────────────────────────

#[tokio::test]
async fn take_while_not_terminated_before_predicate_fails() {
    let stream = tokio_stream::iter(vec![1, 2, 3]).take_while(|x| *x < 10);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn take_while_terminated_after_predicate_fails() {
    let mut stream = tokio_stream::iter(vec![1, 5, 2]).take_while(|x| *x < 3);
    assert_eq!(stream.next().await, Some(1));
    assert!(!stream.is_terminated());
    // predicate fails on 5 → done flag set
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── map_while ─────────────────────────────────────────────────────────────────

#[tokio::test]
async fn map_while_not_terminated_before_closure_returns_none() {
    let stream =
        tokio_stream::iter(vec![1, 2, 3]).map_while(|x| if x < 10 { Some(x) } else { None });
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn map_while_terminated_after_closure_returns_none() {
    let mut stream =
        tokio_stream::iter(vec![1, 5, 2]).map_while(|x| if x < 3 { Some(x) } else { None });
    assert_eq!(stream.next().await, Some(1));
    assert!(!stream.is_terminated());
    // closure returns `None` on 5 → done flag set
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
}

// ── then ─────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn then_not_terminated_before_done() {
    let stream = fused_iter(vec![1, 2]).then(|x| async move { x * 2 });
    tokio::pin!(stream);
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn then_terminated_after_inner_done_and_no_pending_future() {
    let stream = fused_iter(vec![1]).then(|x| async move { x * 2 });
    tokio::pin!(stream);
    assert_eq!(stream.next().await, Some(2));
    assert_eq!(stream.next().await, None);
    // inner stream done AND no in-flight future
    assert!(stream.is_terminated());
}

// ── chain ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn chain_not_terminated_while_either_has_items() {
    let stream = fused_iter(vec![1]).chain(fused_iter(vec![2]));
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn chain_terminated_only_after_both_done() {
    let mut stream = fused_iter(vec![1]).chain(fused_iter(vec![2]));
    assert_eq!(stream.next().await, Some(1));
    assert!(!stream.is_terminated()); // b still has items
    assert_eq!(stream.next().await, Some(2));
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated()); // both done now
}

// ── merge ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn merge_not_terminated_while_either_has_items() {
    let stream = fused_iter(vec![1]).merge(fused_iter(vec![2]));
    assert!(!stream.is_terminated());
}

#[tokio::test]
async fn merge_terminated_only_after_both_done() {
    let mut stream = fused_iter(vec![1]).merge(fused_iter(vec![2]));
    // drain both
    let mut collected = vec![];
    while let Some(x) = stream.next().await {
        collected.push(x);
    }
    assert_eq!(stream.next().await, None);
    assert!(stream.is_terminated());
    assert_eq!(collected.len(), 2);
}