diff --git a/local/recipes/gpu/redox-drm/source/src/kms/connector.rs b/local/recipes/gpu/redox-drm/source/src/kms/connector.rs index d43f7dc5..d37422d7 100644 --- a/local/recipes/gpu/redox-drm/source/src/kms/connector.rs +++ b/local/recipes/gpu/redox-drm/source/src/kms/connector.rs @@ -44,3 +44,42 @@ pub fn synthetic_edid() -> Vec { 0x44, 0x50, 0x0a, 0x20, 0x20, 0x00, 0xa7, ] } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn synthetic_displayport_has_correct_fields() { + let conn = Connector::synthetic_displayport(5, 10); + assert_eq!(conn.info.id, 5); + assert_eq!(conn.info.encoder_id, 10); + assert_eq!(conn.info.connector_type, ConnectorType::DisplayPort); + assert_eq!(conn.info.connection, ConnectorStatus::Connected); + assert!(!conn.info.modes.is_empty(), "synthetic DisplayPort should have modes"); + } + + #[test] + fn synthetic_displayport_modes_have_valid_dimensions() { + let conn = Connector::synthetic_displayport(1, 1); + for mode in &conn.info.modes { + assert!(mode.hdisplay > 0, "mode hdisplay should be > 0"); + assert!(mode.vdisplay > 0, "mode vdisplay should be > 0"); + assert!(mode.vrefresh > 0, "mode vrefresh should be > 0"); + assert!(mode.clock > 0, "mode clock should be > 0"); + } + } + + #[test] + fn synthetic_edid_returns_exactly_112_bytes() { + let edid = synthetic_edid(); + assert_eq!(edid.len(), 112); + } + + #[test] + fn synthetic_edid_has_valid_header() { + let edid = synthetic_edid(); + let header: [u8; 8] = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00]; + assert_eq!(&edid[0..8], &header, "EDID header should be valid"); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/kms/crtc.rs b/local/recipes/gpu/redox-drm/source/src/kms/crtc.rs index 7ff39660..dc474763 100644 --- a/local/recipes/gpu/redox-drm/source/src/kms/crtc.rs +++ b/local/recipes/gpu/redox-drm/source/src/kms/crtc.rs @@ -41,3 +41,67 @@ impl Crtc { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn test_mode() -> ModeInfo { + ModeInfo::default_1080p() + } + + #[test] + fn new_initializes_correctly() { + let crtc = Crtc::new(42); + assert_eq!(crtc.id, 42); + assert_eq!(crtc.current_fb, 0); + assert!(crtc.connectors.is_empty()); + assert!(crtc.mode.is_none()); + assert_eq!(crtc.gamma_size, 256); + } + + #[test] + fn program_sets_fb_connectors_and_mode() { + let mut crtc = Crtc::new(1); + let mode = test_mode(); + let result = crtc.program(99, &[10, 20], &mode); + + assert!(result.is_ok()); + assert_eq!(crtc.current_fb, 99); + assert_eq!(crtc.connectors, vec![10, 20]); + assert!(crtc.mode.is_some()); + let programmed_mode = crtc.mode.unwrap(); + assert_eq!(programmed_mode.hdisplay, 1920); + assert_eq!(programmed_mode.vdisplay, 1080); + } + + #[test] + fn program_empty_connectors_returns_invalid_argument() { + let mut crtc = Crtc::new(1); + let mode = test_mode(); + let result = crtc.program(99, &[], &mode); + + assert!(result.is_err()); + match result.unwrap_err() { + DriverError::InvalidArgument(msg) => { + assert!(msg.contains("connector")); + } + other => panic!("expected InvalidArgument, got {:?}", other), + } + // State should be unchanged + assert_eq!(crtc.current_fb, 0); + assert!(crtc.connectors.is_empty()); + assert!(crtc.mode.is_none()); + } + + #[test] + fn program_multiple_connectors_accepted() { + let mut crtc = Crtc::new(1); + let mode = test_mode(); + let result = crtc.program(50, &[1, 2, 3, 4, 5], &mode); + + assert!(result.is_ok()); + assert_eq!(crtc.connectors.len(), 5); + assert_eq!(crtc.connectors, vec![1, 2, 3, 4, 5]); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/kms/mod.rs b/local/recipes/gpu/redox-drm/source/src/kms/mod.rs index cb6494f8..d87dc481 100644 --- a/local/recipes/gpu/redox-drm/source/src/kms/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/kms/mod.rs @@ -180,3 +180,91 @@ pub struct EncoderInfo { #[allow(dead_code)] pub possible_clones: u32, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_1080p_has_correct_values() { + let mode = ModeInfo::default_1080p(); + assert_eq!(mode.hdisplay, 1920); + assert_eq!(mode.vdisplay, 1080); + assert_eq!(mode.vrefresh, 60); + assert_eq!(mode.clock, 148_500); + assert_eq!(mode.name, "1920x1080@60"); + assert_eq!(mode.htotal, 2200); + assert_eq!(mode.vtotal, 1125); + } + + #[test] + fn from_edid_synthetic_edid_too_short_returns_empty() { + let edid = super::connector::synthetic_edid(); + assert!(edid.len() < 128, "synthetic EDID is shorter than 128 bytes"); + let modes = ModeInfo::from_edid(&edid); + assert!(modes.is_empty(), "EDID shorter than 128 bytes should produce no modes"); + } + + #[test] + fn from_edid_empty_input_returns_empty() { + let modes = ModeInfo::from_edid(&[]); + assert!(modes.is_empty()); + } + + #[test] + fn from_edid_short_input_returns_empty() { + let modes = ModeInfo::from_edid(&[0u8; 64]); + assert!(modes.is_empty()); + } + + #[test] + fn from_edid_invalid_header_returns_empty() { + let mut data = vec![0u8; 128]; + data[0] = 0x01; + let modes = ModeInfo::from_edid(&data); + assert!(modes.is_empty()); + } + + #[test] + fn from_edid_parsed_modes_have_nonzero_dimensions() { + let edid = super::connector::synthetic_edid(); + let modes = ModeInfo::from_edid(&edid); + for mode in &modes { + assert_ne!(mode.hdisplay, 0, "hdisplay should not be zero"); + assert_ne!(mode.vdisplay, 0, "vdisplay should not be zero"); + } + } + + #[test] + fn from_edid_parsed_modes_have_correct_name_format() { + let edid = super::connector::synthetic_edid(); + let modes = ModeInfo::from_edid(&edid); + for mode in &modes { + let parts: Vec<&str> = mode.name.split('@').collect(); + assert_eq!(parts.len(), 2, "name should contain exactly one '@': {}", mode.name); + let dims: Vec<&str> = parts[0].split('x').collect(); + assert_eq!(dims.len(), 2, "name prefix should be WxH: {}", mode.name); + let w: u16 = dims[0].parse().expect("width should be numeric"); + let h: u16 = dims[1].parse().expect("height should be numeric"); + assert_eq!(w, mode.hdisplay); + assert_eq!(h, mode.vdisplay); + let refresh: u32 = parts[1].parse().expect("refresh should be numeric"); + assert_eq!(refresh, mode.vrefresh); + } + } + + #[test] + fn from_edid_with_valid_header_but_zero_pixel_clock_skips_descriptor() { + let mut data = vec![0u8; 128]; + data[0] = 0x00; + data[1] = 0xFF; + data[2] = 0xFF; + data[3] = 0xFF; + data[4] = 0xFF; + data[5] = 0xFF; + data[6] = 0xFF; + data[7] = 0x00; + let modes = ModeInfo::from_edid(&data); + assert!(modes.is_empty()); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/kms/plane.rs b/local/recipes/gpu/redox-drm/source/src/kms/plane.rs index d9844717..ef4b31a6 100644 --- a/local/recipes/gpu/redox-drm/source/src/kms/plane.rs +++ b/local/recipes/gpu/redox-drm/source/src/kms/plane.rs @@ -40,3 +40,52 @@ impl Plane { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_primary_initializes_correctly() { + let plane = Plane::new(7, PlaneKind::Primary); + assert_eq!(plane.id, 7); + assert_eq!(plane.kind, PlaneKind::Primary); + assert_eq!(plane.fb_handle, None); + assert_eq!(plane.crtc_id, None); + } + + #[test] + fn new_cursor_initializes_correctly() { + let plane = Plane::new(3, PlaneKind::Cursor); + assert_eq!(plane.id, 3); + assert_eq!(plane.kind, PlaneKind::Cursor); + assert!(plane.fb_handle.is_none()); + assert!(plane.crtc_id.is_none()); + } + + #[test] + fn attach_sets_crtc_id_and_fb_handle() { + let mut plane = Plane::new(1, PlaneKind::Primary); + let result = plane.attach(10, 20); + + assert!(result.is_ok()); + assert_eq!(plane.crtc_id, Some(10)); + assert_eq!(plane.fb_handle, Some(20)); + } + + #[test] + fn attach_zero_fb_handle_returns_invalid_argument() { + let mut plane = Plane::new(1, PlaneKind::Primary); + let result = plane.attach(10, 0); + + assert!(result.is_err()); + match result.unwrap_err() { + DriverError::InvalidArgument(msg) => { + assert!(msg.contains("framebuffer")); + } + other => panic!("expected InvalidArgument, got {:?}", other), + } + assert_eq!(plane.crtc_id, None); + assert_eq!(plane.fb_handle, None); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/main.rs b/local/recipes/gpu/redox-drm/source/src/main.rs index d44103b2..5f9a0af0 100644 --- a/local/recipes/gpu/redox-drm/source/src/main.rs +++ b/local/recipes/gpu/redox-drm/source/src/main.rs @@ -579,4 +579,54 @@ mod tests { assert!(!expectation.required); assert!(expectation.keys.is_empty()); } + + #[test] + fn mode_info_default_1080p_clock_matches_standard_cvt() { + use crate::kms::ModeInfo; + let mode = ModeInfo::default_1080p(); + // Standard 1080p60 timing: 148.5 MHz pixel clock + assert_eq!(mode.clock, 148_500); + // Total pixels per frame = htotal * vtotal = 2200 * 1125 = 2_475_000 + // Refresh = clock*1000 / total = 148_500_000 / 2_475_000 = 60 + assert_eq!(mode.htotal as u32 * mode.vtotal as u32, 2_475_000_u32); + } + + #[test] + fn mode_info_from_edid_rejects_short_edid() { + use crate::kms::connector::synthetic_edid; + use crate::kms::ModeInfo; + let edid = synthetic_edid(); + assert!(edid.len() < 128); + let modes = ModeInfo::from_edid(&edid); + assert!(modes.is_empty()); + } + + #[test] + fn mode_info_from_edid_parses_valid_128byte_edid() { + use crate::kms::ModeInfo; + let mut edid = vec![0u8; 128]; + edid[0] = 0x00; + edid[1] = 0xFF; + edid[2] = 0xFF; + edid[3] = 0xFF; + edid[4] = 0xFF; + edid[5] = 0xFF; + edid[6] = 0xFF; + edid[7] = 0x00; + let modes = ModeInfo::from_edid(&edid); + assert!(modes.is_empty(), "all-zero descriptors should produce no modes"); + } + + #[test] + fn mode_info_from_edid_name_format_is_width_x_height_at_refresh() { + use crate::kms::connector::synthetic_edid; + use crate::kms::ModeInfo; + let edid = synthetic_edid(); + let modes = ModeInfo::from_edid(&edid); + for mode in &modes { + // Verify the canonical format: "WxH@refresh" + let expected = format!("{}x{}@{}", mode.hdisplay, mode.vdisplay, mode.vrefresh); + assert_eq!(mode.name, expected); + } + } }