testing: expand layout and camera contracts

This commit is contained in:
Brad Stein 2026-04-12 12:22:41 -03:00
parent 31d4ee47b4
commit 659a77ff5f
3 changed files with 170 additions and 1 deletions

View File

@ -3,9 +3,12 @@
"files": [ "files": [
"client/src/app_support.rs", "client/src/app_support.rs",
"client/src/input/keymap.rs", "client/src/input/keymap.rs",
"client/src/layout.rs",
"client/src/output/layout.rs", "client/src/output/layout.rs",
"client/src/paste.rs",
"common/src/cli.rs", "common/src/cli.rs",
"common/src/hid.rs", "common/src/hid.rs",
"common/src/lib.rs",
"common/src/paste.rs", "common/src/paste.rs",
"server/src/handshake.rs", "server/src/handshake.rs",
"server/src/paste.rs", "server/src/paste.rs",

View File

@ -20,9 +20,10 @@ fn install_sway_stub(dir: &std::path::Path) -> std::path::PathBuf {
set -euo pipefail set -euo pipefail
if [[ "${1:-}" == "-t" && "${2:-}" == "get_outputs" && "${3:-}" == "-r" ]]; then if [[ "${1:-}" == "-t" && "${2:-}" == "get_outputs" && "${3:-}" == "-r" ]]; then
printf '%s\n' "${LESAVKA_TEST_SWAY_OUTPUTS:-[]}" printf '%s\n' "${LESAVKA_TEST_SWAY_OUTPUTS:-[]}"
exit 0 exit "${LESAVKA_TEST_SWAY_GET_OUTPUTS_EXIT:-0}"
fi fi
echo "$*" >> "${LESAVKA_TEST_SWAY_LOG}" echo "$*" >> "${LESAVKA_TEST_SWAY_LOG}"
exit "${LESAVKA_TEST_SWAY_PLACE_EXIT:-0}"
"#, "#,
) )
.expect("write swaymsg stub"); .expect("write swaymsg stub");
@ -53,6 +54,16 @@ fn run_apply_with_outputs(layout: Layout, outputs_json: &str) -> Option<String>
fs::read_to_string(log_path).ok() fs::read_to_string(log_path).ok()
} }
fn run_apply_without_swaymsg(layout: Layout) {
with_var(
"PATH",
Some("/definitely-missing-lesavka-test-path"),
|| {
apply(layout);
},
);
}
#[test] #[test]
#[serial] #[serial]
fn side_by_side_places_each_eye_on_half_of_focused_output() { fn side_by_side_places_each_eye_on_half_of_focused_output() {
@ -97,3 +108,45 @@ fn apply_skips_window_commands_when_no_output_is_focused() {
"expected no placement calls, got: {log}" "expected no placement calls, got: {log}"
); );
} }
#[test]
#[serial]
fn apply_skips_window_commands_when_outputs_shape_is_not_an_array() {
let log = run_apply_with_outputs(Layout::SideBySide, r#"{"focused":true}"#).unwrap_or_default();
assert!(
log.trim().is_empty(),
"expected no placement calls, got: {log}"
);
}
#[test]
#[serial]
fn apply_tolerates_nonzero_window_placement_exit_status() {
let dir = tempdir().expect("create temp dir");
install_sway_stub(dir.path());
let log_path = dir.path().join("sway.log");
let old_path = std::env::var("PATH").unwrap_or_default();
let new_path = format!("{}:{old_path}", dir.path().display());
let outputs = r#"[{"focused":true,"rect":{"x":0,"y":0,"width":640,"height":360}}]"#;
let log_value = log_path.to_string_lossy().to_string();
with_var("PATH", Some(new_path.as_str()), || {
with_var("LESAVKA_TEST_SWAY_LOG", Some(log_value.as_str()), || {
with_var("LESAVKA_TEST_SWAY_OUTPUTS", Some(outputs), || {
with_var("LESAVKA_TEST_SWAY_PLACE_EXIT", Some("2"), || {
apply(Layout::SideBySide);
});
});
});
});
let log = fs::read_to_string(log_path).expect("read command log");
assert!(log.contains(r#"[title="^Lesavka-eye-0$"]"#));
assert!(log.contains(r#"[title="^Lesavka-eye-1$"]"#));
}
#[test]
#[serial]
fn apply_handles_missing_sway_binary_without_panicking() {
run_apply_without_swaymsg(Layout::SideBySide);
}

View File

@ -0,0 +1,113 @@
//! Integration coverage for the server camera-selection contract.
//!
//! Scope: exercise environment-driven camera mode selection and stable enum
//! string mappings via the public camera module API.
//! Targets: `server/src/camera.rs`.
//! Why: camera policy decides the entire stream transport path, so these
//! behavior contracts belong in centralized testing.
use lesavka_server::camera::{
CameraCodec, CameraOutput, current_camera_config, update_camera_config,
};
use serial_test::serial;
use temp_env::with_var;
#[test]
fn camera_enum_strings_are_stable() {
assert_eq!(CameraOutput::Uvc.as_str(), "uvc");
assert_eq!(CameraOutput::Hdmi.as_str(), "hdmi");
assert_eq!(CameraCodec::Mjpeg.as_str(), "mjpeg");
assert_eq!(CameraCodec::H264.as_str(), "h264");
}
#[test]
#[serial]
fn camera_config_uses_interval_when_fps_is_unset_or_invalid() {
with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || {
with_var("LESAVKA_UVC_WIDTH", Some("640"), || {
with_var("LESAVKA_UVC_HEIGHT", Some("360"), || {
with_var("LESAVKA_UVC_FPS", None::<&str>, || {
with_var("LESAVKA_UVC_INTERVAL", Some("250000"), || {
let cfg = update_camera_config();
assert_eq!(cfg.output, CameraOutput::Uvc);
assert_eq!(cfg.codec, CameraCodec::Mjpeg);
assert_eq!(cfg.width, 640);
assert_eq!(cfg.height, 360);
assert_eq!(cfg.fps, 40);
});
});
with_var("LESAVKA_UVC_FPS", Some("not-a-number"), || {
with_var("LESAVKA_UVC_INTERVAL", Some("250000"), || {
let cfg = update_camera_config();
assert_eq!(cfg.output, CameraOutput::Uvc);
assert_eq!(cfg.codec, CameraCodec::Mjpeg);
assert_eq!(cfg.width, 640);
assert_eq!(cfg.height, 360);
assert_eq!(cfg.fps, 40);
});
});
});
});
});
}
#[test]
#[serial]
fn camera_config_zero_interval_falls_back_to_default_fps() {
with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || {
with_var("LESAVKA_UVC_WIDTH", Some("800"), || {
with_var("LESAVKA_UVC_HEIGHT", Some("600"), || {
with_var("LESAVKA_UVC_FPS", None::<&str>, || {
with_var("LESAVKA_UVC_INTERVAL", Some("0"), || {
let cfg = update_camera_config();
assert_eq!(cfg.output, CameraOutput::Uvc);
assert_eq!(cfg.codec, CameraCodec::Mjpeg);
assert_eq!(cfg.width, 800);
assert_eq!(cfg.height, 600);
assert_eq!(cfg.fps, 25);
});
});
});
});
});
}
#[test]
#[serial]
fn camera_config_forced_hdmi_tracks_cached_state() {
with_var("LESAVKA_CAM_OUTPUT", Some("hdmi"), || {
let cfg = update_camera_config();
assert_eq!(cfg.output, CameraOutput::Hdmi);
assert_eq!(cfg.codec, CameraCodec::H264);
assert_eq!(cfg.fps, 30);
assert!(matches!(
(cfg.width, cfg.height),
(1920, 1080) | (1280, 720)
));
let cached = current_camera_config();
assert_eq!(cached.output, CameraOutput::Hdmi);
assert_eq!(cached.codec, CameraCodec::H264);
assert_eq!(cached.fps, 30);
});
}
#[test]
#[serial]
fn camera_config_output_override_is_case_insensitive() {
with_var("LESAVKA_CAM_OUTPUT", Some(" UVC "), || {
with_var("LESAVKA_UVC_WIDTH", Some("1024"), || {
with_var("LESAVKA_UVC_HEIGHT", Some("576"), || {
with_var("LESAVKA_UVC_FPS", Some("12"), || {
let cfg = update_camera_config();
assert_eq!(cfg.output, CameraOutput::Uvc);
assert_eq!(cfg.codec, CameraCodec::Mjpeg);
assert_eq!(cfg.width, 1024);
assert_eq!(cfg.height, 576);
assert_eq!(cfg.fps, 12);
});
});
});
});
}