lesavka/testing/tests/client_mouse_include_extra_contract.rs

177 lines
6.4 KiB
Rust

//! Extra include-based coverage for mouse aggregator branches.
//!
//! Scope: keep additional branch assertions in a separate file so each testing
//! module stays under the 500 LOC contract.
//! Targets: `client/src/input/mouse.rs`.
//! Why: keep branch coverage growing without violating testing module size contracts.
#[allow(warnings)]
mod mouse_contract_extra {
include!(env!("LESAVKA_CLIENT_MOUSE_SRC"));
use serial_test::serial;
use std::path::PathBuf;
use std::thread;
fn open_virtual_node(vdev: &mut evdev::uinput::VirtualDevice) -> Option<PathBuf> {
for _ in 0..40 {
if let Ok(mut nodes) = vdev.enumerate_dev_nodes_blocking() {
if let Some(Ok(path)) = nodes.next() {
return Some(path);
}
}
thread::sleep(std::time::Duration::from_millis(10));
}
None
}
fn open_virtual_device(vdev: &mut evdev::uinput::VirtualDevice) -> Option<evdev::Device> {
let node = open_virtual_node(vdev)?;
let dev = evdev::Device::open(node).ok()?;
dev.set_nonblocking(true).ok()?;
Some(dev)
}
fn open_any_mouse_device() -> Option<evdev::Device> {
let entries = std::fs::read_dir("/dev/input").ok()?;
for entry in entries.flatten() {
let path = entry.path();
let name = path.file_name()?.to_string_lossy();
if !name.starts_with("event") {
continue;
}
let dev = evdev::Device::open(path).ok()?;
let _ = dev.set_nonblocking(true);
let rel_mouse = dev
.supported_relative_axes()
.map(|axes| {
axes.contains(evdev::RelativeAxisCode::REL_X)
&& axes.contains(evdev::RelativeAxisCode::REL_Y)
})
.unwrap_or(false)
&& dev
.supported_keys()
.map(|keys| {
keys.contains(evdev::KeyCode::BTN_LEFT)
|| keys.contains(evdev::KeyCode::BTN_RIGHT)
})
.unwrap_or(false);
let abs_touch = dev
.supported_absolute_axes()
.map(|axes| {
axes.contains(evdev::AbsoluteAxisCode::ABS_X)
|| axes.contains(evdev::AbsoluteAxisCode::ABS_MT_POSITION_X)
})
.unwrap_or(false);
if rel_mouse || abs_touch {
return Some(dev);
}
}
None
}
fn build_relative_mouse(name: &str) -> Option<(evdev::uinput::VirtualDevice, evdev::Device)> {
let mut keys = evdev::AttributeSet::<evdev::KeyCode>::new();
keys.insert(evdev::KeyCode::BTN_LEFT);
keys.insert(evdev::KeyCode::BTN_RIGHT);
keys.insert(evdev::KeyCode::BTN_MIDDLE);
let mut rel = evdev::AttributeSet::<evdev::RelativeAxisCode>::new();
rel.insert(evdev::RelativeAxisCode::REL_X);
rel.insert(evdev::RelativeAxisCode::REL_Y);
rel.insert(evdev::RelativeAxisCode::REL_WHEEL);
let mut vdev = evdev::uinput::VirtualDevice::builder()
.ok()?
.name(name)
.with_keys(&keys)
.ok()?
.with_relative_axes(&rel)
.ok()?
.build()
.ok()?;
let dev = open_virtual_device(&mut vdev)?;
Some((vdev, dev))
}
#[test]
#[serial]
fn set_grab_path_and_slog_behave_across_dev_mode_flags() {
let Some(dev_true) = open_any_mouse_device().or_else(|| {
build_relative_mouse("lesavka-include-mouse-slog-true").map(|(_, dev)| dev)
}) else {
return;
};
let Some(dev_false) = open_any_mouse_device().or_else(|| {
build_relative_mouse("lesavka-include-mouse-slog-false").map(|(_, dev)| dev)
}) else {
return;
};
let (tx_true, _rx_true) = tokio::sync::broadcast::channel(4);
let (tx_false, _rx_false) = tokio::sync::broadcast::channel(4);
let mut agg_true = MouseAggregator::new(dev_true, true, tx_true);
let mut agg_false = MouseAggregator::new(dev_false, false, tx_false);
agg_true.set_grab(false);
agg_false.set_grab(false);
let called_true = std::cell::Cell::new(0usize);
agg_true.slog(|| called_true.set(called_true.get() + 1));
assert_eq!(called_true.get(), 1);
let called_false = std::cell::Cell::new(0usize);
agg_false.slog(|| called_false.set(called_false.get() + 1));
assert_eq!(called_false.get(), 0);
}
#[test]
#[serial]
fn flush_covers_dev_mode_send_error_and_send_success_paths() {
let Some(dev_err) = open_any_mouse_device().or_else(|| {
build_relative_mouse("lesavka-include-mouse-flush-dev-err").map(|(_, dev)| dev)
}) else {
return;
};
let Some(dev_ok) = open_any_mouse_device().or_else(|| {
build_relative_mouse("lesavka-include-mouse-flush-dev-ok").map(|(_, dev)| dev)
}) else {
return;
};
let (tx_err, rx_err) = tokio::sync::broadcast::channel(1);
drop(rx_err);
let mut agg_err = MouseAggregator::new(dev_err, true, tx_err);
agg_err.buttons = 1;
agg_err.last_buttons = 0;
agg_err.next_send = std::time::Instant::now() - std::time::Duration::from_millis(1);
agg_err.flush();
assert_eq!(agg_err.last_buttons, 1);
let (tx_ok, mut rx_ok) = tokio::sync::broadcast::channel(4);
let mut agg_ok = MouseAggregator::new(dev_ok, true, tx_ok);
agg_ok.buttons = 2;
agg_ok.last_buttons = 0;
agg_ok.dx = 3;
agg_ok.dy = -2;
agg_ok.next_send = std::time::Instant::now() - std::time::Duration::from_millis(1);
agg_ok.flush();
let pkt = rx_ok.try_recv().expect("flush packet");
assert_eq!(pkt.data[0], 2);
assert_eq!(pkt.data[1], 3);
assert_eq!(pkt.data[2], (-2_i8) as u8);
}
#[test]
#[serial]
fn process_events_tolerates_idle_nonblocking_device() {
let Some((_vdev, dev)) = build_relative_mouse("lesavka-include-mouse-idle") else {
return;
};
let (tx, _rx) = tokio::sync::broadcast::channel(4);
let mut agg = MouseAggregator::new(dev, true, tx);
agg.process_events();
}
}