//! Include-based coverage for client video output window plumbing. //! //! Scope: include `client/src/output/video.rs` with deterministic display/layout //! stubs and exercise backend/placement branches without a real desktop session. //! Targets: `client/src/output/video.rs`. //! Why: monitor window orchestration contains branch-heavy environment logic that //! should remain stable in CI. mod output { pub mod display { #[derive(Clone, Copy, Debug)] pub struct MonitorInfo { pub x: i32, pub y: i32, pub w: i32, pub h: i32, } pub fn enumerate_monitors() -> Vec { vec![ MonitorInfo { x: 0, y: 0, w: 1920, h: 1080, }, MonitorInfo { x: 1920, y: 0, w: 1920, h: 1080, }, ] } } pub mod layout { #[derive(Clone, Copy, Debug)] pub struct Rect { pub x: i32, pub y: i32, pub w: i32, pub h: i32, } pub fn assign_rectangles( monitors: &[super::display::MonitorInfo], streams: &[(&str, i32, i32)], ) -> Vec { streams .iter() .enumerate() .map(|(idx, _)| { let mon = monitors.get(idx).unwrap_or(&monitors[0]); Rect { x: mon.x, y: mon.y, w: mon.w, h: mon.h, } }) .collect() } } } #[allow(warnings)] mod video_include_contract { include!(env!("LESAVKA_CLIENT_OUTPUT_VIDEO_SRC")); use serial_test::serial; use std::fs; use std::os::unix::fs::PermissionsExt; use std::path::Path; use temp_env::with_var; use tempfile::tempdir; fn write_executable(dir: &Path, name: &str, body: &str) { let path = dir.join(name); fs::write(&path, body).expect("write script"); let mut perms = fs::metadata(&path).expect("metadata").permissions(); perms.set_mode(0o755); fs::set_permissions(path, perms).expect("chmod"); } fn with_fake_bin(name: &str, script_body: &str, f: impl FnOnce()) { let dir = tempdir().expect("tempdir"); write_executable(dir.path(), name, script_body); let prior = std::env::var("PATH").unwrap_or_default(); let merged = if prior.is_empty() { dir.path().display().to_string() } else { format!("{}:{prior}", dir.path().display()) }; with_var("PATH", Some(merged), f); } #[test] #[serial] fn monitor_window_new_covers_x11_backend_path() { with_var("GDK_BACKEND", Some("x11"), || { with_var("DISPLAY", Some(":99"), || { with_var("WAYLAND_DISPLAY", None::<&str>, || { let result = MonitorWindow::new(0); if let Ok(window) = result { window.push_packet(VideoPacket { id: 0, pts: 5, data: vec![0, 0, 0, 1, 0x67], }); } }); }); }); } #[test] #[serial] fn monitor_window_new_covers_wayland_swaymsg_placement_branch() { let swaymsg = r#"#!/usr/bin/env sh if [ "$1" = "-t" ] && [ "$2" = "get_tree" ]; then echo '{}' exit 0 fi exit 0 "#; with_fake_bin("swaymsg", swaymsg, || { with_var("WAYLAND_DISPLAY", Some("wayland-0"), || { with_var("DISPLAY", None::<&str>, || { with_var("GDK_BACKEND", None::<&str>, || { let _ = MonitorWindow::new(1); }); }); }); }); } #[test] #[serial] fn monitor_window_new_covers_wayland_hyprctl_fallback_branch() { let hyprctl = r#"#!/usr/bin/env sh if [ "$1" = "version" ]; then echo 'Hyprland test' exit 0 fi if [ "$1" = "dispatch" ]; then exit 0 fi exit 0 "#; with_fake_bin("hyprctl", hyprctl, || { with_var("WAYLAND_DISPLAY", Some("wayland-0"), || { with_var("DISPLAY", None::<&str>, || { with_var("GDK_BACKEND", None::<&str>, || { let _ = MonitorWindow::new(0); }); }); }); }); } #[test] #[serial] fn monitor_window_new_covers_display_wmctrl_branch() { let wmctrl = r#"#!/usr/bin/env sh exit 0 "#; with_fake_bin("wmctrl", wmctrl, || { with_var("WAYLAND_DISPLAY", None::<&str>, || { with_var("DISPLAY", Some(":99"), || { with_var("GDK_BACKEND", None::<&str>, || { let _ = MonitorWindow::new(0); }); }); }); }); } #[test] fn push_packet_sets_pts_on_appsrc_buffers() { gst::init().ok(); let pipeline = gst::Pipeline::new(); let src = gst::ElementFactory::make("appsrc") .build() .expect("appsrc") .downcast::() .expect("downcast appsrc"); pipeline .add(src.upcast_ref::()) .expect("add appsrc"); let window = MonitorWindow { _pipeline: pipeline, src, }; window.push_packet(VideoPacket { id: 1, pts: 12_345, data: vec![0, 0, 0, 1, 0x65], }); } #[test] fn drop_is_safe_for_manually_built_window() { gst::init().ok(); let pipeline = gst::Pipeline::new(); let src = gst::ElementFactory::make("appsrc") .build() .expect("appsrc") .downcast::() .expect("downcast appsrc"); pipeline .add(src.upcast_ref::()) .expect("add appsrc"); let window = MonitorWindow { _pipeline: pipeline, src, }; drop(window); } #[test] fn unified_monitor_window_constructor_and_push_are_stable() { match UnifiedMonitorWindow::new() { Ok(window) => { window.push_packet(VideoPacket { id: 0, pts: 100, data: vec![0, 0, 0, 1, 0x65], }); window.push_packet(VideoPacket { id: 1, pts: 101, data: vec![0, 0, 0, 1, 0x67], }); } Err(err) => { assert!( !err.to_string().trim().is_empty(), "unified constructor returned an empty error" ); } } } #[test] fn unified_drop_is_safe_for_manually_built_window() { gst::init().ok(); let pipeline = gst::Pipeline::new(); let left_src = gst::ElementFactory::make("appsrc") .build() .expect("left appsrc") .downcast::() .expect("downcast left appsrc"); let right_src = gst::ElementFactory::make("appsrc") .build() .expect("right appsrc") .downcast::() .expect("downcast right appsrc"); pipeline .add(left_src.upcast_ref::()) .expect("add left appsrc"); pipeline .add(right_src.upcast_ref::()) .expect("add right appsrc"); let window = UnifiedMonitorWindow { pipeline, left_src, right_src, }; drop(window); } }