test: add Linux rig validation suite
Runs encoder verification, Wayland and X11 capture with segment and remux checks, and five failure scenarios that must end in an honest failed status: source lost, window closed, unwritable segments, low disk space and a portal that grants no source.
This commit is contained in:
@@ -324,6 +324,7 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
|||||||
Duration::from_secs(options.segment_seconds as u64),
|
Duration::from_secs(options.segment_seconds as u64),
|
||||||
);
|
);
|
||||||
let mut status = RecordingStatus::Starting;
|
let mut status = RecordingStatus::Starting;
|
||||||
|
let mut recorded = false;
|
||||||
println!("t={} status={}", elapsed(), status.label());
|
println!("t={} status={}", elapsed(), status.label());
|
||||||
|
|
||||||
let stop_at = started + Duration::from_secs(options.seconds);
|
let stop_at = started + Duration::from_secs(options.seconds);
|
||||||
@@ -349,6 +350,7 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
|||||||
health.written_bytes
|
health.written_bytes
|
||||||
);
|
);
|
||||||
report_status(&mut status, tracker.sample(&health, now), &elapsed());
|
report_status(&mut status, tracker.sample(&health, now), &elapsed());
|
||||||
|
recorded |= status == RecordingStatus::Recording;
|
||||||
}
|
}
|
||||||
if !stopping && (now >= stop_at || matches!(status, RecordingStatus::Failed { .. })) {
|
if !stopping && (now >= stop_at || matches!(status, RecordingStatus::Failed { .. })) {
|
||||||
stopping = true;
|
stopping = true;
|
||||||
@@ -382,7 +384,10 @@ fn record(options: &Options) -> Result<bool, Box<dyn std::error::Error>> {
|
|||||||
if let Some(token) = recording.restore_token() {
|
if let Some(token) = recording.restore_token() {
|
||||||
println!("restore_token={token}");
|
println!("restore_token={token}");
|
||||||
}
|
}
|
||||||
Ok(outcome == Some(StopReason::Success) && !matches!(status, RecordingStatus::Failed { .. }))
|
println!("recorded={recorded}");
|
||||||
|
Ok(recorded
|
||||||
|
&& outcome == Some(StopReason::Success)
|
||||||
|
&& !matches!(status, RecordingStatus::Failed { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_status(shown: &mut RecordingStatus, current: &RecordingStatus, elapsed: &str) {
|
fn report_status(shown: &mut RecordingStatus, current: &RecordingStatus, elapsed: &str) {
|
||||||
|
|||||||
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Runs the recording validation scenarios in the headless rig and reports
|
||||||
|
# PASS/FAIL for each. Needs the Linux OBS runtime (scripts/obs/build-linux.sh),
|
||||||
|
# a VA-API or NVENC capable GPU, and the rig packages listed in
|
||||||
|
# docs/validation.md.
|
||||||
|
#
|
||||||
|
# scripts/validate/linux-rig.sh [output-dir]
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
out="${1:-${TMPDIR:-/tmp}/clipbin-validation}"
|
||||||
|
rig="${repo}/scripts/rig/headless.sh"
|
||||||
|
probe="${repo}/target/debug/clipbin-probe"
|
||||||
|
rm -rf "${out}" && mkdir -p "${out}"
|
||||||
|
|
||||||
|
cargo build --locked -p clipbin-probe --manifest-path "${repo}/Cargo.toml" || exit 1
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
result() {
|
||||||
|
local name="$1" ok="$2" detail="${3:-}"
|
||||||
|
if [[ "${ok}" == 0 ]]; then
|
||||||
|
echo "PASS ${name} ${detail}"
|
||||||
|
else
|
||||||
|
echo "FAIL ${name} ${detail} (log: ${out}/${name}.log)"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
# has <log> <pattern>: the probe printed a line matching pattern.
|
||||||
|
has() { grep -qE "$2" "$1"; }
|
||||||
|
|
||||||
|
# 1. Hardware encoders: H.264 must be usable; HEVC and AV1 are reported.
|
||||||
|
log="${out}/encoders.log"
|
||||||
|
"${rig}" "${probe}" encoders --verify --out "${out}/encoders" > "${log}" 2> "${out}/encoders.obs.log"
|
||||||
|
result encoders "$?" "$(grep -E '^usable\.' "${log}" | tr '\n' ' ')"
|
||||||
|
|
||||||
|
# 2. Wayland monitor capture through the portal: segments, remux, audio.
|
||||||
|
for codec in h264 hevc; do
|
||||||
|
if has "${log}" "^usable\.${codec}=none"; then
|
||||||
|
echo "SKIP wayland-${codec} (no ${codec} hardware encoder)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
name="wayland-${codec}"
|
||||||
|
dir="${out}/${name}"
|
||||||
|
"${rig}" "${probe}" record --codec "${codec}" --seconds 9 --out "${dir}" > "${out}/${name}.log" 2>&1 \
|
||||||
|
&& "${probe}" check --codec "${codec}" --out "${dir}" >> "${out}/${name}.log" 2>&1
|
||||||
|
ok=$?
|
||||||
|
volume="$(ffmpeg -v info -i "${dir}/session/segment-000001.mkv" -map 0:a -af volumedetect -f null - 2>&1 \
|
||||||
|
| awk '/max_volume/ { print $5 }')"
|
||||||
|
if [[ "${ok}" == 0 ]] && awk -v v="${volume:--999}" 'BEGIN { exit !(v > -40) }'; then ok=0; else ok=1; fi
|
||||||
|
result "${name}" "${ok}" "audio_max_volume=${volume:-none}dB"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. X11 window capture through Xcomposite (Xwayland in the rig).
|
||||||
|
name=x11-window
|
||||||
|
"${rig}" --x11 bash -c "'${probe}' record --target window --window \"\${CLIPBIN_RIG_GAME_WINDOW}\" --seconds 9 --out '${out}/${name}'" \
|
||||||
|
> "${out}/${name}.log" 2>&1 \
|
||||||
|
&& "${probe}" check --out "${out}/${name}" >> "${out}/${name}.log" 2>&1
|
||||||
|
result "${name}" "$?"
|
||||||
|
|
||||||
|
# expect_failure <name> <reason pattern>: recording started, then failed honestly.
|
||||||
|
expect_failure() {
|
||||||
|
local name="$1" reason="$2" log="${out}/$1.log"
|
||||||
|
if [[ "${probe_exit}" != 0 ]] && has "${log}" "status=Recording" && has "${log}" "status=failed reason=${reason}"; then
|
||||||
|
result "${name}" 0 "$(grep -m1 -o 'status=failed reason=.*' "${log}")"
|
||||||
|
else
|
||||||
|
result "${name}" 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4. The PipeWire stream ends mid-recording (portal backend exits).
|
||||||
|
name=wayland-source-lost
|
||||||
|
"${rig}" bash -c "'${probe}' record --seconds 15 --out '${out}/${name}' & p=\$!; sleep 5; kill \$(cat \"\${CLIPBIN_RIG_DIR}/xdpw.pid\"); wait \$p" \
|
||||||
|
> "${out}/${name}.log" 2>&1
|
||||||
|
probe_exit=$?
|
||||||
|
expect_failure "${name}" "the capture source stopped"
|
||||||
|
|
||||||
|
# 5. The captured X11 window closes mid-recording.
|
||||||
|
name=x11-window-closed
|
||||||
|
"${rig}" --x11 bash -c "'${probe}' record --target window --window \"\${CLIPBIN_RIG_GAME_WINDOW}\" --seconds 15 --out '${out}/${name}' & p=\$!; sleep 5; kill \$(cat \"\${CLIPBIN_RIG_DIR}/game.pid\"); wait \$p" \
|
||||||
|
> "${out}/${name}.log" 2>&1
|
||||||
|
probe_exit=$?
|
||||||
|
expect_failure "${name}" "the capture source stopped"
|
||||||
|
|
||||||
|
# 6. Segments can no longer be written (directory made read-only).
|
||||||
|
name=write-failure
|
||||||
|
"${rig}" bash -c "'${probe}' record --seconds 20 --out '${out}/${name}' & p=\$!; sleep 4; chmod 500 '${out}/${name}/session'; wait \$p" \
|
||||||
|
> "${out}/${name}.log" 2>&1
|
||||||
|
probe_exit=$?
|
||||||
|
chmod 700 "${out}/${name}/session" 2>/dev/null
|
||||||
|
expect_failure "${name}" "(no footage was written|Error)"
|
||||||
|
|
||||||
|
# 7. Free space falls below the safety margin.
|
||||||
|
name=low-disk-space
|
||||||
|
"${rig}" bash -c "'${probe}' record --seconds 10 --out '${out}/${name}' --min-free-mb \$(( \$(df --output=avail -B 1000000 '${out}' | tail -1) + 1 ))" \
|
||||||
|
> "${out}/${name}.log" 2>&1
|
||||||
|
probe_exit=$?
|
||||||
|
if [[ "${probe_exit}" != 0 ]] && has "${out}/${name}.log" "status=failed reason=not enough disk space" \
|
||||||
|
&& ! has "${out}/${name}.log" "status=Recording"; then
|
||||||
|
result "${name}" 0 "$(grep -m1 -o 'status=failed reason=.*' "${out}/${name}.log")"
|
||||||
|
else
|
||||||
|
result "${name}" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 8. The portal grants nothing usable: recording never starts or claims to.
|
||||||
|
name=portal-no-source
|
||||||
|
CLIPBIN_RIG_PORTAL_OUTPUT=NO-SUCH-OUTPUT "${rig}" "${probe}" record --seconds 8 --out "${out}/${name}" \
|
||||||
|
> "${out}/${name}.log" 2>&1
|
||||||
|
probe_exit=$?
|
||||||
|
if [[ "${probe_exit}" != 0 ]] && ! has "${out}/${name}.log" "status=Recording"; then
|
||||||
|
result "${name}" 0 "$(grep 'status=' "${out}/${name}.log" | tail -1)"
|
||||||
|
else
|
||||||
|
result "${name}" 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [[ "${failures}" == 0 ]]; then
|
||||||
|
echo "All scenarios passed. Logs in ${out}"
|
||||||
|
else
|
||||||
|
echo "${failures} scenario(s) failed. Logs in ${out}"
|
||||||
|
fi
|
||||||
|
exit $(( failures > 0 ))
|
||||||
Reference in New Issue
Block a user