158 lines
6.8 KiB
Bash
Executable File
158 lines
6.8 KiB
Bash
Executable File
#!/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}" --expect-motion --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 --expect-motion --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|EncodeError)"
|
|
|
|
# 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 refuses the request: an actionable failure, never Recording,
|
|
# and no footage written (output starts only once capture has a picture).
|
|
name=portal-refused
|
|
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" \
|
|
&& has "${out}/${name}.log" "status=failed reason=screen capture was not allowed" \
|
|
&& [[ -z "$(ls -A "${out}/${name}/session" 2>/dev/null)" ]]; then
|
|
result "${name}" 0 "$(grep -m1 -o 'status=failed reason=.*' "${out}/${name}.log")"
|
|
else
|
|
result "${name}" 1
|
|
fi
|
|
|
|
# 9. Capture audio disappears while video/output remain healthy.
|
|
name=audio-source-lost
|
|
"${rig}" bash -c "'${probe}' record --seconds 15 --out '${out}/${name}' & p=\$!; sleep 6; kill \$(cat \"\${CLIPBIN_RIG_DIR}/pulse.pid\"); wait \$p" \
|
|
> "${out}/${name}.log" 2>&1
|
|
probe_exit=$?
|
|
if [[ "${probe_exit}" == 0 ]] && has "${out}/${name}.log" "status=Recording" \
|
|
&& has "${out}/${name}.log" "audio capture lost" \
|
|
&& ! has "${out}/${name}.log" "status=failed"; then
|
|
result "${name}" 0 "video continued; audio loss surfaced as degraded"
|
|
else
|
|
result "${name}" 1
|
|
fi
|
|
|
|
# 10. Silence is healthy: no playback streams, idle >10s before monitor capture.
|
|
for mode in silence muted-mic; do
|
|
name="audio-${mode}"
|
|
mic=""
|
|
setup=""
|
|
if [[ "${mode}" == muted-mic ]]; then
|
|
mic="--mic"
|
|
setup="pactl set-default-source clipbin-rig-sink.monitor; pactl set-source-mute clipbin-rig-sink.monitor 1;"
|
|
fi
|
|
CLIPBIN_RIG_NO_AUDIO=1 "${rig}" bash -c "sleep 12; test -z \"\$(pactl list short sink-inputs)\" || exit 1; pactl list short sinks | grep SUSPENDED || exit 1; ${setup} '${probe}' record ${mic} --seconds 16 --out '${out}/${name}'; status=\$?; pactl list short sinks; exit \$status" \
|
|
> "${out}/${name}.log" 2>&1
|
|
probe_exit=$?
|
|
if [[ "${probe_exit}" == 0 ]] && has "${out}/${name}.log" "status=Recording" \
|
|
&& ! has "${out}/${name}.log" "status=(degraded|failed)"; then
|
|
result "${name}" 0 "no playback for >10s, capture remains healthy"
|
|
else
|
|
result "${name}" 1
|
|
fi
|
|
done
|
|
|
|
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 ))
|