Desktop Phase 1: add 42 tests to redox-drm scheme/driver and redbear-hwutils

This commit is contained in:
2026-04-25 01:32:35 +01:00
parent c829dc704b
commit 0a80946457
3 changed files with 561 additions and 1 deletions
@@ -113,3 +113,90 @@ pub trait GpuDriver: Send + Sync {
))
}
}
#[cfg(test)]
mod tests {
use std::mem::{discriminant, offset_of, size_of};
use super::*;
#[test]
fn redox_private_cs_submit_size() {
// src_handle(u32) + dst_handle(u32) + src_offset(u64) + dst_offset(u64) + byte_count(u64)
// = 4 + 4 + 8 + 8 + 8 = 32 bytes
assert_eq!(size_of::<RedoxPrivateCsSubmit>(), 32);
}
#[test]
fn redox_private_cs_submit_result_size() {
// seqno(u64) = 8 bytes
assert_eq!(size_of::<RedoxPrivateCsSubmitResult>(), 8);
}
#[test]
fn redox_private_cs_wait_size() {
// seqno(u64) + timeout_ns(u64) = 16 bytes
assert_eq!(size_of::<RedoxPrivateCsWait>(), 16);
}
#[test]
fn redox_private_cs_wait_result_size() {
// completed(bool) + 7 padding + completed_seqno(u64) = 16 bytes
assert_eq!(size_of::<RedoxPrivateCsWaitResult>(), 16);
}
#[test]
fn driver_event_vblank_size() {
let event = DriverEvent::Vblank {
crtc_id: 0xDEADBEEF,
count: 0x1234_5678_9ABC_DEF0,
};
match event {
DriverEvent::Vblank { crtc_id, count } => {
assert_eq!(crtc_id, 0xDEADBEEF);
assert_eq!(count, 0x1234_5678_9ABC_DEF0);
}
DriverEvent::Hotplug { .. } => panic!("expected Vblank, got Hotplug"),
}
let enum_size = size_of::<DriverEvent>();
assert!(enum_size > 0, "DriverEvent must be non-zero-sized");
}
#[test]
fn driver_event_hotplug_size() {
let event = DriverEvent::Hotplug {
connector_id: 0xCAFEBABE,
};
match event {
DriverEvent::Hotplug { connector_id } => {
assert_eq!(connector_id, 0xCAFEBABE);
}
DriverEvent::Vblank { .. } => panic!("expected Hotplug, got Vblank"),
}
let vblank = DriverEvent::Vblank {
crtc_id: 0,
count: 0,
};
let hotplug = DriverEvent::Hotplug { connector_id: 0 };
assert_ne!(
discriminant(&vblank),
discriminant(&hotplug),
"Vblank and Hotplug must have distinct discriminants"
);
}
#[test]
fn redox_private_cs_submit_is_repr_c() {
assert_eq!(offset_of!(RedoxPrivateCsSubmit, src_handle), 0);
assert_eq!(offset_of!(RedoxPrivateCsSubmit, dst_handle), 4);
assert_eq!(offset_of!(RedoxPrivateCsSubmit, src_offset), 8);
assert_eq!(offset_of!(RedoxPrivateCsSubmit, dst_offset), 16);
assert_eq!(offset_of!(RedoxPrivateCsSubmit, byte_count), 24);
}
#[test]
fn redox_private_cs_wait_is_repr_c() {
assert_eq!(offset_of!(RedoxPrivateCsWait, seqno), 0);
assert_eq!(offset_of!(RedoxPrivateCsWait, timeout_ns), 8);
}
}
@@ -2168,4 +2168,364 @@ mod tests {
assert_eq!(err.errno, EINVAL);
}
#[test]
fn gem_has_other_refs_returns_false_when_only_current_handle_owns_gem() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 4096,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
let gem_handle = created.handle;
assert!(
!scheme.gem_has_other_refs(card, gem_handle),
"only one handle owns the GEM, so gem_has_other_refs should be false"
);
}
#[test]
fn gem_has_other_refs_returns_true_when_another_handle_owns_gem() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card_a = open_card(&mut scheme);
let card_b = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 4096,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card_a, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card_a);
let gem_handle = created.handle;
let export = DrmPrimeHandleToFdWire {
handle: gem_handle,
flags: 0,
};
write_ioctl(&mut scheme, card_a, DRM_IOCTL_PRIME_HANDLE_TO_FD, &export).unwrap();
let exported = read_response::<DrmPrimeHandleToFdResponseWire>(&mut scheme, card_a);
let import = DrmPrimeFdToHandleWire {
fd: exported.fd,
_pad: 0,
};
write_ioctl(&mut scheme, card_b, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import).unwrap();
let imported = read_response::<DrmPrimeFdToHandleResponseWire>(&mut scheme, card_b);
assert!(
scheme.gem_has_other_refs(card_a, imported.handle),
"card_b owns the same GEM, so gem_has_other_refs from card_a should be true"
);
}
#[test]
fn gem_is_mapped_returns_false_initially() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 4096,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
assert!(
!scheme.gem_is_mapped(created.handle),
"freshly created GEM should not be mapped"
);
}
#[test]
fn gem_is_mapped_returns_true_after_pin() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 4096,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
let gem_handle = created.handle;
scheme.pin_mapped_gem(card, gem_handle).unwrap();
assert!(
scheme.gem_is_mapped(gem_handle),
"GEM should be mapped after pin_mapped_gem"
);
}
#[test]
fn gem_export_refcount_starts_at_zero() {
let scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
assert_eq!(
scheme.gem_export_refcount(9999),
0,
"unknown GEM should have refcount 0"
);
}
#[test]
fn bump_export_ref_increments_from_zero_to_one() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let gem_handle: GemHandle = 42;
scheme.bump_export_ref(gem_handle);
assert_eq!(
scheme.gem_export_refcount(gem_handle),
1,
"bumping an unknown GEM should set its refcount to 1"
);
}
#[test]
fn bump_export_ref_saturates_on_overflow() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let gem_handle: GemHandle = 42;
scheme.gem_export_refs.insert(gem_handle, usize::MAX);
scheme.bump_export_ref(gem_handle);
assert_eq!(
scheme.gem_export_refcount(gem_handle),
usize::MAX,
"saturating add should keep refcount at usize::MAX"
);
}
#[test]
fn drop_export_ref_decrements_count() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let gem_handle: GemHandle = 42;
scheme.bump_export_ref(gem_handle);
scheme.bump_export_ref(gem_handle);
assert_eq!(scheme.gem_export_refcount(gem_handle), 2);
scheme.drop_export_ref(gem_handle);
assert_eq!(
scheme.gem_export_refcount(gem_handle),
1,
"dropping once should decrement from 2 to 1"
);
}
#[test]
fn drop_export_ref_removes_entry_when_count_reaches_zero() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let gem_handle: GemHandle = 42;
scheme.bump_export_ref(gem_handle);
assert_eq!(scheme.gem_export_refcount(gem_handle), 1);
scheme.drop_export_ref(gem_handle);
assert_eq!(
scheme.gem_export_refcount(gem_handle),
0,
"dropping the last ref should remove the entry"
);
}
#[test]
fn drop_export_ref_cleans_up_prime_exports() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let gem_handle: GemHandle = 77;
let export_token: u32 = 100;
scheme.prime_exports.insert(export_token, gem_handle);
scheme.bump_export_ref(gem_handle);
assert_eq!(scheme.gem_export_refcount(gem_handle), 1);
assert!(scheme.prime_exports.contains_key(&export_token));
scheme.drop_export_ref(gem_handle);
assert_eq!(scheme.gem_export_refcount(gem_handle), 0);
assert!(
!scheme.prime_exports.values().any(|&h| h == gem_handle),
"drop_export_ref should clean up prime_exports entries for this GEM"
);
}
#[test]
fn gem_can_close_returns_false_when_gem_backs_framebuffer() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 16384,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
let gem_handle = created.handle;
let addfb = DrmAddFbWire {
width: 64,
height: 64,
pitch: 256,
bpp: 32,
depth: 24,
handle: gem_handle,
fb_id: 0,
};
write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_ADDFB, &addfb).unwrap();
let fb_resp = read_response::<DrmAddFbWire>(&mut scheme, card);
if let Some(handle) = scheme.handles.get_mut(&card) {
handle.owned_gems.retain(|&h| h != gem_handle);
}
assert!(
!scheme.gem_can_close(gem_handle),
"GEM backing a framebuffer should not be closeable"
);
assert!(scheme.fb_registry.contains_key(&fb_resp.fb_id));
}
#[test]
fn gem_can_close_returns_false_when_gem_is_mapped() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 4096,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
let gem_handle = created.handle;
scheme.pin_mapped_gem(card, gem_handle).unwrap();
if let Some(handle) = scheme.handles.get_mut(&card) {
handle.owned_gems.retain(|&h| h != gem_handle);
}
assert!(
!scheme.gem_can_close(gem_handle),
"mapped GEM should not be closeable"
);
}
#[test]
fn gem_can_close_returns_true_when_unreferenced() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 4096,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
let gem_handle = created.handle;
if let Some(handle) = scheme.handles.get_mut(&card) {
handle.owned_gems.retain(|&h| h != gem_handle);
}
assert!(
scheme.gem_can_close(gem_handle),
"unreferenced, unmapped GEM with no FB or export refs should be closeable"
);
}
#[test]
fn allocate_handle_returns_sequential_ids() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let id_a = scheme.allocate_handle(NodeKind::Card);
let id_b = scheme.allocate_handle(NodeKind::Card);
assert!(
id_b > id_a,
"second allocated handle ID ({id_b}) should be greater than first ({id_a})"
);
}
#[test]
fn is_fb_active_returns_false_for_unknown_fb() {
let scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
assert!(
!scheme.is_fb_active(12345),
"unknown fb_id should not be active"
);
}
#[test]
fn is_fb_active_returns_true_for_active_crtc_fb() {
let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let card = open_card(&mut scheme);
let create = DrmGemCreateWire {
size: 640 * 480 * 4,
..DrmGemCreateWire::default()
};
write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap();
let created = read_response::<DrmGemCreateWire>(&mut scheme, card);
let gem_handle = created.handle;
let addfb = DrmAddFbWire {
width: 640,
height: 480,
pitch: 2560,
bpp: 32,
depth: 24,
handle: gem_handle,
fb_id: 0,
};
write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_ADDFB, &addfb).unwrap();
let fb_resp = read_response::<DrmAddFbWire>(&mut scheme, card);
let fb_id = fb_resp.fb_id;
let mode = DrmModeWire {
clock: 25200,
hdisplay: 640,
hsync_start: 656,
hsync_end: 752,
htotal: 800,
vdisplay: 480,
vsync_start: 490,
vsync_end: 492,
vtotal: 525,
vrefresh: 60,
..DrmModeWire::default()
};
let setcrtc = DrmSetCrtcWire {
crtc_id: 0,
fb_handle: fb_id,
connector_count: 0,
connectors: [0; 8],
mode,
};
write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_SETCRTC, &setcrtc).unwrap();
assert!(
scheme.is_fb_active(fb_id),
"FB programmed on a CRTC should be active"
);
}
#[test]
fn validate_gem_create_size_rejects_zero() {
let scheme = DrmScheme::new(Arc::new(FakeDriver::new(false)));
let err = scheme
.validate_gem_create_size(0, "test-zero-size")
.unwrap_err();
assert_eq!(err.errno, EINVAL, "zero-sized GEM creation should return EINVAL");
}
}