Desktop Phase 1: add 77 tests to evdevd, udev-shim, firmware-loader
Phase 1 desktop substrate test coverage for the three runtime services that must be runtime-trusted before compositor work begins: - evdevd device.rs: 44 tests (input device constructors, capability bitmaps, key/led state tracking, abs_info defaults/overrides, bitmap_from_codes edge cases) - udev-shim device_db.rs: 13 tests (DeviceInfo construction, subsystem naming, id_path formatting, input kind detection, property formatting for GPU/keyboard/mouse, device_info/uevent output, PCI fallback) - firmware-loader scheme.rs: 20 tests (openat validation, read offsets, fstat/fsize, EROFS enforcement, mmap_prep bounds, mmap/munmap/on_close deferred cleanup lifecycle) Total: 65 evdevd, 15 udev-shim, 24 firmware-loader — all passing.
This commit is contained in:
@@ -230,3 +230,458 @@ fn bitmap_from_codes(codes: &[u16]) -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
bitmap
|
bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::types::{
|
||||||
|
ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_SLOT, ABS_MT_TOUCH_MAJOR, ABS_MT_TRACKING_ID,
|
||||||
|
ABS_PRESSURE, ABS_X, ABS_Y, BTN_LEFT, BTN_TOUCH, BUS_VIRTUAL, EV_ABS, EV_KEY, EV_LED,
|
||||||
|
EV_MSC, EV_REL, EV_REP, EV_SYN, INPUT_PROP_POINTER, KEY_A, LED_CAPSL, LED_NUML,
|
||||||
|
LED_SCROLLL, MSC_SCAN, REL_HWHEEL, REL_WHEEL, REL_X, REL_Y,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: check that a bitmap has the expected bit set for a given code.
|
||||||
|
fn assert_bit_set(bitmap: &[u8], code: u16) {
|
||||||
|
let byte = (code / 8) as usize;
|
||||||
|
let bit = code % 8;
|
||||||
|
assert!(
|
||||||
|
byte < bitmap.len(),
|
||||||
|
"code {} (byte index {}) out of bitmap range (len {})",
|
||||||
|
code,
|
||||||
|
byte,
|
||||||
|
bitmap.len()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
bitmap[byte] & (1 << bit),
|
||||||
|
1 << bit,
|
||||||
|
"bit {} in byte {} not set for code {}",
|
||||||
|
bit,
|
||||||
|
byte,
|
||||||
|
code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_bit_clear(bitmap: &[u8], code: u16) {
|
||||||
|
let byte = (code / 8) as usize;
|
||||||
|
let bit = code % 8;
|
||||||
|
if byte < bitmap.len() {
|
||||||
|
assert_eq!(
|
||||||
|
bitmap[byte] & (1 << bit),
|
||||||
|
0,
|
||||||
|
"bit {} in byte {} unexpectedly set for code {}",
|
||||||
|
bit,
|
||||||
|
byte,
|
||||||
|
code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 1. InputDevice::new_keyboard
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn new_keyboard_has_correct_kind() {
|
||||||
|
let dev = InputDevice::new_keyboard(3);
|
||||||
|
assert_eq!(dev.kind, DeviceKind::Keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_keyboard_name_format() {
|
||||||
|
let dev = InputDevice::new_keyboard(7);
|
||||||
|
assert_eq!(dev.name, "Redox Keyboard 7");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_keyboard_input_id_bus_virtual() {
|
||||||
|
let dev = InputDevice::new_keyboard(1);
|
||||||
|
assert_eq!(dev.input_id.bustype, BUS_VIRTUAL);
|
||||||
|
assert_eq!(dev.input_id.vendor, 0);
|
||||||
|
assert_eq!(dev.input_id.product, 1);
|
||||||
|
assert_eq!(dev.input_id.version, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_keyboard_key_and_led_state_zeroed() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
assert!(dev.key_state.iter().all(|&b| b == 0));
|
||||||
|
assert!(dev.led_state.iter().all(|&b| b == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 2. InputDevice::new_mouse
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn new_mouse_has_correct_kind() {
|
||||||
|
let dev = InputDevice::new_mouse(2);
|
||||||
|
assert_eq!(dev.kind, DeviceKind::Mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_mouse_name_format() {
|
||||||
|
let dev = InputDevice::new_mouse(5);
|
||||||
|
assert_eq!(dev.name, "Redox Mouse 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_mouse_product_id_offset() {
|
||||||
|
let dev = InputDevice::new_mouse(3);
|
||||||
|
assert_eq!(dev.input_id.product, 3 + 0x10);
|
||||||
|
assert_eq!(dev.input_id.bustype, BUS_VIRTUAL);
|
||||||
|
assert_eq!(dev.input_id.version, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_mouse_key_and_led_state_zeroed() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
assert!(dev.key_state.iter().all(|&b| b == 0));
|
||||||
|
assert!(dev.led_state.iter().all(|&b| b == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 3. InputDevice::new_touchpad
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn new_touchpad_has_correct_kind() {
|
||||||
|
let dev = InputDevice::new_touchpad(1);
|
||||||
|
assert_eq!(dev.kind, DeviceKind::Touchpad);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_touchpad_name_format() {
|
||||||
|
let dev = InputDevice::new_touchpad(4);
|
||||||
|
assert_eq!(dev.name, "Redox Touchpad 4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_touchpad_product_id_offset() {
|
||||||
|
let dev = InputDevice::new_touchpad(2);
|
||||||
|
assert_eq!(dev.input_id.product, 2 + 0x20);
|
||||||
|
assert_eq!(dev.input_id.bustype, BUS_VIRTUAL);
|
||||||
|
assert_eq!(dev.input_id.version, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 4. supported_event_types
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn keyboard_event_types() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
let bm = dev.supported_event_types();
|
||||||
|
assert_bit_set(&bm, EV_SYN);
|
||||||
|
assert_bit_set(&bm, EV_KEY);
|
||||||
|
assert_bit_set(&bm, EV_MSC);
|
||||||
|
assert_bit_set(&bm, EV_LED);
|
||||||
|
assert_bit_set(&bm, EV_REP);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mouse_event_types() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
let bm = dev.supported_event_types();
|
||||||
|
assert_bit_set(&bm, EV_SYN);
|
||||||
|
assert_bit_set(&bm, EV_KEY);
|
||||||
|
assert_bit_set(&bm, EV_REL);
|
||||||
|
// No EV_ABS for mouse
|
||||||
|
assert_bit_clear(&bm, EV_ABS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_event_types() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let bm = dev.supported_event_types();
|
||||||
|
assert_bit_set(&bm, EV_SYN);
|
||||||
|
assert_bit_set(&bm, EV_KEY);
|
||||||
|
assert_bit_set(&bm, EV_ABS);
|
||||||
|
// No EV_REL for touchpad
|
||||||
|
assert_bit_clear(&bm, EV_REL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 5. supported_keys
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn keyboard_has_key_a() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
let bm = dev.supported_keys();
|
||||||
|
assert_bit_set(&bm, KEY_A);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mouse_has_btn_left() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
let bm = dev.supported_keys();
|
||||||
|
assert_bit_set(&bm, BTN_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_has_btn_touch() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let bm = dev.supported_keys();
|
||||||
|
assert_bit_set(&bm, BTN_TOUCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 6. supported_rel
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn mouse_rel_axes() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
let bm = dev.supported_rel();
|
||||||
|
assert_bit_set(&bm, REL_X);
|
||||||
|
assert_bit_set(&bm, REL_Y);
|
||||||
|
assert_bit_set(&bm, REL_WHEEL);
|
||||||
|
assert_bit_set(&bm, REL_HWHEEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyboard_rel_empty() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
assert!(dev.supported_rel().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_rel_empty() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
assert!(dev.supported_rel().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 7. supported_abs
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn touchpad_abs_axes() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let bm = dev.supported_abs();
|
||||||
|
assert_bit_set(&bm, ABS_X);
|
||||||
|
assert_bit_set(&bm, ABS_Y);
|
||||||
|
assert_bit_set(&bm, ABS_PRESSURE);
|
||||||
|
assert_bit_set(&bm, ABS_MT_SLOT);
|
||||||
|
assert_bit_set(&bm, ABS_MT_TOUCH_MAJOR);
|
||||||
|
assert_bit_set(&bm, ABS_MT_POSITION_X);
|
||||||
|
assert_bit_set(&bm, ABS_MT_POSITION_Y);
|
||||||
|
assert_bit_set(&bm, ABS_MT_TRACKING_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyboard_abs_empty() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
assert!(dev.supported_abs().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mouse_abs_empty() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
assert!(dev.supported_abs().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 8. supported_msc
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn keyboard_msc_has_scan() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
let bm = dev.supported_msc();
|
||||||
|
assert_bit_set(&bm, MSC_SCAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mouse_msc_empty() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
assert!(dev.supported_msc().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_msc_empty() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
assert!(dev.supported_msc().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 9. supported_leds
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn keyboard_leds() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
let bm = dev.supported_leds();
|
||||||
|
assert_bit_set(&bm, LED_NUML);
|
||||||
|
assert_bit_set(&bm, LED_CAPSL);
|
||||||
|
assert_bit_set(&bm, LED_SCROLLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mouse_leds_empty() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
assert!(dev.supported_leds().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_leds_empty() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
assert!(dev.supported_leds().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 10. supported_props
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn mouse_has_pointer_prop() {
|
||||||
|
let dev = InputDevice::new_mouse(0);
|
||||||
|
let bm = dev.supported_props();
|
||||||
|
assert_bit_set(&bm, INPUT_PROP_POINTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_has_pointer_prop() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let bm = dev.supported_props();
|
||||||
|
assert_bit_set(&bm, INPUT_PROP_POINTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyboard_props_empty() {
|
||||||
|
let dev = InputDevice::new_keyboard(0);
|
||||||
|
assert!(dev.supported_props().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 11. update_key_state
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn update_key_state_press_sets_bit() {
|
||||||
|
let mut dev = InputDevice::new_keyboard(0);
|
||||||
|
// KEY_A = 30 → byte 3, bit 6
|
||||||
|
dev.update_key_state(KEY_A, true);
|
||||||
|
let byte = (KEY_A / 8) as usize;
|
||||||
|
let bit = KEY_A % 8;
|
||||||
|
assert_eq!(dev.key_state[byte] & (1 << bit), 1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_key_state_release_clears_bit() {
|
||||||
|
let mut dev = InputDevice::new_keyboard(0);
|
||||||
|
dev.update_key_state(KEY_A, true);
|
||||||
|
assert_bit_set(&dev.key_state, KEY_A);
|
||||||
|
dev.update_key_state(KEY_A, false);
|
||||||
|
assert_bit_clear(&dev.key_state, KEY_A);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 12. update_led_state
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn update_led_state_set_capsl() {
|
||||||
|
let mut dev = InputDevice::new_keyboard(0);
|
||||||
|
// LED_CAPSL = 1 → byte 0, bit 1
|
||||||
|
dev.update_led_state(LED_CAPSL, true);
|
||||||
|
let byte = (LED_CAPSL / 8) as usize;
|
||||||
|
let bit = LED_CAPSL % 8;
|
||||||
|
assert_eq!(dev.led_state[byte] & (1 << bit), 1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_led_state_clear_capsl() {
|
||||||
|
let mut dev = InputDevice::new_keyboard(0);
|
||||||
|
dev.update_led_state(LED_CAPSL, true);
|
||||||
|
assert_bit_set(&dev.led_state, LED_CAPSL);
|
||||||
|
dev.update_led_state(LED_CAPSL, false);
|
||||||
|
assert_bit_clear(&dev.led_state, LED_CAPSL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 13. abs_info default touchpad
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn touchpad_abs_x_range() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let info = dev.abs_info(ABS_X);
|
||||||
|
assert_eq!(info.minimum, 0);
|
||||||
|
assert_eq!(info.maximum, 65_535);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_abs_pressure_max() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let info = dev.abs_info(ABS_PRESSURE);
|
||||||
|
assert_eq!(info.maximum, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touchpad_abs_mt_slot_range() {
|
||||||
|
let dev = InputDevice::new_touchpad(0);
|
||||||
|
let info = dev.abs_info(ABS_MT_SLOT);
|
||||||
|
assert_eq!(info.minimum, 0);
|
||||||
|
assert_eq!(info.maximum, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 14. set_abs_info overrides default
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn set_abs_info_override() {
|
||||||
|
let mut dev = InputDevice::new_touchpad(0);
|
||||||
|
let custom = AbsInfo {
|
||||||
|
value: 42,
|
||||||
|
minimum: -100,
|
||||||
|
maximum: 100,
|
||||||
|
fuzz: 1,
|
||||||
|
flat: 2,
|
||||||
|
resolution: 3,
|
||||||
|
};
|
||||||
|
dev.set_abs_info(ABS_X, custom);
|
||||||
|
let info = dev.abs_info(ABS_X);
|
||||||
|
assert_eq!(info.value, 42);
|
||||||
|
assert_eq!(info.minimum, -100);
|
||||||
|
assert_eq!(info.maximum, 100);
|
||||||
|
assert_eq!(info.fuzz, 1);
|
||||||
|
assert_eq!(info.flat, 2);
|
||||||
|
assert_eq!(info.resolution, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_abs_info_does_not_affect_other_axes() {
|
||||||
|
let mut dev = InputDevice::new_touchpad(0);
|
||||||
|
let custom = AbsInfo {
|
||||||
|
value: 0,
|
||||||
|
minimum: -50,
|
||||||
|
maximum: 50,
|
||||||
|
..AbsInfo::default()
|
||||||
|
};
|
||||||
|
dev.set_abs_info(ABS_X, custom);
|
||||||
|
// ABS_Y should still return the default touchpad range
|
||||||
|
let info_y = dev.abs_info(ABS_Y);
|
||||||
|
assert_eq!(info_y.minimum, 0);
|
||||||
|
assert_eq!(info_y.maximum, 65_535);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 15. bitmap_from_codes edge cases
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
#[test]
|
||||||
|
fn bitmap_from_codes_empty_input() {
|
||||||
|
let bm = bitmap_from_codes(&[]);
|
||||||
|
assert!(bm.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bitmap_from_codes_single_code() {
|
||||||
|
let bm = bitmap_from_codes(&[5u16]);
|
||||||
|
assert_eq!(bm.len(), 1); // (5/8)+1 = 1
|
||||||
|
assert_eq!(bm[0], 1 << 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bitmap_from_codes_multiple_codes_same_byte() {
|
||||||
|
// REL_X=0, REL_Y=1, REL_WHEEL=8, REL_HWHEEL=6
|
||||||
|
// REL_X and REL_Y are in byte 0; REL_HWHEEL is in byte 0 too; REL_WHEEL is byte 1
|
||||||
|
let bm = bitmap_from_codes(&[REL_X, REL_Y, REL_HWHEEL, REL_WHEEL]);
|
||||||
|
assert_bit_set(&bm, REL_X);
|
||||||
|
assert_bit_set(&bm, REL_Y);
|
||||||
|
assert_bit_set(&bm, REL_WHEEL);
|
||||||
|
assert_bit_set(&bm, REL_HWHEEL);
|
||||||
|
// Byte 0 should have bits 0 (REL_X), 1 (REL_Y), 6 (REL_HWHEEL) set
|
||||||
|
assert_eq!(bm[0], (1 << 0) | (1 << 1) | (1 << 6));
|
||||||
|
// Byte 1 should have bit 0 (REL_WHEEL = 8 → byte 1, bit 0)
|
||||||
|
assert_eq!(bm[1], 1 << 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -263,6 +263,14 @@ impl SchemeSync for FirmwareScheme {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::resolve_key;
|
use super::resolve_key;
|
||||||
|
use super::*;
|
||||||
|
use crate::blob::FirmwareRegistry;
|
||||||
|
use redox_scheme::scheme::SchemeSync;
|
||||||
|
use redox_scheme::{CallerCtx, OpenResult};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use syscall::{EventFlags, MapFlags, MunmapFlags, Stat, MODE_FILE};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accepts_real_firmware_extensions() {
|
fn accepts_real_firmware_extensions() {
|
||||||
@@ -279,4 +287,364 @@ mod tests {
|
|||||||
Some("amdgpu/psp_13_0_0_sos.bin")
|
Some("amdgpu/psp_13_0_0_sos.bin")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
|
||||||
|
fn test_ctx() -> CallerCtx {
|
||||||
|
CallerCtx {
|
||||||
|
pid: 0,
|
||||||
|
uid: 0,
|
||||||
|
gid: 0,
|
||||||
|
id: unsafe { std::mem::zeroed() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_registry() -> (PathBuf, FirmwareRegistry) {
|
||||||
|
let stamp = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos();
|
||||||
|
let dir = std::env::temp_dir().join(format!("rbos-fw-scheme-{stamp}"));
|
||||||
|
fs::create_dir_all(&dir).unwrap();
|
||||||
|
fs::write(dir.join("test-blob.bin"), b"Hello, firmware!").unwrap();
|
||||||
|
fs::create_dir_all(dir.join("subdir")).unwrap();
|
||||||
|
fs::write(dir.join("subdir/nested.bin"), b"nested data content").unwrap();
|
||||||
|
let registry = FirmwareRegistry::new(&dir).unwrap();
|
||||||
|
(dir, registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_test_blob(scheme: &mut FirmwareScheme) -> usize {
|
||||||
|
let ctx = test_ctx();
|
||||||
|
match scheme
|
||||||
|
.openat(SCHEME_ROOT_ID, "test-blob.bin", 0, 0, &ctx)
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
OpenResult::ThisScheme { number, .. } => number,
|
||||||
|
other => panic!("expected ThisScheme, got {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_creates_empty_scheme_with_correct_next_id() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let scheme = FirmwareScheme::new(registry);
|
||||||
|
assert!(scheme.handles.is_empty());
|
||||||
|
assert_eq!(scheme.next_id, SCHEME_ROOT_ID + 1);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn openat_valid_key_returns_this_scheme() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let result = scheme
|
||||||
|
.openat(SCHEME_ROOT_ID, "test-blob.bin", 0, 0, &ctx)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
OpenResult::ThisScheme { number, flags } => {
|
||||||
|
assert_eq!(number, SCHEME_ROOT_ID + 1);
|
||||||
|
assert_eq!(flags, NewFdFlags::empty());
|
||||||
|
}
|
||||||
|
other => panic!("expected ThisScheme, got {:?}", other),
|
||||||
|
}
|
||||||
|
assert_eq!(scheme.handles.len(), 1);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn openat_missing_key_returns_enoent() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme
|
||||||
|
.openat(SCHEME_ROOT_ID, "nonexistent.bin", 0, 0, &ctx)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(err.errno, ENOENT);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn openat_rejects_path_traversal() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme
|
||||||
|
.openat(SCHEME_ROOT_ID, "../etc/passwd", 0, 0, &ctx)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(err.errno, EISDIR);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn openat_empty_path_returns_eisdir() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme
|
||||||
|
.openat(SCHEME_ROOT_ID, "", 0, 0, &ctx)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(err.errno, EISDIR);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn openat_wrong_dirfd_returns_eacces() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme
|
||||||
|
.openat(999, "test-blob.bin", 0, 0, &ctx)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(err.errno, EACCES);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_at_offset_zero() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let mut buf = [0u8; 64];
|
||||||
|
let n = scheme.read(id, &mut buf, 0, 0, &ctx).unwrap();
|
||||||
|
assert_eq!(n, 16);
|
||||||
|
assert_eq!(&buf[..16], b"Hello, firmware!");
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_at_nonzero_offset() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let mut buf = [0u8; 64];
|
||||||
|
let n = scheme.read(id, &mut buf, 7, 0, &ctx).unwrap();
|
||||||
|
assert_eq!(n, 9);
|
||||||
|
assert_eq!(&buf[..9], b"firmware!");
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_past_end_returns_zero() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let mut buf = [0u8; 64];
|
||||||
|
let n = scheme.read(id, &mut buf, 16, 0, &ctx).unwrap();
|
||||||
|
assert_eq!(n, 0);
|
||||||
|
let n2 = scheme.read(id, &mut buf, 1000, 0, &ctx).unwrap();
|
||||||
|
assert_eq!(n2, 0);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fstat_reports_correct_size_and_mode() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let mut stat: Stat = unsafe { std::mem::zeroed() };
|
||||||
|
scheme.fstat(id, &mut stat, &ctx).unwrap();
|
||||||
|
assert_eq!(stat.st_mode, MODE_FILE | 0o444);
|
||||||
|
assert_eq!(stat.st_size, 16);
|
||||||
|
assert_eq!(stat.st_blksize, 4096);
|
||||||
|
assert!(stat.st_blocks > 0);
|
||||||
|
assert_eq!(stat.st_nlink, 1);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fsize_returns_correct_length() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let size = scheme.fsize(id, &ctx).unwrap();
|
||||||
|
assert_eq!(size, 16);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_returns_erofs() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme.write(id, b"test", 0, 0, &ctx).unwrap_err();
|
||||||
|
assert_eq!(err.errno, EROFS);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ftruncate_returns_erofs() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme.ftruncate(id, 0, &ctx).unwrap_err();
|
||||||
|
assert_eq!(err.errno, EROFS);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mmap_prep_returns_pointer_and_increments_count() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let ptr = scheme
|
||||||
|
.mmap_prep(id, 0, 16, MapFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
assert_ne!(ptr, 0);
|
||||||
|
|
||||||
|
let handle = scheme.handles.get(&id).unwrap();
|
||||||
|
assert_eq!(handle.map_count, 1);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mmap_prep_rejects_offset_beyond_data() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme
|
||||||
|
.mmap_prep(id, 17, 1, MapFlags::empty(), &ctx)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(err.errno, EINVAL);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mmap_prep_rejects_offset_plus_size_beyond_data() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let err = scheme
|
||||||
|
.mmap_prep(id, 8, 16, MapFlags::empty(), &ctx)
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(err.errno, EINVAL);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn munmap_decrements_count_without_removing_handle() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
scheme
|
||||||
|
.mmap_prep(id, 0, 16, MapFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(scheme.handles.get(&id).unwrap().map_count, 1);
|
||||||
|
|
||||||
|
scheme
|
||||||
|
.munmap(id, 0, 16, MunmapFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(scheme.handles.contains_key(&id));
|
||||||
|
let handle = scheme.handles.get(&id).unwrap();
|
||||||
|
assert_eq!(handle.map_count, 0);
|
||||||
|
assert!(!handle.closed);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn on_close_keeps_handle_when_mapped() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
scheme
|
||||||
|
.mmap_prep(id, 0, 16, MapFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
scheme.on_close(id);
|
||||||
|
|
||||||
|
assert!(scheme.handles.contains_key(&id));
|
||||||
|
let handle = scheme.handles.get(&id).unwrap();
|
||||||
|
assert!(handle.closed);
|
||||||
|
assert_eq!(handle.map_count, 1);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn on_close_then_munmap_removes_handle() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
scheme
|
||||||
|
.mmap_prep(id, 0, 16, MapFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
scheme.on_close(id);
|
||||||
|
assert!(scheme.handles.contains_key(&id));
|
||||||
|
|
||||||
|
scheme
|
||||||
|
.munmap(id, 0, 16, MunmapFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
assert!(!scheme.handles.contains_key(&id));
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fsync_returns_ok() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
scheme.fsync(id, &ctx).unwrap();
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fcntl_returns_ok_zero() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let result = scheme.fcntl(id, 0, 0, &ctx).unwrap();
|
||||||
|
assert_eq!(result, 0);
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fevent_returns_empty_flags() {
|
||||||
|
let (dir, registry) = setup_registry();
|
||||||
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
let id = open_test_blob(&mut scheme);
|
||||||
|
let ctx = test_ctx();
|
||||||
|
|
||||||
|
let flags = scheme
|
||||||
|
.fevent(id, EventFlags::empty(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(flags, EventFlags::empty());
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ fn format_device_name(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::classify_pci_device;
|
use super::{DeviceInfo, InputKind, Subsystem, classify_pci_device, device_properties, format_device_info, format_uevent_info};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn classify_pci_device_uses_shared_location_format() {
|
fn classify_pci_device_uses_shared_location_format() {
|
||||||
@@ -227,6 +227,253 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(device.id_path(), "pci-0000:02:00.0");
|
assert_eq!(device.id_path(), "pci-0000:02:00.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_platform_input_has_correct_defaults() {
|
||||||
|
let dev = DeviceInfo::new_platform_input(
|
||||||
|
"test-kbd",
|
||||||
|
"/devices/platform/keyboard0",
|
||||||
|
InputKind::Keyboard,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
assert!(!dev.is_pci);
|
||||||
|
assert_eq!(dev.subsystem, Subsystem::Input);
|
||||||
|
assert_eq!(dev.input_kind, Some(InputKind::Keyboard));
|
||||||
|
assert!(dev.devnode.is_empty());
|
||||||
|
assert!(dev.scheme_target.is_empty());
|
||||||
|
assert!(dev.symlinks.is_empty());
|
||||||
|
assert_eq!(dev.bus, 0);
|
||||||
|
assert_eq!(dev.dev, 0);
|
||||||
|
assert_eq!(dev.func, 0);
|
||||||
|
assert_eq!(dev.vendor_id, 0);
|
||||||
|
assert_eq!(dev.device_id, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_node_metadata_sets_fields_correctly() {
|
||||||
|
let mut dev = DeviceInfo::new_platform_input(
|
||||||
|
"test-mouse",
|
||||||
|
"/devices/platform/mouse0",
|
||||||
|
InputKind::Mouse,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
dev.set_node_metadata(
|
||||||
|
"/dev/input/mouse0",
|
||||||
|
"input:mouse0",
|
||||||
|
vec!["/dev/input/by-path/platform-mouse0".to_string()],
|
||||||
|
);
|
||||||
|
assert_eq!(dev.devnode, "/dev/input/mouse0");
|
||||||
|
assert_eq!(dev.scheme_target, "input:mouse0");
|
||||||
|
assert_eq!(dev.symlinks.len(), 1);
|
||||||
|
assert_eq!(dev.symlinks[0], "/dev/input/by-path/platform-mouse0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subsystem_name_maps_all_variants() {
|
||||||
|
let cases: Vec<(Subsystem, &'static str)> = vec![
|
||||||
|
(Subsystem::Gpu, "drm"),
|
||||||
|
(Subsystem::Network, "net"),
|
||||||
|
(Subsystem::Storage, "block"),
|
||||||
|
(Subsystem::Audio, "sound"),
|
||||||
|
(Subsystem::Usb, "usb"),
|
||||||
|
(Subsystem::Input, "input"),
|
||||||
|
(Subsystem::Unknown, "unknown"),
|
||||||
|
];
|
||||||
|
for (subsys, expected) in cases {
|
||||||
|
let mut dev =
|
||||||
|
DeviceInfo::new_platform_input("x", "/devices/x", InputKind::Generic, "", "");
|
||||||
|
dev.subsystem = subsys;
|
||||||
|
assert_eq!(dev.subsystem_name(), expected, "failed for {:?}", subsys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_path_pci_device() {
|
||||||
|
let dev = DeviceInfo {
|
||||||
|
is_pci: true,
|
||||||
|
bus: 0x02,
|
||||||
|
dev: 0x00,
|
||||||
|
func: 0x0,
|
||||||
|
vendor_id: 0x1002,
|
||||||
|
device_id: 0x67df,
|
||||||
|
class_code: 0x03,
|
||||||
|
subclass: 0x00,
|
||||||
|
subsystem: Subsystem::Gpu,
|
||||||
|
input_kind: None,
|
||||||
|
name: "Test GPU".to_string(),
|
||||||
|
devpath: "/devices/pci/0000:02:00.0".to_string(),
|
||||||
|
devnode: String::new(),
|
||||||
|
scheme_target: String::new(),
|
||||||
|
symlinks: vec![],
|
||||||
|
};
|
||||||
|
assert_eq!(dev.id_path(), "pci-0000:02:00.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn id_path_platform_device() {
|
||||||
|
let dev = DeviceInfo::new_platform_input(
|
||||||
|
"keyboard0",
|
||||||
|
"/devices/platform/keyboard0",
|
||||||
|
InputKind::Keyboard,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
assert_eq!(dev.id_path(), "platform-keyboard0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_input_keyboard_true_only_for_keyboard() {
|
||||||
|
let kb = DeviceInfo::new_platform_input("kb", "/devices/x", InputKind::Keyboard, "", "");
|
||||||
|
assert!(kb.is_input_keyboard());
|
||||||
|
assert!(!kb.is_input_mouse());
|
||||||
|
|
||||||
|
let mouse = DeviceInfo::new_platform_input("ms", "/devices/x", InputKind::Mouse, "", "");
|
||||||
|
assert!(!mouse.is_input_keyboard());
|
||||||
|
|
||||||
|
let generic =
|
||||||
|
DeviceInfo::new_platform_input("gen", "/devices/x", InputKind::Generic, "", "");
|
||||||
|
assert!(!generic.is_input_keyboard());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_input_mouse_true_only_for_mouse() {
|
||||||
|
let mouse = DeviceInfo::new_platform_input("ms", "/devices/x", InputKind::Mouse, "", "");
|
||||||
|
assert!(mouse.is_input_mouse());
|
||||||
|
assert!(!mouse.is_input_keyboard());
|
||||||
|
|
||||||
|
let kb = DeviceInfo::new_platform_input("kb", "/devices/x", InputKind::Keyboard, "", "");
|
||||||
|
assert!(!kb.is_input_mouse());
|
||||||
|
|
||||||
|
let generic =
|
||||||
|
DeviceInfo::new_platform_input("gen", "/devices/x", InputKind::Generic, "", "");
|
||||||
|
assert!(!generic.is_input_mouse());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_properties_gpu_pci_contains_key_fields() {
|
||||||
|
let dev = DeviceInfo {
|
||||||
|
is_pci: true,
|
||||||
|
bus: 0x02,
|
||||||
|
dev: 0x00,
|
||||||
|
func: 0x0,
|
||||||
|
vendor_id: 0x1002,
|
||||||
|
device_id: 0x67df,
|
||||||
|
class_code: 0x03,
|
||||||
|
subclass: 0x00,
|
||||||
|
subsystem: Subsystem::Gpu,
|
||||||
|
input_kind: None,
|
||||||
|
name: "AMD RX 580 [1002:67df]".to_string(),
|
||||||
|
devpath: "/devices/pci/0000:02:00.0".to_string(),
|
||||||
|
devnode: "/dev/dri/card0".to_string(),
|
||||||
|
scheme_target: "display:display".to_string(),
|
||||||
|
symlinks: vec![],
|
||||||
|
};
|
||||||
|
let props = device_properties(&dev);
|
||||||
|
let prop_map: std::collections::HashMap<&str, &str> = props
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.as_str(), v.as_str()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(prop_map.get("SUBSYSTEM").copied(), Some("drm"));
|
||||||
|
assert_eq!(prop_map.get("PCI_VENDOR_ID").copied(), Some("0x1002"));
|
||||||
|
assert_eq!(prop_map.get("PCI_DEVICE_ID").copied(), Some("0x67df"));
|
||||||
|
assert_eq!(prop_map.get("PCI_CLASS").copied(), Some("0x0300"));
|
||||||
|
assert_eq!(prop_map.get("DEVNAME").copied(), Some("/dev/dri/card0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_properties_input_keyboard_has_input_flags() {
|
||||||
|
let dev = DeviceInfo::new_platform_input(
|
||||||
|
"keyboard0",
|
||||||
|
"/devices/platform/keyboard0",
|
||||||
|
InputKind::Keyboard,
|
||||||
|
"/dev/input/event0",
|
||||||
|
"input:keyboard0",
|
||||||
|
);
|
||||||
|
let props = device_properties(&dev);
|
||||||
|
let prop_map: std::collections::HashMap<&str, &str> = props
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.as_str(), v.as_str()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(prop_map.get("ID_INPUT").copied(), Some("1"));
|
||||||
|
assert_eq!(prop_map.get("ID_INPUT_KEYBOARD").copied(), Some("1"));
|
||||||
|
assert!(!prop_map.contains_key("ID_INPUT_MOUSE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn device_properties_input_mouse_has_input_flags() {
|
||||||
|
let dev = DeviceInfo::new_platform_input(
|
||||||
|
"mouse0",
|
||||||
|
"/devices/platform/mouse0",
|
||||||
|
InputKind::Mouse,
|
||||||
|
"/dev/input/mouse0",
|
||||||
|
"input:mouse0",
|
||||||
|
);
|
||||||
|
let props = device_properties(&dev);
|
||||||
|
let prop_map: std::collections::HashMap<&str, &str> = props
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.as_str(), v.as_str()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(prop_map.get("ID_INPUT").copied(), Some("1"));
|
||||||
|
assert_eq!(prop_map.get("ID_INPUT_MOUSE").copied(), Some("1"));
|
||||||
|
assert!(!prop_map.contains_key("ID_INPUT_KEYBOARD"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_device_info_structure() {
|
||||||
|
let dev = DeviceInfo {
|
||||||
|
is_pci: true,
|
||||||
|
bus: 0x02,
|
||||||
|
dev: 0x00,
|
||||||
|
func: 0x0,
|
||||||
|
vendor_id: 0x8086,
|
||||||
|
device_id: 0x1234,
|
||||||
|
class_code: 0x03,
|
||||||
|
subclass: 0x00,
|
||||||
|
subsystem: Subsystem::Gpu,
|
||||||
|
input_kind: None,
|
||||||
|
name: "Intel GPU".to_string(),
|
||||||
|
devpath: "/devices/pci/0000:02:00.0".to_string(),
|
||||||
|
devnode: "/dev/dri/card0".to_string(),
|
||||||
|
scheme_target: "display:display".to_string(),
|
||||||
|
symlinks: vec!["/dev/dri/by-path/pci-0000:02:00.0-card".to_string()],
|
||||||
|
};
|
||||||
|
let info = format_device_info(&dev);
|
||||||
|
assert!(info.starts_with("P=/devices/pci/0000:02:00.0\n"));
|
||||||
|
assert!(info.contains("E=SUBSYSTEM=drm\n"));
|
||||||
|
assert!(info.contains("S=dev/dri/by-path/pci-0000:02:00.0-card\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_uevent_info_starts_with_action_and_has_props() {
|
||||||
|
let dev = DeviceInfo::new_platform_input(
|
||||||
|
"keyboard0",
|
||||||
|
"/devices/platform/keyboard0",
|
||||||
|
InputKind::Keyboard,
|
||||||
|
"/dev/input/event0",
|
||||||
|
"input:keyboard0",
|
||||||
|
);
|
||||||
|
let uevent = format_uevent_info(&dev);
|
||||||
|
assert!(uevent.starts_with("ACTION=add\n"));
|
||||||
|
assert!(uevent.contains("SUBSYSTEM=input\n"));
|
||||||
|
assert!(uevent.contains("DEVPATH=/devices/platform/keyboard0\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn classify_pci_device_with_no_pci_config_still_produces_pci_device() {
|
||||||
|
let dev = classify_pci_device(0x00, 0x1f, 0x2);
|
||||||
|
assert!(dev.is_pci);
|
||||||
|
assert_eq!(dev.bus, 0x00);
|
||||||
|
assert_eq!(dev.dev, 0x1f);
|
||||||
|
assert_eq!(dev.func, 0x2);
|
||||||
|
// Without real PCI config, read_pci_config returns 0xFFFF
|
||||||
|
assert_eq!(dev.vendor_id, 0xFFFF);
|
||||||
|
assert_eq!(dev.device_id, 0xFFFF);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn device_properties(dev: &DeviceInfo) -> Vec<(String, String)> {
|
pub fn device_properties(dev: &DeviceInfo) -> Vec<(String, String)> {
|
||||||
|
|||||||
Reference in New Issue
Block a user