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.
138 lines
5.9 KiB
Bash
Executable File
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 ))
|