Files
clipbin-desktop/scripts/validate/linux-rig.sh
T
harry f1c31d6c90 feat: keep Clipbin recording in the tray
Add native Linux SNI and Windows notification-area trays, single-instance reopening, background startup and login entries. Preserve recording and discovery after close, with clean Quit and reachable no-tray fallbacks.

Track capture-source audio delivery independently of silent output tracks and warn without stopping video. Give clip metadata its own row and shape labels at final bounds to avoid GPUI 0.2.2 truncation cache and text-run bugs.

Validated with strict workspace checks, 57 tests, Windows core/OBS/probe cross-clippy, all ten capture scenarios, tray/session/clips/playback rigs and narrow/wide screenshots. Document runtime validation gaps.
2026-10-13 18:29:37 +02:00

138 lines
5.9 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
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 ))