lesavka/testing/tests/client_server_rc_matrix_script_contract.rs

171 lines
11 KiB
Rust
Raw Permalink Normal View History

//! Contract tests for the server-to-RCT mode matrix harness.
//!
//! Scope: statically guard the hardware-in-the-loop matrix script defaults and
//! summary artifacts.
//! Targets: `scripts/manual/run_server_to_rc_mode_matrix.sh`.
//! Why: server-to-RCT calibration is now considered complete, so the matrix
//! script must keep the blessed offsets and evidence outputs stable.
const SERVER_RC_MODE_MATRIX_SCRIPT: &str =
include_str!("../../scripts/manual/run_server_to_rc_mode_matrix.sh");
#[test]
fn server_rc_mode_matrix_validates_advertised_uvc_profiles() {
for expected in [
"LESAVKA_SERVER_RC_CORE_WEBCAM_MODES=${LESAVKA_SERVER_RC_CORE_WEBCAM_MODES:-1280x720@20,1280x720@30,1920x1080@20,1920x1080@30}",
"LESAVKA_SERVER_RC_MODES=${LESAVKA_SERVER_RC_MODES:-${LESAVKA_SERVER_RC_CORE_WEBCAM_MODES}}",
"LESAVKA_SERVER_REPO=${LESAVKA_SERVER_REPO:-auto}",
"LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US:-${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US:-0}}",
"LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US=${LESAVKA_SERVER_RC_DEFAULT_VIDEO_DELAY_US:-135090}",
"LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US:-1280x720@20=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1280x720@30=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1920x1080@20=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US},1920x1080@30=${LESAVKA_SERVER_RC_DEFAULT_AUDIO_DELAY_US}}",
"LESAVKA_SERVER_RC_MODE_DELAYS_US=${LESAVKA_SERVER_RC_MODE_DELAYS_US:-1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_SIZES=${LESAVKA_SERVER_RC_MODE_DISCOVERY_SIZES:-1280x720,1920x1080}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_FPS=${LESAVKA_SERVER_RC_MODE_DISCOVERY_FPS:-20,30}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_INCLUDE_REGEX=${LESAVKA_SERVER_RC_MODE_DISCOVERY_INCLUDE_REGEX:-Logitech|BRIO|C9[0-9]+|HD UVC WebCam|USB2[.]0 HD|Integrated Camera|Webcam|Camera}",
"LESAVKA_SERVER_RC_MODE_DISCOVERY_EXCLUDE_REGEX=${LESAVKA_SERVER_RC_MODE_DISCOVERY_EXCLUDE_REGEX:-Lesavka|UGREEN|MACROSILICON|Composite|Capture}",
"LESAVKA_SERVER_RC_MODE_SOURCE=${LESAVKA_SERVER_RC_MODE_SOURCE:-configured}",
"LESAVKA_SERVER_RC_RECONFIGURE=${LESAVKA_SERVER_RC_RECONFIGURE:-0}",
"LESAVKA_SERVER_RC_RECONFIGURE_UPDATE=${LESAVKA_SERVER_RC_RECONFIGURE_UPDATE:-0}",
"LESAVKA_SERVER_RC_RECONFIGURE_STRATEGY=${LESAVKA_SERVER_RC_RECONFIGURE_STRATEGY:-runtime}",
"LESAVKA_SERVER_RC_ALLOW_GADGET_RESET=${LESAVKA_SERVER_RC_ALLOW_GADGET_RESET:-1}",
"LESAVKA_SERVER_RC_RECONFIGURE_VERBOSE=${LESAVKA_SERVER_RC_RECONFIGURE_VERBOSE:-0}",
"LESAVKA_SERVER_RC_PROMPT_SUDO_EARLY=${LESAVKA_SERVER_RC_PROMPT_SUDO_EARLY:-1}",
"LESAVKA_SERVER_RC_START_DELAY_SECONDS=${LESAVKA_SERVER_RC_START_DELAY_SECONDS:-0}",
"LESAVKA_SERVER_RC_WAIT_TETHYS_READY=${LESAVKA_SERVER_RC_WAIT_TETHYS_READY:-1}",
"LESAVKA_SERVER_RC_TETHYS_READY_TIMEOUT_SECONDS=${LESAVKA_SERVER_RC_TETHYS_READY_TIMEOUT_SECONDS:-60}",
"LESAVKA_SERVER_RC_TETHYS_SETTLE_SECONDS=${LESAVKA_SERVER_RC_TETHYS_SETTLE_SECONDS:-6}",
"LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS=${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS:-3}",
"LESAVKA_SERVER_RC_PROBE_PREBUILD=${LESAVKA_SERVER_RC_PROBE_PREBUILD:-1}",
"LESAVKA_SERVER_RC_TUNE_DELAYS=${LESAVKA_SERVER_RC_TUNE_DELAYS:-1}",
"LESAVKA_SERVER_RC_TUNE_CONFIRM=${LESAVKA_SERVER_RC_TUNE_CONFIRM:-1}",
"LESAVKA_SERVER_RC_TUNE_MIN_PAIRS=${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS:-13}",
"LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS=${LESAVKA_SERVER_RC_TUNE_MAX_ABS_SKEW_MS:-1000}",
"LESAVKA_SERVER_RC_TUNE_MAX_STEP_US=${LESAVKA_SERVER_RC_TUNE_MAX_STEP_US:-500000}",
"LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US=${LESAVKA_SERVER_RC_TUNE_MIN_CHANGE_US:-5000}",
"Theia sudo password for %s",
"==> priming remote sudo on ${LESAVKA_SERVER_HOST}",
"sleep_start_delay",
"==> delaying server-to-RC matrix start for ${LESAVKA_SERVER_RC_START_DELAY_SECONDS}s",
"remote sudo has already been primed; sleeping before prebuild/reconfigure/capture",
"LESAVKA_SERVER_RC_START_DELAY_SECONDS must be a non-negative number",
"start_delay=${LESAVKA_SERVER_RC_START_DELAY_SECONDS}s",
"==> prebuilding relay control/analyzer once for the mode matrix",
"LESAVKA_SERVER_RC_MODES=auto",
"discover_local_webcam_modes",
"lookup_audio_delay_us",
"local webcam",
"mode_source=${LESAVKA_SERVER_RC_MODE_SOURCE}",
"video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}",
"audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}",
"pulse_tool=${REMOTE_PULSE_CAPTURE_TOOL}",
"fast runtime env updated: CAM_OUTPUT=uvc",
"cycling UVC gadget descriptors",
"lesavka-core reconfigure log:",
"missing /usr/local/bin/lesavka-core.sh",
"wait_tethys_media_ready",
"==> waiting for Tethys media endpoints for ${mode}",
"Tethys media ready: video=%s mode=%s audio_stack=%s",
"timed out waiting for Tethys Lesavka media endpoints",
"LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS:-350}",
"LESAVKA_SERVER_RC_FRESHNESS_MIN_PAIRS=${LESAVKA_SERVER_RC_FRESHNESS_MIN_PAIRS:-${LESAVKA_SERVER_RC_TUNE_MIN_PAIRS}}",
"LESAVKA_SERVER_RC_MIN_CODED_PAIRS=${LESAVKA_SERVER_RC_MIN_CODED_PAIRS:-${LESAVKA_SERVER_RC_FRESHNESS_MIN_PAIRS}}",
"LESAVKA_SERVER_RC_REQUIRE_ALL_CODED_PAIRS=${LESAVKA_SERVER_RC_REQUIRE_ALL_CODED_PAIRS:-0}",
"LESAVKA_SERVER_RC_REQUIRE_SMOOTHNESS_PASS=${LESAVKA_SERVER_RC_REQUIRE_SMOOTHNESS_PASS:-0}",
"LESAVKA_SERVER_RC_SIGNAL_READY=${LESAVKA_SERVER_RC_SIGNAL_READY:-1}",
"LESAVKA_SERVER_RC_SIGNAL_READY_MODE=${LESAVKA_SERVER_RC_SIGNAL_READY_MODE:-conditioned_capture}",
"LESAVKA_SERVER_RC_SIGNAL_READY_MIN_PAIRS=${LESAVKA_SERVER_RC_SIGNAL_READY_MIN_PAIRS:-3}",
"LESAVKA_SERVER_RC_SIGNAL_READY_DURATION_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_READY_DURATION_SECONDS:-12}",
"LESAVKA_SERVER_RC_SIGNAL_READY_ATTEMPTS=${LESAVKA_SERVER_RC_SIGNAL_READY_ATTEMPTS:-4}",
"LESAVKA_SERVER_RC_SIGNAL_READY_RETRY_DELAY_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_READY_RETRY_DELAY_SECONDS:-5}",
"LESAVKA_SERVER_RC_SIGNAL_CONDITION_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_CONDITION_SECONDS:-12}",
"LESAVKA_SERVER_RC_SIGNAL_CONDITION_WARMUP_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_CONDITION_WARMUP_SECONDS:-1}",
"LESAVKA_SERVER_RC_SIGNAL_CONDITION_GAP_SECONDS=${LESAVKA_SERVER_RC_SIGNAL_CONDITION_GAP_SECONDS:-1}",
"LESAVKA_SERVER_RC_ANALYSIS_TIMELINE_WINDOW=${LESAVKA_SERVER_RC_ANALYSIS_TIMELINE_WINDOW:-auto}",
"signal_readiness_passed",
"write_signal_readiness_attempt_result",
"write_signal_readiness_attempts_summary",
"schema\": \"lesavka.server-rc-signal-readiness-attempt.v1\"",
"schema\": \"lesavka.server-rc-signal-readiness-summary.v1\"",
"using same-capture signal conditioning before measured probe",
"proving Tethys signal readiness before measured probe",
"readiness attempt ${readiness_attempt}/${LESAVKA_SERVER_RC_SIGNAL_READY_ATTEMPTS}",
"waiting ${LESAVKA_SERVER_RC_SIGNAL_READY_RETRY_DELAY_SECONDS}s before retrying signal readiness",
"signal readiness did not pass",
"signal attempt ",
"smoothness_required",
"smoothness_warnings",
"smoothness warning:",
"coded visibility:",
"LESAVKA_SERVER_RC_MAX_VIDEO_HICCUPS=${LESAVKA_SERVER_RC_MAX_VIDEO_HICCUPS:-0}",
"LESAVKA_SERVER_RC_MAX_AUDIO_HICCUPS=${LESAVKA_SERVER_RC_MAX_AUDIO_HICCUPS:-0}",
"LESAVKA_SERVER_RC_MAX_VIDEO_MISSING_FRAMES=${LESAVKA_SERVER_RC_MAX_VIDEO_MISSING_FRAMES:-12}",
"mode-matrix-summary.json",
"mode-matrix-summary.csv",
"mode-matrix-summary.txt",
"mode-delay-recommendations.json",
"mode-delay-recommendations.env",
"schema\": \"lesavka.server-rc-mode-result.v1\"",
"schema\": \"lesavka.server-rc-mode-matrix-summary.v1\"",
"schema\": \"lesavka.server-rc-mode-delay-recommendations.v1\"",
"output_delay_calibration",
"write_tune_candidate_env",
"annotate_mode_result",
"LESAVKA_SERVER_RC_REPEAT_COUNT=${LESAVKA_SERVER_RC_REPEAT_COUNT:-1}",
"mode-static-calibration.json",
"mode-matrix-run.log",
"mode-result-seed.json",
"mode-result-tuned.json",
"==> mode ${mode} run ${mode_run_index}: confirming tuned delays",
"calibration_ready",
"calibration_video_target_offset_us",
"calibration_audio_target_offset_us",
"calibration:",
"capture_timebase_status",
"capture timebase invalid",
"capture timing:",
"signature_coverage",
"paired coded signatures",
"signature_missing_codes",
"probe_env=(",
"\"REMOTE_PULSE_CAPTURE_TOOL=${REMOTE_PULSE_CAPTURE_TOOL}\"",
"\"REMOTE_PULSE_VIDEO_MODE=${REMOTE_PULSE_VIDEO_MODE}\"",
"\"REMOTE_CAPTURE_STACK=${REMOTE_CAPTURE_STACK}\"",
"\"REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK}\"",
"\"REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS=${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}\"",
"\"REMOTE_CAPTURE_READY_SETTLE_SECONDS=${REMOTE_CAPTURE_READY_SETTLE_SECONDS}\"",
"\"PROBE_PREBUILD=0\"",
"\"VIDEO_SIZE=${width}x${height}\"",
"\"VIDEO_FPS=${fps}\"",
"\"REMOTE_EXPECT_UVC_WIDTH=${width}\"",
"\"REMOTE_EXPECT_UVC_HEIGHT=${height}\"",
"\"REMOTE_EXPECT_UVC_FPS=${fps}\"",
"\"LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US=${audio_delay_us}\"",
"\"LESAVKA_OUTPUT_DELAY_PROBE_VIDEO_DELAY_US=${video_delay_us}\"",
"\"LESAVKA_OUTPUT_DELAY_APPLY=0\"",
"\"LESAVKA_OUTPUT_DELAY_SAVE=0\"",
"\"LESAVKA_OUTPUT_FRESHNESS_MAX_AGE_MS=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}\"",
"\"LESAVKA_OUTPUT_FRESHNESS_MAX_CLOCK_UNCERTAINTY_MS=${LESAVKA_SERVER_RC_FRESHNESS_MAX_CLOCK_UNCERTAINTY_MS}\"",
"\"LESAVKA_OUTPUT_FRESHNESS_MIN_PAIRS=${min_pairs}\"",
"sync did not pass",
"freshness did not pass",
"video hiccups",
"estimated missing video frames",
"audio hiccups",
] {
assert!(
SERVER_RC_MODE_MATRIX_SCRIPT.contains(expected),
"server-to-RC mode matrix script should contain {expected}"
);
}
let prime = SERVER_RC_MODE_MATRIX_SCRIPT
.find("prime_remote_sudo\nsleep_start_delay\nprebuild_probe_tools")
.expect("matrix should prime remote sudo before delayed start and prebuild");
let prompt = SERVER_RC_MODE_MATRIX_SCRIPT
.find("Theia sudo password for %s")
.expect("matrix should contain the immediate Theia password prompt");
assert!(
prompt < prime,
"password prompt machinery should be defined before the matrix startup sequence"
);
}