Medius - Rust LibraryMock

Mock

Test without hardware

A MockBox is an in-process fake Medius box behind the mock cargo feature.

cargo add medius --features mock

It's a cheap Clone: hand one to the Device, keep one to script and inspect.

See also: testing with MockBox.

Building a MockBox

new, and why you clone it
fn new() -> MockBox

No round-trip

new() records every command and auto-answers QUERY(VERSION) and QUERY(HEALTH) with defaults.

EXAMPLE
use medius::{Device, MockBox};

let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
// `device` drives the fake; `mock` still scripts and observes it.

Wrapping it in a Device

with_mock and open_mock
fn with_mock(mock: MockBox) -> Device

No round-trip

fn open_mock(mock: MockBox) -> Result<Device>

Blocks

CONSTRUCTORS
ConstructorHandshakeReturnsDescription
with_mockNoDeviceWraps the fake and hands back the device directly.
open_mockYesResult<Device>Also runs the version handshake, so it can fail the same way a real port can.
EXAMPLE
use medius::{Device, MockBox};

let device = Device::open_mock(MockBox::new())?;
device.move_rel(5, 5)?;

See the dead-box card for the two ways open_mock can fail.

Scripting query answers

Set the version, health, and device-info a query returns
fn with_version(self, version: Version) -> MockBox

No round-trip

fn with_health(self, health: Health) -> MockBox

No round-trip

fn with_device_info(self, device_info: DeviceInfo) -> MockBox

No round-trip

fn with_caps(self, caps: Caps) -> MockBox

No round-trip

fn with_mouse_caps(self, mouse: MouseCaps) -> MockBox

No round-trip

fn with_kbd_caps(self, keyboard: KbdCaps) -> MockBox

No round-trip

fn with_rate(self, rate: Rate) -> MockBox

No round-trip

fn with_stats(self, stats: Stats) -> MockBox

No round-trip

fn set_version(&self, version: Version)

No round-trip

fn set_health(&self, health: Health)

No round-trip

The with_* builders set what each query returns: query_version, query_health, and the device-info queries (device_info, caps, query_rate, query_stats). set_* changes a live fake in place to flip the version or health mid-test. Version, Health, and the device-info structs live on the types page, and Health::from_flags builds one from the raw status byte.

EXAMPLE
use medius::{Device, Health, MockBox, Version};

let mock = MockBox::new()
    .with_version(Version { proto_ver: 3, fw_major: 5, fw_minor: 6, fw_patch: 7, mac: [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], name: "Loki".into() })
    .with_health(Health::from_flags(0x0F));
let device = Device::with_mock(mock.clone());

let v = device.query_version()?;
assert_eq!((v.fw_major, v.fw_minor, v.fw_patch), (5, 6, 7));
assert!(device.query_health()?.mouse_attached);

// Change it mid-test: flip a later query_health.
mock.set_health(Health::from_flags(0x00));
assert!(!device.query_health()?.mouse_attached);

Injecting inbound traffic

push_log and push_raw
fn push_log(&self, level: LogLevel, text: &str)

No round-trip

fn push_raw(&self, bytes: &[u8])

No round-trip

fn push_motion(&self, seq: u8, dx: i16, dy: i16, dz: i16)

No round-trip

fn push_usages(&self, seq: u8, usages: &[Usage])

No round-trip

All put bytes on the inbound stream as if the box emitted them. push_log frames a LOG line that surfaces on logs() as a LogLine ( LogLevel plus text); push_raw sends arbitrary bytes. The two event calls feed the EventStream: push_motion arrives as a CatchEvent::Motion (a MotionEvent) and push_usages as a CatchEvent::Usages (a UsageSnapshot), with seq as the rolling counter so a test can assert gap detection.

EXAMPLE
use std::time::Duration;
use medius::{CatchEvent, CatchMask, Device, Key, LogLevel, MockBox, Usage};

let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let rx = device.logs();

mock.push_log(LogLevel::Warn, "overheating");
let line = rx.recv_timeout(Duration::from_secs(1))?;
assert_eq!(line.text, "overheating");

// Fake a catch subscription seeing the user hold A.
let stream = device.catch_events(CatchMask::KEYS)?;
mock.push_usages(0, &[Usage::from(Key::A)]);
assert!(matches!(stream.recv()?, CatchEvent::Usages(s) if s.is_held(Key::A)));

Asserting what was sent

recorded_frames, saw, recorded, clear_recorded
fn recorded_frames(&self) -> Vec<DecodedFrame>

No round-trip

fn recorded(&self) -> usize

No round-trip

fn saw(&self, ty: FrameType) -> bool

No round-trip

fn clear_recorded(&self)

No round-trip

METHODS
MethodReturnsDescription
recorded_framesVec<DecodedFrame>Every command the host sent so far, decoded, in order.
recordedusizeThe count of commands recorded so far.
sawboolWhether the host sent at least one frame of the given type.
clear_recorded()Drops the recorded history so you can assert only on the next phase.

A DecodedFrame is { ty, seq, payload }; a press(Button::Left) records a FrameType::Inject frame with payload [0, 0, 0, 1] (class 0 = button, id 0, action 1).

EXAMPLE
use medius::{Button, Device, FrameType, MockBox};

let mock = MockBox::new();
let device = Device::with_mock(mock.clone());

device.press(Button::Left)?;

let frames = mock.recorded_frames();
let inject = frames
    .iter()
    .find(|f| f.ty == FrameType::Inject)
    .expect("press recorded");
assert_eq!(inject.payload, vec![0, 0, 0, 1]);
assert!(mock.saw(FrameType::Inject));

mock.clear_recorded(); // next assertions see a fresh log

Simulating a dead box

silent, and the handshake failures
fn silent(self) -> MockBox

No round-trip

silent() records commands but never answers a query. The two open_mock failures are a silent box (Error::NoReply) and an unknown protocol version (Error::BadProtoVer).

EXAMPLE
use medius::{Device, Error, MockBox, Version};

// A silent box answers nothing: no reply.
let err = Device::open_mock(MockBox::new().silent()).unwrap_err();
assert!(matches!(err, Error::NoReply));

// A box on an unknown protocol version fails the handshake.
let mock = MockBox::new().with_version(Version {
    proto_ver: 9,
    fw_major: 0,
    fw_minor: 0,
    fw_patch: 0,
    mac: [0; 6],
    name: String::new(),
});
let err = Device::open_mock(mock).unwrap_err();
assert!(matches!(err, Error::BadProtoVer { got: 9 }));