mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-21 16:01:04 +00:00
feat: hand off ptys during updates
This commit is contained in:
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
HERDR_BIN="${HERDR_BIN:-$ROOT/target/debug/herdr}"
|
||||
BASE="${BASE:-$(mktemp -d /tmp/herdr-handoff-smoke.XXXXXX)}"
|
||||
CONFIG_HOME="$BASE/config"
|
||||
RUNTIME_DIR="$BASE/runtime"
|
||||
STATE_DIR="$BASE/state"
|
||||
|
||||
if [[ "$CONFIG_HOME" == "$HOME/.config" || "$CONFIG_HOME" == "$HOME/.config/"* ]]; then
|
||||
echo "refusing to run smoke test against $CONFIG_HOME" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sessions=("default" "work" "api")
|
||||
ports=()
|
||||
server_pids=()
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
for session in "${sessions[@]}"; do
|
||||
run_herdr "$session" server stop >/dev/null 2>&1 || true
|
||||
done
|
||||
for pid in "${server_pids[@]}"; do
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
done
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
run_herdr() {
|
||||
local session="$1"
|
||||
shift
|
||||
local socket
|
||||
socket="$(api_socket "$session")"
|
||||
assert_smoke_socket "$socket"
|
||||
mkdir -p "$(dirname "$socket")" "$RUNTIME_DIR" "$STATE_DIR"
|
||||
env -u HERDR_SOCKET_PATH \
|
||||
-u HERDR_CLIENT_SOCKET_PATH \
|
||||
-u HERDR_SESSION \
|
||||
XDG_CONFIG_HOME="$CONFIG_HOME" \
|
||||
XDG_RUNTIME_DIR="$RUNTIME_DIR" \
|
||||
XDG_STATE_HOME="$STATE_DIR" \
|
||||
"$HERDR_BIN" --session "$session" "$@"
|
||||
}
|
||||
|
||||
session_dir() {
|
||||
local session="$1"
|
||||
if [[ "$session" == "default" ]]; then
|
||||
printf '%s/herdr-dev' "$CONFIG_HOME"
|
||||
else
|
||||
printf '%s/herdr-dev/sessions/%s' "$CONFIG_HOME" "$session"
|
||||
fi
|
||||
}
|
||||
|
||||
api_socket() {
|
||||
printf '%s/herdr.sock' "$(session_dir "$1")"
|
||||
}
|
||||
|
||||
client_socket() {
|
||||
printf '%s/herdr-client.sock' "$(session_dir "$1")"
|
||||
}
|
||||
|
||||
assert_smoke_socket() {
|
||||
local socket="$1"
|
||||
case "$socket" in
|
||||
"$CONFIG_HOME"/herdr-dev/herdr.sock | "$CONFIG_HOME"/herdr-dev/sessions/*/herdr.sock)
|
||||
;;
|
||||
*)
|
||||
echo "refusing to use non-smoke socket: $socket" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
wait_for_socket() {
|
||||
local socket="$1"
|
||||
for _ in {1..200}; do
|
||||
[[ -S "$socket" ]] && return 0
|
||||
sleep 0.05
|
||||
done
|
||||
echo "socket did not appear: $socket" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_http() {
|
||||
local port="$1"
|
||||
local expected="$2"
|
||||
for _ in {1..200}; do
|
||||
if curl -fsS "http://127.0.0.1:$port/" | grep -q "$expected"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
echo "http server on port $port did not return $expected" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
json_request() {
|
||||
local socket="$1"
|
||||
local body="$2"
|
||||
python3 - "$socket" "$body" <<'PY'
|
||||
import socket
|
||||
import sys
|
||||
|
||||
path, body = sys.argv[1], sys.argv[2]
|
||||
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
client.connect(path)
|
||||
client.sendall(body.encode() + b"\n")
|
||||
response = b""
|
||||
while not response.endswith(b"\n"):
|
||||
chunk = client.recv(65536)
|
||||
if not chunk:
|
||||
break
|
||||
response += chunk
|
||||
print(response.decode().strip())
|
||||
PY
|
||||
}
|
||||
|
||||
pane_id_for_session() {
|
||||
local session="$1"
|
||||
local socket
|
||||
socket="$(api_socket "$session")"
|
||||
json_request "$socket" '{"id":"smoke:workspace:create","method":"workspace.create","params":{"cwd":"/tmp","focus":true}}' \
|
||||
| python3 -c 'import json,sys; print(json.load(sys.stdin)["result"]["root_pane"]["pane_id"])'
|
||||
}
|
||||
|
||||
send_text() {
|
||||
local session="$1"
|
||||
local pane="$2"
|
||||
local text="$3"
|
||||
local socket
|
||||
socket="$(api_socket "$session")"
|
||||
python3 - "$socket" "$pane" "$text" <<'PY'
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
|
||||
path, pane, text = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
request = {
|
||||
"id": "smoke:pane:send",
|
||||
"method": "pane.send_input",
|
||||
"params": {"pane_id": pane, "text": text, "keys": ["Enter"]},
|
||||
}
|
||||
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
client.connect(path)
|
||||
client.sendall(json.dumps(request).encode() + b"\n")
|
||||
response = b""
|
||||
while not response.endswith(b"\n"):
|
||||
chunk = client.recv(65536)
|
||||
if not chunk:
|
||||
break
|
||||
response += chunk
|
||||
if b'"error"' in response:
|
||||
raise SystemExit(response.decode())
|
||||
PY
|
||||
}
|
||||
|
||||
unused_port() {
|
||||
python3 - <<'PY'
|
||||
import socket
|
||||
sock = socket.socket()
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
print(sock.getsockname()[1])
|
||||
sock.close()
|
||||
PY
|
||||
}
|
||||
|
||||
smoke_http_count() {
|
||||
local count=0
|
||||
local port matches
|
||||
for port in "${ports[@]}"; do
|
||||
matches="$(pgrep -fc "python3 -m http.server $port --bind 127.0.0.1" || true)"
|
||||
if [[ -n "$matches" ]]; then
|
||||
count=$((count + matches))
|
||||
fi
|
||||
done
|
||||
printf '%s\n' "$count"
|
||||
}
|
||||
|
||||
echo "using herdr: $HERDR_BIN"
|
||||
echo "smoke base: $BASE"
|
||||
|
||||
cargo build --locked --manifest-path "$ROOT/Cargo.toml" >/dev/null
|
||||
mkdir -p "$CONFIG_HOME/herdr-dev" "$RUNTIME_DIR" "$STATE_DIR"
|
||||
printf 'onboarding = false\n' > "$CONFIG_HOME/herdr-dev/config.toml"
|
||||
|
||||
for session in "${sessions[@]}"; do
|
||||
echo "starting smoke session $session at $(api_socket "$session")"
|
||||
run_herdr "$session" server >/dev/null 2>&1 &
|
||||
server_pids+=("$!")
|
||||
wait_for_socket "$(api_socket "$session")"
|
||||
done
|
||||
|
||||
for session in "${sessions[@]}"; do
|
||||
port="$(unused_port)"
|
||||
ports+=("$port")
|
||||
web="$BASE/web-$session"
|
||||
mkdir -p "$web"
|
||||
printf 'hello-from-%s\n' "$session" > "$web/index.html"
|
||||
pane="$(pane_id_for_session "$session")"
|
||||
send_text "$session" "$pane" "cd '$web' && python3 -m http.server $port --bind 127.0.0.1"
|
||||
wait_for_http "$port" "hello-from-$session"
|
||||
done
|
||||
|
||||
before_count="$(smoke_http_count)"
|
||||
echo "smoke python http.server process count before handoff: $before_count"
|
||||
|
||||
for session in "${sessions[@]}"; do
|
||||
socket="$(api_socket "$session")"
|
||||
json_request "$socket" '{"id":"smoke:handoff","method":"server.live_handoff","params":{}}' >/dev/null
|
||||
wait_for_socket "$socket"
|
||||
done
|
||||
|
||||
for i in "${!sessions[@]}"; do
|
||||
wait_for_http "${ports[$i]}" "hello-from-${sessions[$i]}"
|
||||
done
|
||||
|
||||
after_count="$(smoke_http_count)"
|
||||
echo "smoke python http.server process count after handoff: $after_count"
|
||||
echo "multi-session live handoff smoke passed"
|
||||
Reference in New Issue
Block a user