chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,110 @@
#![allow(clippy::diverging_sub_expression)]
use std::rc::Rc;
#[allow(dead_code)]
type BoxStream<T> = std::pin::Pin<Box<dyn tokio_stream::Stream<Item = T>>>;
#[allow(dead_code)]
fn require_send<T: Send>(_t: &T) {}
#[allow(dead_code)]
fn require_sync<T: Sync>(_t: &T) {}
#[allow(dead_code)]
fn require_unpin<T: Unpin>(_t: &T) {}
#[allow(dead_code)]
struct Invalid;
#[allow(unused)]
trait AmbiguousIfSend<A> {
fn some_item(&self) {}
}
impl<T: ?Sized> AmbiguousIfSend<()> for T {}
impl<T: ?Sized + Send> AmbiguousIfSend<Invalid> for T {}
#[allow(unused)]
trait AmbiguousIfSync<A> {
fn some_item(&self) {}
}
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
impl<T: ?Sized + Sync> AmbiguousIfSync<Invalid> for T {}
#[allow(unused)]
trait AmbiguousIfUnpin<A> {
fn some_item(&self) {}
}
impl<T: ?Sized> AmbiguousIfUnpin<()> for T {}
impl<T: ?Sized + Unpin> AmbiguousIfUnpin<Invalid> for T {}
macro_rules! into_todo {
($typ:ty) => {{
let x: $typ = todo!();
x
}};
}
macro_rules! async_assert_fn {
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): Send & Sync) => {
#[allow(unreachable_code)]
#[allow(unused_variables)]
const _: fn() = || {
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
require_send(&f);
require_sync(&f);
};
};
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): Send & !Sync) => {
#[allow(unreachable_code)]
#[allow(unused_variables)]
const _: fn() = || {
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
require_send(&f);
AmbiguousIfSync::some_item(&f);
};
};
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): !Send & Sync) => {
#[allow(unreachable_code)]
#[allow(unused_variables)]
const _: fn() = || {
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
AmbiguousIfSend::some_item(&f);
require_sync(&f);
};
};
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): !Send & !Sync) => {
#[allow(unreachable_code)]
#[allow(unused_variables)]
const _: fn() = || {
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
AmbiguousIfSend::some_item(&f);
AmbiguousIfSync::some_item(&f);
};
};
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): !Unpin) => {
#[allow(unreachable_code)]
#[allow(unused_variables)]
const _: fn() = || {
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
AmbiguousIfUnpin::some_item(&f);
};
};
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): Unpin) => {
#[allow(unreachable_code)]
#[allow(unused_variables)]
const _: fn() = || {
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
require_unpin(&f);
};
};
}
async_assert_fn!(tokio_stream::empty<Rc<u8>>(): Send & Sync);
async_assert_fn!(tokio_stream::pending<Rc<u8>>(): Send & Sync);
async_assert_fn!(tokio_stream::iter(std::vec::IntoIter<u8>): Send & Sync);
async_assert_fn!(tokio_stream::StreamExt::next(&mut BoxStream<()>): !Unpin);
async_assert_fn!(tokio_stream::StreamExt::try_next(&mut BoxStream<Result<(), ()>>): !Unpin);
async_assert_fn!(tokio_stream::StreamExt::all(&mut BoxStream<()>, fn(())->bool): !Unpin);
async_assert_fn!(tokio_stream::StreamExt::any(&mut BoxStream<()>, fn(())->bool): !Unpin);
async_assert_fn!(tokio_stream::StreamExt::fold(&mut BoxStream<()>, (), fn((), ())->()): !Unpin);
async_assert_fn!(tokio_stream::StreamExt::collect<Vec<()>>(&mut BoxStream<()>): !Unpin);

View File

@@ -0,0 +1,84 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
use tokio::time;
use tokio_stream::{self as stream, StreamExt};
use tokio_test::assert_pending;
use tokio_test::task;
use futures::FutureExt;
use std::time::Duration;
#[tokio::test(start_paused = true)]
async fn usage() {
let iter = vec![1, 2, 3].into_iter();
let stream0 = stream::iter(iter);
let iter = vec![4].into_iter();
let stream1 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(3)).map(move |_| n));
let chunk_stream = stream0
.chain(stream1)
.chunks_timeout(4, Duration::from_secs(2));
let mut chunk_stream = task::spawn(chunk_stream);
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![1, 2, 3]));
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![4]));
}
#[tokio::test(start_paused = true)]
async fn full_chunk_with_timeout() {
let iter = vec![1, 2].into_iter();
let stream0 = stream::iter(iter);
let iter = vec![3].into_iter();
let stream1 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(1)).map(move |_| n));
let iter = vec![4].into_iter();
let stream2 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(3)).map(move |_| n));
let chunk_stream = stream0
.chain(stream1)
.chain(stream2)
.chunks_timeout(3, Duration::from_secs(2));
let mut chunk_stream = task::spawn(chunk_stream);
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![1, 2, 3]));
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![4]));
}
#[tokio::test]
#[ignore]
async fn real_time() {
let iter = vec![1, 2, 3, 4].into_iter();
let stream0 = stream::iter(iter);
let iter = vec![5].into_iter();
let stream1 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(5)).map(move |_| n));
let chunk_stream = stream0
.chain(stream1)
.chunks_timeout(3, Duration::from_secs(2));
let mut chunk_stream = task::spawn(chunk_stream);
assert_eq!(chunk_stream.next().await, Some(vec![1, 2, 3]));
assert_eq!(chunk_stream.next().await, Some(vec![4]));
assert_eq!(chunk_stream.next().await, Some(vec![5]));
}

View File

@@ -0,0 +1,109 @@
use futures::{Stream, StreamExt};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
#[tokio::test]
async fn size_hint_stream_open() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
let mut stream = ReceiverStream::new(rx);
assert_eq!(stream.size_hint(), (2, None));
stream.next().await;
assert_eq!(stream.size_hint(), (1, None));
stream.next().await;
assert_eq!(stream.size_hint(), (0, None));
}
#[tokio::test]
async fn size_hint_stream_closed() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_sender_dropped() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
let mut stream = ReceiverStream::new(rx);
drop(tx);
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[test]
fn size_hint_stream_instantly_closed() {
let (_tx, rx) = mpsc::channel::<i32>(4);
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_stream_closed_permits_send() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
let permit1 = tx.reserve().await.unwrap();
let permit2 = tx.reserve().await.unwrap();
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (1, Some(3)));
permit1.send(2);
assert_eq!(stream.size_hint(), (2, Some(3)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(1)));
permit2.send(3);
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
}
#[tokio::test]
async fn size_hint_stream_closed_permits_drop() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
let permit1 = tx.reserve().await.unwrap();
let permit2 = tx.reserve().await.unwrap();
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (1, Some(3)));
drop(permit1);
assert_eq!(stream.size_hint(), (1, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(1)));
drop(permit2);
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
}

View File

@@ -0,0 +1,63 @@
use futures::{Stream, StreamExt};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
#[tokio::test]
async fn size_hint_stream_open() {
let (tx, rx) = mpsc::unbounded_channel();
tx.send(1).unwrap();
tx.send(2).unwrap();
let mut stream = UnboundedReceiverStream::new(rx);
assert_eq!(stream.size_hint(), (2, None));
stream.next().await;
assert_eq!(stream.size_hint(), (1, None));
stream.next().await;
assert_eq!(stream.size_hint(), (0, None));
}
#[tokio::test]
async fn size_hint_stream_closed() {
let (tx, rx) = mpsc::unbounded_channel();
tx.send(1).unwrap();
tx.send(2).unwrap();
let mut stream = UnboundedReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_sender_dropped() {
let (tx, rx) = mpsc::unbounded_channel();
tx.send(1).unwrap();
tx.send(2).unwrap();
let mut stream = UnboundedReceiverStream::new(rx);
drop(tx);
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[test]
fn size_hint_stream_instantly_closed() {
let (_tx, rx) = mpsc::unbounded_channel::<i32>();
let mut stream = UnboundedReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (0, Some(0)));
}

View File

@@ -0,0 +1,110 @@
use tokio_stream::{self as stream, Stream, StreamExt};
use tokio_test::{assert_pending, assert_ready, task};
mod support {
pub(crate) mod mpsc;
}
use support::mpsc;
use tokio_stream::adapters::Chain;
#[tokio::test]
async fn basic_usage() {
let one = stream::iter(vec![1, 2, 3]);
let two = stream::iter(vec![4, 5, 6]);
let mut stream = visibility_test(one, two);
assert_eq!(stream.size_hint(), (6, Some(6)));
assert_eq!(stream.next().await, Some(1));
assert_eq!(stream.size_hint(), (5, Some(5)));
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.size_hint(), (4, Some(4)));
assert_eq!(stream.next().await, Some(3));
assert_eq!(stream.size_hint(), (3, Some(3)));
assert_eq!(stream.next().await, Some(4));
assert_eq!(stream.size_hint(), (2, Some(2)));
assert_eq!(stream.next().await, Some(5));
assert_eq!(stream.size_hint(), (1, Some(1)));
assert_eq!(stream.next().await, Some(6));
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
}
fn visibility_test<I, S1, S2>(s1: S1, s2: S2) -> Chain<S1, S2>
where
S1: Stream<Item = I>,
S2: Stream<Item = I>,
{
s1.chain(s2)
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // Block on https://github.com/tokio-rs/tokio/issues/6860
async fn pending_first() {
let (tx1, rx1) = mpsc::unbounded_channel_stream();
let (tx2, rx2) = mpsc::unbounded_channel_stream();
let mut stream = task::spawn(rx1.chain(rx2));
assert_eq!(stream.size_hint(), (0, None));
assert_pending!(stream.poll_next());
tx2.send(2).unwrap();
assert!(!stream.is_woken());
assert_pending!(stream.poll_next());
tx1.send(1).unwrap();
assert!(stream.is_woken());
assert_eq!(Some(1), assert_ready!(stream.poll_next()));
assert_pending!(stream.poll_next());
drop(tx1);
assert_eq!(stream.size_hint(), (0, None));
assert!(stream.is_woken());
assert_eq!(Some(2), assert_ready!(stream.poll_next()));
assert_eq!(stream.size_hint(), (0, None));
drop(tx2);
assert_eq!(stream.size_hint(), (0, None));
assert_eq!(None, assert_ready!(stream.poll_next()));
}
#[test]
fn size_overflow() {
struct Monster;
impl tokio_stream::Stream for Monster {
type Item = ();
fn poll_next(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<()>> {
panic!()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, Some(usize::MAX))
}
}
let m1 = Monster;
let m2 = Monster;
let m = m1.chain(m2);
assert_eq!(m.size_hint(), (usize::MAX, None));
}

View File

@@ -0,0 +1,30 @@
#![warn(rust_2018_idioms)]
use futures::FutureExt;
use std::error::Error;
use tokio::time;
use tokio::time::Duration;
use tokio_stream::{self as stream, StreamExt};
use tokio_test::assert_pending;
use tokio_test::task;
#[tokio::test(start_paused = true)]
async fn stream_chunks_remainder() -> Result<(), Box<dyn Error>> {
let stream1 =
stream::iter([5]).then(move |n| time::sleep(Duration::from_secs(1)).map(move |_| n));
let inner = stream::iter([1, 2, 3, 4]).chain(stream1);
tokio::pin!(inner);
let chunked = (&mut inner).chunks_timeout(10, Duration::from_millis(20));
let mut chunked = task::spawn(chunked);
assert_pending!(chunked.poll_next());
let remainder = chunked.enter(|_, stream| stream.into_remainder());
assert_eq!(remainder, vec![1, 2, 3, 4]);
time::advance(Duration::from_secs(2)).await;
assert_eq!(inner.next().await, Some(5));
Ok(())
}

View File

@@ -0,0 +1,11 @@
use tokio_stream::{StreamExt, StreamNotifyClose};
#[tokio::test]
async fn basic_usage() {
let mut stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
assert_eq!(stream.next().await, Some(Some(0)));
assert_eq!(stream.next().await, Some(Some(1)));
assert_eq!(stream.next().await, Some(None));
assert_eq!(stream.next().await, None);
}

View File

@@ -0,0 +1,146 @@
use tokio_stream::{self as stream, StreamExt};
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
mod support {
pub(crate) mod mpsc;
}
use support::mpsc;
#[allow(clippy::let_unit_value)]
#[tokio::test]
async fn empty_unit() {
// Drains the stream.
let mut iter = vec![(), (), ()].into_iter();
let _: () = stream::iter(&mut iter).collect().await;
assert!(iter.next().is_none());
}
#[tokio::test]
async fn empty_vec() {
let coll: Vec<u32> = stream::empty().collect().await;
assert!(coll.is_empty());
}
#[tokio::test]
async fn empty_box_slice() {
let coll: Box<[u32]> = stream::empty().collect().await;
assert!(coll.is_empty());
}
#[tokio::test]
async fn empty_string() {
let coll: String = stream::empty::<&str>().collect().await;
assert!(coll.is_empty());
}
#[tokio::test]
async fn empty_result() {
let coll: Result<Vec<u32>, &str> = stream::empty().collect().await;
assert_eq!(Ok(vec![]), coll);
}
#[tokio::test]
async fn collect_vec_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<Vec<i32>>());
assert_pending!(fut.poll());
tx.send(1).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send(2).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(vec![1, 2], coll);
}
#[tokio::test]
async fn collect_string_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<String>());
assert_pending!(fut.poll());
tx.send("hello ".to_string()).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send("world".to_string()).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!("hello world", coll);
}
#[tokio::test]
async fn collect_str_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<String>());
assert_pending!(fut.poll());
tx.send("hello ").unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send("world").unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!("hello world", coll);
}
#[tokio::test]
async fn collect_results_ok() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<Result<String, &str>>());
assert_pending!(fut.poll());
tx.send(Ok("hello ")).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send(Ok("world")).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready_ok!(fut.poll());
assert_eq!("hello world", coll);
}
#[tokio::test]
async fn collect_results_err() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<Result<String, &str>>());
assert_pending!(fut.poll());
tx.send(Ok("hello ")).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send(Err("oh no")).unwrap();
assert!(fut.is_woken());
let err = assert_ready_err!(fut.poll());
assert_eq!("oh no", err);
}

View File

@@ -0,0 +1,11 @@
use tokio_stream::{self as stream, Stream, StreamExt};
#[tokio::test]
async fn basic_usage() {
let mut stream = stream::empty::<i32>();
for _ in 0..2 {
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(None, stream.next().await);
}
}

View File

@@ -0,0 +1,50 @@
use tokio_stream::{Stream, StreamExt};
use std::pin::Pin;
use std::task::{Context, Poll};
// a stream which alternates between Some and None
struct Alternate {
state: i32,
}
impl Stream for Alternate {
type Item = i32;
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<i32>> {
let val = self.state;
self.state += 1;
// if it's even, Some(i32), else None
if val % 2 == 0 {
Poll::Ready(Some(val))
} else {
Poll::Ready(None)
}
}
}
#[tokio::test]
async fn basic_usage() {
let mut stream = Alternate { state: 0 };
// the stream goes back and forth
assert_eq!(stream.next().await, Some(0));
assert_eq!(stream.next().await, None);
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.next().await, None);
// however, once it is fused
let mut stream = stream.fuse();
assert_eq!(stream.size_hint(), (0, None));
assert_eq!(stream.next().await, Some(4));
assert_eq!(stream.size_hint(), (0, None));
assert_eq!(stream.next().await, None);
// it will always return `None` after the first time.
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
assert_eq!(stream.size_hint(), (0, Some(0)));
}

View File

@@ -0,0 +1,18 @@
use tokio_stream as stream;
use tokio_test::task;
use std::iter;
#[tokio::test]
async fn coop() {
let mut stream = task::spawn(stream::iter(iter::repeat(1)));
for _ in 0..10_000 {
if stream.poll_next().is_pending() {
assert!(stream.is_woken());
return;
}
}
panic!("did not yield");
}

View File

@@ -0,0 +1,83 @@
use tokio_stream::{self as stream, Stream, StreamExt};
use tokio_test::task;
use tokio_test::{assert_pending, assert_ready};
mod support {
pub(crate) mod mpsc;
}
use support::mpsc;
#[tokio::test]
async fn merge_sync_streams() {
let mut s = stream::iter(vec![0, 2, 4, 6]).merge(stream::iter(vec![1, 3, 5]));
for i in 0..7 {
let rem = 7 - i;
assert_eq!(s.size_hint(), (rem, Some(rem)));
assert_eq!(Some(i), s.next().await);
}
assert!(s.next().await.is_none());
}
#[tokio::test]
async fn merge_async_streams() {
let (tx1, rx1) = mpsc::unbounded_channel_stream();
let (tx2, rx2) = mpsc::unbounded_channel_stream();
let mut rx = task::spawn(rx1.merge(rx2));
assert_eq!(rx.size_hint(), (0, None));
assert_pending!(rx.poll_next());
tx1.send(1).unwrap();
assert!(rx.is_woken());
assert_eq!(Some(1), assert_ready!(rx.poll_next()));
assert_pending!(rx.poll_next());
tx2.send(2).unwrap();
assert!(rx.is_woken());
assert_eq!(Some(2), assert_ready!(rx.poll_next()));
assert_pending!(rx.poll_next());
drop(tx1);
assert!(rx.is_woken());
assert_pending!(rx.poll_next());
tx2.send(3).unwrap();
assert!(rx.is_woken());
assert_eq!(Some(3), assert_ready!(rx.poll_next()));
assert_pending!(rx.poll_next());
drop(tx2);
assert!(rx.is_woken());
assert_eq!(None, assert_ready!(rx.poll_next()));
}
#[test]
fn size_overflow() {
struct Monster;
impl tokio_stream::Stream for Monster {
type Item = ();
fn poll_next(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<()>> {
panic!()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, Some(usize::MAX))
}
}
let m1 = Monster;
let m2 = Monster;
let m = m1.merge(m2);
assert_eq!(m.size_hint(), (usize::MAX, None));
}

View File

@@ -0,0 +1,12 @@
use tokio_stream::{self as stream, Stream, StreamExt};
#[tokio::test]
async fn basic_usage() {
let mut one = stream::once(1);
assert_eq!(one.size_hint(), (1, Some(1)));
assert_eq!(Some(1), one.next().await);
assert_eq!(one.size_hint(), (0, Some(0)));
assert_eq!(None, one.next().await);
}

View File

@@ -0,0 +1,56 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "time", not(target_os = "wasi")))] // Wasi does not support panic recovery
#![cfg(panic = "unwind")]
use parking_lot::{const_mutex, Mutex};
use std::error::Error;
use std::panic;
use std::sync::Arc;
use tokio::time::Duration;
use tokio_stream::{self as stream, StreamExt};
fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
static PANIC_MUTEX: Mutex<()> = const_mutex(());
{
let _guard = PANIC_MUTEX.lock();
let panic_file: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let prev_hook = panic::take_hook();
{
let panic_file = panic_file.clone();
panic::set_hook(Box::new(move |panic_info| {
let panic_location = panic_info.location().unwrap();
panic_file
.lock()
.clone_from(&Some(panic_location.file().to_string()));
}));
}
let result = panic::catch_unwind(func);
// Return to the previously set panic hook (maybe default) so that we get nice error
// messages in the tests.
panic::set_hook(prev_hook);
if result.is_err() {
panic_file.lock().clone()
} else {
None
}
}
}
#[test]
fn stream_chunks_timeout_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let iter = vec![1, 2, 3].into_iter();
let stream0 = stream::iter(iter);
let _chunk_stream = stream0.chunks_timeout(0, Duration::from_secs(2));
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}

View File

@@ -0,0 +1,14 @@
use tokio_stream::{self as stream, Stream, StreamExt};
use tokio_test::{assert_pending, task};
#[tokio::test]
async fn basic_usage() {
let mut stream = stream::pending::<i32>();
for _ in 0..2 {
assert_eq!(stream.size_hint(), (0, None));
let mut next = task::spawn(async { stream.next().await });
assert_pending!(next.poll());
}
}

View File

@@ -0,0 +1,563 @@
use futures::stream::iter;
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
use std::future::{poll_fn, Future};
use std::pin::{pin, Pin};
use std::task::Poll;
mod support {
pub(crate) mod mpsc;
}
use support::mpsc;
macro_rules! assert_ready_some {
($($t:tt)*) => {
match assert_ready!($($t)*) {
Some(v) => v,
None => panic!("expected `Some`, got `None`"),
}
};
}
macro_rules! assert_ready_none {
($($t:tt)*) => {
match assert_ready!($($t)*) {
None => {}
Some(v) => panic!("expected `None`, got `Some({:?})`", v),
}
};
}
#[tokio::test]
async fn empty() {
let mut map = StreamMap::<&str, stream::Pending<()>>::new();
assert_eq!(map.len(), 0);
assert!(map.is_empty());
assert!(map.next().await.is_none());
assert!(map.next().await.is_none());
assert!(map.remove("foo").is_none());
}
#[tokio::test]
async fn single_entry() {
let mut map = task::spawn(StreamMap::new());
let (tx, rx) = mpsc::unbounded_channel_stream();
let rx = Box::pin(rx);
assert_ready_none!(map.poll_next());
assert!(map.insert("foo", rx).is_none());
assert!(map.contains_key("foo"));
assert!(!map.contains_key("bar"));
assert_eq!(map.len(), 1);
assert!(!map.is_empty());
assert_pending!(map.poll_next());
assert_ok!(tx.send(1));
assert!(map.is_woken());
let (k, v) = assert_ready_some!(map.poll_next());
assert_eq!(k, "foo");
assert_eq!(v, 1);
assert_pending!(map.poll_next());
assert_ok!(tx.send(2));
assert!(map.is_woken());
let (k, v) = assert_ready_some!(map.poll_next());
assert_eq!(k, "foo");
assert_eq!(v, 2);
assert_pending!(map.poll_next());
drop(tx);
assert!(map.is_woken());
assert_ready_none!(map.poll_next());
}
#[tokio::test]
async fn multiple_entries() {
let mut map = task::spawn(StreamMap::new());
let (tx1, rx1) = mpsc::unbounded_channel_stream();
let (tx2, rx2) = mpsc::unbounded_channel_stream();
let rx1 = Box::pin(rx1);
let rx2 = Box::pin(rx2);
map.insert("foo", rx1);
map.insert("bar", rx2);
assert_pending!(map.poll_next());
assert_ok!(tx1.send(1));
assert!(map.is_woken());
let (k, v) = assert_ready_some!(map.poll_next());
assert_eq!(k, "foo");
assert_eq!(v, 1);
assert_pending!(map.poll_next());
assert_ok!(tx2.send(2));
assert!(map.is_woken());
let (k, v) = assert_ready_some!(map.poll_next());
assert_eq!(k, "bar");
assert_eq!(v, 2);
assert_pending!(map.poll_next());
assert_ok!(tx1.send(3));
assert_ok!(tx2.send(4));
assert!(map.is_woken());
// Given the randomization, there is no guarantee what order the values will
// be received in.
let mut v = (0..2)
.map(|_| assert_ready_some!(map.poll_next()))
.collect::<Vec<_>>();
assert_pending!(map.poll_next());
v.sort_unstable();
assert_eq!(v[0].0, "bar");
assert_eq!(v[0].1, 4);
assert_eq!(v[1].0, "foo");
assert_eq!(v[1].1, 3);
drop(tx1);
assert!(map.is_woken());
assert_pending!(map.poll_next());
drop(tx2);
assert_ready_none!(map.poll_next());
}
#[tokio::test]
async fn insert_remove() {
let mut map = task::spawn(StreamMap::new());
let (tx, rx) = mpsc::unbounded_channel_stream();
let rx = Box::pin(rx);
assert_ready_none!(map.poll_next());
assert!(map.insert("foo", rx).is_none());
let rx = map.remove("foo").unwrap();
assert_ok!(tx.send(1));
assert!(!map.is_woken());
assert_ready_none!(map.poll_next());
assert!(map.insert("bar", rx).is_none());
let v = assert_ready_some!(map.poll_next());
assert_eq!(v.0, "bar");
assert_eq!(v.1, 1);
assert!(map.remove("bar").is_some());
assert_ready_none!(map.poll_next());
assert!(map.is_empty());
assert_eq!(0, map.len());
}
#[tokio::test]
async fn replace() {
let mut map = task::spawn(StreamMap::new());
let (tx1, rx1) = mpsc::unbounded_channel_stream();
let (tx2, rx2) = mpsc::unbounded_channel_stream();
let rx1 = Box::pin(rx1);
let rx2 = Box::pin(rx2);
assert!(map.insert("foo", rx1).is_none());
assert_pending!(map.poll_next());
let _rx1 = map.insert("foo", rx2).unwrap();
assert_pending!(map.poll_next());
tx1.send(1).unwrap();
assert_pending!(map.poll_next());
tx2.send(2).unwrap();
assert!(map.is_woken());
let v = assert_ready_some!(map.poll_next());
assert_eq!(v.0, "foo");
assert_eq!(v.1, 2);
}
#[test]
fn size_hint_with_upper() {
let mut map = StreamMap::new();
map.insert("a", stream::iter(vec![1]));
map.insert("b", stream::iter(vec![1, 2]));
map.insert("c", stream::iter(vec![1, 2, 3]));
assert_eq!(3, map.len());
assert!(!map.is_empty());
let size_hint = map.size_hint();
assert_eq!(size_hint, (6, Some(6)));
}
#[test]
fn size_hint_without_upper() {
let mut map = StreamMap::new();
map.insert("a", pin_box(stream::iter(vec![1])));
map.insert("b", pin_box(stream::iter(vec![1, 2])));
map.insert("c", pin_box(pending()));
let size_hint = map.size_hint();
assert_eq!(size_hint, (3, None));
}
#[test]
fn new_capacity_zero() {
let map = StreamMap::<&str, stream::Pending<()>>::new();
assert_eq!(0, map.capacity());
assert!(map.keys().next().is_none());
}
#[test]
fn with_capacity() {
let map = StreamMap::<&str, stream::Pending<()>>::with_capacity(10);
assert!(10 <= map.capacity());
assert!(map.keys().next().is_none());
}
#[test]
fn iter_keys() {
let mut map = StreamMap::new();
map.insert("a", pending::<i32>());
map.insert("b", pending());
map.insert("c", pending());
let mut keys = map.keys().collect::<Vec<_>>();
keys.sort_unstable();
assert_eq!(&keys[..], &[&"a", &"b", &"c"]);
}
#[test]
fn iter_values() {
let mut map = StreamMap::new();
map.insert("a", stream::iter(vec![1]));
map.insert("b", stream::iter(vec![1, 2]));
map.insert("c", stream::iter(vec![1, 2, 3]));
let mut size_hints = map.values().map(|s| s.size_hint().0).collect::<Vec<_>>();
size_hints.sort_unstable();
assert_eq!(&size_hints[..], &[1, 2, 3]);
}
#[test]
fn iter_values_mut() {
let mut map = StreamMap::new();
map.insert("a", stream::iter(vec![1]));
map.insert("b", stream::iter(vec![1, 2]));
map.insert("c", stream::iter(vec![1, 2, 3]));
let mut size_hints = map
.values_mut()
.map(|s: &mut _| s.size_hint().0)
.collect::<Vec<_>>();
size_hints.sort_unstable();
assert_eq!(&size_hints[..], &[1, 2, 3]);
}
#[test]
fn clear() {
let mut map = task::spawn(StreamMap::new());
map.insert("a", stream::iter(vec![1]));
map.insert("b", stream::iter(vec![1, 2]));
map.insert("c", stream::iter(vec![1, 2, 3]));
assert_ready_some!(map.poll_next());
map.clear();
assert_ready_none!(map.poll_next());
assert!(map.is_empty());
}
#[test]
fn contains_key_borrow() {
let mut map = StreamMap::new();
map.insert("foo".to_string(), pending::<()>());
assert!(map.contains_key("foo"));
}
#[test]
fn one_ready_many_none() {
// Run a few times because of randomness
for _ in 0..100 {
let mut map = task::spawn(StreamMap::new());
map.insert(0, pin_box(stream::empty()));
map.insert(1, pin_box(stream::empty()));
map.insert(2, pin_box(stream::once("hello")));
map.insert(3, pin_box(stream::pending()));
let v = assert_ready_some!(map.poll_next());
assert_eq!(v, (2, "hello"));
}
}
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
Box::pin(s)
}
type UsizeStream = Pin<Box<dyn Stream<Item = usize> + Send>>;
#[tokio::test]
async fn poll_next_many_zero() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 0)).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn poll_next_many_empty() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 1)).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn poll_next_many_pending() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let mut is_pending = false;
poll_fn(|cx| {
let poll = stream_map.poll_next_many(cx, &mut vec![], 1);
is_pending = poll.is_pending();
Poll::Ready(())
})
.await;
assert!(is_pending);
}
#[tokio::test]
async fn poll_next_many_not_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn poll_next_many_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn poll_next_many_correctly_loops_around() {
for _ in 0..10 {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
assert_eq!(n, 3);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![0, 0, 0]
);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
assert_eq!(n, 2);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![1, 1]
);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 1)).await;
assert_eq!(n, 1);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![2]
);
}
}
#[tokio::test]
async fn next_many_zero() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut vec![], 0)).poll(cx)).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn next_many_empty() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
let n = stream_map.next_many(&mut vec![], 1).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn next_many_pending() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let mut is_pending = false;
poll_fn(|cx| {
let poll = pin!(stream_map.next_many(&mut vec![], 1)).poll(cx);
is_pending = poll.is_pending();
Poll::Ready(())
})
.await;
assert!(is_pending);
}
#[tokio::test]
async fn next_many_not_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn next_many_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn next_many_correctly_loops_around() {
for _ in 0..10 {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
assert_eq!(n, 3);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![0, 0, 0]
);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
assert_eq!(n, 2);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![1, 1]
);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 1)).poll(cx)).await;
assert_eq!(n, 1);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![2]
);
}
}

View File

@@ -0,0 +1,109 @@
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
use tokio::time::{self, sleep, Duration};
use tokio_stream::StreamExt;
use tokio_test::*;
use futures::stream;
async fn maybe_sleep(idx: i32) -> i32 {
if idx % 2 == 0 {
sleep(ms(200)).await;
}
idx
}
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
#[tokio::test]
async fn basic_usage() {
time::pause();
// Items 2 and 4 time out. If we run the stream until it completes,
// we end up with the following items:
//
// [Ok(1), Err(Elapsed), Ok(2), Ok(3), Err(Elapsed), Ok(4)]
let stream = stream::iter(1..=4).then(maybe_sleep).timeout(ms(100));
let mut stream = task::spawn(stream);
// First item completes immediately
assert_ready_eq!(stream.poll_next(), Some(Ok(1)));
// Second item is delayed 200ms, times out after 100ms
assert_pending!(stream.poll_next());
time::advance(ms(150)).await;
let v = assert_ready!(stream.poll_next());
assert!(v.unwrap().is_err());
assert_pending!(stream.poll_next());
time::advance(ms(100)).await;
assert_ready_eq!(stream.poll_next(), Some(Ok(2)));
// Third item is ready immediately
assert_ready_eq!(stream.poll_next(), Some(Ok(3)));
// Fourth item is delayed 200ms, times out after 100ms
assert_pending!(stream.poll_next());
time::advance(ms(60)).await;
assert_pending!(stream.poll_next()); // nothing ready yet
time::advance(ms(60)).await;
let v = assert_ready!(stream.poll_next());
assert!(v.unwrap().is_err()); // timeout!
time::advance(ms(120)).await;
assert_ready_eq!(stream.poll_next(), Some(Ok(4)));
// Done.
assert_ready_eq!(stream.poll_next(), None);
}
#[tokio::test]
async fn return_elapsed_errors_only_once() {
time::pause();
let stream = stream::iter(1..=3).then(maybe_sleep).timeout(ms(50));
let mut stream = task::spawn(stream);
// First item completes immediately
assert_ready_eq!(stream.poll_next(), Some(Ok(1)));
// Second item is delayed 200ms, times out after 50ms. Only one `Elapsed`
// error is returned.
assert_pending!(stream.poll_next());
//
time::advance(ms(51)).await;
let v = assert_ready!(stream.poll_next());
assert!(v.unwrap().is_err()); // timeout!
// deadline elapses again, but no error is returned
time::advance(ms(50)).await;
assert_pending!(stream.poll_next());
time::advance(ms(100)).await;
assert_ready_eq!(stream.poll_next(), Some(Ok(2)));
assert_ready_eq!(stream.poll_next(), Some(Ok(3)));
// Done
assert_ready_eq!(stream.poll_next(), None);
}
#[tokio::test]
async fn no_timeouts() {
let stream = stream::iter(vec![1, 3, 5])
.then(maybe_sleep)
.timeout(ms(100));
let mut stream = task::spawn(stream);
assert_ready_eq!(stream.poll_next(), Some(Ok(1)));
assert_ready_eq!(stream.poll_next(), Some(Ok(3)));
assert_ready_eq!(stream.poll_next(), Some(Ok(5)));
assert_ready_eq!(stream.poll_next(), None);
}

View File

@@ -0,0 +1,15 @@
use async_stream::stream;
use tokio::sync::mpsc::{self, UnboundedSender};
use tokio_stream::Stream;
pub fn unbounded_channel_stream<T: Unpin>() -> (UnboundedSender<T>, impl Stream<Item = T>) {
let (tx, mut rx) = mpsc::unbounded_channel();
let stream = stream! {
while let Some(item) = rx.recv().await {
yield item;
}
};
(tx, stream)
}

View File

@@ -0,0 +1,28 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
use tokio::time;
use tokio_stream::StreamExt;
use tokio_test::*;
use std::time::Duration;
#[tokio::test]
async fn usage() {
time::pause();
let mut stream = task::spawn(futures::stream::repeat(()).throttle(Duration::from_millis(100)));
assert_ready!(stream.poll_next());
assert_pending!(stream.poll_next());
time::advance(Duration::from_millis(90)).await;
assert_pending!(stream.poll_next());
time::advance(Duration::from_millis(101)).await;
assert!(stream.is_woken());
assert_ready!(stream.poll_next());
}

57
vendor/tokio-stream/tests/watch.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
#![cfg(feature = "sync")]
use tokio::sync::watch;
use tokio_stream::wrappers::WatchStream;
use tokio_stream::StreamExt;
use tokio_test::assert_pending;
use tokio_test::task::spawn;
#[tokio::test]
async fn watch_stream_message_not_twice() {
let (tx, rx) = watch::channel("hello");
let mut counter = 0;
let mut stream = WatchStream::new(rx).map(move |payload| {
println!("{payload}");
if payload == "goodbye" {
counter += 1;
}
if counter >= 2 {
panic!("too many goodbyes");
}
});
let task = tokio::spawn(async move { while stream.next().await.is_some() {} });
// Send goodbye just once
tx.send("goodbye").unwrap();
drop(tx);
task.await.unwrap();
}
#[tokio::test]
async fn watch_stream_from_rx() {
let (tx, rx) = watch::channel("hello");
let mut stream = WatchStream::from(rx);
assert_eq!(stream.next().await.unwrap(), "hello");
tx.send("bye").unwrap();
assert_eq!(stream.next().await.unwrap(), "bye");
}
#[tokio::test]
async fn watch_stream_from_changes() {
let (tx, rx) = watch::channel("hello");
let mut stream = WatchStream::from_changes(rx);
assert_pending!(spawn(&mut stream).poll_next());
tx.send("bye").unwrap();
assert_eq!(stream.next().await.unwrap(), "bye");
}