fix: fail session validation promptly without missing tools
This commit is contained in:
@@ -196,6 +196,9 @@ Wayland rig. It verifies Recording status, non-empty segment files and metadata,
|
|||||||
closes the only window while recording continues, kills the game process,
|
closes the only window while recording continues, kills the game process,
|
||||||
checks finalisation, reopens the app to load its library from disk, captures
|
checks finalisation, reopens the app to load its library from disk, captures
|
||||||
screenshots and uses Ctrl+Q during a second recording for clean shutdown.
|
screenshots and uses Ctrl+Q during a second recording for clean shutdown.
|
||||||
|
The script checks required tools before starting and distinguishes pending
|
||||||
|
status from failed checks. The recovery run also passed with `PATH=/usr/bin:/bin`
|
||||||
|
without ripgrep installed (2026-10-08); scripts use grep instead.
|
||||||
The recording and library screenshots were inspected; storage totals came from
|
The recording and library screenshots were inspected; storage totals came from
|
||||||
real Matroska file sizes. The existing Linux rig also passed all nine scenarios
|
real Matroska file sizes. The existing Linux rig also passed all nine scenarios
|
||||||
after integration (2026-10-08). Its unwritable-output assertion accepts both
|
after integration (2026-10-08). Its unwritable-output assertion accepts both
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# End-to-end desktop/Steam-process/session test in the private Wayland rig.
|
# End-to-end desktop/Steam-process/session test in the private Wayland rig.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# Check before building or starting the rig; missing tools must not look like
|
||||||
|
# a status that has simply not appeared yet.
|
||||||
|
for tool in bash cargo grep python3 ffprobe grim wtype swaymsg dirname mkdir sleep cat seq \
|
||||||
|
env dbus-run-session mktemp chmod rm basename head dbus-update-activation-environment \
|
||||||
|
pipewire wireplumber pipewire-pulse pactl sway Xwayland ffplay fusermount3; do
|
||||||
|
if ! command -v "${tool}" > /dev/null; then
|
||||||
|
echo "FAIL required tool '${tool}' is missing from PATH" >&2
|
||||||
|
exit 127
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for tool in /usr/lib/xdg-desktop-portal /usr/lib/xdg-desktop-portal-wlr; do
|
||||||
|
if [[ ! -x "${tool}" ]]; then
|
||||||
|
echo "FAIL required tool '${tool}' is missing or not executable" >&2
|
||||||
|
exit 127
|
||||||
|
fi
|
||||||
|
done
|
||||||
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
out="${1:-${TMPDIR:-/tmp}/clipbin-session-validation}"
|
out="${1:-${TMPDIR:-/tmp}/clipbin-session-validation}"
|
||||||
mkdir -p "${out}"
|
mkdir -p "${out}"
|
||||||
@@ -8,6 +24,28 @@ cargo build --locked -p clipbin-desktop --manifest-path "${repo}/Cargo.toml"
|
|||||||
export CLIPBIN_SESSION_REPO="${repo}" CLIPBIN_SESSION_OUT="${out}"
|
export CLIPBIN_SESSION_REPO="${repo}" CLIPBIN_SESSION_OUT="${out}"
|
||||||
"${repo}/scripts/rig/headless.sh" bash <<'INNER'
|
"${repo}/scripts/rig/headless.sh" bash <<'INNER'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# grep returns 1 for no matches; every other error must fail immediately.
|
||||||
|
wait_log() {
|
||||||
|
local pattern="$1" log="$2" minimum="$3" attempts="$4" count status attempt
|
||||||
|
for ((attempt = 0; attempt < attempts; attempt++)); do
|
||||||
|
if count=$(grep -c "${pattern}" "${log}"); then
|
||||||
|
if ((count >= minimum)); then return 0; fi
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
if [[ ${status} != 1 ]]; then
|
||||||
|
echo "FAIL could not check '${pattern}' in ${log} (grep exited ${status})" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if ! kill -0 "${app}" 2>/dev/null; then
|
||||||
|
echo "FAIL desktop exited while waiting for '${pattern}'; log: ${log}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "FAIL timed out waiting for ${minimum} '${pattern}' entries; log: ${log}" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
out="${CLIPBIN_SESSION_OUT}"
|
out="${CLIPBIN_SESSION_OUT}"
|
||||||
portal_config="${XDG_CONFIG_HOME}"
|
portal_config="${XDG_CONFIG_HOME}"
|
||||||
export XDG_DATA_HOME="${out}/data" XDG_CONFIG_HOME="${out}/config"
|
export XDG_DATA_HOME="${out}/data" XDG_CONFIG_HOME="${out}/config"
|
||||||
@@ -29,29 +67,17 @@ for _ in range(20):
|
|||||||
argv = open(f'/proc/{sys.argv[1]}/cmdline', 'rb').read().split(b'\0')
|
argv = open(f'/proc/{sys.argv[1]}/cmdline', 'rb').read().split(b'\0')
|
||||||
assert argv[0] == b'reaper' and b'SteamLaunch' in argv and b'AppId=480' in argv, argv
|
assert argv[0] == b'reaper' and b'SteamLaunch' in argv and b'AppId=480' in argv, argv
|
||||||
PY
|
PY
|
||||||
for _ in $(seq 30); do
|
wait_log 'recorder_status=Recording' "${out}/desktop.log" 1 30
|
||||||
if rg -q 'recorder_status=Recording' "${out}/desktop.log"; then break; fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
rg -q 'recorder_status=Recording' "${out}/desktop.log"
|
|
||||||
swaymsg '[app_id="dev.clipbin.Clipbin"] focus' > /dev/null
|
swaymsg '[app_id="dev.clipbin.Clipbin"] focus' > /dev/null
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
grim "${out}/recording.png"
|
grim "${out}/recording.png"
|
||||||
if [[ "${CLIPBIN_VALIDATE_RECOVERY:-0}" == 1 ]]; then
|
if [[ "${CLIPBIN_VALIDATE_RECOVERY:-0}" == 1 ]]; then
|
||||||
kill "$(cat "${CLIPBIN_RIG_DIR}/xdpw.pid")"
|
kill "$(cat "${CLIPBIN_RIG_DIR}/xdpw.pid")"
|
||||||
for _ in $(seq 15); do
|
wait_log 'recorder_status=Failed' "${out}/desktop.log" 1 15
|
||||||
if rg -q 'recorder_status=Failed' "${out}/desktop.log"; then break; fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
rg -q 'recorder_status=Failed' "${out}/desktop.log"
|
|
||||||
grim "${out}/capture-failed.png"
|
grim "${out}/capture-failed.png"
|
||||||
XDG_CONFIG_HOME="${portal_config}" /usr/lib/xdg-desktop-portal-wlr > "${out}/recovered-portal.log" 2>&1 &
|
XDG_CONFIG_HOME="${portal_config}" /usr/lib/xdg-desktop-portal-wlr > "${out}/recovered-portal.log" 2>&1 &
|
||||||
portal=$!
|
portal=$!
|
||||||
for _ in $(seq 30); do
|
wait_log 'recorder_status=Recording' "${out}/desktop.log" 2 30
|
||||||
if [[ $(rg -c 'recorder_status=Recording' "${out}/desktop.log") -ge 2 ]]; then break; fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
[[ $(rg -c 'recorder_status=Recording' "${out}/desktop.log") -ge 2 ]]
|
|
||||||
python3 - "${XDG_DATA_HOME}" <<'PY'
|
python3 - "${XDG_DATA_HOME}" <<'PY'
|
||||||
import pathlib,json,sys
|
import pathlib,json,sys
|
||||||
p=max(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'), key=lambda p:int(p.parent.name))
|
p=max(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'), key=lambda p:int(p.parent.name))
|
||||||
@@ -70,22 +96,30 @@ kill "${game}"
|
|||||||
wait "${game}" 2>/dev/null || true
|
wait "${game}" 2>/dev/null || true
|
||||||
finalised=0
|
finalised=0
|
||||||
for _ in $(seq 20); do
|
for _ in $(seq 20); do
|
||||||
if python3 - "${XDG_DATA_HOME}" 2>/dev/null <<'PY'
|
status=0
|
||||||
|
python3 - "${XDG_DATA_HOME}" <<'PY' || status=$?
|
||||||
import pathlib, json, sys
|
import pathlib, json, sys
|
||||||
files = list(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'))
|
files = list(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'))
|
||||||
assert files
|
if not files: sys.exit(2)
|
||||||
path = max(files, key=lambda p: int(p.parent.name))
|
path = max(files, key=lambda p: int(p.parent.name))
|
||||||
m = json.loads(path.read_text())
|
m = json.loads(path.read_text())
|
||||||
assert m['app_id'] == 480 and not m['session']['active']
|
if m['session']['active']: sys.exit(2)
|
||||||
|
assert m['app_id'] == 480
|
||||||
assert m['segments'] and all(s['complete'] for s in m['segments'])
|
assert m['segments'] and all(s['complete'] for s in m['segments'])
|
||||||
assert all((path.parent / s['path']).stat().st_size > 0 for s in m['segments'])
|
assert all((path.parent / s['path']).stat().st_size > 0 for s in m['segments'])
|
||||||
assert any(s['kind'] == 'Footage' for s in m['session']['spans'])
|
assert any(s['kind'] == 'Footage' for s in m['session']['spans'])
|
||||||
print('PASS session finalised with footage')
|
print('PASS session finalised with footage')
|
||||||
PY
|
PY
|
||||||
then finalised=1; break; fi
|
case "${status}" in
|
||||||
sleep 1
|
0) finalised=1; break ;;
|
||||||
|
2) sleep 1 ;;
|
||||||
|
*) echo "FAIL session finalisation check exited ${status}" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
[[ ${finalised} == 1 ]]
|
if [[ ${finalised} != 1 ]]; then
|
||||||
|
echo "FAIL timed out waiting for session finalisation; log: ${out}/desktop.log" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
python3 - "${XDG_DATA_HOME}" <<'PY'
|
python3 - "${XDG_DATA_HOME}" <<'PY'
|
||||||
import pathlib,json,subprocess,sys
|
import pathlib,json,subprocess,sys
|
||||||
path=max(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'), key=lambda p:int(p.parent.name))
|
path=max(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'), key=lambda p:int(p.parent.name))
|
||||||
@@ -129,11 +163,7 @@ sleep 0.5
|
|||||||
grim "${out}/library.png"
|
grim "${out}/library.png"
|
||||||
bash -c 'exec -a reaper python3 -c "import time; time.sleep(120)" SteamLaunch AppId=480 --' &
|
bash -c 'exec -a reaper python3 -c "import time; time.sleep(120)" SteamLaunch AppId=480 --' &
|
||||||
game=$!
|
game=$!
|
||||||
for _ in $(seq 30); do
|
wait_log 'recorder_status=Recording' "${out}/reopened.log" 1 30
|
||||||
if rg -q 'recorder_status=Recording' "${out}/reopened.log"; then break; fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
rg -q 'recorder_status=Recording' "${out}/reopened.log"
|
|
||||||
swaymsg '[app_id="dev.clipbin.Clipbin"] focus' > /dev/null
|
swaymsg '[app_id="dev.clipbin.Clipbin"] focus' > /dev/null
|
||||||
wtype -M ctrl -k q -m ctrl
|
wtype -M ctrl -k q -m ctrl
|
||||||
for _ in $(seq 20); do
|
for _ in $(seq 20); do
|
||||||
|
|||||||
Reference in New Issue
Block a user