summaryrefslogtreecommitdiff
path: root/9harness/test
diff options
context:
space:
mode:
Diffstat (limited to '9harness/test')
-rwxr-xr-x9harness/test/e2e.sh81
1 files changed, 81 insertions, 0 deletions
diff --git a/9harness/test/e2e.sh b/9harness/test/e2e.sh
index 6e57727..254d999 100755
--- a/9harness/test/e2e.sh
+++ b/9harness/test/e2e.sh
@@ -209,6 +209,87 @@ sleep 0.2
[ -S "$REG/harness" ] && fail "SIGTERM unposts the name" "socket still there" || pass "SIGTERM unposts the name"
# ============================================================================
+echo "# part C: /active, the derived view"
+# ============================================================================
+# A fake agent: a process whose argv[0] basename is a harness name, with a
+# session record Claude Code's own shape. The proc root is the real /proc
+# (the fixture seam is unit-tested); nothing real is ever killed, because
+# the only process this part touches is the sleep it started itself.
+ACT="$TMP/act"
+mkdir -p "$ACT/bin" "$ACT/home/.claude/sessions" "$ACT/home/.claude/projects" "$ACT/rt/9p/zmx"
+cp /bin/sleep "$ACT/bin/claude"
+# A zmx that records how it was called and posts the name, as the real one
+# does; a move must never be tested against the user's live sessions.
+cat > "$ACT/bin/zmx" <<ZEOF
+#!/bin/sh
+echo "\$@" > "$ACT/zmx-argv"
+: > "$ACT/rt/9p/zmx/\$2"
+exit 0
+ZEOF
+chmod +x "$ACT/bin/zmx"
+
+"$ACT/bin/claude" 600 &
+AGENT=$!
+PIDS+=("$AGENT")
+sleep 0.3
+# Field 22 of /proc/<pid>/stat, counted from the last ')' — the executable
+# name can hold spaces and parentheses, so the line is never just split.
+AGENT_START=$(sed 's/.*) //' "/proc/$AGENT/stat" | awk '{print $20}')
+AGENT_CWD=$(readlink "/proc/$AGENT/cwd")
+printf '{"pid":%d,"sessionId":"sess-e2e","cwd":"%s","name":"e2e-agent","status":"idle","procStart":"%s"}\n' \
+ "$AGENT" "$AGENT_CWD" "$AGENT_START" > "$ACT/home/.claude/sessions/$AGENT.json"
+
+act_roots() {
+ echo --root "claude=$ACT/home/.claude" --root "codex=$ACT/home/.codex" \
+ --root "omp=$ACT/home/.omp" --root "hermes=$ACT/home/.hermes" --root "dsh=$ACT/home/.dsh"
+}
+
+# First: the read-only default. No --allow-move, so zmx cannot be written.
+XDG_RUNTIME_DIR="$ACT/rt" "$H9" --no-post --unix "$ACT/ro.sock" $(act_roots) >"$ACT/ro.log" 2>&1 &
+PIDS+=("$!")
+wait_posted "$ACT/ro.sock" || { fail "read-only daemon listens" "$(cat "$ACT/ro.log")"; }
+expect_contains "the agent lists under its harness" "$AGENT" "$(p9 "$ACT/ro.sock" ls /active/claude)"
+expect_eq "its session comes from the harness's own record" "sess-e2e" "$(p9 "$ACT/ro.sock" read "/active/claude/$AGENT/session")"
+expect_eq "the route taken is named" "registry" "$(p9 "$ACT/ro.sock" read "/active/claude/$AGENT/via")"
+expect_eq "the harness's own name is served" "e2e-agent" "$(p9 "$ACT/ro.sock" read "/active/claude/$AGENT/name")"
+expect_eq "the harness's own status is served" "idle" "$(p9 "$ACT/ro.sock" read "/active/claude/$AGENT/status")"
+if echo "nope" | p9 "$ACT/ro.sock" write "/active/claude/$AGENT/zmx" 2>/dev/null; then
+ fail "without --allow-move the tree stays read-only" "the write was accepted"
+else
+ pass "without --allow-move the tree stays read-only"
+fi
+expect_eq "a refused move leaves the agent running" "yes" "$([ -d "/proc/$AGENT" ] && echo yes)"
+
+# Now a daemon that allows moves.
+XDG_RUNTIME_DIR="$ACT/rt" "$H9" --no-post --unix "$ACT/rw.sock" $(act_roots) \
+ --allow-move --zmx "$ACT/bin/zmx" >"$ACT/rw.log" 2>&1 &
+PIDS+=("$!")
+wait_posted "$ACT/rw.sock" || { fail "move-allowing daemon listens" "$(cat "$ACT/rw.log")"; }
+
+# Every refusal must come before anything is destroyed.
+for bad in "has space" "../escape" "semi;colon"; do
+ echo "$bad" | p9 "$ACT/rw.sock" write "/active/claude/$AGENT/zmx" 2>/dev/null
+ if [ $? -eq 0 ]; then fail "an illegal zmx name is refused ($bad)"; else pass "an illegal zmx name is refused ($bad)"; fi
+done
+: > "$ACT/rt/9p/zmx/occupied"
+echo "occupied" | p9 "$ACT/rw.sock" write "/active/claude/$AGENT/zmx" 2>/dev/null \
+ && fail "a name a live session holds is refused" || pass "a name a live session holds is refused"
+expect_eq "no refusal killed the agent" "yes" "$([ -d "/proc/$AGENT" ] && echo yes)"
+
+# The move itself.
+if echo "e2e-moved" | p9 "$ACT/rw.sock" write "/active/claude/$AGENT/zmx" 2>"$ACT/move.err"; then
+ pass "a legal name moves the agent"
+else
+ fail "a legal name moves the agent" "$(cat "$ACT/move.err")"
+fi
+sleep 0.4
+expect_eq "the agent it replaced is gone" "gone" "$([ -d "/proc/$AGENT" ] || echo gone)"
+# The command is fixed by the harness: the client supplied only the name.
+expect_eq "zmx ran the harness with its session resumed" \
+ "run e2e-moved -d claude --resume sess-e2e" "$(cat "$ACT/zmx-argv")"
+expect_missing "no client byte reached the command" "e2e-moved -d claude --resume sess-e2e;" "$(cat "$ACT/zmx-argv")"
+
+# ============================================================================
echo "# part B: the real roots, the real registry (read-only)"
# ============================================================================
export XDG_RUNTIME_DIR="${HARNESS_TMP_XDG:-/run/user/$(id -u)}"