a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
151 lines
5.2 KiB
Bash
Executable File
151 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# End-to-end smoke test for desktop stable-host /reload behavior under niri.
|
|
#
|
|
# It launches jcode-desktop in stable-host mode, records the compositor window id
|
|
# and layout, injects `/reload`, then verifies the same OS window is still present
|
|
# with the same niri placement and the app-worker child process changed. This catches
|
|
# regressions where slash reload falls back to the old full-process handoff path
|
|
# that closes/reopens the desktop window.
|
|
#
|
|
# Requirements: niri, jq, wtype, a Wayland session, and a built jcode-desktop.
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
BIN="${JCODE_DESKTOP_BIN:-$ROOT_DIR/target/debug/jcode-desktop}"
|
|
TIMEOUT_SECS="${JCODE_DESKTOP_RELOAD_E2E_TIMEOUT_SECS:-15}"
|
|
LOG_FILE="${JCODE_DESKTOP_RELOAD_E2E_LOG:-$(mktemp -t jcode-desktop-reload-e2e.XXXXXX.log)}"
|
|
|
|
if [[ ! -x "$BIN" ]]; then
|
|
echo "desktop binary not found or not executable: $BIN" >&2
|
|
echo "hint: cargo build -p jcode-desktop --bin jcode-desktop" >&2
|
|
exit 2
|
|
fi
|
|
for tool in niri jq wtype; do
|
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
echo "required tool not found: $tool" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
cleanup() {
|
|
if [[ -n "${APP_PID:-}" ]] && kill -0 "$APP_PID" 2>/dev/null; then
|
|
kill "$APP_PID" 2>/dev/null || true
|
|
wait "$APP_PID" 2>/dev/null || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
child_pids() {
|
|
pgrep -P "$APP_PID" 2>/dev/null | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//'
|
|
}
|
|
|
|
layouts_match_with_tolerance() {
|
|
local before="$1"
|
|
local after="$2"
|
|
jq -e -n --argjson before "$before" --argjson after "$after" '
|
|
def abs: if . < 0 then -. else . end;
|
|
def close($a; $b): (($a - $b) | abs) <= 2;
|
|
($before.id == $after.id)
|
|
and ($before.pid == $after.pid)
|
|
and ($before.workspace_id == $after.workspace_id)
|
|
and ($before.is_floating == $after.is_floating)
|
|
and ($before.layout.pos_in_scrolling_layout == $after.layout.pos_in_scrolling_layout)
|
|
and ($before.layout.tile_pos_in_workspace_view == $after.layout.tile_pos_in_workspace_view)
|
|
and ($before.layout.window_offset_in_tile == $after.layout.window_offset_in_tile)
|
|
and close($before.layout.tile_size[0]; $after.layout.tile_size[0])
|
|
and close($before.layout.tile_size[1]; $after.layout.tile_size[1])
|
|
and close($before.layout.window_size[0]; $after.layout.window_size[0])
|
|
and close($before.layout.window_size[1]; $after.layout.window_size[1])
|
|
' >/dev/null
|
|
}
|
|
|
|
"$BIN" \
|
|
--desktop-process-role stable-host \
|
|
--startup-log \
|
|
>"$LOG_FILE" 2>&1 &
|
|
APP_PID=$!
|
|
|
|
deadline=$((SECONDS + TIMEOUT_SECS))
|
|
WINDOW_JSON=""
|
|
while (( SECONDS < deadline )); do
|
|
if ! kill -0 "$APP_PID" 2>/dev/null; then
|
|
echo "desktop process exited before window appeared" >&2
|
|
cat "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
fi
|
|
WINDOW_JSON="$(niri msg -j windows | jq -c --argjson pid "$APP_PID" 'map(select(.pid == $pid)) | first // empty')"
|
|
if [[ -n "$WINDOW_JSON" ]]; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
|
|
if [[ -z "$WINDOW_JSON" ]]; then
|
|
echo "timed out waiting for desktop window for pid $APP_PID" >&2
|
|
cat "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
WINDOW_ID="$(jq -r '.id' <<<"$WINDOW_JSON")"
|
|
BEFORE_LAYOUT="$(jq -c '{id, pid, workspace_id, is_floating, layout}' <<<"$WINDOW_JSON")"
|
|
BEFORE_CHILDREN=""
|
|
deadline=$((SECONDS + TIMEOUT_SECS))
|
|
while (( SECONDS < deadline )); do
|
|
BEFORE_CHILDREN="$(child_pids)"
|
|
if [[ -n "$BEFORE_CHILDREN" ]]; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
if [[ -z "$BEFORE_CHILDREN" ]]; then
|
|
echo "timed out waiting for app-worker child process" >&2
|
|
cat "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
niri msg action focus-window --id "$WINDOW_ID" >/dev/null
|
|
sleep 0.2
|
|
# Trigger the user-visible slash command path. In stable-host mode this should
|
|
# request a host-side app-worker restart, not a full desktop process handoff.
|
|
wtype '/reload'
|
|
wtype -k Return
|
|
|
|
# Wait for the reload request to be processed. The stable-host path should keep
|
|
# the same compositor window alive while restarting only the app worker.
|
|
deadline=$((SECONDS + TIMEOUT_SECS))
|
|
AFTER_CHILDREN=""
|
|
while (( SECONDS < deadline )); do
|
|
AFTER_CHILDREN="$(child_pids)"
|
|
if [[ -n "$AFTER_CHILDREN" && "$AFTER_CHILDREN" != "$BEFORE_CHILDREN" ]]; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
if [[ -z "$AFTER_CHILDREN" || "$AFTER_CHILDREN" == "$BEFORE_CHILDREN" ]]; then
|
|
echo "app-worker child process did not change after hot reload trigger" >&2
|
|
echo "before children: $BEFORE_CHILDREN" >&2
|
|
echo "after children: $AFTER_CHILDREN" >&2
|
|
cat "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
AFTER_JSON="$(niri msg -j windows | jq -c --argjson id "$WINDOW_ID" 'map(select(.id == $id)) | first // empty')"
|
|
if [[ -z "$AFTER_JSON" ]]; then
|
|
echo "window id $WINDOW_ID disappeared after /reload" >&2
|
|
echo "before: $BEFORE_LAYOUT" >&2
|
|
cat "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
AFTER_LAYOUT="$(jq -c '{id, pid, workspace_id, is_floating, layout}' <<<"$AFTER_JSON")"
|
|
if ! layouts_match_with_tolerance "$BEFORE_LAYOUT" "$AFTER_LAYOUT"; then
|
|
echo "window layout changed after /reload" >&2
|
|
echo "before: $BEFORE_LAYOUT" >&2
|
|
echo "after: $AFTER_LAYOUT" >&2
|
|
cat "$LOG_FILE" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "desktop reload window e2e ok: window_id=$WINDOW_ID host_pid=$APP_PID before_worker='$BEFORE_CHILDREN' after_worker='$AFTER_CHILDREN' log=$LOG_FILE"
|