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:
2026-04-25 01:05:14 +01:00
parent f7ffafa0c4
commit 3ec32b3351
3 changed files with 1071 additions and 1 deletions
@@ -230,3 +230,458 @@ fn bitmap_from_codes(codes: &[u16]) -> Vec<u8> {
}
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)]
mod tests {
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]
fn accepts_real_firmware_extensions() {
@@ -279,4 +287,364 @@ mod tests {
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)]
mod tests {
use super::classify_pci_device;
use super::{DeviceInfo, InputKind, Subsystem, classify_pci_device, device_properties, format_device_info, format_uevent_info};
#[test]
fn classify_pci_device_uses_shared_location_format() {
@@ -227,6 +227,253 @@ mod tests {
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)> {