177 lines
6.4 KiB
Rust
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();
|
||
|
|
}
|
||
|
|
}
|