aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Vander Stoep <jeffv@google.com>2021-02-18 12:05:23 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-02-18 12:05:23 +0000
commitb91c3f7f8ba5417c1870118d3d41c0fc8b2dbd46 (patch)
treeec822486fe14c777765c09f2b4d259f0a2e9c618
parentb7f806c7cf95981b7162d895495ede050d1ad1c7 (diff)
parent0a25d85a3b41f4f72cc34706a4144d6db12437de (diff)
downloadtokio-test-b91c3f7f8ba5417c1870118d3d41c0fc8b2dbd46.tar.gz
Import tokio-test v0.4.0 am: bf372733d2 am: d149649306 am: 0a25d85a3b
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tokio-test/+/1593991 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I873cf1904aed721f14e5655b677ecb3f9fe4837f
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--Android.bp144
-rw-r--r--CHANGELOG.md15
-rw-r--r--Cargo.toml46
-rw-r--r--Cargo.toml.orig35
-rw-r--r--LICENSE25
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS1
-rw-r--r--README.md13
-rw-r--r--TEST_MAPPING17
-rw-r--r--src/io.rs498
-rw-r--r--src/lib.rs37
-rw-r--r--src/macros.rs261
-rw-r--r--src/task.rs253
-rw-r--r--tests/block_on.rs27
-rw-r--r--tests/io.rs86
-rw-r--r--tests/macros.rs107
18 files changed, 1589 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..888a4fb
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "a66017f04985f5e7b7cb981e370e78a7291a5537"
+ }
+}
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..125f4c9
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,144 @@
+// This file is generated by cargo2android.py --run --dependencies --device --tests.
+
+rust_library {
+ name: "libtokio_test",
+ host_supported: true,
+ crate_name: "tokio_test",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libasync_stream",
+ "libbytes",
+ "libfutures_core",
+ "libtokio",
+ "libtokio_stream",
+ ],
+}
+
+rust_defaults {
+ name: "tokio-test_defaults",
+ crate_name: "tokio_test",
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libasync_stream",
+ "libbytes",
+ "libfutures_core",
+ "libfutures_util",
+ "libtokio",
+ "libtokio_stream",
+ ],
+}
+
+rust_test_host {
+ name: "tokio-test_host_test_src_lib",
+ defaults: ["tokio-test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "tokio-test_device_test_src_lib",
+ defaults: ["tokio-test_defaults"],
+}
+
+rust_defaults {
+ name: "tokio-test_defaults_tokio_test",
+ crate_name: "tokio_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libasync_stream",
+ "libbytes",
+ "libfutures_core",
+ "libfutures_util",
+ "libtokio",
+ "libtokio_stream",
+ "libtokio_test",
+ ],
+}
+
+rust_test_host {
+ name: "tokio-test_host_test_tests_block_on",
+ defaults: ["tokio-test_defaults_tokio_test"],
+ srcs: ["tests/block_on.rs"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "tokio-test_device_test_tests_block_on",
+ defaults: ["tokio-test_defaults_tokio_test"],
+ srcs: ["tests/block_on.rs"],
+}
+
+rust_test_host {
+ name: "tokio-test_host_test_tests_io",
+ defaults: ["tokio-test_defaults_tokio_test"],
+ srcs: ["tests/io.rs"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "tokio-test_device_test_tests_io",
+ defaults: ["tokio-test_defaults_tokio_test"],
+ srcs: ["tests/io.rs"],
+}
+
+rust_test_host {
+ name: "tokio-test_host_test_tests_macros",
+ defaults: ["tokio-test_defaults_tokio_test"],
+ srcs: ["tests/macros.rs"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "tokio-test_device_test_tests_macros",
+ defaults: ["tokio-test_defaults_tokio_test"],
+ srcs: ["tests/macros.rs"],
+}
+
+// dependent_library ["feature_list"]
+// async-stream-0.3.0
+// async-stream-impl-0.3.0
+// autocfg-1.0.1
+// bytes-1.0.1 "default,std"
+// cfg-if-1.0.0
+// futures-core-0.3.12 "alloc,default,std"
+// futures-macro-0.3.12
+// futures-task-0.3.12 "alloc,once_cell,std"
+// futures-util-0.3.12 "alloc,async-await,async-await-macro,default,futures-macro,proc-macro-hack,proc-macro-nested,slab,std"
+// instant-0.1.9
+// libc-0.2.86 "align,default,std"
+// lock_api-0.4.2
+// log-0.4.14
+// memchr-2.3.4 "default,std"
+// mio-0.7.7 "default,net,os-ext,os-poll,os-util,tcp,udp,uds"
+// num_cpus-1.13.0
+// once_cell-1.5.2 "alloc,default,std"
+// parking_lot-0.11.1 "default"
+// parking_lot_core-0.8.2
+// pin-project-lite-0.2.4
+// pin-utils-0.1.0
+// proc-macro-hack-0.5.19
+// proc-macro-nested-0.1.7
+// proc-macro2-1.0.24 "default,proc-macro"
+// quote-1.0.8 "default,proc-macro"
+// scopeguard-1.1.0
+// signal-hook-registry-1.3.0
+// slab-0.4.2
+// smallvec-1.6.1
+// syn-1.0.60 "clone-impls,default,derive,extra-traits,full,parsing,printing,proc-macro,quote,visit-mut"
+// tokio-1.2.0 "bytes,default,fs,full,io-std,io-util,libc,macros,memchr,mio,net,num_cpus,once_cell,parking_lot,process,rt,rt-multi-thread,signal,signal-hook-registry,sync,test-util,time,tokio-macros,winapi"
+// tokio-macros-1.1.0
+// tokio-stream-0.1.3 "default,time"
+// unicode-xid-0.2.1 "default"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0094de6
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,15 @@
+# 0.4.0 (December 23, 2020)
+
+- Track `tokio` 1.0 release.
+
+# 0.3.0 (October 15, 2020)
+
+- Track `tokio` 0.3 release.
+
+# 0.2.1 (April 17, 2020)
+
+- Add `Future` and `Stream` implementations for `task::Spawn<T>`.
+
+# 0.2.0 (November 25, 2019)
+
+- Initial release
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..0f78a92
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,46 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "tokio-test"
+version = "0.4.0"
+authors = ["Tokio Contributors <team@tokio.rs>"]
+description = "Testing utilities for Tokio- and futures-based code\n"
+homepage = "https://tokio.rs"
+documentation = "https://docs.rs/tokio-test/0.4.0/tokio_test"
+categories = ["asynchronous", "testing"]
+license = "MIT"
+repository = "https://github.com/tokio-rs/tokio"
+[package.metadata.docs.rs]
+all-features = true
+[dependencies.async-stream]
+version = "0.3"
+
+[dependencies.bytes]
+version = "1.0.0"
+
+[dependencies.futures-core]
+version = "0.3.0"
+
+[dependencies.tokio]
+version = "1.0.0"
+features = ["rt", "sync", "time", "test-util"]
+
+[dependencies.tokio-stream]
+version = "0.1"
+[dev-dependencies.futures-util]
+version = "0.3.0"
+
+[dev-dependencies.tokio]
+version = "1.0.0"
+features = ["full"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..52e48e1
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,35 @@
+[package]
+name = "tokio-test"
+# When releasing to crates.io:
+# - Remove path dependencies
+# - Update html_root_url.
+# - Update doc url
+# - Cargo.toml
+# - Update CHANGELOG.md.
+# - Create "tokio-test-0.4.x" git tag.
+version = "0.4.0"
+edition = "2018"
+authors = ["Tokio Contributors <team@tokio.rs>"]
+license = "MIT"
+repository = "https://github.com/tokio-rs/tokio"
+homepage = "https://tokio.rs"
+documentation = "https://docs.rs/tokio-test/0.4.0/tokio_test"
+description = """
+Testing utilities for Tokio- and futures-based code
+"""
+categories = ["asynchronous", "testing"]
+
+[dependencies]
+tokio = { version = "1.0.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
+tokio-stream = { version = "0.1", path = "../tokio-stream" }
+async-stream = "0.3"
+
+bytes = "1.0.0"
+futures-core = "0.3.0"
+
+[dev-dependencies]
+tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
+futures-util = "0.3.0"
+
+[package.metadata.docs.rs]
+all-features = true
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..243fcd6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2020 Tokio Contributors
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..5d9ec54
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "tokio-test"
+description: "Testing utilities for Tokio- and futures-based code"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/tokio-test"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/tokio-test/tokio-test-0.4.0.crate"
+ }
+ version: "0.4.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2021
+ month: 2
+ day: 8
+ }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..46fc303
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..64174d9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# tokio-test
+
+Tokio and Futures based testing utilities
+
+## License
+
+This project is licensed under the [MIT license](LICENSE).
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in Tokio by you, shall be licensed as MIT, without any additional
+terms or conditions.
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..f89e407
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,17 @@
+// Generated by update_crate_tests.py for tests that depend on this crate.
+{
+ "presubmit": [
+ {
+ "name": "tokio-test_device_test_tests_io"
+ },
+ {
+ "name": "tokio-test_device_test_src_lib"
+ },
+ {
+ "name": "tokio-test_device_test_tests_block_on"
+ },
+ {
+ "name": "tokio-test_device_test_tests_macros"
+ }
+ ]
+}
diff --git a/src/io.rs b/src/io.rs
new file mode 100644
index 0000000..77adfc3
--- /dev/null
+++ b/src/io.rs
@@ -0,0 +1,498 @@
+#![cfg(not(loom))]
+
+//! A mock type implementing [`AsyncRead`] and [`AsyncWrite`].
+//!
+//!
+//! # Overview
+//!
+//! Provides a type that implements [`AsyncRead`] + [`AsyncWrite`] that can be configured
+//! to handle an arbitrary sequence of read and write operations. This is useful
+//! for writing unit tests for networking services as using an actual network
+//! type is fairly non deterministic.
+//!
+//! # Usage
+//!
+//! Attempting to write data that the mock isn't expecting will result in a
+//! panic.
+//!
+//! [`AsyncRead`]: tokio::io::AsyncRead
+//! [`AsyncWrite`]: tokio::io::AsyncWrite
+
+use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+use tokio::sync::mpsc;
+use tokio::time::{self, Duration, Instant, Sleep};
+
+use futures_core::{ready, Stream};
+use std::collections::VecDeque;
+use std::fmt;
+use std::future::Future;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{self, Poll, Waker};
+use std::{cmp, io};
+
+/// An I/O object that follows a predefined script.
+///
+/// This value is created by `Builder` and implements `AsyncRead` + `AsyncWrite`. It
+/// follows the scenario described by the builder and panics otherwise.
+#[derive(Debug)]
+pub struct Mock {
+ inner: Inner,
+}
+
+/// A handle to send additional actions to the related `Mock`.
+#[derive(Debug)]
+pub struct Handle {
+ tx: mpsc::UnboundedSender<Action>,
+}
+
+/// Builds `Mock` instances.
+#[derive(Debug, Clone, Default)]
+pub struct Builder {
+ // Sequence of actions for the Mock to take
+ actions: VecDeque<Action>,
+}
+
+#[derive(Debug, Clone)]
+enum Action {
+ Read(Vec<u8>),
+ Write(Vec<u8>),
+ Wait(Duration),
+ // Wrapped in Arc so that Builder can be cloned and Send.
+ // Mock is not cloned as does not need to check Rc for ref counts.
+ ReadError(Option<Arc<io::Error>>),
+ WriteError(Option<Arc<io::Error>>),
+}
+
+struct Inner {
+ actions: VecDeque<Action>,
+ waiting: Option<Instant>,
+ sleep: Option<Pin<Box<Sleep>>>,
+ read_wait: Option<Waker>,
+ // rx: mpsc::UnboundedReceiver<Action>,
+ rx: Pin<Box<dyn Stream<Item = Action> + Send>>,
+}
+
+impl Builder {
+ /// Return a new, empty `Builder.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Sequence a `read` operation.
+ ///
+ /// The next operation in the mock's script will be to expect a `read` call
+ /// and return `buf`.
+ pub fn read(&mut self, buf: &[u8]) -> &mut Self {
+ self.actions.push_back(Action::Read(buf.into()));
+ self
+ }
+
+ /// Sequence a `read` operation that produces an error.
+ ///
+ /// The next operation in the mock's script will be to expect a `read` call
+ /// and return `error`.
+ pub fn read_error(&mut self, error: io::Error) -> &mut Self {
+ let error = Some(error.into());
+ self.actions.push_back(Action::ReadError(error));
+ self
+ }
+
+ /// Sequence a `write` operation.
+ ///
+ /// The next operation in the mock's script will be to expect a `write`
+ /// call.
+ pub fn write(&mut self, buf: &[u8]) -> &mut Self {
+ self.actions.push_back(Action::Write(buf.into()));
+ self
+ }
+
+ /// Sequence a `write` operation that produces an error.
+ ///
+ /// The next operation in the mock's script will be to expect a `write`
+ /// call that provides `error`.
+ pub fn write_error(&mut self, error: io::Error) -> &mut Self {
+ let error = Some(error.into());
+ self.actions.push_back(Action::WriteError(error));
+ self
+ }
+
+ /// Sequence a wait.
+ ///
+ /// The next operation in the mock's script will be to wait without doing so
+ /// for `duration` amount of time.
+ pub fn wait(&mut self, duration: Duration) -> &mut Self {
+ let duration = cmp::max(duration, Duration::from_millis(1));
+ self.actions.push_back(Action::Wait(duration));
+ self
+ }
+
+ /// Build a `Mock` value according to the defined script.
+ pub fn build(&mut self) -> Mock {
+ let (mock, _) = self.build_with_handle();
+ mock
+ }
+
+ /// Build a `Mock` value paired with a handle
+ pub fn build_with_handle(&mut self) -> (Mock, Handle) {
+ let (inner, handle) = Inner::new(self.actions.clone());
+
+ let mock = Mock { inner };
+
+ (mock, handle)
+ }
+}
+
+impl Handle {
+ /// Sequence a `read` operation.
+ ///
+ /// The next operation in the mock's script will be to expect a `read` call
+ /// and return `buf`.
+ pub fn read(&mut self, buf: &[u8]) -> &mut Self {
+ self.tx.send(Action::Read(buf.into())).unwrap();
+ self
+ }
+
+ /// Sequence a `read` operation error.
+ ///
+ /// The next operation in the mock's script will be to expect a `read` call
+ /// and return `error`.
+ pub fn read_error(&mut self, error: io::Error) -> &mut Self {
+ let error = Some(error.into());
+ self.tx.send(Action::ReadError(error)).unwrap();
+ self
+ }
+
+ /// Sequence a `write` operation.
+ ///
+ /// The next operation in the mock's script will be to expect a `write`
+ /// call.
+ pub fn write(&mut self, buf: &[u8]) -> &mut Self {
+ self.tx.send(Action::Write(buf.into())).unwrap();
+ self
+ }
+
+ /// Sequence a `write` operation error.
+ ///
+ /// The next operation in the mock's script will be to expect a `write`
+ /// call error.
+ pub fn write_error(&mut self, error: io::Error) -> &mut Self {
+ let error = Some(error.into());
+ self.tx.send(Action::WriteError(error)).unwrap();
+ self
+ }
+}
+
+impl Inner {
+ fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
+ let (tx, mut rx) = mpsc::unbounded_channel();
+
+ let rx = Box::pin(async_stream::stream! {
+ while let Some(item) = rx.recv().await {
+ yield item;
+ }
+ });
+
+ let inner = Inner {
+ actions,
+ sleep: None,
+ read_wait: None,
+ rx,
+ waiting: None,
+ };
+
+ let handle = Handle { tx };
+
+ (inner, handle)
+ }
+
+ fn poll_action(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Action>> {
+ Pin::new(&mut self.rx).poll_next(cx)
+ }
+
+ fn read(&mut self, dst: &mut ReadBuf<'_>) -> io::Result<()> {
+ match self.action() {
+ Some(&mut Action::Read(ref mut data)) => {
+ // Figure out how much to copy
+ let n = cmp::min(dst.remaining(), data.len());
+
+ // Copy the data into the `dst` slice
+ dst.put_slice(&data[..n]);
+
+ // Drain the data from the source
+ data.drain(..n);
+
+ Ok(())
+ }
+ Some(&mut Action::ReadError(ref mut err)) => {
+ // As the
+ let err = err.take().expect("Should have been removed from actions.");
+ let err = Arc::try_unwrap(err).expect("There are no other references.");
+ Err(err)
+ }
+ Some(_) => {
+ // Either waiting or expecting a write
+ Err(io::ErrorKind::WouldBlock.into())
+ }
+ None => Ok(()),
+ }
+ }
+
+ fn write(&mut self, mut src: &[u8]) -> io::Result<usize> {
+ let mut ret = 0;
+
+ if self.actions.is_empty() {
+ return Err(io::ErrorKind::BrokenPipe.into());
+ }
+
+ if let Some(&mut Action::Wait(..)) = self.action() {
+ return Err(io::ErrorKind::WouldBlock.into());
+ }
+
+ if let Some(&mut Action::WriteError(ref mut err)) = self.action() {
+ let err = err.take().expect("Should have been removed from actions.");
+ let err = Arc::try_unwrap(err).expect("There are no other references.");
+ return Err(err);
+ }
+
+ for i in 0..self.actions.len() {
+ match self.actions[i] {
+ Action::Write(ref mut expect) => {
+ let n = cmp::min(src.len(), expect.len());
+
+ assert_eq!(&src[..n], &expect[..n]);
+
+ // Drop data that was matched
+ expect.drain(..n);
+ src = &src[n..];
+
+ ret += n;
+
+ if src.is_empty() {
+ return Ok(ret);
+ }
+ }
+ Action::Wait(..) | Action::WriteError(..) => {
+ break;
+ }
+ _ => {}
+ }
+
+ // TODO: remove write
+ }
+
+ Ok(ret)
+ }
+
+ fn remaining_wait(&mut self) -> Option<Duration> {
+ match self.action() {
+ Some(&mut Action::Wait(dur)) => Some(dur),
+ _ => None,
+ }
+ }
+
+ fn action(&mut self) -> Option<&mut Action> {
+ loop {
+ if self.actions.is_empty() {
+ return None;
+ }
+
+ match self.actions[0] {
+ Action::Read(ref mut data) => {
+ if !data.is_empty() {
+ break;
+ }
+ }
+ Action::Write(ref mut data) => {
+ if !data.is_empty() {
+ break;
+ }
+ }
+ Action::Wait(ref mut dur) => {
+ if let Some(until) = self.waiting {
+ let now = Instant::now();
+
+ if now < until {
+ break;
+ }
+ } else {
+ self.waiting = Some(Instant::now() + *dur);
+ break;
+ }
+ }
+ Action::ReadError(ref mut error) | Action::WriteError(ref mut error) => {
+ if error.is_some() {
+ break;
+ }
+ }
+ }
+
+ let _action = self.actions.pop_front();
+ }
+
+ self.actions.front_mut()
+ }
+}
+
+// ===== impl Inner =====
+
+impl Mock {
+ fn maybe_wakeup_reader(&mut self) {
+ match self.inner.action() {
+ Some(&mut Action::Read(_)) | Some(&mut Action::ReadError(_)) | None => {
+ if let Some(waker) = self.inner.read_wait.take() {
+ waker.wake();
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+impl AsyncRead for Mock {
+ fn poll_read(
+ mut self: Pin<&mut Self>,
+ cx: &mut task::Context<'_>,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<io::Result<()>> {
+ loop {
+ if let Some(ref mut sleep) = self.inner.sleep {
+ ready!(Pin::new(sleep).poll(cx));
+ }
+
+ // If a sleep is set, it has already fired
+ self.inner.sleep = None;
+
+ // Capture 'filled' to monitor if it changed
+ let filled = buf.filled().len();
+
+ match self.inner.read(buf) {
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ if let Some(rem) = self.inner.remaining_wait() {
+ let until = Instant::now() + rem;
+ self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
+ } else {
+ self.inner.read_wait = Some(cx.waker().clone());
+ return Poll::Pending;
+ }
+ }
+ Ok(()) => {
+ if buf.filled().len() == filled {
+ match ready!(self.inner.poll_action(cx)) {
+ Some(action) => {
+ self.inner.actions.push_back(action);
+ continue;
+ }
+ None => {
+ return Poll::Ready(Ok(()));
+ }
+ }
+ } else {
+ return Poll::Ready(Ok(()));
+ }
+ }
+ Err(e) => return Poll::Ready(Err(e)),
+ }
+ }
+ }
+}
+
+impl AsyncWrite for Mock {
+ fn poll_write(
+ mut self: Pin<&mut Self>,
+ cx: &mut task::Context<'_>,
+ buf: &[u8],
+ ) -> Poll<io::Result<usize>> {
+ loop {
+ if let Some(ref mut sleep) = self.inner.sleep {
+ ready!(Pin::new(sleep).poll(cx));
+ }
+
+ // If a sleep is set, it has already fired
+ self.inner.sleep = None;
+
+ match self.inner.write(buf) {
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ if let Some(rem) = self.inner.remaining_wait() {
+ let until = Instant::now() + rem;
+ self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
+ } else {
+ panic!("unexpected WouldBlock");
+ }
+ }
+ Ok(0) => {
+ // TODO: Is this correct?
+ if !self.inner.actions.is_empty() {
+ return Poll::Pending;
+ }
+
+ // TODO: Extract
+ match ready!(self.inner.poll_action(cx)) {
+ Some(action) => {
+ self.inner.actions.push_back(action);
+ continue;
+ }
+ None => {
+ panic!("unexpected write");
+ }
+ }
+ }
+ ret => {
+ self.maybe_wakeup_reader();
+ return Poll::Ready(ret);
+ }
+ }
+ }
+ }
+
+ fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
+ Poll::Ready(Ok(()))
+ }
+}
+
+/// Ensures that Mock isn't dropped with data "inside".
+impl Drop for Mock {
+ fn drop(&mut self) {
+ // Avoid double panicking, since makes debugging much harder.
+ if std::thread::panicking() {
+ return;
+ }
+
+ self.inner.actions.iter().for_each(|a| match a {
+ Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
+ Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
+ _ => (),
+ })
+ }
+}
+/*
+/// Returns `true` if called from the context of a futures-rs Task
+fn is_task_ctx() -> bool {
+ use std::panic;
+
+ // Save the existing panic hook
+ let h = panic::take_hook();
+
+ // Install a new one that does nothing
+ panic::set_hook(Box::new(|_| {}));
+
+ // Attempt to call the fn
+ let r = panic::catch_unwind(|| task::current()).is_ok();
+
+ // Re-install the old one
+ panic::set_hook(h);
+
+ // Return the result
+ r
+}
+*/
+
+impl fmt::Debug for Inner {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Inner {{...}}")
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..47e2b03
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,37 @@
+#![doc(html_root_url = "https://docs.rs/tokio-test/0.4.0")]
+#![warn(
+ missing_debug_implementations,
+ missing_docs,
+ rust_2018_idioms,
+ unreachable_pub
+)]
+#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
+#![doc(test(
+ no_crate_inject,
+ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
+))]
+
+//! Tokio and Futures based testing utilites
+
+pub mod io;
+
+mod macros;
+pub mod task;
+
+/// Runs the provided future, blocking the current thread until the
+/// future completes.
+///
+/// For more information, see the documentation for
+/// [`tokio::runtime::current_thread::Runtime::block_on`][runtime-block-on].
+///
+/// [runtime-block-on]: https://docs.rs/tokio/0.2.0-alpha.2/tokio/runtime/current_thread/struct.Runtime.html#method.block_on
+pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
+ use tokio::runtime;
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .unwrap();
+
+ rt.block_on(future)
+}
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644
index 0000000..299bd77
--- /dev/null
+++ b/src/macros.rs
@@ -0,0 +1,261 @@
+//! A collection of useful macros for testing futures and tokio based code
+
+/// Asserts a `Poll` is ready, returning the value.
+///
+/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready` at
+/// runtime.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use futures_util::future;
+/// use tokio_test::{assert_ready, task};
+///
+/// let mut fut = task::spawn(future::ready(()));
+/// assert_ready!(fut.poll());
+/// ```
+#[macro_export]
+macro_rules! assert_ready {
+ ($e:expr) => {{
+ use core::task::Poll::*;
+ match $e {
+ Ready(v) => v,
+ Pending => panic!("pending"),
+ }
+ }};
+ ($e:expr, $($msg:tt)+) => {{
+ use core::task::Poll::*;
+ match $e {
+ Ready(v) => v,
+ Pending => {
+ panic!("pending; {}", format_args!($($msg)+))
+ }
+ }
+ }};
+}
+
+/// Asserts a `Poll<Result<...>>` is ready and `Ok`, returning the value.
+///
+/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready(Ok(..))` at
+/// runtime.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use futures_util::future;
+/// use tokio_test::{assert_ready_ok, task};
+///
+/// let mut fut = task::spawn(future::ok::<_, ()>(()));
+/// assert_ready_ok!(fut.poll());
+/// ```
+#[macro_export]
+macro_rules! assert_ready_ok {
+ ($e:expr) => {{
+ use tokio_test::{assert_ready, assert_ok};
+ let val = assert_ready!($e);
+ assert_ok!(val)
+ }};
+ ($e:expr, $($msg:tt)+) => {{
+ use tokio_test::{assert_ready, assert_ok};
+ let val = assert_ready!($e, $($msg)*);
+ assert_ok!(val, $($msg)*)
+ }};
+}
+
+/// Asserts a `Poll<Result<...>>` is ready and `Err`, returning the error.
+///
+/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready(Err(..))` at
+/// runtime.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use futures_util::future;
+/// use tokio_test::{assert_ready_err, task};
+///
+/// let mut fut = task::spawn(future::err::<(), _>(()));
+/// assert_ready_err!(fut.poll());
+/// ```
+#[macro_export]
+macro_rules! assert_ready_err {
+ ($e:expr) => {{
+ use tokio_test::{assert_ready, assert_err};
+ let val = assert_ready!($e);
+ assert_err!(val)
+ }};
+ ($e:expr, $($msg:tt)+) => {{
+ use tokio_test::{assert_ready, assert_err};
+ let val = assert_ready!($e, $($msg)*);
+ assert_err!(val, $($msg)*)
+ }};
+}
+
+/// Asserts a `Poll` is pending.
+///
+/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Pending` at
+/// runtime.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use futures_util::future;
+/// use tokio_test::{assert_pending, task};
+///
+/// let mut fut = task::spawn(future::pending::<()>());
+/// assert_pending!(fut.poll());
+/// ```
+#[macro_export]
+macro_rules! assert_pending {
+ ($e:expr) => {{
+ use core::task::Poll::*;
+ match $e {
+ Pending => {}
+ Ready(v) => panic!("ready; value = {:?}", v),
+ }
+ }};
+ ($e:expr, $($msg:tt)+) => {{
+ use core::task::Poll::*;
+ match $e {
+ Pending => {}
+ Ready(v) => {
+ panic!("ready; value = {:?}; {}", v, format_args!($($msg)+))
+ }
+ }
+ }};
+}
+
+/// Asserts if a poll is ready and check for equality on the value
+///
+/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready` at
+/// runtime and the value produced does not partially equal the expected value.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use futures_util::future;
+/// use tokio_test::{assert_ready_eq, task};
+///
+/// let mut fut = task::spawn(future::ready(42));
+/// assert_ready_eq!(fut.poll(), 42);
+/// ```
+#[macro_export]
+macro_rules! assert_ready_eq {
+ ($e:expr, $expect:expr) => {
+ let val = $crate::assert_ready!($e);
+ assert_eq!(val, $expect)
+ };
+
+ ($e:expr, $expect:expr, $($msg:tt)+) => {
+ let val = $crate::assert_ready!($e, $($msg)*);
+ assert_eq!(val, $expect, $($msg)*)
+ };
+}
+
+/// Asserts that the expression evaluates to `Ok` and returns the value.
+///
+/// This will invoke the `panic!` macro if the provided expression does not evaluate to `Ok` at
+/// runtime.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use tokio_test::assert_ok;
+///
+/// let n: u32 = assert_ok!("123".parse());
+///
+/// let s = "123";
+/// let n: u32 = assert_ok!(s.parse(), "testing parsing {:?} as a u32", s);
+/// ```
+#[macro_export]
+macro_rules! assert_ok {
+ ($e:expr) => {
+ assert_ok!($e,)
+ };
+ ($e:expr,) => {{
+ use std::result::Result::*;
+ match $e {
+ Ok(v) => v,
+ Err(e) => panic!("assertion failed: Err({:?})", e),
+ }
+ }};
+ ($e:expr, $($arg:tt)+) => {{
+ use std::result::Result::*;
+ match $e {
+ Ok(v) => v,
+ Err(e) => panic!("assertion failed: Err({:?}): {}", e, format_args!($($arg)+)),
+ }
+ }};
+}
+
+/// Asserts that the expression evaluates to `Err` and returns the error.
+///
+/// This will invoke the `panic!` macro if the provided expression does not evaluate to `Err` at
+/// runtime.
+///
+/// # Custom Messages
+///
+/// This macro has a second form, where a custom panic message can be provided with or without
+/// arguments for formatting.
+///
+/// # Examples
+///
+/// ```
+/// use tokio_test::assert_err;
+/// use std::str::FromStr;
+///
+///
+/// let err = assert_err!(u32::from_str("fail"));
+///
+/// let msg = "fail";
+/// let err = assert_err!(u32::from_str(msg), "testing parsing {:?} as u32", msg);
+/// ```
+#[macro_export]
+macro_rules! assert_err {
+ ($e:expr) => {
+ assert_err!($e,);
+ };
+ ($e:expr,) => {{
+ use std::result::Result::*;
+ match $e {
+ Ok(v) => panic!("assertion failed: Ok({:?})", v),
+ Err(e) => e,
+ }
+ }};
+ ($e:expr, $($arg:tt)+) => {{
+ use std::result::Result::*;
+ match $e {
+ Ok(v) => panic!("assertion failed: Ok({:?}): {}", v, format_args!($($arg)+)),
+ Err(e) => e,
+ }
+ }};
+}
diff --git a/src/task.rs b/src/task.rs
new file mode 100644
index 0000000..fa98bae
--- /dev/null
+++ b/src/task.rs
@@ -0,0 +1,253 @@
+//! Futures task based helpers
+
+#![allow(clippy::mutex_atomic)]
+
+use std::future::Future;
+use std::mem;
+use std::ops;
+use std::pin::Pin;
+use std::sync::{Arc, Condvar, Mutex};
+use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
+
+use tokio_stream::Stream;
+
+/// TODO: dox
+pub fn spawn<T>(task: T) -> Spawn<T> {
+ Spawn {
+ task: MockTask::new(),
+ future: Box::pin(task),
+ }
+}
+
+/// Future spawned on a mock task
+#[derive(Debug)]
+pub struct Spawn<T> {
+ task: MockTask,
+ future: Pin<Box<T>>,
+}
+
+/// Mock task
+///
+/// A mock task is able to intercept and track wake notifications.
+#[derive(Debug, Clone)]
+struct MockTask {
+ waker: Arc<ThreadWaker>,
+}
+
+#[derive(Debug)]
+struct ThreadWaker {
+ state: Mutex<usize>,
+ condvar: Condvar,
+}
+
+const IDLE: usize = 0;
+const WAKE: usize = 1;
+const SLEEP: usize = 2;
+
+impl<T> Spawn<T> {
+ /// Consumes `self` returning the inner value
+ pub fn into_inner(self) -> T
+ where
+ T: Unpin,
+ {
+ *Pin::into_inner(self.future)
+ }
+
+ /// Returns `true` if the inner future has received a wake notification
+ /// since the last call to `enter`.
+ pub fn is_woken(&self) -> bool {
+ self.task.is_woken()
+ }
+
+ /// Returns the number of references to the task waker
+ ///
+ /// The task itself holds a reference. The return value will never be zero.
+ pub fn waker_ref_count(&self) -> usize {
+ self.task.waker_ref_count()
+ }
+
+ /// Enter the task context
+ pub fn enter<F, R>(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut Context<'_>, Pin<&mut T>) -> R,
+ {
+ let fut = self.future.as_mut();
+ self.task.enter(|cx| f(cx, fut))
+ }
+}
+
+impl<T: Unpin> ops::Deref for Spawn<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.future
+ }
+}
+
+impl<T: Unpin> ops::DerefMut for Spawn<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut self.future
+ }
+}
+
+impl<T: Future> Spawn<T> {
+ /// Polls a future
+ pub fn poll(&mut self) -> Poll<T::Output> {
+ let fut = self.future.as_mut();
+ self.task.enter(|cx| fut.poll(cx))
+ }
+}
+
+impl<T: Stream> Spawn<T> {
+ /// Polls a stream
+ pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
+ let stream = self.future.as_mut();
+ self.task.enter(|cx| stream.poll_next(cx))
+ }
+}
+
+impl<T: Future> Future for Spawn<T> {
+ type Output = T::Output;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ self.future.as_mut().poll(cx)
+ }
+}
+
+impl<T: Stream> Stream for Spawn<T> {
+ type Item = T::Item;
+
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+ self.future.as_mut().poll_next(cx)
+ }
+}
+
+impl MockTask {
+ /// Creates new mock task
+ fn new() -> Self {
+ MockTask {
+ waker: Arc::new(ThreadWaker::new()),
+ }
+ }
+
+ /// Runs a closure from the context of the task.
+ ///
+ /// Any wake notifications resulting from the execution of the closure are
+ /// tracked.
+ fn enter<F, R>(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut Context<'_>) -> R,
+ {
+ self.waker.clear();
+ let waker = self.waker();
+ let mut cx = Context::from_waker(&waker);
+
+ f(&mut cx)
+ }
+
+ /// Returns `true` if the inner future has received a wake notification
+ /// since the last call to `enter`.
+ fn is_woken(&self) -> bool {
+ self.waker.is_woken()
+ }
+
+ /// Returns the number of references to the task waker
+ ///
+ /// The task itself holds a reference. The return value will never be zero.
+ fn waker_ref_count(&self) -> usize {
+ Arc::strong_count(&self.waker)
+ }
+
+ fn waker(&self) -> Waker {
+ unsafe {
+ let raw = to_raw(self.waker.clone());
+ Waker::from_raw(raw)
+ }
+ }
+}
+
+impl Default for MockTask {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl ThreadWaker {
+ fn new() -> Self {
+ ThreadWaker {
+ state: Mutex::new(IDLE),
+ condvar: Condvar::new(),
+ }
+ }
+
+ /// Clears any previously received wakes, avoiding potential spurrious
+ /// wake notifications. This should only be called immediately before running the
+ /// task.
+ fn clear(&self) {
+ *self.state.lock().unwrap() = IDLE;
+ }
+
+ fn is_woken(&self) -> bool {
+ match *self.state.lock().unwrap() {
+ IDLE => false,
+ WAKE => true,
+ _ => unreachable!(),
+ }
+ }
+
+ fn wake(&self) {
+ // First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
+ let mut state = self.state.lock().unwrap();
+ let prev = *state;
+
+ if prev == WAKE {
+ return;
+ }
+
+ *state = WAKE;
+
+ if prev == IDLE {
+ return;
+ }
+
+ // The other half is sleeping, so we wake it up.
+ assert_eq!(prev, SLEEP);
+ self.condvar.notify_one();
+ }
+}
+
+static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
+
+unsafe fn to_raw(waker: Arc<ThreadWaker>) -> RawWaker {
+ RawWaker::new(Arc::into_raw(waker) as *const (), &VTABLE)
+}
+
+unsafe fn from_raw(raw: *const ()) -> Arc<ThreadWaker> {
+ Arc::from_raw(raw as *const ThreadWaker)
+}
+
+unsafe fn clone(raw: *const ()) -> RawWaker {
+ let waker = from_raw(raw);
+
+ // Increment the ref count
+ mem::forget(waker.clone());
+
+ to_raw(waker)
+}
+
+unsafe fn wake(raw: *const ()) {
+ let waker = from_raw(raw);
+ waker.wake();
+}
+
+unsafe fn wake_by_ref(raw: *const ()) {
+ let waker = from_raw(raw);
+ waker.wake();
+
+ // We don't actually own a reference to the unparker
+ mem::forget(waker);
+}
+
+unsafe fn drop_waker(raw: *const ()) {
+ let _ = from_raw(raw);
+}
diff --git a/tests/block_on.rs b/tests/block_on.rs
new file mode 100644
index 0000000..efaaf51
--- /dev/null
+++ b/tests/block_on.rs
@@ -0,0 +1,27 @@
+#![warn(rust_2018_idioms)]
+
+use tokio::time::{sleep_until, Duration, Instant};
+use tokio_test::block_on;
+
+#[test]
+fn async_block() {
+ assert_eq!(4, block_on(async { 4 }));
+}
+
+async fn five() -> u8 {
+ 5
+}
+
+#[test]
+fn async_fn() {
+ assert_eq!(5, block_on(five()));
+}
+
+#[test]
+fn test_sleep() {
+ let deadline = Instant::now() + Duration::from_millis(100);
+
+ block_on(async {
+ sleep_until(deadline).await;
+ });
+}
diff --git a/tests/io.rs b/tests/io.rs
new file mode 100644
index 0000000..f164aba
--- /dev/null
+++ b/tests/io.rs
@@ -0,0 +1,86 @@
+#![warn(rust_2018_idioms)]
+
+use std::io;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio_test::io::Builder;
+
+#[tokio::test]
+async fn read() {
+ let mut mock = Builder::new().read(b"hello ").read(b"world!").build();
+
+ let mut buf = [0; 256];
+
+ let n = mock.read(&mut buf).await.expect("read 1");
+ assert_eq!(&buf[..n], b"hello ");
+
+ let n = mock.read(&mut buf).await.expect("read 2");
+ assert_eq!(&buf[..n], b"world!");
+}
+
+#[tokio::test]
+async fn read_error() {
+ let error = io::Error::new(io::ErrorKind::Other, "cruel");
+ let mut mock = Builder::new()
+ .read(b"hello ")
+ .read_error(error)
+ .read(b"world!")
+ .build();
+ let mut buf = [0; 256];
+
+ let n = mock.read(&mut buf).await.expect("read 1");
+ assert_eq!(&buf[..n], b"hello ");
+
+ match mock.read(&mut buf).await {
+ Err(error) => {
+ assert_eq!(error.kind(), io::ErrorKind::Other);
+ assert_eq!("cruel", format!("{}", error));
+ }
+ Ok(_) => panic!("error not received"),
+ }
+
+ let n = mock.read(&mut buf).await.expect("read 1");
+ assert_eq!(&buf[..n], b"world!");
+}
+
+#[tokio::test]
+async fn write() {
+ let mut mock = Builder::new().write(b"hello ").write(b"world!").build();
+
+ mock.write_all(b"hello ").await.expect("write 1");
+ mock.write_all(b"world!").await.expect("write 2");
+}
+
+#[tokio::test]
+async fn write_error() {
+ let error = io::Error::new(io::ErrorKind::Other, "cruel");
+ let mut mock = Builder::new()
+ .write(b"hello ")
+ .write_error(error)
+ .write(b"world!")
+ .build();
+ mock.write_all(b"hello ").await.expect("write 1");
+
+ match mock.write_all(b"whoa").await {
+ Err(error) => {
+ assert_eq!(error.kind(), io::ErrorKind::Other);
+ assert_eq!("cruel", format!("{}", error));
+ }
+ Ok(_) => panic!("error not received"),
+ }
+
+ mock.write_all(b"world!").await.expect("write 2");
+}
+
+#[tokio::test]
+#[should_panic]
+async fn mock_panics_read_data_left() {
+ use tokio_test::io::Builder;
+ Builder::new().read(b"read").build();
+}
+
+#[tokio::test]
+#[should_panic]
+async fn mock_panics_write_data_left() {
+ use tokio_test::io::Builder;
+ Builder::new().write(b"write").build();
+}
diff --git a/tests/macros.rs b/tests/macros.rs
new file mode 100644
index 0000000..2183fc8
--- /dev/null
+++ b/tests/macros.rs
@@ -0,0 +1,107 @@
+#![warn(rust_2018_idioms)]
+
+use std::task::Poll;
+use tokio_test::{
+ assert_pending, assert_ready, assert_ready_eq, assert_ready_err, assert_ready_ok,
+};
+
+fn ready() -> Poll<()> {
+ Poll::Ready(())
+}
+
+fn ready_ok() -> Poll<Result<(), ()>> {
+ Poll::Ready(Ok(()))
+}
+
+fn ready_err() -> Poll<Result<(), ()>> {
+ Poll::Ready(Err(()))
+}
+
+fn pending() -> Poll<()> {
+ Poll::Pending
+}
+
+#[derive(Debug)]
+enum Test {
+ Data,
+}
+
+#[test]
+fn assert_ready() {
+ let poll = ready();
+ assert_ready!(poll);
+ assert_ready!(poll, "some message");
+ assert_ready!(poll, "{:?}", ());
+ assert_ready!(poll, "{:?}", Test::Data);
+}
+
+#[test]
+#[should_panic]
+fn assert_ready_on_pending() {
+ let poll = pending();
+ assert_ready!(poll);
+}
+
+#[test]
+fn assert_pending() {
+ let poll = pending();
+ assert_pending!(poll);
+ assert_pending!(poll, "some message");
+ assert_pending!(poll, "{:?}", ());
+ assert_pending!(poll, "{:?}", Test::Data);
+}
+
+#[test]
+#[should_panic]
+fn assert_pending_on_ready() {
+ let poll = ready();
+ assert_pending!(poll);
+}
+
+#[test]
+fn assert_ready_ok() {
+ let poll = ready_ok();
+ assert_ready_ok!(poll);
+ assert_ready_ok!(poll, "some message");
+ assert_ready_ok!(poll, "{:?}", ());
+ assert_ready_ok!(poll, "{:?}", Test::Data);
+}
+
+#[test]
+#[should_panic]
+fn assert_ok_on_err() {
+ let poll = ready_err();
+ assert_ready_ok!(poll);
+}
+
+#[test]
+fn assert_ready_err() {
+ let poll = ready_err();
+ assert_ready_err!(poll);
+ assert_ready_err!(poll, "some message");
+ assert_ready_err!(poll, "{:?}", ());
+ assert_ready_err!(poll, "{:?}", Test::Data);
+}
+
+#[test]
+#[should_panic]
+fn assert_err_on_ok() {
+ let poll = ready_ok();
+ assert_ready_err!(poll);
+}
+
+#[test]
+fn assert_ready_eq() {
+ let poll = ready();
+ assert_ready_eq!(poll, ());
+ assert_ready_eq!(poll, (), "some message");
+ assert_ready_eq!(poll, (), "{:?}", ());
+ assert_ready_eq!(poll, (), "{:?}", Test::Data);
+}
+
+#[test]
+#[should_panic]
+fn assert_eq_on_not_eq() {
+ let poll = ready_err();
+ assert_ready_eq!(poll, Ok(()));
+}