Skip to main content

rustdv_sim/
testing.rs

1//! Running futures in a unit test, with no simulator.
2//!
3//! Most of a verification framework is not about time. The ConfigDb, the
4//! factory, port binding, the analysis broadcast and the whole sequencer
5//! handshake are built from `Event`s and `Queue`s, neither of which touches
6//! the simulator — so they can be tested in milliseconds under `cargo test`
7//! instead of minutes under Icarus.
8//!
9//! The line is sharp and this module is where it is enforced: [`block_on`]
10//! drives a future on a bare executor, and if the future is still pending when
11//! the run queue empties, it says so and names the likely cause. A test that
12//! awaits `Timer` or a clock edge **needs a simulator**, and will fail here
13//! rather than hanging.
14//!
15//! ```ignore
16//! #[test]
17//! fn a_queue_round_trips() {
18//!     block_on(async {
19//!         let q: Queue<u8> = Queue::unbounded();
20//!         q.put(7).await;
21//!         assert_eq!(q.get().await, 7);
22//!     });
23//! }
24//! ```
25
26use std::future::Future;
27use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
28
29use crate::executor;
30
31/// How many times we alternate polling the future with draining the run
32/// queue before declaring it stuck. Any test needing more than this is
33/// either broken or wants the simulator.
34const MAX_ROUNDS: usize = 10_000;
35
36fn noop_raw_waker() -> RawWaker {
37    fn no_op(_: *const ()) {}
38    fn clone(_: *const ()) -> RawWaker {
39        noop_raw_waker()
40    }
41    static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, no_op, no_op, no_op);
42    RawWaker::new(std::ptr::null(), &VTABLE)
43}
44
45/// A waker that does nothing, because [`block_on`] re-polls unconditionally.
46fn noop_waker() -> Waker {
47    // Safety: the vtable's functions are all no-ops over a null pointer and
48    // never dereference it.
49    unsafe { Waker::from_raw(noop_raw_waker()) }
50}
51
52/// Run `fut` to completion on a fresh executor, with no simulator.
53///
54/// Each round polls the future once and then drains the run queue, so a
55/// future waiting on an `Event` makes progress as soon as a spawned task sets
56/// it. Panics if the future is still pending after 10,000 rounds.
57pub fn block_on<F: Future>(fut: F) -> F::Output {
58    let ex = executor::init();
59    let mut fut = Box::pin(fut);
60    let waker = noop_waker();
61    let mut cx = Context::from_waker(&waker);
62
63    for _ in 0..MAX_ROUNDS {
64        if let Poll::Ready(v) = fut.as_mut().poll(&mut cx) {
65            return v;
66        }
67        ex.run_until_idle();
68    }
69
70    panic!(
71        "block_on: the future is still pending after {MAX_ROUNDS} rounds.\n\
72         Either it deadlocked, or it awaited simulated time — `Timer`, a clock \
73         edge, `ReadOnly`/`ReadWrite`, or anything that touches a signal. Those \
74         need a real simulator: write the test as a `sim-*` case under \
75         output/regression/tests/ instead."
76    );
77}
78
79/// Like [`block_on`], but expects the future **not** to finish.
80///
81/// For the cases where blocking is the behaviour under test — a `get` on an
82/// empty queue, a sequence waiting for a grant that never comes, a
83/// `get_response` for a ticket nobody will answer. Returns once the run queue
84/// is quiet, having proved the future is still waiting.
85pub fn assert_pending<F: Future>(fut: F) {
86    let ex = executor::init();
87    let mut fut = Box::pin(fut);
88    let waker = noop_waker();
89    let mut cx = Context::from_waker(&waker);
90
91    for _ in 0..64 {
92        if let Poll::Ready(_) = fut.as_mut().poll(&mut cx) {
93            panic!("assert_pending: the future completed, but the test expected it to wait");
94        }
95        ex.run_until_idle();
96    }
97}
98
99/// Install a fresh executor without running anything — for tests that drive
100/// the executor by hand, or that only need `spawn` to be legal.
101pub fn fresh_executor() -> executor::Executor {
102    executor::init()
103}