223 lines
8.6 KiB
Rust
223 lines
8.6 KiB
Rust
|
|
use super::{
|
||
|
|
extract_audio_samples, extract_video_brightness, extract_video_colors,
|
||
|
|
extract_video_timestamps, run_command,
|
||
|
|
};
|
||
|
|
use crate::sync_probe::analyze::test_support::{
|
||
|
|
audio_samples_to_bytes, frame_json, thumbnail_rgb_video_bytes, thumbnail_video_bytes,
|
||
|
|
with_fake_media_tools,
|
||
|
|
};
|
||
|
|
use std::process::Command;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_timestamps_reads_fake_ffprobe_output` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_timestamps_reads_fake_ffprobe_output() {
|
||
|
|
let timestamps = vec![0.0, 0.5, 1.0];
|
||
|
|
with_fake_media_tools(
|
||
|
|
&frame_json(×tamps),
|
||
|
|
&[1, 2, 3],
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let parsed = extract_video_timestamps(capture_path).expect("video timestamps");
|
||
|
|
assert_eq!(parsed, timestamps);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_timestamps_rejects_empty_and_invalid_outputs` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_timestamps_rejects_empty_and_invalid_outputs() {
|
||
|
|
with_fake_media_tools(br#"{"frames":[]}"#, &[1], &[1, 0], |capture_path| {
|
||
|
|
let error = extract_video_timestamps(capture_path).expect_err("empty frames fail");
|
||
|
|
assert!(
|
||
|
|
error
|
||
|
|
.to_string()
|
||
|
|
.contains("did not return any video frame timestamps")
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
with_fake_media_tools(
|
||
|
|
br#"{"frames":[{"best_effort_timestamp_time":"bad"}]}"#,
|
||
|
|
&[1],
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let error =
|
||
|
|
extract_video_timestamps(capture_path).expect_err("invalid timestamp fails");
|
||
|
|
assert!(error.to_string().contains("parsing frame timestamp"));
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_brightness_reads_fake_ffmpeg_output` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_brightness_reads_fake_ffmpeg_output() {
|
||
|
|
let brightness = vec![5u8, 100, 250];
|
||
|
|
with_fake_media_tools(
|
||
|
|
br#"{"frames":[{"best_effort_timestamp_time":"0.0"}]}"#,
|
||
|
|
&thumbnail_video_bytes(&brightness),
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let parsed = extract_video_brightness(capture_path).expect("video brightness");
|
||
|
|
assert_eq!(parsed, brightness);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_brightness_rejects_empty_output` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_brightness_rejects_empty_output() {
|
||
|
|
with_fake_media_tools(
|
||
|
|
br#"{"frames":[{"best_effort_timestamp_time":"0.0"}]}"#,
|
||
|
|
&[],
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let error = extract_video_brightness(capture_path).expect_err("empty brightness");
|
||
|
|
assert!(
|
||
|
|
error
|
||
|
|
.to_string()
|
||
|
|
.contains("did not emit any video brightness data")
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_brightness_uses_full_frame_thumbnail_average` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_brightness_uses_full_frame_thumbnail_average() {
|
||
|
|
let brightness = vec![20u8, 45, 20];
|
||
|
|
with_fake_media_tools(
|
||
|
|
&frame_json(&[0.0, 0.1, 0.2]),
|
||
|
|
&thumbnail_video_bytes(&brightness),
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let parsed = extract_video_brightness(capture_path).expect("video brightness");
|
||
|
|
assert_eq!(parsed, brightness);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn extract_video_brightness_rejects_truncated_frame_data() {
|
||
|
|
with_fake_media_tools(&frame_json(&[0.0]), &[1, 2, 3], &[1, 0], |capture_path| {
|
||
|
|
let error = extract_video_brightness(capture_path).expect_err("truncated frame bytes");
|
||
|
|
assert!(error.to_string().contains("not divisible"));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_colors_reads_fake_ffmpeg_output` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_colors_reads_fake_ffmpeg_output() {
|
||
|
|
let colors = vec![(255, 45, 45), (0, 230, 118), (41, 121, 255)];
|
||
|
|
with_fake_media_tools(
|
||
|
|
&frame_json(&[0.0, 0.1, 0.2]),
|
||
|
|
&thumbnail_rgb_video_bytes(&colors),
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let parsed = extract_video_colors(capture_path).expect("video colors");
|
||
|
|
assert_eq!(parsed[0].r, 255);
|
||
|
|
assert_eq!(parsed[1].g, 230);
|
||
|
|
assert_eq!(parsed[2].b, 255);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_video_colors_tracks_small_flashing_screen_region` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_video_colors_tracks_small_flashing_screen_region() {
|
||
|
|
const SIDE: usize = 64;
|
||
|
|
let mut bytes = Vec::new();
|
||
|
|
for color in [(24, 28, 32), (255, 45, 45), (24, 28, 32), (0, 230, 118)] {
|
||
|
|
let mut frame = vec![34u8; SIDE * SIDE * 3];
|
||
|
|
for y in 6..18 {
|
||
|
|
for x in 40..54 {
|
||
|
|
let offset = (y * SIDE + x) * 3;
|
||
|
|
frame[offset] = color.0;
|
||
|
|
frame[offset + 1] = color.1;
|
||
|
|
frame[offset + 2] = color.2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bytes.extend_from_slice(&frame);
|
||
|
|
}
|
||
|
|
|
||
|
|
with_fake_media_tools(
|
||
|
|
&frame_json(&[0.0, 0.1, 0.2, 0.3]),
|
||
|
|
&bytes,
|
||
|
|
&[1, 0],
|
||
|
|
|capture_path| {
|
||
|
|
let parsed = extract_video_colors(capture_path).expect("video colors");
|
||
|
|
assert!(
|
||
|
|
parsed[1].r > 220 && parsed[1].g < 80,
|
||
|
|
"red pulse should dominate selected ROI: {:?}",
|
||
|
|
parsed[1]
|
||
|
|
);
|
||
|
|
assert!(
|
||
|
|
parsed[3].g > 190 && parsed[3].r < 60,
|
||
|
|
"green pulse should dominate selected ROI: {:?}",
|
||
|
|
parsed[3]
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_audio_samples_reads_fake_ffmpeg_output` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_audio_samples_reads_fake_ffmpeg_output() {
|
||
|
|
let samples = vec![1i16, -2, 32_000];
|
||
|
|
with_fake_media_tools(
|
||
|
|
br#"{"frames":[{"best_effort_timestamp_time":"0.0"}]}"#,
|
||
|
|
&[1],
|
||
|
|
&audio_samples_to_bytes(&samples),
|
||
|
|
|capture_path| {
|
||
|
|
let parsed = extract_audio_samples(capture_path).expect("audio samples");
|
||
|
|
assert_eq!(parsed, samples);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `extract_audio_samples_rejects_too_short_output` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn extract_audio_samples_rejects_too_short_output() {
|
||
|
|
with_fake_media_tools(
|
||
|
|
br#"{"frames":[{"best_effort_timestamp_time":"0.0"}]}"#,
|
||
|
|
&[1],
|
||
|
|
&[7],
|
||
|
|
|capture_path| {
|
||
|
|
let error = extract_audio_samples(capture_path).expect_err("short audio");
|
||
|
|
assert!(
|
||
|
|
error
|
||
|
|
.to_string()
|
||
|
|
.contains("did not emit enough audio data to analyze")
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
/// Keeps `run_command_reports_success_and_failure` explicit because it sits on sync-probe analysis, where small timestamp or pairing mistakes can hide real A/V skew.
|
||
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||
|
|
fn run_command_reports_success_and_failure() {
|
||
|
|
let output = run_command(
|
||
|
|
Command::new("sh").arg("-c").arg("printf 'ok'"),
|
||
|
|
"success command",
|
||
|
|
)
|
||
|
|
.expect("success output");
|
||
|
|
assert_eq!(output, b"ok");
|
||
|
|
|
||
|
|
let error = run_command(
|
||
|
|
Command::new("sh")
|
||
|
|
.arg("-c")
|
||
|
|
.arg("printf 'boom' >&2; exit 7"),
|
||
|
|
"failing command",
|
||
|
|
)
|
||
|
|
.expect_err("failing command should error");
|
||
|
|
assert!(error.to_string().contains("failing command failed: boom"));
|
||
|
|
}
|