Skip to content

Commit a63da6e

Browse files
committed
refactor(sim): move the simulator harness into tests/common
The simulator lived in src/ as a std-gated `pub mod sim` so it could reach pub(crate) internals, putting ~1.1k lines of test harness in the shipping crate. Move it to tests/common/sim, where it drives only rmk's public API: - Vial/Rynk now go through the public `run_session` over in-memory pipes (the rynk_link pattern), so host/via and host/rynk revert to base — the pub(crate) seams are gone. - A std-gated #[doc(hidden)] `rmk::test_exports` wraps the few internal signals the harness still needs (flash/connection/keycode). std is dev-only, so the firmware API is unchanged. - src/test_support.rs regains its own block_on (src unit tests can't reach the test crate); check_sim_tests.sh skips the harness itself. Whole feature matrix passes (scripts/test_all.sh).
1 parent a34ba23 commit a63da6e

26 files changed

Lines changed: 382 additions & 233 deletions

rmk/src/host/rynk/mod.rs

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub struct RynkService<'a> {
5151
lock_config: LockConfig,
5252
}
5353

54-
pub(crate) struct RynkSession<'a> {
54+
struct RynkSession<'a> {
5555
locker: HostLock<'a>,
5656
topics: topics::TopicSubscribers,
5757
}
@@ -90,23 +90,9 @@ impl<'a> RynkService<'a> {
9090
}
9191
}
9292

93-
/// Build a fresh per-connection session (authorization gate + topic
94-
/// subscriptions). Each transport calls this once at connect.
95-
pub(crate) fn new_session(&self) -> RynkSession<'a> {
96-
RynkSession {
97-
locker: HostLock::new(
98-
self.lock_config.unlock_keys,
99-
self.ctx.keymap,
100-
self.lock_config.insecure,
101-
RYNK_UNLOCK_WINDOW,
102-
),
103-
topics: topics::TopicSubscribers::new(),
104-
}
105-
}
106-
10793
/// Process one inbound message in place and replace its payload with a
10894
/// response envelope. `cmd` and `seq` remain unchanged.
109-
pub(crate) async fn dispatch(&self, session: &RynkSession<'_>, msg: &mut RynkMessage<'_>) {
95+
async fn dispatch(&self, session: &RynkSession<'_>, msg: &mut RynkMessage<'_>) {
11096
let cmd = msg.header().cmd;
11197

11298
if self.requires_unlock(cmd) && !session.locker.is_unlocked() {
@@ -183,7 +169,15 @@ impl<'a> RynkService<'a> {
183169
///
184170
/// Owns frame reassembly/dispatch; transport setup and reconnect stay outside.
185171
pub async fn run_session<R: Read, T: Write>(&self, rx: &mut R, tx: &mut T) {
186-
let mut session = self.new_session();
172+
let mut session = RynkSession {
173+
locker: HostLock::new(
174+
self.lock_config.unlock_keys,
175+
self.ctx.keymap,
176+
self.lock_config.insecure,
177+
RYNK_UNLOCK_WINDOW,
178+
),
179+
topics: topics::TopicSubscribers::new(),
180+
};
187181
let mut buf = [0u8; RYNK_BUFFER_SIZE];
188182

189183
loop {

rmk/src/host/via/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl<'a> VialService<'a> {
3737
}
3838
}
3939

40-
pub(crate) async fn process_via_packet(&self, report: &mut ViaReport) {
40+
async fn process_via_packet(&self, report: &mut ViaReport) {
4141
let command_id = report.output_data[0];
4242

4343
// Caller pre-fills `input_data` from `output_data`, so individual arms

rmk/src/lib.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,54 @@ pub mod usb;
107107
#[cfg(feature = "watchdog")]
108108
pub mod watchdog;
109109

110+
// Test-only re-exports of crate internals the simulator harness (in
111+
// `tests/common/sim`) drives. Gated on the dev-only `std` feature, so none of
112+
// this is part of the shipping firmware API.
110113
#[cfg(feature = "std")]
111114
#[doc(hidden)]
112-
pub mod sim;
115+
pub mod test_exports {
116+
//! Thin accessors for the few `pub(crate)` internals the simulator harness
117+
//! (in `tests/common/sim`) drives. Wrappers rather than `pub use` — the
118+
//! latter can't widen `pub(crate)` visibility. Std-gated, so none of this is
119+
//! compiled into firmware.
120+
121+
pub const COMBO_MAX_LENGTH: usize = crate::COMBO_MAX_LENGTH;
122+
pub const MACRO_SPACE_SIZE: usize = crate::MACRO_SPACE_SIZE;
123+
124+
#[cfg(feature = "vial")]
125+
pub fn to_via_keycode(action: rmk_types::action::KeyAction) -> u16 {
126+
crate::host::via::keycode_convert::to_via_keycode(action)
127+
}
128+
129+
#[cfg(all(feature = "_no_usb", feature = "_ble"))]
130+
pub fn set_ble_state(state: rmk_types::ble::BleState) {
131+
crate::state::set_ble_state(state);
132+
}
133+
134+
#[cfg(any(not(feature = "_no_usb"), feature = "_ble"))]
135+
pub fn reset_connection_status() {
136+
crate::state::CONNECTION_STATUS.lock(|c| c.set(rmk_types::connection::ConnectionStatus::default()));
137+
}
138+
139+
#[cfg(feature = "storage")]
140+
pub fn clear_flash_channel() {
141+
crate::channel::FLASH_CHANNEL.clear();
142+
}
143+
144+
#[cfg(feature = "storage")]
145+
pub fn reset_flash_operation() {
146+
crate::storage::FLASH_OPERATION_FINISHED.reset();
147+
}
148+
149+
#[cfg(feature = "storage")]
150+
pub async fn flash_operation_finished() -> bool {
151+
crate::storage::FLASH_OPERATION_FINISHED.wait().await
152+
}
153+
}
113154

114-
// Makes the shared simulator executor available to `#[cfg(test)]` modules
115-
// while preserving the nextest process guard.
155+
// Self-contained `block_on` over embassy-time's mock clock for `#[cfg(test)]`
156+
// modules under `src/` (the simulator harness lives in the separate test crate,
157+
// out of their reach). Also runs the nextest process guard.
116158
#[cfg(test)]
117159
pub(crate) mod test_support;
118160

rmk/src/test_support.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
//! Test-only `block_on` that drives `embassy-time`'s mock clock.
2+
//!
3+
//! Mirrors the executor in `tests/common/sim/executor.rs`. Lives under `src/`
4+
//! because `#[cfg(test)] mod tests` blocks inside library files cannot import
5+
//! from the `tests/` directory (that's a separate compilation target).
6+
//!
7+
//! Use as a drop-in replacement for `embassy_futures::block_on`:
8+
//!
9+
//! ```ignore
10+
//! use crate::test_support::test_block_on as block_on;
11+
//! ```
12+
113
use core::future::Future;
14+
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
15+
16+
use embassy_time::{Duration, MockDriver};
217

318
// `embassy-time`'s MockDriver is a process-global singleton, so running the
419
// suite under plain `cargo test` lets tests race on it and hang at the 60 s
5-
// virtual-time kill switch. Abort at test-binary startup with a pointer
20+
// virtual-time kill switch below. Abort at test-binary startup with a pointer
621
// to the right runner instead of making the user wait for that timeout.
722
#[ctor::ctor(unsafe)]
823
fn require_nextest() {
@@ -20,6 +35,40 @@ fn require_nextest() {
2035
}
2136
}
2237

23-
pub(crate) fn test_block_on<F: Future>(future: F) -> F::Output {
24-
crate::sim::test_block_on(future)
38+
const STEP: Duration = Duration::from_micros(100);
39+
const MAX_ITERS: usize = 60_000_000; // 60 s of virtual time
40+
41+
pub(crate) fn test_block_on<F: Future>(fut: F) -> F::Output {
42+
MockDriver::get().reset();
43+
44+
let waker = noop_waker();
45+
let mut cx = Context::from_waker(&waker);
46+
47+
let mut fut = Box::pin(fut);
48+
for _ in 0..MAX_ITERS {
49+
if let Poll::Ready(out) = fut.as_mut().poll(&mut cx) {
50+
return out;
51+
}
52+
MockDriver::get().advance(STEP);
53+
}
54+
panic!(
55+
"test_block_on: future did not resolve within {} iterations ({} s of virtual time)",
56+
MAX_ITERS,
57+
(MAX_ITERS as u64 * STEP.as_micros()) / 1_000_000,
58+
);
59+
}
60+
61+
fn noop_waker() -> Waker {
62+
// Safety: every vtable function is a true no-op; no state is ever
63+
// dereferenced through the data pointer.
64+
unsafe { Waker::from_raw(RAW) }
2565
}
66+
67+
const RAW: RawWaker = RawWaker::new(core::ptr::null(), &VTABLE);
68+
69+
const VTABLE: RawWakerVTable = RawWakerVTable::new(
70+
|_| RAW, // clone
71+
|_| {}, // wake
72+
|_| {}, // wake_by_ref
73+
|_| {}, // drop
74+
);

rmk/tests/common/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pub mod morse;
33
pub mod rynk_hid_link;
44
#[cfg(feature = "rynk")]
55
pub mod rynk_link;
6+
pub mod sim;
7+
68
use core::future::Future;
79

810
use rmk::types::action::KeyAction;
@@ -40,7 +42,7 @@ pub fn init_log() {
4042
}
4143

4244
pub fn test_block_on<F: Future>(future: F) -> F::Output {
43-
rmk::sim::test_block_on(future)
45+
sim::test_block_on(future)
4446
}
4547

4648
pub const KC_LCTRL: u8 = 1 << 0;

rmk/tests/common/morse.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use rmk::sim::{KeymapOverride, SimKeyboardSetup};
21
use rmk::types::action::{Action, KeyAction};
32
use rmk::types::keycode::{HidKeyCode, KeyCode};
43
use rmk::types::modifier::ModifierCombination;
54
use rmk::{k, lt, mt, td};
65

6+
use crate::common::sim::{KeymapOverride, SimKeyboardSetup};
7+
78
pub const SIMPLE_MORSE_KEY_OVERRIDES: [KeymapOverride; 10] = [
89
KeymapOverride::new(0, 0, 0, k!(A)),
910
KeymapOverride::new(0, 0, 1, mt!(B, ModifierCombination::LSHIFT)),

0 commit comments

Comments
 (0)