/async-metronome

Unit testing framework for async Rust

Primary LanguageRustApache License 2.0Apache-2.0

async-metronome

Unit testing framework for async Rust

Crates.io version Download docs.rs docs

This crate implements a async unit testing framework, which is based on the MutithreadedTC developed by William Pugh and Nathaniel Ayewah at the University of Maryland.

"MultithreadedTC is a framework for testing concurrent applications. It features a metronome that is used to provide fine control over the sequence of activities in multiple threads." ...

Concurrent applications are often not deterministic, and so are failures. Depending on the interleavings of threads, they may appear and disappear.

With async-metronome, it is possible to define test cases that demonstrate a fixed interleaving of threads. If concurrent abstractions under test are reasonably small, it should be feasible to create multiple test cases that will deterministically cover all relevant interleavings.

To avoid copying original documentation here, please check MultithreadedTC web site, and the corresponding paper.

Example

#[async_metronome::test]
async fn test_send_receive() {
    let (mut sender, mut receiver) = mpsc::channel::<usize>(1);

    let sender = async move {
        assert_tick!(0);
        sender.send(42).await.unwrap();
        sender.send(17).await.unwrap();
        assert_tick!(1);
    };
    
    let receiver = async move {
        assert_tick!(0);
        await_tick!(1);
        receiver.next().await;
        receiver.next().await;
    };

    let sender = async_metronome::spawn(sender);
    let receiver = async_metronome::spawn(receiver);

    sender.await;
    receiver.await;
}

Explanation (adapted from the MutithreadedTC)

async-metronome has an internal clock. The clock only advances to the next tick when all tasks are in a pending state.

The clock starts at tick 0. In this example, the macro await_tick!(1) makes the receiver block until the clock reaches tick 1 before resuming. Thread 1 is allowed to run freely in tick 0, until it blocks on the call to sender.send(17). At this point, all threads are blocked, and the clock can advance to the next tick.

In tick 1, the statement receiver.next(42) in the receiver is executed, and this frees up sender. The final statement in sender asserts that the clock is in tick 1, in effect asserting that the task blocked on the call to sender.send(17).

Complete example, can be found in the tests/send_receive.rs.

Installation

$ cargo add async-metronome
This is the initial pre-alpha release. Bugs in testing tools are especially dangerous - use with care.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.