Skip to main content

Crate futures_testing

Crate futures_testing 

Source
Expand description

A property-based testing framework for Futures.

§What it tests

  • Waker registration – a future that returns Pending must retain or wake the Waker it was given. Forgetting it causes deadlocks.
  • Waker freshness – a future must accept a new waker on every poll, not cache a stale one.
  • Spurious wakeup tolerance – futures must handle being polled without the driver having made progress, as commonly happens inside select/join.
  • Cancel safety – the factory is called multiple times, exercising cancellation between iterations.

§Architecture

A test is defined by two collaborating pieces returned from TestCase::init:

  Driver -----> Leaf Future <----- Future
 (e.g. tx)     (e.g. channel)    (from factory)
      |                              ^
      |                              |
      +--- drives progress     factory called
           wakes waker         multiple times

  Runner randomly interleaves:
    poll | drive | spurious poll | swap waker | cancel
  • Driver – represents the other side of the leaf future under test (e.g. a channel sender for a receiver future). When it reports progress (Poll::Ready), the framework asserts the future’s waker was called. Poll::Pending means no progress was made and skips that assertion.
  • Factory – an async closure called multiple times to produce the futures under test. Each call may receive an arbitrary item (see TestCase::FactoryItem).

Under the hood the runner uses [arbtest] to fuzz the interleaving of these actions, catching waker bugs that deterministic tests would miss.

§Example

use std::ops::ControlFlow;
use futures_testing::{drive_poll_fn, testcase};
use futures::StreamExt;

let testcase = testcase!(|| {
    let (mut tx, mut rx) = futures::channel::mpsc::channel::<u8>(4);

    let driver = drive_poll_fn(move |item: u8| {
        match tx.try_send(item) {
            Ok(()) => std::task::Poll::Ready(ControlFlow::Continue(())),
            Err(_) => std::task::Poll::Pending,
        }
    });

    let factory = async move |_: ()| {
        let _ = rx.next().await;
    };

    (driver, factory)
});

futures_testing::tests(testcase).run();

Re-exports§

pub use arbitrary;

Macros§

testcase
Shorthand for implementing TestCase.

Structs§

ArbitraryDefault
An Arbitrary wrapper that constructs T via Default.
AsyncFnDriver
See drive_fn
PollFnDriver
See drive_poll_fn
SinkDriver

Traits§

Driver
The other side of the leaf future under test, responsible for making it progress.
TestCase
Defines a future to test for waker correctness, along with the Driver that makes it progress.

Functions§

drive_fn
Construct a Driver from an AsyncFnMut.
drive_poll_fn
Construct a Driver from a synchronous FnMut.
drive_sink
Construct a Driver from a Sink.
tests
Construct the test runner for this TestCase.