#!/usr/bin/env bash # Run deterministic media/device contracts and emit Atlas quality metrics. set -euo pipefail ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) REPORT_DIR="${ROOT_DIR}/target/media-reliability-gate" TEST_LOG="${REPORT_DIR}/cargo-test.log" SUMMARY_JSON="${REPORT_DIR}/summary.json" SUMMARY_TXT="${REPORT_DIR}/summary.txt" METRICS_FILE="${REPORT_DIR}/metrics.prom" PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-} PUSHGATEWAY_JOB=${LESAVKA_MEDIA_GATE_PUSHGATEWAY_JOB:-lesavka-media-reliability-gate} mkdir -p "${REPORT_DIR}" cd "${ROOT_DIR}" branch=${BRANCH_NAME:-${GIT_BRANCH:-}} if [[ -z "${branch}" ]]; then branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown) fi commit=${GIT_COMMIT:-} if [[ -z "${commit}" ]]; then commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown) fi build_url=${BUILD_URL:-} MEDIA_TESTS=( --test client_camera_include_contract --test client_launcher_runtime_contract --test client_microphone_include_contract --test client_microphone_source_contract --test client_output_video_include_contract --test handshake_camera_contract --test server_camera_contract --test server_camera_runtime_contract --test server_upstream_media_contract --test server_uvc_runtime_contract --test server_video_include_contract --test server_video_sink_smoke_contract --test server_video_sinks_include_contract --test video_downstream_feed_contract --test video_support_contract ) start_seconds=$(date +%s) status=0 set +e cargo test -p lesavka_testing "${MEDIA_TESTS[@]}" --color never 2>&1 | tee "${TEST_LOG}" status=${PIPESTATUS[0]} set -e end_seconds=$(date +%s) duration_seconds=$((end_seconds - start_seconds)) python3 - "${SUMMARY_JSON}" "${SUMMARY_TXT}" "${METRICS_FILE}" "${status}" "${duration_seconds}" "${branch}" "${commit}" "${build_url}" "${REPORT_DIR}" <<'PY' import json import pathlib import sys from datetime import datetime, timezone summary_path = pathlib.Path(sys.argv[1]) text_path = pathlib.Path(sys.argv[2]) metrics_path = pathlib.Path(sys.argv[3]) status = int(sys.argv[4]) duration_seconds = int(sys.argv[5]) branch = sys.argv[6] commit = sys.argv[7] build_url = sys.argv[8] report_dir = pathlib.Path(sys.argv[9]) manual_report = report_dir / 'manual-soak.json' def esc(value: str) -> str: return value.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"') manual_checks = [ { 'name': 'zoom_equivalent_webcam_consumer', 'status': 'not_applicable' if not manual_report.exists() else 'reported', 'why': 'requires a real UVC/HDMI consumer such as Zoom, Teams, Slack, or capture software', }, { 'name': 'ten_minute_soak', 'status': 'not_applicable' if not manual_report.exists() else 'reported', 'why': 'requires sustained live hardware output and should be attached as manual-soak.json', }, { 'name': 'hdmi_capture_adapter_path', 'status': 'not_applicable' if not manual_report.exists() else 'reported', 'why': 'requires the Theia HDMI -> UGREEN -> Tethys USB path', }, ] tracked_signals = [ 'frame_count', 'effective_fps', 'dropped_frames', 'queue_depth', 'max_inter_frame_gap_ms', 'decode_errors', 'appsrc_push_failures', 'artifact_score', 'latency_estimate_ms', 'idr_recovery_after_drop', 'synthetic_moving_pattern_distortion', ] summary = { 'suite': 'lesavka', 'branch': branch, 'commit': commit, 'build_url': build_url, 'generated_at': datetime.now(timezone.utc).isoformat(), 'status': 'ok' if status == 0 else 'failed', 'duration_seconds': duration_seconds, 'deterministic_tests': 'cargo test -p lesavka_testing media reliability contract subset', 'tracked_media_signals': tracked_signals, 'manual_checks': manual_checks, } summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + '\n', encoding='utf-8') lines = [ 'media reliability gate report', f'status: {summary["status"]}', f'branch: {branch}', f'commit: {commit}', f'duration_seconds: {duration_seconds}', '', 'deterministic coverage', '- bounded appsrc/appsink queue contracts', '- stale-frame/drop-over-latency contracts', '- local monotonic timestamp contracts', '- IDR/keyframe recovery contracts', '- HDMI/UVC sink construction contracts', '- preview telemetry and freeze-signal contracts', '', 'manual hardware evidence slots', ] for check in manual_checks: lines.append(f'- {check["name"]}: {check["status"]} ({check["why"]})') text_path.write_text('\n'.join(lines) + '\n', encoding='utf-8') labels = f'suite="lesavka",branch="{esc(branch)}",commit="{esc(commit)}"' ok = 1 if status == 0 else 0 failed = 0 if status == 0 else 1 metrics = [ '# HELP platform_quality_gate_checks_total Check outcomes from the latest lesavka gate run.', '# TYPE platform_quality_gate_checks_total gauge', f'platform_quality_gate_checks_total{{{labels},check="media_reliability",status="ok"}} {ok}', f'platform_quality_gate_checks_total{{{labels},check="media_reliability",status="failed"}} {failed}', '# HELP lesavka_media_reliability_manual_check_info Manual media reliability evidence slots.', '# TYPE lesavka_media_reliability_manual_check_info gauge', ] for check in manual_checks: metrics.append( f'lesavka_media_reliability_manual_check_info{{{labels},check="{esc(check["name"])}",status="{esc(check["status"])}"}} 1' ) metrics_path.write_text('\n'.join(metrics) + '\n', encoding='utf-8') print(text_path.read_text(encoding='utf-8')) PY if [[ -n "${PUSHGATEWAY_URL}" ]]; then curl --fail --silent --show-error \ --data-binary @"${METRICS_FILE}" \ "${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka" || status=$? fi exit "${status}"