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}