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}