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:
2026-10-08 14:29:28 +02:00
parent 444758f9ec
commit 9178a303ca
2 changed files with 127 additions and 1 deletions
+121
View File
@@ -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 ))