use rand::{Rng, RngExt};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[cfg(feature = "js")]
use wasm_bindgen_test::wasm_bindgen_test;
use crate::loop_channel;
use remoc::{exec, rch::io};
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_simple() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(11);
assert_eq!(rx.size(), Some(11));
assert_eq!(tx.expected_size(), Some(11));
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
assert_eq!(rx.size(), Some(11));
let write_task = exec::spawn(async move {
tx.write_all(b"hello world").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"hello world");
assert_eq!(rx.bytes_received(), 11);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn unsized_simple() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::channel();
assert_eq!(rx.size(), None);
assert_eq!(tx.expected_size(), None);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
assert_eq!(rx.size(), None);
let write_task = exec::spawn(async move {
tx.write_all(b"hello world").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"hello world");
assert_eq!(rx.bytes_received(), 11);
assert_eq!(rx.size(), Some(11));
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_send_sender() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Sender>().await;
let (tx, mut rx) = io::sized(11);
a_tx.send(tx).await.unwrap();
let mut tx = b_rx.recv().await.unwrap().unwrap();
let read_task = exec::spawn(async move {
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, b"hello world");
assert_eq!(rx.bytes_received(), 11);
});
tx.write_all(b"hello world").await.unwrap();
tx.shutdown().await.unwrap();
read_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_zero() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(0);
assert_eq!(rx.size(), Some(0));
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert!(buf.is_empty());
assert_eq!(rx.bytes_received(), 0);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn unsized_zero() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::channel();
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert!(buf.is_empty());
assert_eq!(rx.bytes_received(), 0);
assert_eq!(rx.size(), Some(0));
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_send_more_than_announced() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(5);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let read_task = exec::spawn(async move {
let mut buf = Vec::new();
let _ = rx.read_to_end(&mut buf).await;
});
let result = tx.write_all(b"hello world").await;
assert!(result.is_err(), "writing more than announced should fail");
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::WriteZero);
read_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_send_less_than_announced_sender_shutdown() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(100);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let read_task = exec::spawn(async move {
let mut buf = Vec::new();
let _ = rx.read_to_end(&mut buf).await;
});
tx.write_all(b"short").await.unwrap();
let result = tx.shutdown().await;
assert!(result.is_err(), "shutdown with insufficient data should fail");
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
read_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_send_less_than_announced_receiver_eof() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(100);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
tx.write_all(b"short").await.unwrap();
drop(tx);
let mut buf = Vec::new();
let result = rx.read_to_end(&mut buf).await;
assert!(result.is_err(), "receiver should fail on size mismatch");
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn unsized_no_shutdown() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::channel();
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
tx.write_all(b"hello").await.unwrap();
drop(tx);
let mut buf = Vec::new();
let result = rx.read_to_end(&mut buf).await;
assert!(result.is_err(), "receiver should fail without proper shutdown");
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_large_data() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let mut rng = rand::rng();
let size: usize = rng.random_range(100_000..500_000);
let mut data = vec![0u8; size];
rng.fill_bytes(&mut data);
let (mut tx, rx) = io::sized(size as u64);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let data_clone = data.clone();
let write_task = exec::spawn(async move {
tx.write_all(&data_clone).await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf.len(), size);
assert_eq!(buf, data);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn unsized_large_data() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let mut rng = rand::rng();
let size: usize = rng.random_range(100_000..500_000);
let mut data = vec![0u8; size];
rng.fill_bytes(&mut data);
let (mut tx, rx) = io::channel();
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let data_clone = data.clone();
let write_task = exec::spawn(async move {
tx.write_all(&data_clone).await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf.len(), size);
assert_eq!(buf, data);
assert_eq!(rx.size(), Some(size as u64));
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_multiple_writes() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(15);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.write_all(b"hello").await.unwrap();
tx.write_all(b" ").await.unwrap();
tx.write_all(b"world").await.unwrap();
tx.write_all(b"!!!!").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"hello world!!!!");
assert_eq!(rx.bytes_received(), 15);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn unsized_multiple_writes() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::channel();
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.write_all(b"one ").await.unwrap();
tx.write_all(b"two ").await.unwrap();
tx.write_all(b"three").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"one two three");
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn bytes_tracking() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::channel();
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
assert_eq!(tx.bytes_written(), 0);
assert_eq!(rx.bytes_received(), 0);
let write_task = exec::spawn(async move {
tx.write_all(b"12345").await.unwrap();
assert_eq!(tx.bytes_written(), 5);
tx.write_all(b"67890").await.unwrap();
assert_eq!(tx.bytes_written(), 10);
tx.shutdown().await.unwrap();
});
let mut buf = [0u8; 5];
rx.read_exact(&mut buf).await.unwrap();
assert_eq!(rx.bytes_received(), 5);
rx.read_exact(&mut buf).await.unwrap();
assert_eq!(rx.bytes_received(), 10);
write_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_forward() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let ((mut c_tx, _), (_, mut d_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(11);
a_tx.send(rx).await.unwrap();
let rx = b_rx.recv().await.unwrap().unwrap();
c_tx.send(rx).await.unwrap();
let mut rx = d_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.write_all(b"hello world").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"hello world");
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn unsized_forward() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let ((mut c_tx, _), (_, mut d_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::channel();
assert_eq!(rx.size(), None);
a_tx.send(rx).await.unwrap();
let rx = b_rx.recv().await.unwrap().unwrap();
c_tx.send(rx).await.unwrap();
let mut rx = d_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.write_all(b"hello world").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"hello world");
assert_eq!(rx.size(), Some(11));
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn empty_write() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(5);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
tx.write_all(b"").await.unwrap();
tx.write_all(b"hello").await.unwrap();
tx.write_all(b"").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = Vec::new();
rx.read_to_end(&mut buf).await.unwrap();
write_task.await.unwrap();
assert_eq!(buf, b"hello");
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn sized_partial_reads() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let (mut tx, rx) = io::sized(10);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
assert_eq!(rx.remaining(), Some(10));
let write_task = exec::spawn(async move {
tx.write_all(b"0123456789").await.unwrap();
tx.shutdown().await.unwrap();
});
let mut buf = [0u8; 3];
rx.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"012");
assert_eq!(rx.bytes_received(), 3);
assert_eq!(rx.remaining(), Some(7));
let mut buf = [0u8; 4];
rx.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"3456");
assert_eq!(rx.bytes_received(), 7);
assert_eq!(rx.remaining(), Some(3));
let mut buf = [0u8; 3];
rx.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"789");
assert_eq!(rx.bytes_received(), 10);
assert_eq!(rx.remaining(), Some(0));
let mut buf = [0u8; 1];
let n = rx.read(&mut buf).await.unwrap();
assert_eq!(n, 0);
write_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn file_transfer_simulation() {
use serde::{Deserialize, Serialize};
crate::init();
#[derive(Debug, Serialize, Deserialize)]
struct FileTransfer {
filename: String,
size: u64,
data: io::Receiver,
}
let ((mut client_tx, _), (_, mut server_rx)) = loop_channel::<FileTransfer>().await;
let file_content = b"This is the content of the file being transferred.\n".repeat(100);
let file_size = file_content.len() as u64;
let (mut io_tx, io_rx) = io::sized(file_size);
client_tx
.send(FileTransfer { filename: "example.txt".to_string(), size: file_size, data: io_rx })
.await
.unwrap();
let file_content_clone = file_content.clone();
let write_task = exec::spawn(async move {
for chunk in file_content_clone.chunks(1024) {
io_tx.write_all(chunk).await.unwrap();
}
io_tx.shutdown().await.unwrap();
});
let transfer = server_rx.recv().await.unwrap().unwrap();
assert_eq!(transfer.filename, "example.txt");
assert_eq!(transfer.size, file_size);
let mut data_rx = transfer.data;
assert_eq!(data_rx.size(), Some(file_size));
assert_eq!(data_rx.remaining(), Some(file_size));
let mut received = Vec::new();
data_rx.read_to_end(&mut received).await.unwrap();
assert_eq!(received.len() as u64, file_size);
assert_eq!(received, file_content);
assert_eq!(data_rx.bytes_received(), file_size);
assert_eq!(data_rx.remaining(), Some(0));
write_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn streaming_transfer_unknown_size() {
use serde::{Deserialize, Serialize};
crate::init();
#[derive(Debug, Serialize, Deserialize)]
struct StreamTransfer {
name: String,
data: io::Receiver,
}
let ((mut client_tx, _), (_, mut server_rx)) = loop_channel::<StreamTransfer>().await;
let (mut io_tx, io_rx) = io::channel();
client_tx.send(StreamTransfer { name: "live_stream".to_string(), data: io_rx }).await.unwrap();
let write_task = exec::spawn(async move {
for i in 0..10 {
let chunk = format!("Chunk {}\n", i);
io_tx.write_all(chunk.as_bytes()).await.unwrap();
}
io_tx.shutdown().await.unwrap();
});
let transfer = server_rx.recv().await.unwrap().unwrap();
assert_eq!(transfer.name, "live_stream");
let mut data_rx = transfer.data;
assert_eq!(data_rx.size(), None);
assert_eq!(data_rx.remaining(), None);
let mut received = Vec::new();
data_rx.read_to_end(&mut received).await.unwrap();
assert!(data_rx.size().is_some());
assert_eq!(data_rx.remaining(), Some(0));
let expected: String = (0..10).map(|i| format!("Chunk {}\n", i)).collect();
assert_eq!(String::from_utf8(received).unwrap(), expected);
write_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn tokio_copy_integration() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let data = b"Hello from tokio::io::copy!".repeat(100);
let size = data.len() as u64;
let (mut tx, rx) = io::sized(size);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let data_clone = data.clone();
let write_task = exec::spawn(async move {
let mut reader = std::io::Cursor::new(data_clone);
let copied = tokio::io::copy(&mut reader, &mut tx).await.unwrap();
assert_eq!(copied, size);
tx.shutdown().await.unwrap();
});
let mut output = Vec::new();
let received = tokio::io::copy(&mut rx, &mut output).await.unwrap();
write_task.await.unwrap();
assert_eq!(received, size);
assert_eq!(output, data);
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn tokio_copy_sized_no_shutdown_succeeds() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let data = b"data without explicit shutdown";
let size = data.len() as u64;
let (mut tx, rx) = io::sized(size);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
let mut reader = std::io::Cursor::new(data);
tokio::io::copy(&mut reader, &mut tx).await.unwrap();
drop(tx);
});
let mut output = Vec::new();
let result = tokio::io::copy(&mut rx, &mut output).await;
assert!(result.is_ok());
assert_eq!(output, data);
write_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn tokio_copy_unsized_no_shutdown_fails() {
crate::init();
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let data = b"data without proper shutdown";
let (mut tx, rx) = io::channel();
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
let mut reader = std::io::Cursor::new(data);
tokio::io::copy(&mut reader, &mut tx).await.unwrap();
drop(tx);
});
let mut output = Vec::new();
let result = tokio::io::copy(&mut rx, &mut output).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::UnexpectedEof);
write_task.await.unwrap();
}
#[cfg_attr(not(feature = "js"), tokio::test)]
#[cfg_attr(feature = "js", wasm_bindgen_test)]
async fn tokio_copy_read_error_no_shutdown() {
use std::{
pin::Pin,
task::{Context, Poll},
};
use tokio::io::AsyncRead;
crate::init();
struct FailingReader {
data: Vec<u8>,
pos: usize,
fail_at: usize,
}
impl AsyncRead for FailingReader {
fn poll_read(
mut self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
if self.pos >= self.fail_at {
return Poll::Ready(Err(std::io::Error::other("simulated read error")));
}
let remaining = self.data.len() - self.pos;
let to_read = remaining.min(buf.remaining()).min(self.fail_at - self.pos);
if to_read > 0 {
buf.put_slice(&self.data[self.pos..self.pos + to_read]);
self.pos += to_read;
}
Poll::Ready(Ok(()))
}
}
let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<io::Receiver>().await;
let full_data = b"0123456789".repeat(100); let size = full_data.len() as u64;
let (mut tx, rx) = io::sized(size);
a_tx.send(rx).await.unwrap();
let mut rx = b_rx.recv().await.unwrap().unwrap();
let write_task = exec::spawn(async move {
let mut reader = FailingReader {
data: full_data.clone(),
pos: 0,
fail_at: 500, };
let result = tokio::io::copy(&mut reader, &mut tx).await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), std::io::ErrorKind::Other);
drop(tx);
});
let mut output = Vec::new();
let result = tokio::io::copy(&mut rx, &mut output).await;
assert!(result.is_err());
write_task.await.unwrap();
}