Skip to main content

futures_testing/
lib.rs

1//! A property-based testing framework for [`Future`]s.
2//!
3//! # What it tests
4//!
5//! - **Waker registration** -- a future that returns `Pending` must retain or wake the
6//!   [`Waker`](std::task::Waker) it was given. Forgetting it causes deadlocks.
7//! - **Waker freshness** -- a future must accept a new waker on every poll, not cache
8//!   a stale one.
9//! - **Spurious wakeup tolerance** -- futures must handle being polled without the
10//!   driver having made progress, as commonly happens inside `select`/`join`.
11//! - **Cancel safety** -- the factory is called multiple times, exercising
12//!   cancellation between iterations.
13//!
14//! # Architecture
15//!
16//! A test is defined by two collaborating pieces returned from [`TestCase::init`]:
17//!
18//! ```text
19//!   Driver -----> Leaf Future <----- Future
20//!  (e.g. tx)     (e.g. channel)    (from factory)
21//!       |                              ^
22//!       |                              |
23//!       +--- drives progress     factory called
24//!            wakes waker         multiple times
25//!
26//!   Runner randomly interleaves:
27//!     poll | drive | spurious poll | swap waker | cancel
28//! ```
29//!
30//! - **Driver** -- represents the other side of the leaf future under test (e.g. a
31//!   channel sender for a receiver future). When it reports progress
32//!   (`Poll::Ready`), the framework asserts the future's waker was called.
33//!   `Poll::Pending` means no progress was made and skips that assertion.
34//! - **Factory** -- an async closure called multiple times to produce the futures
35//!   under test. Each call may receive an arbitrary item (see [`TestCase::FactoryItem`]).
36//!
37//! Under the hood the runner uses [`arbtest`] to fuzz the interleaving of these
38//! actions, catching waker bugs that deterministic tests would miss.
39//!
40//! # Example
41//!
42//! ```
43//! use std::ops::ControlFlow;
44//! use futures_testing::{drive_poll_fn, testcase};
45//! use futures::StreamExt;
46//!
47//! let testcase = testcase!(|| {
48//!     let (mut tx, mut rx) = futures::channel::mpsc::channel::<u8>(4);
49//!
50//!     let driver = drive_poll_fn(move |item: u8| {
51//!         match tx.try_send(item) {
52//!             Ok(()) => std::task::Poll::Ready(ControlFlow::Continue(())),
53//!             Err(_) => std::task::Poll::Pending,
54//!         }
55//!     });
56//!
57//!     let factory = async move |_: ()| {
58//!         let _ = rx.next().await;
59//!     };
60//!
61//!     (driver, factory)
62//! });
63//!
64//! futures_testing::tests(testcase).run();
65//! ```
66
67extern crate alloc;
68use core::ops::ControlFlow;
69use std::{pin::Pin, task::Poll};
70
71mod driver;
72mod runner;
73
74pub use arbitrary;
75use arbitrary::{Arbitrary, Unstructured};
76use arbtest::{ArbTest, arbtest};
77pub use driver::{AsyncFnDriver, PollFnDriver, SinkDriver, drive_fn, drive_poll_fn, drive_sink};
78
79/// Defines a future to test for waker correctness, along with the [`Driver`] that
80/// makes it progress.
81///
82/// Use the [`testcase!`] macro for a concise way to implement this trait.
83pub trait TestCase {
84    /// Shared state constructed once per test iteration. Both the driver and the
85    /// factory close over references to this value.
86    ///
87    /// Use `()` when no shared state is needed, or [`ArbitraryDefault<T>`] when you
88    /// need a `Default`-constructed `T` that doesn't implement [`Arbitrary`].
89    type Args<'a>: Arbitrary<'a>;
90
91    /// An arbitrary value passed to the factory on each invocation. Use `()` if the
92    /// future under test doesn't need external input; use a concrete type (e.g. `u8`)
93    /// when the future itself consumes data.
94    type FactoryItem<'a>: Arbitrary<'a>;
95
96    /// Construct a ([`Driver`], factory) pair for one test iteration.
97    ///
98    /// The factory is an async closure that will be called multiple times per
99    /// iteration, each time receiving a new [`FactoryItem`](Self::FactoryItem).
100    /// Cancellation between calls exercises cancel-safety.
101    ///
102    /// This function should be deterministic -- derive any randomness from `args`.
103    fn init<'a>(
104        &self,
105        args: &mut Self::Args<'a>,
106    ) -> (impl Driver<'a>, impl AsyncFnMut(Self::FactoryItem<'a>));
107}
108
109/// The other side of the leaf future under test, responsible for making it
110/// progress.
111///
112/// For example:
113/// * if the leaf future is the receiver of a channel, the driver is the sender.
114/// * if the leaf future is a timeout, the driver is the timer system.
115///
116/// See [`drive_poll_fn`], [`drive_fn`], and [`drive_sink`] for convenient
117/// constructors.
118pub trait Driver<'a> {
119    /// Drive the leaf future to make progress.
120    ///
121    /// **Key invariant:** when this returns `Poll::Ready`, the framework asserts
122    /// that the future's waker was called. Return `Poll::Pending` if no progress
123    /// was made (e.g. the channel is full) to skip that assertion.
124    ///
125    /// - `Poll::Ready(ControlFlow::Continue(()))` -- progress made
126    /// - `Poll::Ready(ControlFlow::Break(()))` -- driver is done, exit after
127    ///   current future completes
128    /// - `Poll::Pending` -- no progress
129    ///
130    /// This function is allowed to block.
131    fn poll(
132        self: Pin<&mut Self>,
133        args: &mut Unstructured<'a>,
134    ) -> arbitrary::Result<Poll<ControlFlow<()>>>;
135}
136
137/// Shorthand for implementing [`TestCase`].
138///
139/// Four forms are supported:
140///
141/// ```ignore
142/// // No shared state, FactoryItem defaults to ()
143/// testcase!(|| {
144///     // ... return (driver, factory)
145/// })
146///
147/// // No shared state, explicit FactoryItem
148/// testcase!(|| -> ItemType {
149///     // ... return (driver, factory)
150/// })
151///
152/// // With shared state, FactoryItem defaults to ()
153/// testcase!(|args: &mut ArgsType| {
154///     // ... return (driver, factory)
155/// })
156///
157/// // With shared state, explicit FactoryItem
158/// testcase!(|args: &mut ArgsType| -> ItemType {
159///     // ... return (driver, factory)
160/// })
161/// ```
162///
163/// The body must return a `(Driver, factory)` tuple where `factory` is an
164/// `async move |item: ItemType| { ... }` closure.
165#[macro_export]
166macro_rules! testcase {
167    (|$args:ident: &mut $arg_ty:ty| -> $item_ty:ty $body:block) => {{
168        struct TestCase;
169        impl $crate::TestCase for TestCase {
170            type Args<'a> = $arg_ty;
171            type FactoryItem<'a> = $item_ty;
172            fn init<'a>(
173                &self,
174                $args: &mut $arg_ty,
175            ) -> (impl $crate::Driver<'a>, impl AsyncFnMut($item_ty)) {
176                $body
177            }
178        }
179        TestCase
180    }};
181    (|$args:ident: &mut $arg_ty:ty| $body:expr) => {
182        testcase!(|$args: &mut $arg_ty| -> () { $body })
183    };
184    (|| -> $item_ty:ty $body:block) => {
185        testcase!(|_args: &mut ()| -> $item_ty $body)
186    };
187    (|| $body:expr) => {
188        testcase!(|_args: &mut ()| -> () { $body })
189    };
190}
191
192/// Construct the test runner for this [`TestCase`].
193///
194/// Returns an [`ArbTest`] runner -- see [`arbtest`](mod@arbtest) for
195/// configuration options like `.seed()` and `.budget_ms()`.
196pub fn tests<T: TestCase>(
197    mut t: T,
198) -> ArbTest<impl FnMut(&mut Unstructured<'_>) -> arbitrary::Result<()>> {
199    arbtest(move |u| runner::test(&mut t, u))
200}
201
202/// An [`Arbitrary`] wrapper that constructs `T` via [`Default`].
203///
204/// [`TestCase::Args`] must implement [`Arbitrary`], but shared-state types like
205/// `AtomicBool` or `Mutex<Option<Waker>>` typically don't. Wrap them in
206/// `ArbitraryDefault` to bridge the gap:
207///
208/// ```ignore
209/// testcase!(|args: &mut ArbitraryDefault<AtomicBool>| {
210///     let ready = &args.0;
211///     // ...
212/// })
213/// ```
214pub struct ArbitraryDefault<T>(pub T);
215
216impl<'a, A: Default> arbitrary::Arbitrary<'a> for ArbitraryDefault<A> {
217    fn arbitrary(_u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
218        Ok(Self(A::default()))
219    }
220
221    #[inline]
222    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
223        (0, Some(0))
224    }
225}