Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tokio-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ futures-core = "0.3.0"
[dev-dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["full"] }
futures-util = "0.3.0"
futures-test = "0.3.5"

[package.metadata.docs.rs]
all-features = true
Expand Down
11 changes: 6 additions & 5 deletions tokio-test/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ impl Inner {
let err = Arc::try_unwrap(err).expect("There are no other references.");
Err(err)
}
Some(_) => {
Some(&mut Action::Write(_))
| Some(&mut Action::WriteError(_))
| Some(&mut Action::Wait(_)) => {
// Either waiting or expecting a write
Err(io::ErrorKind::WouldBlock.into())
}
Expand Down Expand Up @@ -280,10 +282,8 @@ impl Inner {
Action::Wait(..) | Action::WriteError(..) => {
break;
}
_ => {}
Action::Read(_) | Action::ReadError(_) => (),
}

// TODO: remove write
}

Ok(ret)
Expand Down Expand Up @@ -441,6 +441,7 @@ impl AsyncWrite for Mock {
panic!("unexpected WouldBlock {}", self.pmsg());
}
}
Ok(0) if buf.is_empty() => return Poll::Ready(Ok(0)),
Ok(0) => {
// TODO: Is this correct?
if !self.inner.actions.is_empty() {
Expand Down Expand Up @@ -494,7 +495,7 @@ impl Drop for Mock {
"There is still data left to write. {}",
self.pmsg()
),
_ => (),
Action::ReadError(_) | Action::WriteError(_) | Action::Wait(_) => (),
});
}
}
Expand Down
87 changes: 87 additions & 0 deletions tokio-test/tests/io.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
#![warn(rust_2018_idioms)]

use futures_test::task::{noop_context, panic_waker};
use futures_util::pin_mut;
use std::future::Future;
use std::io;
use std::task::{Context, Poll};
use tokio::io::AsyncWrite;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::time::{Duration, Instant};
use tokio_test::assert_pending;
use tokio_test::io::Builder;

#[tokio::test]
Expand Down Expand Up @@ -170,3 +176,84 @@ async fn multiple_wait() {
start.elapsed().as_millis()
);
}

// No matter which usecase, it doesn't make sense for a read
// hang forever. However, currently, if there is no sequenced read
// action, it will hang forever.
//
// Since we want be aware of the fixing of this bug,
// no matter intentionally or unintentionally,
// we add this test to catch the behavior change.
//
// It looks like fixing it is not hard, but not sure the downstream
// impact, which might be a breaking change due to the
// `Mock::inner::read_wait` field, so we keep it as is for now.
//
// TODO: fix this bug
#[test]
fn should_hang_forever_on_read_but_no_sequenced_read_action() {
let mut mock = Builder::new()
.write_error(io::Error::new(io::ErrorKind::Other, "cruel"))
.build();

let mut buf = [0; 1];
let read_exact_fut = mock.read(&mut buf);
pin_mut!(read_exact_fut);
assert_pending!(read_exact_fut.poll(&mut Context::from_waker(&panic_waker())));
}

// The `Mock` is expected to always panic if there is an unconsumed error action,
// rather than silently ignoring it. However,
// currently it only panics on unconsumed read/write actions,
// not on error actions. Fixing this requires a breaking change.
//
// This test verifies that it does not panic yet,
// to prevent accidentally introducing the breaking change prematurely.
//
// TODO: fix this bug in the next major release
#[test]
fn do_not_panic_unconsumed_error() {
let _mock = Builder::new()
.read_error(io::Error::new(io::ErrorKind::Other, "cruel"))
.build();
}

// The `Mock` must never panic, even if cloned multiple times.
// However, at present, cloning the builder under certain
// conditions causes a panic.
//
// Fixing this would require making `Mock` non-`Clone`,
// which is a breaking change.
//
// Since we want be aware of the fixing of this bug,
// no matter intentionally or unintentionally,
// we add this test to catch the behavior change.
//
// TODO: fix this bug in the next major release
#[tokio::test]
#[should_panic = "There are no other references.: Custom { kind: Other, error: \"cruel\" }"]
async fn panic_if_clone_the_build_with_error_action() {
let mut builder = Builder::new();
builder.write_error(io::Error::new(io::ErrorKind::Other, "cruel"));
let mut builder2 = builder.clone();

let mut mock = builder.build();
let _mock2 = builder2.build();

// this write_all will panic due to unwrapping the error from `Arc`
mock.write_all(b"hello").await.unwrap();
unreachable!();
}

#[tokio::test]
async fn should_not_hang_forever_on_zero_length_write() {
let mock = Builder::new().write(b"write").build();
pin_mut!(mock);
match mock.as_mut().poll_write(&mut noop_context(), &[0u8; 0]) {
// drain the remaining write action to avoid panic at drop of the `mock`
Poll::Ready(Ok(0)) => mock.write_all(b"write").await.unwrap(),
Poll::Ready(Ok(n)) => panic!("expected to write 0 bytes, wrote {n} bytes instead"),
Poll::Ready(Err(e)) => panic!("expected to write 0 bytes, got error {e} instead"),
Poll::Pending => panic!("expected to write 0 bytes immediately, but pending instead"),
}
}
Loading