Skip to content

Commit b17f254

Browse files
committed
io: add shutdown() and shutdown_error() on tokio_test::io::Builder
Signed-off-by: ADD-SP <[email protected]>
1 parent 43f6dce commit b17f254

File tree

2 files changed

+159
-4
lines changed

2 files changed

+159
-4
lines changed

tokio-test/src/io.rs

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,13 @@ pub struct Builder {
6060
enum Action {
6161
Read(Vec<u8>),
6262
Write(Vec<u8>),
63+
Shutdown,
6364
Wait(Duration),
6465
// Wrapped in Arc so that Builder can be cloned and Send.
6566
// Mock is not cloned as does not need to check Rc for ref counts.
6667
ReadError(Option<Arc<io::Error>>),
6768
WriteError(Option<Arc<io::Error>>),
69+
ShutdownError(Option<Arc<io::Error>>),
6870
}
6971

7072
struct Inner {
@@ -76,6 +78,13 @@ struct Inner {
7678
name: String,
7779
}
7880

81+
enum Shutdown {
82+
ShouldSuccess,
83+
ShouldError(io::Error),
84+
NeedWait,
85+
NoActions,
86+
}
87+
7988
impl Builder {
8089
/// Return a new, empty `Builder`.
8190
pub fn new() -> Self {
@@ -130,6 +139,25 @@ impl Builder {
130139
self
131140
}
132141

142+
/// Sequence a shutdown operation.
143+
///
144+
/// The next operation in the mock's script will be to expect a
145+
/// [`AsyncWrite::poll_shutdown`] call.
146+
pub fn shutdown(&mut self) -> &mut Self {
147+
self.actions.push_back(Action::Shutdown);
148+
self
149+
}
150+
151+
/// Sequence a shutdown operation that produces an error.
152+
///
153+
/// The next operation in the mock's script will be to expect a
154+
/// [`AsyncWrite::poll_shutdown`] call that returns `error`.
155+
pub fn shutdown_error(&mut self, error: io::Error) -> &mut Self {
156+
let error = Some(error.into());
157+
self.actions.push_back(Action::ShutdownError(error));
158+
self
159+
}
160+
133161
/// Set name of the mock IO object to include in panic messages and debug output
134162
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
135163
self.name = name.into();
@@ -198,6 +226,10 @@ impl Inner {
198226

199227
let rx = UnboundedReceiverStream::new(rx);
200228

229+
// Actually, we should deny any write action after the shutdown action.
230+
// However, since we currently doesn't check the write action after the error
231+
// like BrokenPipe error, we ignore this case to keep the behavior consistent.
232+
201233
let inner = Inner {
202234
actions,
203235
sleep: None,
@@ -242,6 +274,9 @@ impl Inner {
242274
// Either waiting or expecting a write
243275
Err(io::ErrorKind::WouldBlock.into())
244276
}
277+
Some(&mut Action::Shutdown) | Some(&mut Action::ShutdownError(_)) => {
278+
panic!("unexpected read, expect poll_shutdown");
279+
}
245280
None => Ok(()),
246281
}
247282
}
@@ -284,6 +319,9 @@ impl Inner {
284319
break;
285320
}
286321
Action::Read(_) | Action::ReadError(_) => (),
322+
Action::Shutdown | Action::ShutdownError(_) => {
323+
panic!("unexpected write, expect poll_shutdown");
324+
}
287325
}
288326
}
289327

@@ -297,6 +335,25 @@ impl Inner {
297335
}
298336
}
299337

338+
fn shutdown(&mut self) -> Shutdown {
339+
match self.action() {
340+
Some(&mut Action::Shutdown) => Shutdown::ShouldSuccess,
341+
Some(&mut Action::ShutdownError(ref mut err)) => {
342+
let err = err.take().expect("Should have been removed from actions.");
343+
let err = Arc::try_unwrap(err).expect("There are no other references.");
344+
Shutdown::ShouldError(err)
345+
}
346+
Some(&mut Action::Read(_)) | Some(&mut Action::ReadError(_)) => {
347+
panic!("unexpected poll_shutdown, expect read");
348+
}
349+
Some(&mut Action::Write(_)) | Some(&mut Action::WriteError(_)) => {
350+
panic!("unexpected poll_shutdown, expect write");
351+
}
352+
Some(&mut Action::Wait(_)) => Shutdown::NeedWait,
353+
None => Shutdown::NoActions,
354+
}
355+
}
356+
300357
fn action(&mut self) -> Option<&mut Action> {
301358
loop {
302359
if self.actions.is_empty() {
@@ -333,6 +390,12 @@ impl Inner {
333390
break;
334391
}
335392
}
393+
Action::Shutdown => break,
394+
Action::ShutdownError(ref mut error) => {
395+
if error.is_some() {
396+
break;
397+
}
398+
}
336399
}
337400

338401
let _action = self.actions.pop_front();
@@ -472,8 +535,55 @@ impl AsyncWrite for Mock {
472535
Poll::Ready(Ok(()))
473536
}
474537

475-
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
476-
Poll::Ready(Ok(()))
538+
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
539+
loop {
540+
if let Some(ref mut sleep) = self.inner.sleep {
541+
ready!(Pin::new(sleep).poll(cx));
542+
}
543+
544+
// If a sleep is set, it has already fired
545+
self.inner.sleep = None;
546+
547+
match self.inner.shutdown() {
548+
Shutdown::ShouldSuccess => {
549+
assert!(matches!(
550+
self.inner.actions.pop_front(),
551+
Some(Action::Shutdown)
552+
));
553+
self.maybe_wakeup_reader();
554+
return Poll::Ready(Ok(()));
555+
}
556+
Shutdown::ShouldError(e) => {
557+
assert!(matches!(
558+
self.inner.actions.pop_front(),
559+
Some(Action::ShutdownError(_))
560+
));
561+
self.maybe_wakeup_reader();
562+
return Poll::Ready(Err(e));
563+
}
564+
Shutdown::NeedWait => {
565+
if let Some(rem) = self.inner.remaining_wait() {
566+
let until = Instant::now() + rem;
567+
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
568+
} else {
569+
panic!(
570+
"unexpected poll_shutdown, expect read or write {}",
571+
self.pmsg()
572+
);
573+
}
574+
}
575+
Shutdown::NoActions => {
576+
if let Some(action) = ready!(self.inner.poll_action(cx)) {
577+
self.inner.actions.push_back(action);
578+
} else {
579+
panic!(
580+
"unexpected poll_shutdown, but actually no more sequenced actions {}",
581+
self.pmsg()
582+
);
583+
}
584+
}
585+
}
586+
}
477587
}
478588
}
479589

@@ -496,7 +606,11 @@ impl Drop for Mock {
496606
"There is still data left to write. {}",
497607
self.pmsg()
498608
),
609+
Action::Shutdown => panic!("AsyncWrite::poll_shutdown was not called. {}", self.pmsg()),
499610
Action::ReadError(_) | Action::WriteError(_) | Action::Wait(_) => (),
611+
// Since the existing implementation ignores the read/write error, so we also ignore the
612+
// shutdown error here to keep the behavior consistent.
613+
Action::ShutdownError(_) => (),
500614
});
501615
}
502616
}

tokio-test/tests/io.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::task::{Context, Poll};
88
use tokio::io::AsyncWrite;
99
use tokio::io::{AsyncReadExt, AsyncWriteExt};
1010
use tokio::time::{Duration, Instant};
11-
use tokio_test::assert_pending;
1211
use tokio_test::io::Builder;
12+
use tokio_test::{assert_pending, assert_ready};
1313

1414
#[tokio::test]
1515
async fn read() {
@@ -232,7 +232,7 @@ fn do_not_panic_unconsumed_error() {
232232
// TODO: fix this bug in the next major release
233233
#[tokio::test]
234234
#[should_panic = "There are no other references.: Custom { kind: Other, error: \"cruel\" }"]
235-
async fn panic_if_clone_the_build_with_error_action() {
235+
async fn should_panic_if_clone_the_builder_with_error_action() {
236236
let mut builder = Builder::new();
237237
builder.write_error(io::Error::new(io::ErrorKind::Other, "cruel"));
238238
let mut builder2 = builder.clone();
@@ -257,3 +257,44 @@ async fn should_not_hang_forever_on_zero_length_write() {
257257
Poll::Pending => panic!("expected to write 0 bytes immediately, but pending instead"),
258258
}
259259
}
260+
261+
#[tokio::test]
262+
async fn shutdown() {
263+
let mut mock = Builder::new().shutdown().build();
264+
mock.shutdown().await.unwrap();
265+
}
266+
267+
#[tokio::test]
268+
async fn shutdown_error() {
269+
let error = io::Error::new(io::ErrorKind::Other, "cruel");
270+
let mut mock = Builder::new().shutdown_error(error).build();
271+
let err = mock.shutdown().await.unwrap_err();
272+
assert_eq!(err.kind(), io::ErrorKind::Other);
273+
assert_eq!("cruel", format!("{err}"));
274+
}
275+
276+
#[tokio::test(start_paused = true)]
277+
async fn shutdown_wait() {
278+
const WAIT: Duration = Duration::from_secs(1);
279+
280+
let mock = Builder::new().wait(WAIT).shutdown().build();
281+
pin_mut!(mock);
282+
283+
assert_pending!(mock.as_mut().poll_shutdown(&mut noop_context()));
284+
285+
tokio::time::advance(WAIT).await;
286+
let _ = assert_ready!(mock.as_mut().poll_shutdown(&mut noop_context()));
287+
}
288+
289+
#[test]
290+
#[should_panic = "AsyncWrite::poll_shutdown was not called. (1 actions remain)"]
291+
fn should_panic_on_leftover_shutdown_action() {
292+
let _mock = Builder::new().shutdown().build();
293+
}
294+
295+
#[test]
296+
fn should_not_panic_on_leftover_shutdown_error_action() {
297+
let _mock = Builder::new()
298+
.shutdown_error(io::Error::new(io::ErrorKind::Other, "cruel"))
299+
.build();
300+
}

0 commit comments

Comments
 (0)