Skip to content

Commit 8fb41ab

Browse files
Merge pull request #1176 from Nemo157/testing-utilities
Initial testing utilities
2 parents e4a963f + e3db5e6 commit 8fb41ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+862
-299
lines changed

.travis.yml

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ matrix:
5252
- cargo build --manifest-path futures-io/Cargo.toml --all-features
5353
- cargo build --manifest-path futures-sink/Cargo.toml --all-features
5454
- cargo build --manifest-path futures-util/Cargo.toml --all-features
55+
- cargo build --manifest-path futures-test/Cargo.toml --all-features
5556

5657
- name: cargo build --all-features (with minimal versions)
5758
rust: nightly
@@ -81,6 +82,7 @@ matrix:
8182
- RUSTDOCFLAGS=-Dwarnings cargo doc --all
8283
--exclude futures-preview
8384
--exclude futures-executor-preview
85+
--exclude futures-test-preview
8486
- cargo doc
8587

8688
script:

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ members = [
77
"futures-io",
88
"futures-sink",
99
"futures-util",
10+
"futures-test",
1011
]

futures-test/Cargo.toml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
cargo-features = ["edition"]
2+
3+
[package]
4+
name = "futures-test-preview"
5+
edition = "2018"
6+
version = "0.3.0-alpha.3"
7+
authors = ["Wim Looman <wim@nemo157.com>"]
8+
license = "MIT OR Apache-2.0"
9+
repository = "https://github.com/rust-lang-nursery/futures-rs"
10+
homepage = "https://rust-lang-nursery.github.io/futures-rs"
11+
documentation = "https://rust-lang-nursery.github.io/futures-doc/0.3.0-alpha.3/futures_test"
12+
description = """
13+
Common utilities for testing components built off futures-rs.
14+
"""
15+
16+
[lib]
17+
name = "futures_test"
18+
19+
[dependencies]
20+
futures-core-preview = { version = "0.3.0-alpha.2", path = "../futures-core", default-features = false }
21+
futures-util-preview = { version = "0.3.0-alpha.2", path = "../futures-util", default-features = false }
22+
futures-executor-preview = { version = "0.3.0-alpha.2", path = "../futures-executor", default-features = false }
23+
pin-utils = { version = "0.1.0-alpha.1", default-features = false }
24+
25+
[dev-dependencies]
26+
futures-preview = { version = "0.3.0-alpha.2", path = "../futures", default-features = false, features = ["std"] }

futures-test/LICENSE-APACHE

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-APACHE

futures-test/LICENSE-MIT

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-MIT

futures-test/src/assert.rs

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use futures_core::stream::Stream;
2+
use std::marker::Unpin;
3+
4+
#[doc(hidden)]
5+
pub fn assert_is_unpin_stream<S: Stream + Unpin>(_: &mut S) {}
6+
7+
/// Assert that the next poll to the provided stream will return
8+
/// [`Poll::Pending`](futures_core::task::Poll::Pending).
9+
///
10+
/// # Examples
11+
///
12+
/// ```
13+
/// #![feature(async_await, futures_api, pin)]
14+
/// use futures::stream;
15+
/// use futures_test::future::FutureTestExt;
16+
/// use futures_test::{
17+
/// assert_stream_pending, assert_stream_next, assert_stream_done,
18+
/// };
19+
/// use pin_utils::pin_mut;
20+
///
21+
/// let mut stream = stream::once((async { 5 }).delay());
22+
/// pin_mut!(stream);
23+
///
24+
/// assert_stream_pending!(stream);
25+
/// assert_stream_next!(stream, 5);
26+
/// assert_stream_done!(stream);
27+
/// ```
28+
#[macro_export]
29+
macro_rules! assert_stream_pending {
30+
($stream:expr) => {{
31+
let mut stream = &mut $stream;
32+
$crate::assert::assert_is_unpin_stream(stream);
33+
let stream = $crate::std_reexport::mem::PinMut::new(stream);
34+
let cx = &mut $crate::task::no_spawn_context();
35+
let poll = $crate::futures_core_reexport::stream::Stream::poll_next(
36+
stream, cx,
37+
);
38+
if poll.is_ready() {
39+
panic!("assertion failed: stream is not pending");
40+
}
41+
}};
42+
}
43+
44+
/// Assert that the next poll to the provided stream will return
45+
/// [`Poll::Ready`](futures_core::task::Poll::Ready) with the provided item.
46+
///
47+
/// # Examples
48+
///
49+
/// ```
50+
/// #![feature(async_await, futures_api, pin)]
51+
/// use futures::stream;
52+
/// use futures_test::future::FutureTestExt;
53+
/// use futures_test::{
54+
/// assert_stream_pending, assert_stream_next, assert_stream_done,
55+
/// };
56+
/// use pin_utils::pin_mut;
57+
///
58+
/// let mut stream = stream::once((async { 5 }).delay());
59+
/// pin_mut!(stream);
60+
///
61+
/// assert_stream_pending!(stream);
62+
/// assert_stream_next!(stream, 5);
63+
/// assert_stream_done!(stream);
64+
/// ```
65+
#[macro_export]
66+
macro_rules! assert_stream_next {
67+
($stream:expr, $item:expr) => {{
68+
let mut stream = &mut $stream;
69+
$crate::assert::assert_is_unpin_stream(stream);
70+
let stream = $crate::std_reexport::mem::PinMut::new(stream);
71+
let cx = &mut $crate::task::no_spawn_context();
72+
match $crate::futures_core_reexport::stream::Stream::poll_next(stream, cx) {
73+
$crate::futures_core_reexport::task::Poll::Ready(Some(x)) => {
74+
assert_eq!(x, $item);
75+
}
76+
$crate::futures_core_reexport::task::Poll::Ready(None) => {
77+
panic!("assertion failed: expected stream to provide item but stream is at its end");
78+
}
79+
$crate::futures_core_reexport::task::Poll::Pending => {
80+
panic!("assertion failed: expected stream to provide item but stream wasn't ready");
81+
}
82+
}
83+
}}
84+
}
85+
86+
/// Assert that the next poll to the provided stream will return an empty
87+
/// [`Poll::Ready`](futures_core::task::Poll::Ready) signalling the
88+
/// completion of the stream.
89+
///
90+
/// # Examples
91+
///
92+
/// ```
93+
/// #![feature(async_await, futures_api, pin)]
94+
/// use futures::stream;
95+
/// use futures_test::future::FutureTestExt;
96+
/// use futures_test::{
97+
/// assert_stream_pending, assert_stream_next, assert_stream_done,
98+
/// };
99+
/// use pin_utils::pin_mut;
100+
///
101+
/// let mut stream = stream::once((async { 5 }).delay());
102+
/// pin_mut!(stream);
103+
///
104+
/// assert_stream_pending!(stream);
105+
/// assert_stream_next!(stream, 5);
106+
/// assert_stream_done!(stream);
107+
/// ```
108+
#[macro_export]
109+
macro_rules! assert_stream_done {
110+
($stream:expr) => {{
111+
let mut stream = &mut $stream;
112+
$crate::assert::assert_is_unpin_stream(stream);
113+
let stream = $crate::std_reexport::mem::PinMut::new(stream);
114+
let cx = &mut $crate::task::no_spawn_context();
115+
match $crate::futures_core_reexport::stream::Stream::poll_next(stream, cx) {
116+
$crate::futures_core_reexport::task::Poll::Ready(Some(_)) => {
117+
panic!("assertion failed: expected stream to be done but had more elements");
118+
}
119+
$crate::futures_core_reexport::task::Poll::Ready(None) => {}
120+
$crate::futures_core_reexport::task::Poll::Pending => {
121+
panic!("assertion failed: expected stream to be done but was pending");
122+
}
123+
}
124+
}}
125+
}

futures-test/src/future/delay.rs

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use futures_core::future::Future;
2+
use futures_core::task::{self, Poll};
3+
use std::mem::PinMut;
4+
use pin_utils::{unsafe_pinned, unsafe_unpinned};
5+
6+
/// Combinator that guarantees one [`Poll::Pending`] before polling its inner
7+
/// future.
8+
///
9+
/// This is created by the [`FutureTestExt::delay`](super::FutureTestExt::delay)
10+
/// method.
11+
#[derive(Debug)]
12+
#[must_use = "futures do nothing unless polled"]
13+
pub struct Delayed<Fut: Future> {
14+
future: Fut,
15+
polled_before: bool,
16+
}
17+
18+
impl<Fut: Future> Delayed<Fut> {
19+
unsafe_pinned!(future: Fut);
20+
unsafe_unpinned!(polled_before: bool);
21+
22+
pub(super) fn new(future: Fut) -> Self {
23+
Self {
24+
future,
25+
polled_before: false,
26+
}
27+
}
28+
}
29+
30+
impl<Fut: Future> Future for Delayed<Fut> {
31+
type Output = Fut::Output;
32+
33+
fn poll(
34+
mut self: PinMut<Self>,
35+
cx: &mut task::Context,
36+
) -> Poll<Self::Output> {
37+
if *self.polled_before() {
38+
self.future().poll(cx)
39+
} else {
40+
*self.polled_before() = true;
41+
cx.waker().wake();
42+
Poll::Pending
43+
}
44+
}
45+
}

futures-test/src/future/mod.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! Additional combinators for testing futures.
2+
3+
mod delay;
4+
5+
use self::delay::Delayed;
6+
use futures_core::future::Future;
7+
use futures_executor;
8+
use std::thread;
9+
10+
/// Additional combinators for testing futures.
11+
pub trait FutureTestExt: Future {
12+
/// Introduces one [`Poll::Pending`](futures_core::task::Poll::Pending)
13+
/// before polling the given future
14+
///
15+
/// # Examples
16+
///
17+
/// ```
18+
/// #![feature(async_await, futures_api, pin)]
19+
/// use futures::task::Poll;
20+
/// use futures::future::FutureExt;
21+
/// use futures_test::task;
22+
/// use futures_test::future::FutureTestExt;
23+
/// use pin_utils::pin_mut;
24+
///
25+
/// let future = (async { 5 }).delay();
26+
/// pin_mut!(future);
27+
///
28+
/// let cx = &mut task::no_spawn_context();
29+
///
30+
/// assert_eq!(future.poll_unpin(cx), Poll::Pending);
31+
/// assert_eq!(future.poll_unpin(cx), Poll::Ready(5));
32+
/// ```
33+
fn delay(self) -> Delayed<Self>
34+
where
35+
Self: Sized,
36+
{
37+
delay::Delayed::new(self)
38+
}
39+
40+
/// Runs this future on a dedicated executor running in a background thread.
41+
///
42+
/// # Examples
43+
///
44+
/// ```
45+
/// #![feature(async_await, futures_api, pin)]
46+
/// use futures::channel::oneshot;
47+
/// use futures::executor::block_on;
48+
/// use futures_test::future::FutureTestExt;
49+
///
50+
/// let (tx, rx) = oneshot::channel::<i32>();
51+
///
52+
/// (async { tx.send(5).unwrap() }).run_in_background();
53+
///
54+
/// assert_eq!(block_on(rx), Ok(5));
55+
/// ```
56+
fn run_in_background(self)
57+
where
58+
Self: Sized + Send + 'static,
59+
Self::Output: Send,
60+
{
61+
thread::spawn(|| futures_executor::block_on(self));
62+
}
63+
}
64+
65+
impl<Fut> FutureTestExt for Fut where Fut: Future {}

futures-test/src/lib.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//! Utilities to make testing [`Future`s](futures_core::Future) easier
2+
3+
#![feature(
4+
arbitrary_self_types,
5+
async_await,
6+
await_macro,
7+
futures_api,
8+
pin,
9+
)]
10+
#![warn(missing_docs, missing_debug_implementations)]
11+
#![deny(bare_trait_objects)]
12+
#![doc(
13+
html_root_url = "https://rust-lang-nursery.github.io/futures-doc/0.3.0-alpha.3/futures_test"
14+
)]
15+
16+
#[doc(hidden)]
17+
pub use std as std_reexport;
18+
19+
#[doc(hidden)]
20+
pub extern crate futures_core as futures_core_reexport;
21+
22+
#[macro_use]
23+
#[doc(hidden)]
24+
pub mod assert;
25+
26+
pub mod task;
27+
28+
pub mod future;

futures-test/src/task/context.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use crate::task::{spawn, wake};
2+
use futures_core::task::Context;
3+
4+
/// Create a new [`task::Context`](futures_core::task::Context) where both
5+
/// the [`waker`](futures_core::task::Context::waker) and
6+
/// [`spawner`](futures_core::task::Context::spawner) will panic if used.
7+
///
8+
/// # Examples
9+
///
10+
/// ```should_panic
11+
/// #![feature(futures_api)]
12+
/// use futures_test::task;
13+
///
14+
/// let cx = task::panic_context();
15+
/// cx.waker().wake(); // Will panic
16+
/// ```
17+
pub fn panic_context() -> Context<'static> {
18+
Context::new(wake::panic_local_waker_ref(), spawn::panic_mut())
19+
}
20+
21+
/// Create a new [`task::Context`](futures_core::task::Context) where the
22+
/// [`waker`](futures_core::task::Context::waker) will ignore any calls to
23+
/// `wake` while the [`spawner`](futures_core::task::Context::spawner) will
24+
/// panic if used.
25+
///
26+
/// # Examples
27+
///
28+
/// ```
29+
/// #![feature(async_await, futures_api, pin)]
30+
/// use futures::future::Future;
31+
/// use futures::task::Poll;
32+
/// use futures_test::task::no_spawn_context;
33+
/// use pin_utils::pin_mut;
34+
///
35+
/// let mut future = async { 5 };
36+
/// pin_mut!(future);
37+
///
38+
/// assert_eq!(future.poll(&mut no_spawn_context()), Poll::Ready(5));
39+
/// ```
40+
pub fn no_spawn_context() -> Context<'static> {
41+
Context::new(wake::noop_local_waker_ref(), spawn::panic_mut())
42+
}
43+
44+
/// Create a new [`task::Context`](futures_core::task::Context) where the
45+
/// [`waker`](futures_core::task::Context::waker) and
46+
/// [`spawner`](futures_core::task::Context::spawner) will both ignore any
47+
/// uses.
48+
///
49+
/// # Examples
50+
///
51+
/// ```
52+
/// #![feature(async_await, futures_api, pin)]
53+
/// use futures::future::Future;
54+
/// use futures::task::Poll;
55+
/// use futures_test::task::noop_context;
56+
/// use pin_utils::pin_mut;
57+
///
58+
/// let mut future = async { 5 };
59+
/// pin_mut!(future);
60+
///
61+
/// assert_eq!(future.poll(&mut noop_context()), Poll::Ready(5));
62+
/// ```
63+
pub fn noop_context() -> Context<'static> {
64+
Context::new(wake::noop_local_waker_ref(), spawn::noop_mut())
65+
}

0 commit comments

Comments
 (0)