Skip to main content

xwt_tests/tests/
tokio_io_read_partial_buf.rs

1//! A test for driving `poll_read` manually with partially-filled and
2//! fully-filled read buffers.
3//!
4//! The `tokio::io::AsyncReadExt` helpers always pass a freshly created
5//! [`tokio::io::ReadBuf`], so the `filled` portion is always empty and
6//! the remaining capacity never differs from the full capacity. Callers
7//! invoking [`tokio::io::AsyncRead::poll_read`] directly, however, can pass
8//! a buffer that is already partially or even fully filled. This test
9//! verifies the implementations honor the [`tokio::io::ReadBuf`] contract in
10//! this mode:
11//!
12//! - a poll with no remaining capacity completes immediately without
13//!   waiting for (or requesting) more stream data, and leaves the buffer
14//!   contents intact;
15//! - a poll with a partially-filled buffer only appends to the unfilled
16//!   region, preserving the already-filled prefix;
17//! - the data queued beyond the buffer remaining capacity is not lost and
18//!   arrives in order on the subsequent reads.
19
20use std::task::Poll;
21
22use tokio::io::AsyncRead;
23use xwt_core::prelude::*;
24
25/// The sentinel byte used to detect the writes outside of the unfilled
26/// region of the read buffer.
27const SENTINEL: u8 = 0xAA;
28
29#[derive(Debug, thiserror::Error)]
30pub enum Error<Endpoint>
31where
32    Endpoint: xwt_core::endpoint::Connect + std::fmt::Debug,
33    Endpoint::Connecting: std::fmt::Debug,
34    ConnectSessionFor<Endpoint>: xwt_core::session::stream::OpenBi + std::fmt::Debug,
35{
36    #[error("connect: {0}")]
37    Connect(#[source] xwt_error::Connect<Endpoint>),
38    #[error("open: {0}")]
39    Open(#[source] xwt_error::OpenBi<ConnectSessionFor<Endpoint>>),
40    #[error("write: {0}")]
41    Write(#[source] std::io::Error),
42    #[error("read: {0}")]
43    Read(#[source] std::io::Error),
44    #[error("no response")]
45    NoResponse,
46    #[error("poll_read with a fully-filled buffer returned `Poll::Pending` instead of completing immediately")]
47    FullBufPending,
48    #[error("poll_read wrote outside of the unfilled region: {actual:?}")]
49    ClobberedBuf { actual: Vec<u8> },
50    #[error("bad data: expected {expected:?}, got {actual:?}")]
51    BadData { expected: Vec<u8>, actual: Vec<u8> },
52}
53
54pub async fn run<Endpoint>(endpoint: Endpoint, url: &str) -> Result<(), Error<Endpoint>>
55where
56    Endpoint: xwt_core::endpoint::Connect + std::fmt::Debug,
57    Endpoint::Connecting: std::fmt::Debug,
58    ConnectSessionFor<Endpoint>: xwt_core::session::stream::OpenBi + std::fmt::Debug,
59    SendStreamFor<ConnectSessionFor<Endpoint>>: tokio::io::AsyncWrite,
60    RecvStreamFor<ConnectSessionFor<Endpoint>>: tokio::io::AsyncRead,
61{
62    let session = crate::utils::connect(&endpoint, url)
63        .await
64        .map_err(Error::Connect)?;
65
66    let (send_stream, recv_stream) = crate::utils::open_bi(&session).await.map_err(Error::Open)?;
67
68    tokio::pin!(send_stream);
69    tokio::pin!(recv_stream);
70
71    // Echo a payload and read only half of it back, so that the rest of
72    // the payload is queued for reading.
73    let payload = b"ABCD";
74    write_all(&mut send_stream, payload).await?;
75    read_exact_and_verify(recv_stream.as_mut(), &payload[..2]).await?;
76
77    // Poll a read with a buffer that has no remaining capacity.
78    // This must complete immediately - not wait for anything - and must not
79    // alter the buffer, even though more stream data is queued.
80    let mut array = [SENTINEL; 4];
81    let mut read_buf = tokio::io::ReadBuf::new(&mut array);
82    let filled = read_buf.capacity();
83    read_buf.set_filled(filled);
84    let poll =
85        std::future::poll_fn(|cx| Poll::Ready(recv_stream.as_mut().poll_read(cx, &mut read_buf)))
86            .await;
87    match poll {
88        Poll::Ready(Ok(())) => {}
89        Poll::Ready(Err(error)) => return Err(Error::Read(error)),
90        Poll::Pending => return Err(Error::FullBufPending),
91    }
92    if read_buf.filled() != [SENTINEL; 4] {
93        return Err(Error::ClobberedBuf {
94            actual: read_buf.filled().to_vec(),
95        });
96    }
97
98    // The queued rest of the payload must still be readable.
99    read_exact_and_verify(recv_stream.as_mut(), &payload[2..]).await?;
100
101    // Echo another payload and read it through a partially-filled buffer:
102    // only the unfilled region may be written to.
103    let payload = b"EFGHIJKL";
104    write_all(&mut send_stream, payload).await?;
105
106    let mut array = [SENTINEL; 8];
107    let mut read_buf = tokio::io::ReadBuf::new(&mut array);
108    read_buf.set_filled(5);
109    let read = std::future::poll_fn(|cx| recv_stream.as_mut().poll_read(cx, &mut read_buf))
110        .await
111        .map(|()| read_buf.filled().len() - 5)
112        .map_err(Error::Read)?;
113    if read == 0 {
114        return Err(Error::NoResponse);
115    }
116    if read_buf.filled()[..5] != [SENTINEL; 5] {
117        return Err(Error::ClobberedBuf {
118            actual: read_buf.filled().to_vec(),
119        });
120    }
121    if read_buf.filled()[5..] != payload[..read] {
122        return Err(Error::BadData {
123            expected: payload[..read].to_vec(),
124            actual: read_buf.filled()[5..].to_vec(),
125        });
126    }
127
128    // The rest of the payload - including anything the implementation may
129    // have pulled off the stream beyond the buffer remaining capacity -
130    // must arrive in order.
131    read_exact_and_verify(recv_stream.as_mut(), &payload[read..]).await?;
132
133    Ok(())
134}
135
136/// Write the whole payload to the stream.
137async fn write_all<Endpoint, S>(
138    send_stream: &mut S,
139    mut to_write: &[u8],
140) -> Result<(), Error<Endpoint>>
141where
142    Endpoint: xwt_core::endpoint::Connect + std::fmt::Debug,
143    Endpoint::Connecting: std::fmt::Debug,
144    ConnectSessionFor<Endpoint>: xwt_core::session::stream::OpenBi + std::fmt::Debug,
145    S: tokio::io::AsyncWrite + Unpin,
146{
147    loop {
148        let written = tokio::io::AsyncWriteExt::write(send_stream, to_write)
149            .await
150            .map_err(Error::Write)?;
151        to_write = &to_write[written..];
152        if to_write.is_empty() {
153            return Ok(());
154        }
155    }
156}
157
158/// Read exactly `expected.len()` bytes from the stream and verify they match
159/// the `expected` bytes.
160async fn read_exact_and_verify<Endpoint, S>(
161    mut recv_stream: std::pin::Pin<&mut S>,
162    expected: &[u8],
163) -> Result<(), Error<Endpoint>>
164where
165    Endpoint: xwt_core::endpoint::Connect + std::fmt::Debug,
166    Endpoint::Connecting: std::fmt::Debug,
167    ConnectSessionFor<Endpoint>: xwt_core::session::stream::OpenBi + std::fmt::Debug,
168    S: tokio::io::AsyncRead,
169{
170    let mut received = vec![0u8; expected.len()];
171    let mut filled = 0;
172    while filled < expected.len() {
173        let mut read_buf = tokio::io::ReadBuf::new(&mut received[filled..]);
174        std::future::poll_fn(|cx| recv_stream.as_mut().poll_read(cx, &mut read_buf))
175            .await
176            .map_err(Error::Read)?;
177        let read = read_buf.filled().len();
178        if read == 0 {
179            return Err(Error::NoResponse);
180        }
181        filled += read;
182    }
183
184    if received != expected {
185        return Err(Error::BadData {
186            expected: expected.to_vec(),
187            actual: received,
188        });
189    }
190
191    Ok(())
192}