From 10d390d353f9f4a3bed1f9add8a5dbc513edeada Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 19 May 2026 10:55:10 -0300 Subject: [PATCH] ci(lesavka): keep video support source under loc gate --- docs/operational-env.md | 1 + server/src/video_support.rs | 170 +----------------------------- server/src/video_support/tests.rs | 165 +++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 168 deletions(-) create mode 100644 server/src/video_support/tests.rs diff --git a/docs/operational-env.md b/docs/operational-env.md index a5f7035..47e6c54 100644 --- a/docs/operational-env.md +++ b/docs/operational-env.md @@ -263,6 +263,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta | `LESAVKA_TLS_KEY` | server TLS private-key path override | | `LESAVKA_TLS_SAN` | server installer extra certificate SAN list for additional relay hostnames/IPs | | `LESAVKA_SYNTHETIC_PKI_DIR` | server installer destination for the SSH user's local synthetic-probe mTLS identity; defaults to `~/.config/lesavka/pki` | +| `LESAVKA_SYNTHETIC_TEST_PATH` | test-only synthetic-uplink path override used by coverage contracts; not runtime operator config | | `LESAVKA_UAC_BUFFER_TIME_US` | server audio sink latency override | | `LESAVKA_UAC_COMPENSATION_US` | server audio sink latency override | | `LESAVKA_UAC_DEV` | server hardware/device override | diff --git a/server/src/video_support.rs b/server/src/video_support.rs index 3d36e41..97679f3 100644 --- a/server/src/video_support.rs +++ b/server/src/video_support.rs @@ -338,171 +338,5 @@ pub fn reserve_local_pts(counter: &AtomicU64, preferred_pts_us: u64, frame_step_ } #[cfg(test)] -mod tests { - use super::{ - SOFTWARE_VIDEO_FALLBACK_ENV, adjust_effective_fps, contains_hevc_irap, contains_idr, - default_eye_fps, env_u32, env_usize, is_hardware_h264_decoder, is_hardware_hevc_decoder, - next_local_pts, pick_h264_decoder, pick_hevc_decoder, require_h264_decoder, - require_hevc_decoder, reserve_local_pts, should_send_frame, - software_video_fallback_allowed, - }; - use serial_test::serial; - use std::sync::atomic::AtomicU64; - use temp_env::with_var; - - #[test] - fn default_eye_fps_tracks_bitrate_tiers() { - assert_eq!(default_eye_fps(0), 25); - assert_eq!(default_eye_fps(2_000), 15); - assert_eq!(default_eye_fps(3_000), 20); - assert_eq!(default_eye_fps(8_000), 25); - } - - #[test] - fn contains_idr_finds_annex_b_keyframes() { - let sample = [0, 0, 0, 1, 0x65, 0x88, 0x99]; - assert!(contains_idr(&sample)); - assert!(contains_idr(&[0, 0, 1, 0x65, 0x88])); - assert!(!contains_idr(&[0, 0, 0, 1, 0x41, 0x99])); - assert!(!contains_idr(&[0, 0, 2, 0x65, 0x88])); - } - - #[test] - fn contains_hevc_irap_finds_annex_b_recovery_frames() { - assert!(contains_hevc_irap(&[0, 0, 0, 1, 0x26, 0x01, 0xaa])); - assert!(contains_hevc_irap(&[0, 0, 1, 0x28, 0x01, 0xaa])); - assert!(!contains_hevc_irap(&[0, 0, 0, 1, 0x02, 0x01, 0xaa])); - } - - #[test] - fn adjust_effective_fps_reacts_to_drop_windows() { - assert_eq!(adjust_effective_fps(20, 12, 25, 5, 10), 17); - assert_eq!(adjust_effective_fps(20, 12, 25, 0, 20), 21); - assert_eq!(adjust_effective_fps(12, 12, 25, 10, 10), 12); - } - - #[test] - fn should_send_frame_enforces_interval() { - assert!(should_send_frame(0, 10, 25)); - assert!(!should_send_frame(40_000, 50_000, 25)); - assert!(should_send_frame(40_000, 90_000, 25)); - assert!(!should_send_frame(40_000, 40_001, 0)); - assert!(should_send_frame(40_000, 1_040_000, 0)); - } - - #[test] - fn next_local_pts_monotonically_advances() { - let counter = AtomicU64::new(0); - assert_eq!(next_local_pts(&counter, 40_000), 0); - assert_eq!(next_local_pts(&counter, 40_000), 40_000); - } - - #[test] - fn reserve_local_pts_prefers_preferred_value_but_stays_monotonic() { - let counter = AtomicU64::new(0); - assert_eq!(reserve_local_pts(&counter, 0, 40_000), 0); - assert_eq!(reserve_local_pts(&counter, 10_000, 40_000), 40_000); - assert_eq!(reserve_local_pts(&counter, 120_000, 40_000), 120_000); - } - - #[test] - #[serial] - fn env_helpers_parse_values_and_fallbacks() { - with_var("LESAVKA_TEST_U32", Some("42"), || { - assert_eq!(env_u32("LESAVKA_TEST_U32", 7), 42); - }); - with_var("LESAVKA_TEST_U32", Some("oops"), || { - assert_eq!(env_u32("LESAVKA_TEST_U32", 7), 7); - }); - with_var("LESAVKA_TEST_USIZE", Some("128"), || { - assert_eq!(env_usize("LESAVKA_TEST_USIZE", 64), 128); - }); - with_var("LESAVKA_TEST_USIZE", None::<&str>, || { - assert_eq!(env_usize("LESAVKA_TEST_USIZE", 64), 64); - }); - } - - #[test] - #[serial] - fn software_video_fallback_requires_clear_operator_opt_in() { - for disabled in [ - None, - Some(""), - Some("0"), - Some("false"), - Some("no"), - Some("off"), - ] { - with_var(SOFTWARE_VIDEO_FALLBACK_ENV, disabled, || { - assert!(!software_video_fallback_allowed()); - }); - } - - for enabled in ["1", "true", "yes", "on", "lab"] { - with_var(SOFTWARE_VIDEO_FALLBACK_ENV, Some(enabled), || { - assert!(software_video_fallback_allowed()); - }); - } - } - - #[test] - fn hardware_decoder_classification_keeps_software_fallbacks_out() { - for name in [ - "v4l2h264dec", - "v4l2slh264dec", - "omxh264dec", - "vulkanh264dec", - ] { - assert!(is_hardware_h264_decoder(name)); - } - assert!(!is_hardware_h264_decoder("avdec_h264")); - - for name in [ - "v4l2slh265dec", - "v4l2h265dec", - "vulkanh265dec", - "nvh265dec", - "nvh265sldec", - ] { - assert!(is_hardware_hevc_decoder(name)); - } - assert!(!is_hardware_hevc_decoder("libde265dec")); - } - - #[test] - #[serial] - fn explicit_bad_decoder_overrides_fail_loudly_without_registry_assumptions() { - gstreamer::init().expect("initialize gstreamer registry for decoder lookup"); - - with_var( - "LESAVKA_H264_DECODER", - Some("definitely-not-a-decoder"), - || { - let err = require_h264_decoder().expect_err("bogus H.264 decoder should fail"); - assert!( - err.to_string().contains("not buildable"), - "unexpected H.264 override error: {err:#}" - ); - assert_eq!(pick_h264_decoder(), "missing-hardware-h264dec"); - }, - ); - - with_var( - "LESAVKA_HEVC_DECODER", - Some("definitely-not-a-decoder"), - || { - let err = require_hevc_decoder().expect_err("bogus HEVC decoder should fail"); - assert!( - err.to_string().contains("not buildable"), - "unexpected HEVC override error: {err:#}" - ); - assert_eq!(pick_hevc_decoder(), "missing-hardware-hevcdec"); - }, - ); - } - - #[test] - fn adjust_effective_fps_keeps_current_rate_when_no_samples() { - assert_eq!(adjust_effective_fps(18, 12, 25, 0, 0), 18); - } -} +#[path = "video_support/tests.rs"] +mod tests; diff --git a/server/src/video_support/tests.rs b/server/src/video_support/tests.rs new file mode 100644 index 0000000..a9a8ad7 --- /dev/null +++ b/server/src/video_support/tests.rs @@ -0,0 +1,165 @@ +use super::{ + SOFTWARE_VIDEO_FALLBACK_ENV, adjust_effective_fps, contains_hevc_irap, contains_idr, + default_eye_fps, env_u32, env_usize, is_hardware_h264_decoder, is_hardware_hevc_decoder, + next_local_pts, pick_h264_decoder, pick_hevc_decoder, require_h264_decoder, + require_hevc_decoder, reserve_local_pts, should_send_frame, software_video_fallback_allowed, +}; +use serial_test::serial; +use std::sync::atomic::AtomicU64; +use temp_env::with_var; + +#[test] +fn default_eye_fps_tracks_bitrate_tiers() { + assert_eq!(default_eye_fps(0), 25); + assert_eq!(default_eye_fps(2_000), 15); + assert_eq!(default_eye_fps(3_000), 20); + assert_eq!(default_eye_fps(8_000), 25); +} + +#[test] +fn contains_idr_finds_annex_b_keyframes() { + let sample = [0, 0, 0, 1, 0x65, 0x88, 0x99]; + assert!(contains_idr(&sample)); + assert!(contains_idr(&[0, 0, 1, 0x65, 0x88])); + assert!(!contains_idr(&[0, 0, 0, 1, 0x41, 0x99])); + assert!(!contains_idr(&[0, 0, 2, 0x65, 0x88])); +} + +#[test] +fn contains_hevc_irap_finds_annex_b_recovery_frames() { + assert!(contains_hevc_irap(&[0, 0, 0, 1, 0x26, 0x01, 0xaa])); + assert!(contains_hevc_irap(&[0, 0, 1, 0x28, 0x01, 0xaa])); + assert!(!contains_hevc_irap(&[0, 0, 0, 1, 0x02, 0x01, 0xaa])); +} + +#[test] +fn adjust_effective_fps_reacts_to_drop_windows() { + assert_eq!(adjust_effective_fps(20, 12, 25, 5, 10), 17); + assert_eq!(adjust_effective_fps(20, 12, 25, 0, 20), 21); + assert_eq!(adjust_effective_fps(12, 12, 25, 10, 10), 12); +} + +#[test] +fn should_send_frame_enforces_interval() { + assert!(should_send_frame(0, 10, 25)); + assert!(!should_send_frame(40_000, 50_000, 25)); + assert!(should_send_frame(40_000, 90_000, 25)); + assert!(!should_send_frame(40_000, 40_001, 0)); + assert!(should_send_frame(40_000, 1_040_000, 0)); +} + +#[test] +fn next_local_pts_monotonically_advances() { + let counter = AtomicU64::new(0); + assert_eq!(next_local_pts(&counter, 40_000), 0); + assert_eq!(next_local_pts(&counter, 40_000), 40_000); +} + +#[test] +fn reserve_local_pts_prefers_preferred_value_but_stays_monotonic() { + let counter = AtomicU64::new(0); + assert_eq!(reserve_local_pts(&counter, 0, 40_000), 0); + assert_eq!(reserve_local_pts(&counter, 10_000, 40_000), 40_000); + assert_eq!(reserve_local_pts(&counter, 120_000, 40_000), 120_000); +} + +#[test] +#[serial] +fn env_helpers_parse_values_and_fallbacks() { + with_var("LESAVKA_TEST_U32", Some("42"), || { + assert_eq!(env_u32("LESAVKA_TEST_U32", 7), 42); + }); + with_var("LESAVKA_TEST_U32", Some("oops"), || { + assert_eq!(env_u32("LESAVKA_TEST_U32", 7), 7); + }); + with_var("LESAVKA_TEST_USIZE", Some("128"), || { + assert_eq!(env_usize("LESAVKA_TEST_USIZE", 64), 128); + }); + with_var("LESAVKA_TEST_USIZE", None::<&str>, || { + assert_eq!(env_usize("LESAVKA_TEST_USIZE", 64), 64); + }); +} + +#[test] +#[serial] +fn software_video_fallback_requires_clear_operator_opt_in() { + for disabled in [ + None, + Some(""), + Some("0"), + Some("false"), + Some("no"), + Some("off"), + ] { + with_var(SOFTWARE_VIDEO_FALLBACK_ENV, disabled, || { + assert!(!software_video_fallback_allowed()); + }); + } + + for enabled in ["1", "true", "yes", "on", "lab"] { + with_var(SOFTWARE_VIDEO_FALLBACK_ENV, Some(enabled), || { + assert!(software_video_fallback_allowed()); + }); + } +} + +#[test] +fn hardware_decoder_classification_keeps_software_fallbacks_out() { + for name in [ + "v4l2h264dec", + "v4l2slh264dec", + "omxh264dec", + "vulkanh264dec", + ] { + assert!(is_hardware_h264_decoder(name)); + } + assert!(!is_hardware_h264_decoder("avdec_h264")); + + for name in [ + "v4l2slh265dec", + "v4l2h265dec", + "vulkanh265dec", + "nvh265dec", + "nvh265sldec", + ] { + assert!(is_hardware_hevc_decoder(name)); + } + assert!(!is_hardware_hevc_decoder("libde265dec")); +} + +#[test] +#[serial] +fn explicit_bad_decoder_overrides_fail_loudly_without_registry_assumptions() { + gstreamer::init().expect("initialize gstreamer registry for decoder lookup"); + + with_var( + "LESAVKA_H264_DECODER", + Some("definitely-not-a-decoder"), + || { + let err = require_h264_decoder().expect_err("bogus H.264 decoder should fail"); + assert!( + err.to_string().contains("not buildable"), + "unexpected H.264 override error: {err:#}" + ); + assert_eq!(pick_h264_decoder(), "missing-hardware-h264dec"); + }, + ); + + with_var( + "LESAVKA_HEVC_DECODER", + Some("definitely-not-a-decoder"), + || { + let err = require_hevc_decoder().expect_err("bogus HEVC decoder should fail"); + assert!( + err.to_string().contains("not buildable"), + "unexpected HEVC override error: {err:#}" + ); + assert_eq!(pick_hevc_decoder(), "missing-hardware-hevcdec"); + }, + ); +} + +#[test] +fn adjust_effective_fps_keeps_current_rate_when_no_samples() { + assert_eq!(adjust_effective_fps(18, 12, 25, 0, 0), 18); +}