153 lines
4.6 KiB
Bash
153 lines
4.6 KiB
Bash
#!/usr/bin/env bash
|
|||
|
|
# Shared helpers for the manual desktop smoke test. Sourced by
|
||
|
|
# scripts/smoke/desktop-smoke.sh inside the captioneer-dev Distrobox.
|
||
|
|
set -Eeuo pipefail
|
||
|
|
|
||
|
|
SMOKE_SINK_NAME="captioneer-smoke"
|
||
|
|
SMOKE_SINK_MODULE=""
|
||
|
|
SMOKE_PREVIOUS_SINK=""
|
||
|
|
SMOKE_COMPOSITOR_PID=""
|
||
|
|
SMOKE_APP_PID=""
|
||
|
|
SMOKE_PLAYER_PID=""
|
||
|
|
SMOKE_FAILURES=()
|
||
|
|
|
||
|
|
smoke_log() {
|
||
|
|
printf 'smoke: %s\n' "$*" >&2
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_die() {
|
||
|
|
printf 'smoke: FATAL: %s\n' "$*" >&2
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_fail() {
|
||
|
|
SMOKE_FAILURES+=("$1")
|
||
|
|
smoke_log "FAIL: $1"
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_pass() {
|
||
|
|
smoke_log "ok: $1"
|
||
|
|
}
|
||
|
|
|
||
|
|
# smoke_wait_for_line FILE PATTERN TIMEOUT_SECONDS
|
||
|
|
smoke_wait_for_line() {
|
||
|
|
local file=$1 pattern=$2 timeout=$3 waited=0
|
||
|
|
until grep -q -- "$pattern" "$file" 2>/dev/null; do
|
||
|
|
if (( waited >= timeout )); then
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
sleep 1
|
||
|
|
waited=$((waited + 1))
|
||
|
|
done
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
# smoke_wait_for_exit PID TIMEOUT_SECONDS; returns 1 on timeout
|
||
|
|
smoke_wait_for_exit() {
|
||
|
|
local pid=$1 timeout=$2 waited=0
|
||
|
|
while kill -0 "$pid" 2>/dev/null; do
|
||
|
|
if (( waited >= timeout )); then
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
sleep 1
|
||
|
|
waited=$((waited + 1))
|
||
|
|
done
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_setup_audio() {
|
||
|
|
local workdir=$1
|
||
|
|
command -v pactl >/dev/null 2>&1 || smoke_die "pactl is required"
|
||
|
|
command -v paplay >/dev/null 2>&1 || smoke_die "paplay is required"
|
||
|
|
SMOKE_PREVIOUS_SINK="$(pactl get-default-sink 2>/dev/null || true)"
|
||
|
|
SMOKE_SINK_MODULE="$(pactl load-module module-null-sink \
|
||
|
|
"sink_name=$SMOKE_SINK_NAME" \
|
||
|
|
"sink_properties=device.description=Captioneer-Smoke")"
|
||
|
|
pactl set-default-sink "$SMOKE_SINK_NAME"
|
||
|
|
smoke_log "null sink $SMOKE_SINK_NAME loaded (module $SMOKE_SINK_MODULE, previous default: ${SMOKE_PREVIOUS_SINK:-none})"
|
||
|
|
|
||
|
|
command -v espeak-ng >/dev/null 2>&1 || smoke_die "espeak-ng is required"
|
||
|
|
espeak-ng -v en-us -s 140 -w "$workdir/speech.wav" \
|
||
|
|
"The quick brown fox jumps over the lazy dog. Live captions should appear for this sentence."
|
||
|
|
[[ -s "$workdir/speech.wav" ]] || smoke_die "espeak-ng produced no audio fixture"
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_teardown_audio() {
|
||
|
|
if [[ -n "$SMOKE_PLAYER_PID" ]] && kill -0 "$SMOKE_PLAYER_PID" 2>/dev/null; then
|
||
|
|
kill "$SMOKE_PLAYER_PID" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
if [[ -n "$SMOKE_PREVIOUS_SINK" ]]; then
|
||
|
|
pactl set-default-sink "$SMOKE_PREVIOUS_SINK" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
if [[ -n "$SMOKE_SINK_MODULE" ]]; then
|
||
|
|
pactl unload-module "$SMOKE_SINK_MODULE" 2>/dev/null || true
|
||
|
|
SMOKE_SINK_MODULE=""
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# smoke_play_speech WORKDIR SECONDS: loop the fixture into the null sink.
|
||
|
|
smoke_play_speech() {
|
||
|
|
local workdir=$1 seconds=$2
|
||
|
|
(
|
||
|
|
end=$((SECONDS + seconds))
|
||
|
|
while (( SECONDS < end )); do
|
||
|
|
paplay --device="$SMOKE_SINK_NAME" "$workdir/speech.wav" 2>/dev/null || sleep 1
|
||
|
|
done
|
||
|
|
) &
|
||
|
|
SMOKE_PLAYER_PID=$!
|
||
|
|
}
|
||
|
|
|
||
|
|
# smoke_start_compositor WORKDIR: starts headless sway; prints new socket name.
|
||
|
|
# sway is wlroots-based (Layer Shell capable) and, unlike labwc, can disable
|
||
|
|
# Xwayland, whose sockets are not creatable inside a rootless Distrobox.
|
||
|
|
smoke_start_compositor() {
|
||
|
|
local workdir=$1 candidate socket="" waited=0
|
||
|
|
command -v sway >/dev/null 2>&1 || smoke_die "sway is required"
|
||
|
|
local before="$workdir/sockets-before.txt"
|
||
|
|
find "$XDG_RUNTIME_DIR" -maxdepth 1 -name 'wayland-[0-9]*' ! -name '*.lock' \
|
||
|
|
-printf '%f\n' 2>/dev/null | sort > "$before"
|
||
|
|
|
||
|
|
printf 'xwayland disable\n' > "$workdir/sway-config"
|
||
|
|
WLR_BACKENDS=headless \
|
||
|
|
WLR_LIBINPUT_NO_DEVICES=1 \
|
||
|
|
WLR_RENDERER=pixman \
|
||
|
|
sway -c "$workdir/sway-config" > "$workdir/compositor.log" 2>&1 &
|
||
|
|
SMOKE_COMPOSITOR_PID=$!
|
||
|
|
|
||
|
|
while (( waited < 15 )); do
|
||
|
|
while IFS= read -r candidate; do
|
||
|
|
socket="$candidate"
|
||
|
|
done < <(find "$XDG_RUNTIME_DIR" -maxdepth 1 -name 'wayland-[0-9]*' ! -name '*.lock' \
|
||
|
|
-printf '%f\n' 2>/dev/null | sort | comm -13 "$before" -)
|
||
|
|
if [[ -n "$socket" ]]; then
|
||
|
|
printf '%s\n' "$socket"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
kill -0 "$SMOKE_COMPOSITOR_PID" 2>/dev/null || smoke_die "sway exited early; see $workdir/compositor.log"
|
||
|
|
sleep 1
|
||
|
|
waited=$((waited + 1))
|
||
|
|
done
|
||
|
|
smoke_die "sway did not create a Wayland socket; see $workdir/compositor.log"
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_teardown_compositor() {
|
||
|
|
if [[ -n "$SMOKE_COMPOSITOR_PID" ]] && kill -0 "$SMOKE_COMPOSITOR_PID" 2>/dev/null; then
|
||
|
|
kill "$SMOKE_COMPOSITOR_PID" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_stop_app() {
|
||
|
|
if [[ -n "$SMOKE_APP_PID" ]] && kill -0 "$SMOKE_APP_PID" 2>/dev/null; then
|
||
|
|
kill -INT "$SMOKE_APP_PID" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
smoke_cleanup() {
|
||
|
|
smoke_stop_app
|
||
|
|
if [[ -n "$SMOKE_APP_PID" ]]; then
|
||
|
|
smoke_wait_for_exit "$SMOKE_APP_PID" 10 || kill -KILL "$SMOKE_APP_PID" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
smoke_teardown_audio
|
||
|
|
smoke_teardown_compositor
|
||
|
|
}
|