Files
clipbin-desktop/scripts/validate/linux-background.sh
T

154 lines
7.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Serial tray/window/autostart validation in the private SNI-host rig.
set -euo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
out="${1:-${TMPDIR:-/tmp}/clipbin-background-validation}"
mkdir -p "${out}"
cargo build --locked -p clipbin-desktop -p rig-pointer
cargo build --locked -p clipbin-core --example sni-host
export CLIPBIN_BACKGROUND_REPO="${repo}" CLIPBIN_BACKGROUND_OUT="${out}"
"${repo}/scripts/rig/headless.sh" bash <<'INNER'
set -euo pipefail
repo="${CLIPBIN_BACKGROUND_REPO}" out="${CLIPBIN_BACKGROUND_OUT}"
export XDG_CONFIG_HOME="${out}/config" XDG_DATA_HOME="${out}/data"
mkdir -p "${XDG_CONFIG_HOME}" "${XDG_DATA_HOME}"
source "${repo}/scripts/rig/keys.sh"
"${repo}/target/debug/examples/sni-host" > "${out}/host.log" 2>&1 & host=$!
app= game=
trap 'kill "${app:-}" "${game:-}" "${host:-}" 2>/dev/null || true' EXIT
sleep 1
swaymsg 'for_window [app_id="dev.clipbin.Clipbin"] floating enable' >/dev/null
swaymsg 'for_window [app_id="dev.clipbin.Clipbin"] resize set width 1360 px height 860 px' >/dev/null
swaymsg 'for_window [app_id="dev.clipbin.Clipbin"] move position 280 110' >/dev/null
windows() {
swaymsg -t get_tree | python3 -c 'import json,sys
nodes=[json.load(sys.stdin)]; total=0
while nodes:
n=nodes.pop(); nodes.extend(n.get("nodes",[])+n.get("floating_nodes",[])); total+=n.get("app_id")=="dev.clipbin.Clipbin"
print(total)'
}
wait_windows() {
for _ in $(seq 50); do [[ "$(windows)" == "$1" ]] && return 0; sleep .2; done
echo "FAIL expected $1 Clipbin windows" >&2; exit 1
}
wait_exit() {
for _ in $(seq 100); do if ! kill -0 "${app}" 2>/dev/null; then wait "${app}"; return; fi; sleep .2; done
echo 'FAIL Quit did not exit' >&2; exit 1
}
CLIPBIN_ROUTE=settings "${repo}/target/debug/clipbin-desktop" --background > "${out}/desktop.log" 2>&1 & app=$!
sleep 3
kill -0 "${app}"
[[ "$(windows)" == 0 ]]
[[ -f "${XDG_CONFIG_HOME}/autostart/clipbin.desktop" ]]
grep -q -- '--background' "${XDG_CONFIG_HOME}/autostart/clipbin.desktop"
item="$(sed -n 's/^registered=//p' "${out}/host.log" | tail -1)"
[[ -n "${item}" ]]
gdbus call --session --dest org.kde.StatusNotifierWatcher --object-path /StatusNotifierWatcher \
--method org.freedesktop.DBus.Properties.Get org.kde.StatusNotifierWatcher RegisteredStatusNotifierItems > "${out}/registered.txt"
grep -q "${item}" "${out}/registered.txt"
gdbus call --session --dest "${item}" --object-path /MenuBar \
--method com.canonical.dbusmenu.GetLayout -- 0 -1 '[]' > "${out}/menu.txt"
grep -q 'Open Clipbin' "${out}/menu.txt"
menu() { gdbus call --session --dest "${item}" --object-path /MenuBar --method com.canonical.dbusmenu.Event "$1" clicked '<0>' 0 > /dev/null; }
menu 1
wait_windows 1
swaymsg '[title="clipbin-test-game"] fullscreen disable' >/dev/null
swaymsg '[app_id="dev.clipbin.Clipbin"] floating enable, resize set width 1360 px height 860 px, move position center, focus' >/dev/null
sleep 1
grim "${out}/settings.png"
# Coordinates within the centred 1360x860 settings window at 1920x1080.
printf 'move 1330 550\nsleep .2\nclick\nsleep .3\n' | "${repo}/target/debug/rig-pointer" 1920 1080
sleep 1
[[ ! -f "${XDG_CONFIG_HOME}/autostart/clipbin.desktop" ]]
printf 'move 1330 550\nsleep .2\nclick\nsleep .3\n' | "${repo}/target/debug/rig-pointer" 1920 1080
sleep 1
[[ -f "${XDG_CONFIG_HOME}/autostart/clipbin.desktop" ]]
echo 'PASS SNI registration, menu Open, background startup and autostart setting'
bash -c 'exec -a reaper python3 -c "import time; time.sleep(120)" SteamLaunch AppId=480 --' & game=$!
for _ in $(seq 40); do grep -q 'recorder_status=Recording' "${out}/desktop.log" && break; sleep 1; done
grep -q 'recorder_status=Recording' "${out}/desktop.log"
gdbus call --session --dest "${item}" --object-path /StatusNotifierItem --method org.freedesktop.DBus.Properties.Get org.kde.StatusNotifierItem ToolTip > "${out}/recording-tooltip.txt"
grep -q Recording "${out}/recording-tooltip.txt"
swaymsg '[app_id="dev.clipbin.Clipbin"] kill' >/dev/null
wait_windows 0
before="$(find "${XDG_DATA_HOME}/clipbin/sessions" -name '*.mkv' | wc -l)"
sleep 5
after="$(find "${XDG_DATA_HOME}/clipbin/sessions" -name '*.mkv' | wc -l)"
(( after > before ))
"${repo}/target/debug/clipbin-desktop" > "${out}/second-launch.log" 2>&1
wait_windows 1
swaymsg '[app_id="dev.clipbin.Clipbin"] kill' >/dev/null
wait_windows 0
menu 1
wait_windows 1
# Repeated reopen must release each UI's threads and allocation state.
measure() {
python3 - "${app}" "$1" <<'PYMETRICS'
import pathlib,sys
status=dict(line.split(':',1) for line in pathlib.Path(f'/proc/{sys.argv[1]}/status').read_text().splitlines() if ':' in line)
print(f"{sys.argv[2]}\t{status['Threads'].strip()}\t{status['VmRSS'].split()[0]}")
PYMETRICS
}
measure 0 > "${out}/reopen-metrics.tsv"
for cycle in $(seq 5); do
swaymsg '[app_id="dev.clipbin.Clipbin"] kill' >/dev/null
wait_windows 0
sleep 1
menu 1
wait_windows 1
sleep 1
measure "${cycle}" >> "${out}/reopen-metrics.tsv"
done
cat "${out}/reopen-metrics.tsv"
python3 - "${out}/reopen-metrics.tsv" <<'PYBOUNDS'
import pathlib,sys
rows=[tuple(map(int,line.split())) for line in pathlib.Path(sys.argv[1]).read_text().splitlines()]
assert max(r[1] for r in rows[1:]) <= rows[0][1]+4, rows
# Allow the first reopen to warm caches; later windows must stay bounded.
rss=[r[2] for r in rows[1:]]
assert max(rss)-min(rss) <= 32*1024 and rows[-1][2] <= rows[0][2]+64*1024, rows
print('PASS five close/reopen cycles have bounded thread count and RSS')
PYBOUNDS
# Audio delivery loss must be visible in the application and real tray tooltip.
kill "$(cat "${CLIPBIN_RIG_DIR}/pulse.pid")"
for _ in $(seq 15); do grep -q 'recorder_status=Degraded' "${out}/desktop.log" && break; sleep 1; done
grep -q 'recorder_status=Degraded' "${out}/desktop.log"
sleep .3
gdbus call --session --dest "${item}" --object-path /StatusNotifierItem --method org.freedesktop.DBus.Properties.Get org.kde.StatusNotifierItem ToolTip > "${out}/audio-lost-tooltip.txt"
grep -q 'audio capture lost' "${out}/audio-lost-tooltip.txt"
grim "${out}/audio-lost.png"
echo 'PASS audio loss is degraded in the UI and tray while video continues'
# Quit through D-Bus menu while a game is still alive.
menu 4
wait_exit
python3 - "${XDG_DATA_HOME}" <<'PY'
import pathlib,json,sys
files=list(pathlib.Path(sys.argv[1]).glob('clipbin/sessions/*/session.json'))
assert files and all(not json.loads(p.read_text())['session']['active'] for p in files)
assert all(s['complete'] for p in files for s in json.loads(p.read_text())['segments'])
PY
kill "${game}"; wait "${game}" 2>/dev/null || true; game=
echo 'PASS close keeps segments growing, second launch reopens, tray Open reopens, tray Quit finalises'
# Losing the watcher must reveal the UI; closing then quits cleanly.
"${repo}/target/debug/clipbin-desktop" --background > "${out}/host-loss.log" 2>&1 & app=$!
sleep 2
wait_windows 0
kill "${host}"; wait "${host}" 2>/dev/null || true; host=
wait_windows 1
swaymsg '[app_id="dev.clipbin.Clipbin"] kill' >/dev/null
wait_exit
# Reconcile an externally disabled saved setting, removing the existing entry.
python3 - "${XDG_CONFIG_HOME}/clipbin/settings.json" <<'PYSETTINGS'
import json,pathlib,sys
p=pathlib.Path(sys.argv[1]); settings=json.loads(p.read_text()); settings['start_on_login']=False; p.write_text(json.dumps(settings))
PYSETTINGS
# With no watcher at startup --background must open a reachable window.
"${repo}/target/debug/clipbin-desktop" --background > "${out}/no-tray.log" 2>&1 & app=$!
wait_windows 1
grep -q 'Tray unavailable' "${out}/no-tray.log"
[[ ! -f "${XDG_CONFIG_HOME}/autostart/clipbin.desktop" ]]
swaymsg '[app_id="dev.clipbin.Clipbin"] kill' >/dev/null
wait_exit
echo 'PASS watcher loss reveals UI; no-tray background falls back to close=Quit'
INNER