192 lines
8.2 KiB
Rust
192 lines
8.2 KiB
Rust
//! Contract tests for the manual upstream A/V sync harness.
|
|
//!
|
|
//! Scope: statically guard the workstation-side tunnel/bootstrap behavior.
|
|
//! Targets: `scripts/manual/run_upstream_av_sync.sh`.
|
|
//! Why: the manual probe should reach Theia through SSH even when the gRPC
|
|
//! port is not exposed on the public SSH endpoint.
|
|
|
|
const SYNC_SCRIPT: &str = include_str!("../../scripts/manual/run_upstream_av_sync.sh");
|
|
const BROWSER_SYNC_SCRIPT: &str =
|
|
include_str!("../../scripts/manual/run_upstream_browser_av_sync.sh");
|
|
const MIRRORED_SYNC_SCRIPT: &str =
|
|
include_str!("../../scripts/manual/run_upstream_mirrored_av_sync.sh");
|
|
const LOCAL_STIMULUS: &str = include_str!("../../scripts/manual/local_av_stimulus.py");
|
|
|
|
#[test]
|
|
fn upstream_sync_script_tunnels_auto_server_addr_through_ssh() {
|
|
for expected in [
|
|
"cleanup_server_tunnel",
|
|
"pick_local_server_tunnel_port",
|
|
"wait_for_server_tunnel",
|
|
"start_server_tunnel",
|
|
"ExitOnForwardFailure=yes",
|
|
"127.0.0.1:${local_port}:127.0.0.1:${remote_port}",
|
|
"LESAVKA_SERVER_SCHEME=${LESAVKA_SERVER_SCHEME:-https}",
|
|
"LESAVKA_TLS_DOMAIN=${LESAVKA_TLS_DOMAIN:-lesavka-server}",
|
|
"RESOLVED_LESAVKA_SERVER_ADDR=\"${LESAVKA_SERVER_SCHEME}://127.0.0.1:${SERVER_TUNNEL_LOCAL_PORT}\"",
|
|
"LESAVKA_TLS_DOMAIN=\"${LESAVKA_TLS_DOMAIN}\"",
|
|
"tunneled to ${LESAVKA_SERVER_HOST}:127.0.0.1:${SERVER_TUNNEL_REMOTE_PORT}",
|
|
"CAPTURE_READY_MARKER=\"__LESAVKA_CAPTURE_READY__\"",
|
|
"LOCAL_OUTPUT_DIR=${LOCAL_OUTPUT_DIR:-/tmp}",
|
|
"LOCAL_REPORT_DIR=\"${LOCAL_OUTPUT_DIR%/}/lesavka-sync-probe-${STAMP}\"",
|
|
"LOCAL_ANALYSIS_JSON=\"${LOCAL_REPORT_DIR}/report.json\"",
|
|
"LOCAL_EVENTS_CSV=\"${LOCAL_REPORT_DIR}/events.csv\"",
|
|
"LEAD_IN_SECONDS=${LEAD_IN_SECONDS:-0}",
|
|
"PROBE_TIMEOUT_SECONDS=${PROBE_TIMEOUT_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + 20))}",
|
|
"timeout --signal=INT \"${PROBE_TIMEOUT_SECONDS}\" \"${PROBE_BIN}\"",
|
|
"sync probe timed out after ${PROBE_TIMEOUT_SECONDS}s",
|
|
"VIDIOC_STREAMON.*Connection timed out",
|
|
"the UVC host opened before MJPEG frames reached the gadget",
|
|
"Tethys capture failed before the sync probe could start",
|
|
"wait_for_capture_ready",
|
|
"Timed out waiting for Tethys capture to become ready",
|
|
"grep -q \"${CAPTURE_READY_MARKER}\"",
|
|
"Lesavka UVC video device not found on Tethys; refusing to fall back to an unrelated webcam/capture card.",
|
|
"resolve_alsa_audio_device",
|
|
"PipeWire Lesavka source not found; falling back to ALSA device",
|
|
"Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.",
|
|
"artifact_dir: ${LOCAL_REPORT_DIR}",
|
|
"events_csv: ${LOCAL_EVENTS_CSV}",
|
|
"==> Lesavka versions under test",
|
|
"lesavka-relayctl",
|
|
"--bin lesavka-relayctl",
|
|
"client_revision=",
|
|
"server_version=",
|
|
"server_revision=",
|
|
"combined version+revision",
|
|
] {
|
|
assert!(
|
|
SYNC_SCRIPT.contains(expected),
|
|
"manual sync script should contain {expected}"
|
|
);
|
|
}
|
|
assert!(
|
|
!SYNC_SCRIPT.contains(
|
|
"RESOLVED_LESAVKA_SERVER_ADDR=\"http://${LESAVKA_SERVER_CONNECT_HOST}:${port}\""
|
|
),
|
|
"auto server resolution should not guess a public gRPC host when SSH is already required"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn browser_sync_script_can_delegate_to_a_real_path_driver() {
|
|
for expected in [
|
|
"BROWSER_RECORD_SECONDS=${BROWSER_RECORD_SECONDS:-${PROBE_DURATION_SECONDS}}",
|
|
"BROWSER_SYNC_DRIVER_COMMAND=${BROWSER_SYNC_DRIVER_COMMAND:-}",
|
|
"SYNC_ANALYZE_EVENT_WIDTH_CODES=${SYNC_ANALYZE_EVENT_WIDTH_CODES:-}",
|
|
"==> running custom browser sync driver",
|
|
"bash -lc \"${BROWSER_SYNC_DRIVER_COMMAND}\"",
|
|
"--event-width-codes",
|
|
"--report-dir \"${LOCAL_REPORT_DIR}\"",
|
|
"for attempt in 1 2 3 4 5",
|
|
"capture fetch attempt ${attempt} failed; retrying",
|
|
"failed to fetch browser capture from ${TETHYS_HOST}:${REMOTE_CAPTURE}",
|
|
] {
|
|
assert!(
|
|
BROWSER_SYNC_SCRIPT.contains(expected),
|
|
"browser sync script should contain {expected}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn mirrored_sync_script_uses_real_client_capture_path() {
|
|
for expected in [
|
|
"local_av_stimulus.py",
|
|
"lesavka-client",
|
|
"LESAVKA_HEADLESS=1",
|
|
"LESAVKA_MEDIA_CONTROL=\"${MEDIA_CONTROL}\"",
|
|
"--no-launcher --server \"${RESOLVED_LESAVKA_SERVER_ADDR}\"",
|
|
"BROWSER_SYNC_DRIVER_COMMAND=\"${driver_command}\"",
|
|
"SYNC_ANALYZE_EVENT_WIDTH_CODES=\"${PROBE_EVENT_WIDTH_CODES}\"",
|
|
"run_upstream_browser_av_sync.sh",
|
|
"wait_for_stimulus_page_ready 15",
|
|
"Point the real webcam at the stimulus window",
|
|
"==> Lesavka versions under test",
|
|
"lesavka-relayctl",
|
|
"--bin lesavka-relayctl",
|
|
"client_revision=",
|
|
"server_version=",
|
|
"server_revision=",
|
|
"combined version+revision",
|
|
"run_status=0",
|
|
"run_mirrored_segments || run_status=$?",
|
|
"LESAVKA_SYNC_APPLY_CALIBRATION=${LESAVKA_SYNC_APPLY_CALIBRATION:-0}",
|
|
"LESAVKA_SYNC_SAVE_CALIBRATION=${LESAVKA_SYNC_SAVE_CALIBRATION:-0}",
|
|
"LESAVKA_SYNC_CALIBRATION_TARGET=${LESAVKA_SYNC_CALIBRATION_TARGET:-video}",
|
|
"LESAVKA_SYNC_ADAPTIVE_CALIBRATION=${LESAVKA_SYNC_ADAPTIVE_CALIBRATION:-0}",
|
|
"LESAVKA_SYNC_CALIBRATION_SEGMENTS=${LESAVKA_SYNC_CALIBRATION_SEGMENTS:-1}",
|
|
"LESAVKA_SYNC_SEGMENT_SETTLE_SECONDS=${LESAVKA_SYNC_SEGMENT_SETTLE_SECONDS:-3}",
|
|
"LESAVKA_SYNC_ADAPTIVE_CALIBRATION",
|
|
"LESAVKA_SYNC_CALIBRATION_SEGMENTS=4",
|
|
"LESAVKA_SYNC_CALIBRATION_SEGMENTS must be a positive integer",
|
|
"run_mirrored_segments",
|
|
"summarize_adaptive_probe_metrics",
|
|
"for segment in $(seq 1 \"${LESAVKA_SYNC_CALIBRATION_SEGMENTS}\")",
|
|
"segment-${segment}",
|
|
"calibration-before.env",
|
|
"planner-before.env",
|
|
"calibration-decision.env",
|
|
"segment-metrics.csv",
|
|
"segment-metrics.jsonl",
|
|
"blind-targets.json",
|
|
"no segment produced a passing probe verdict; refusing to invent blind targets",
|
|
"planner_live_lag_ms_after",
|
|
"probe_p95_abs_skew_ms",
|
|
"settling ${LESAVKA_SYNC_SEGMENT_SETTLE_SECONDS}s before next calibration segment",
|
|
"print_upstream_calibration_state \"before mirrored run\"",
|
|
"maybe_apply_probe_calibration",
|
|
"calibration_ready=${calibration_ready}",
|
|
"calibration apply refused: analyzer did not mark this report calibration-ready",
|
|
"calibrate \"${calibration_apply_audio_delta_us}\" \"${calibration_apply_video_delta_us}\"",
|
|
"calibration-save-default",
|
|
"print_upstream_sync_state \"after mirrored run\"",
|
|
"print_upstream_calibration_state \"after mirrored run\"",
|
|
"==> mirrored probe failed",
|
|
] {
|
|
assert!(
|
|
MIRRORED_SYNC_SCRIPT.contains(expected),
|
|
"mirrored sync script should contain {expected}"
|
|
);
|
|
}
|
|
assert!(
|
|
!MIRRORED_SYNC_SCRIPT.contains("lesavka-sync-probe"),
|
|
"mirrored sync must not use the synthetic direct sender"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn local_stimulus_matches_sync_analyzer_pulse_contract() {
|
|
for expected in [
|
|
"--warmup-seconds",
|
|
"--pulse-period-ms",
|
|
"--pulse-width-ms",
|
|
"--marker-tick-period",
|
|
"--event-width-codes",
|
|
"event_width_codes",
|
|
"widthCode",
|
|
"oscillator.frequency.value = 880",
|
|
"setStatus(`ready",
|
|
"Point the real webcam at this window",
|
|
] {
|
|
assert!(
|
|
LOCAL_STIMULUS.contains(expected),
|
|
"local stimulus should contain {expected}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn manual_probe_python_servers_use_reentrant_state_locks() {
|
|
const BROWSER_CONSUMER: &str = include_str!("../../scripts/manual/browser_consumer_probe.py");
|
|
for (name, script) in [
|
|
("local stimulus", LOCAL_STIMULUS),
|
|
("browser consumer", BROWSER_CONSUMER),
|
|
] {
|
|
assert!(
|
|
script.contains("threading.RLock()"),
|
|
"{name} server request handlers call snapshot while holding state lock; use RLock to avoid /start deadlocks"
|
|
);
|
|
}
|
|
}
|