test(video): enforce downstream coverage gate
This commit is contained in:
parent
c17777b831
commit
a50560ccd1
27
scripts/ci/video_downstream_gate.sh
Executable file
27
scripts/ci/video_downstream_gate.sh
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Guard downstream eye-video stability before pushing video-related changes.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
VIDEO_TESTS=(
|
||||||
|
--test video_downstream_feed_contract
|
||||||
|
--test server_video_include_contract
|
||||||
|
--test video_support_contract
|
||||||
|
--test client_output_video_include_contract
|
||||||
|
--test server_video_sinks_include_contract
|
||||||
|
--test server_video_sink_smoke_contract
|
||||||
|
)
|
||||||
|
|
||||||
|
VIDEO_IGNORE_REGEX='(/common/src/(hid|paste|process_metrics)\.rs|/server/src/(audio|camera|gadget|paste|runtime_support|uvc_runtime)\.rs)'
|
||||||
|
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo check -q --bin lesavka-client --bin lesavka-server
|
||||||
|
cargo test -q -p lesavka_testing "${VIDEO_TESTS[@]}"
|
||||||
|
|
||||||
|
cargo llvm-cov clean --workspace
|
||||||
|
cargo llvm-cov --workspace "${VIDEO_TESTS[@]}" \
|
||||||
|
--ignore-filename-regex "$VIDEO_IGNORE_REGEX" \
|
||||||
|
--fail-under-lines 95 \
|
||||||
|
--summary-only
|
||||||
@ -336,12 +336,17 @@ pub async fn eye_ball_with_request(
|
|||||||
let pipeline = gst::Pipeline::new();
|
let pipeline = gst::Pipeline::new();
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(64);
|
let (tx, rx) = tokio::sync::mpsc::channel(64);
|
||||||
|
|
||||||
|
for seq in 0..8 {
|
||||||
let _ = tx.try_send(Ok(VideoPacket {
|
let _ = tx.try_send(Ok(VideoPacket {
|
||||||
id: id.min(1),
|
id: id.min(1),
|
||||||
pts: 0,
|
pts: seq * 16_666,
|
||||||
data: vec![0, 0, 0, 1, 0x65, 0x88, 0x84],
|
data: vec![0, 0, 0, 1, 0x65, 0x88, 0x84],
|
||||||
|
seq: seq + 1,
|
||||||
|
effective_fps: 60,
|
||||||
|
server_encoder_label: "coverage-testsrc".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(VideoStream {
|
Ok(VideoStream {
|
||||||
_pipeline: pipeline,
|
_pipeline: pipeline,
|
||||||
|
|||||||
@ -95,6 +95,33 @@ mod video_include_contract {
|
|||||||
with_var("PATH", Some(merged), f);
|
with_var("PATH", Some(merged), f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn h264_decoder_selection_honors_env_and_fallbacks() {
|
||||||
|
gst::init().expect("initialize gstreamer");
|
||||||
|
with_var("LESAVKA_H264_DECODER", Some("decodebin"), || {
|
||||||
|
assert_eq!(pick_h264_decoder(), "decodebin");
|
||||||
|
});
|
||||||
|
with_var("LESAVKA_H264_DECODER", Some("fakesink"), || {
|
||||||
|
assert_eq!(pick_h264_decoder(), "fakesink");
|
||||||
|
});
|
||||||
|
with_var(
|
||||||
|
"LESAVKA_H264_DECODER",
|
||||||
|
Some("definitely-not-a-decoder"),
|
||||||
|
|| {
|
||||||
|
assert_ne!(pick_h264_decoder(), "definitely-not-a-decoder");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
with_var("LESAVKA_H264_DECODER", Some(" "), || {
|
||||||
|
assert!(!pick_h264_decoder().trim().is_empty());
|
||||||
|
});
|
||||||
|
with_var("LESAVKA_H264_DECODER", None::<&str>, || {
|
||||||
|
assert!(!pick_h264_decoder().trim().is_empty());
|
||||||
|
});
|
||||||
|
assert!(buildable_decoder("fakesink"));
|
||||||
|
assert!(!buildable_decoder("definitely-not-a-real-gst-element"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn monitor_window_new_covers_x11_backend_path() {
|
fn monitor_window_new_covers_x11_backend_path() {
|
||||||
|
|||||||
@ -31,6 +31,30 @@ mod video_include_contract {
|
|||||||
let _ = gst::init();
|
let _ = gst::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eye_profile_and_telemetry_helpers_are_stable() {
|
||||||
|
assert_eq!(eye_source_profile(), (1920, 1080, 60));
|
||||||
|
|
||||||
|
let metric = server_process_cpu_metric();
|
||||||
|
metric.store(123, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
assert_eq!(metric.load(std::sync::atomic::Ordering::Relaxed), 123);
|
||||||
|
|
||||||
|
let last_window = AtomicU64::new(5);
|
||||||
|
let source_gap = AtomicU32::new(99);
|
||||||
|
let send_gap = AtomicU32::new(88);
|
||||||
|
let queue_peak = AtomicU32::new(77);
|
||||||
|
|
||||||
|
reset_stream_telemetry_window(&last_window, 5, &source_gap, &send_gap, &queue_peak);
|
||||||
|
assert_eq!(source_gap.load(Ordering::Relaxed), 99);
|
||||||
|
assert_eq!(send_gap.load(Ordering::Relaxed), 88);
|
||||||
|
assert_eq!(queue_peak.load(Ordering::Relaxed), 77);
|
||||||
|
|
||||||
|
reset_stream_telemetry_window(&last_window, 6, &source_gap, &send_gap, &queue_peak);
|
||||||
|
assert_eq!(source_gap.load(Ordering::Relaxed), 0);
|
||||||
|
assert_eq!(send_gap.load(Ordering::Relaxed), 0);
|
||||||
|
assert_eq!(queue_peak.load(Ordering::Relaxed), 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn video_stream_forwards_inner_packets() {
|
async fn video_stream_forwards_inner_packets() {
|
||||||
init_gst();
|
init_gst();
|
||||||
@ -248,4 +272,24 @@ mod video_include_contract {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn wait_for_eye_device_reports_non_character_paths() {
|
||||||
|
let rt = tokio::runtime::Runtime::new().expect("runtime");
|
||||||
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
|
let regular_file = dir.path().join("not-a-video-device");
|
||||||
|
std::fs::write(®ular_file, "not a device").expect("write marker file");
|
||||||
|
with_var("LESAVKA_EYE_DEVICE_WAIT_MS", Some("50"), || {
|
||||||
|
with_var("LESAVKA_EYE_DEVICE_POLL_MS", Some("25"), || {
|
||||||
|
rt.block_on(async {
|
||||||
|
let err = wait_for_eye_device(regular_file.to_str().expect("utf8 path"), "l")
|
||||||
|
.await
|
||||||
|
.expect_err("regular files should not count as eye devices");
|
||||||
|
let rendered = format!("{err:#}");
|
||||||
|
assert!(rendered.contains("not a character device"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,22 @@ fn native_downstream_eye_modes_stay_widescreen_and_square_pixel() {
|
|||||||
(mode.width, mode.height)
|
(mode.width, mode.height)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 720,
|
||||||
|
height: 576,
|
||||||
|
fps: 50,
|
||||||
|
}),
|
||||||
|
(1024, 576)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 720,
|
||||||
|
height: 480,
|
||||||
|
fps: 60,
|
||||||
|
}),
|
||||||
|
(854, 480)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -29,6 +29,7 @@ fn contains_idr_handles_short_and_multi_nal_annex_b_streams() {
|
|||||||
assert!(contains_idr(&[0, 0, 1, 0x65, 0x00]));
|
assert!(contains_idr(&[0, 0, 1, 0x65, 0x00]));
|
||||||
assert!(contains_idr(&[0, 0, 0, 1, 0x41, 0x00, 0, 0, 1, 0x65, 0x00]));
|
assert!(contains_idr(&[0, 0, 0, 1, 0x41, 0x00, 0, 0, 1, 0x65, 0x00]));
|
||||||
assert!(!contains_idr(&[0, 0, 0, 1, 0x41, 0x00, 0x00]));
|
assert!(!contains_idr(&[0, 0, 0, 1, 0x41, 0x00, 0x00]));
|
||||||
|
assert!(!contains_idr(&[0, 0, 2, 0x65, 0, 0, 0, 2, 0x65]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user