windows-lifecycle.test.sh 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/env bash
  2. # Windows lifecycle tests for the brainstorm server.
  3. #
  4. # Verifies that the brainstorm server survives the 60-second lifecycle
  5. # check on Windows, where OWNER_PID monitoring is disabled because the
  6. # MSYS2 PID namespace is invisible to Node.js.
  7. #
  8. # Requirements:
  9. # - Node.js in PATH
  10. # - Run from the repository root, or set SUPERPOWERS_ROOT
  11. # - On Windows: Git Bash (OSTYPE=msys*)
  12. #
  13. # Usage:
  14. # bash tests/brainstorm-server/windows-lifecycle.test.sh
  15. set -uo pipefail
  16. # ========== Configuration ==========
  17. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  18. REPO_ROOT="${SUPERPOWERS_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
  19. START_SCRIPT="$REPO_ROOT/skills/brainstorming/scripts/start-server.sh"
  20. STOP_SCRIPT="$REPO_ROOT/skills/brainstorming/scripts/stop-server.sh"
  21. SERVER_JS="$REPO_ROOT/skills/brainstorming/scripts/server.js"
  22. TEST_DIR="${TMPDIR:-/tmp}/brainstorm-win-test-$$"
  23. passed=0
  24. failed=0
  25. skipped=0
  26. # ========== Helpers ==========
  27. cleanup() {
  28. # Kill any server processes we started
  29. for pidvar in SERVER_PID CONTROL_PID STOP_TEST_PID; do
  30. pid="${!pidvar:-}"
  31. if [[ -n "$pid" ]]; then
  32. kill "$pid" 2>/dev/null || true
  33. wait "$pid" 2>/dev/null || true
  34. fi
  35. done
  36. if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then
  37. rm -rf "$TEST_DIR"
  38. fi
  39. }
  40. trap cleanup EXIT
  41. pass() {
  42. echo " PASS: $1"
  43. passed=$((passed + 1))
  44. }
  45. fail() {
  46. echo " FAIL: $1"
  47. echo " $2"
  48. failed=$((failed + 1))
  49. }
  50. skip() {
  51. echo " SKIP: $1 ($2)"
  52. skipped=$((skipped + 1))
  53. }
  54. wait_for_server_info() {
  55. local dir="$1"
  56. for _ in $(seq 1 50); do
  57. if [[ -f "$dir/.server-info" ]]; then
  58. return 0
  59. fi
  60. sleep 0.1
  61. done
  62. return 1
  63. }
  64. get_port_from_info() {
  65. # Read the port from .server-info. Use grep/sed instead of Node.js
  66. # to avoid MSYS2-to-Windows path translation issues.
  67. grep -o '"port":[0-9]*' "$1/.server-info" | head -1 | sed 's/"port"://'
  68. }
  69. http_check() {
  70. local port="$1"
  71. node -e "
  72. const http = require('http');
  73. http.get('http://localhost:$port/', (res) => {
  74. process.exit(res.statusCode === 200 ? 0 : 1);
  75. }).on('error', () => process.exit(1));
  76. " 2>/dev/null
  77. }
  78. # ========== Platform Detection ==========
  79. echo ""
  80. echo "=== Brainstorm Server Windows Lifecycle Tests ==="
  81. echo "Platform: ${OSTYPE:-unknown}"
  82. echo "MSYSTEM: ${MSYSTEM:-unset}"
  83. echo "Node: $(node --version 2>/dev/null || echo 'not found')"
  84. echo ""
  85. is_windows="false"
  86. case "${OSTYPE:-}" in
  87. msys*|cygwin*|mingw*) is_windows="true" ;;
  88. esac
  89. if [[ -n "${MSYSTEM:-}" ]]; then
  90. is_windows="true"
  91. fi
  92. if [[ "$is_windows" != "true" ]]; then
  93. echo "NOTE: Not running on Windows/MSYS2 (OSTYPE=${OSTYPE:-unset})."
  94. echo "Windows-specific tests will be skipped. Tests 4-6 still run."
  95. echo ""
  96. fi
  97. mkdir -p "$TEST_DIR"
  98. SERVER_PID=""
  99. CONTROL_PID=""
  100. STOP_TEST_PID=""
  101. # ========== Test 1: OWNER_PID is empty on Windows ==========
  102. echo "--- Owner PID Resolution ---"
  103. if [[ "$is_windows" == "true" ]]; then
  104. # Replicate the PID resolution logic from start-server.sh lines 104-112
  105. TEST_OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ' || true)"
  106. if [[ -z "$TEST_OWNER_PID" || "$TEST_OWNER_PID" == "1" ]]; then
  107. TEST_OWNER_PID="$PPID"
  108. fi
  109. # The fix: clear on Windows
  110. case "${OSTYPE:-}" in
  111. msys*|cygwin*|mingw*) TEST_OWNER_PID="" ;;
  112. esac
  113. if [[ -z "$TEST_OWNER_PID" ]]; then
  114. pass "OWNER_PID is empty on Windows after fix"
  115. else
  116. fail "OWNER_PID is empty on Windows after fix" \
  117. "Expected empty, got '$TEST_OWNER_PID'"
  118. fi
  119. else
  120. skip "OWNER_PID is empty on Windows" "not on Windows"
  121. fi
  122. # ========== Test 2: start-server.sh passes empty BRAINSTORM_OWNER_PID ==========
  123. if [[ "$is_windows" == "true" ]]; then
  124. # Use a fake 'node' that captures the env var and exits
  125. FAKE_NODE_DIR="$TEST_DIR/fake-bin"
  126. mkdir -p "$FAKE_NODE_DIR"
  127. cat > "$FAKE_NODE_DIR/node" <<'FAKENODE'
  128. #!/usr/bin/env bash
  129. echo "CAPTURED_OWNER_PID=${BRAINSTORM_OWNER_PID:-__UNSET__}"
  130. exit 0
  131. FAKENODE
  132. chmod +x "$FAKE_NODE_DIR/node"
  133. captured=$(PATH="$FAKE_NODE_DIR:$PATH" bash "$START_SCRIPT" --project-dir "$TEST_DIR/session" --foreground 2>/dev/null || true)
  134. owner_pid_value=$(echo "$captured" | grep "CAPTURED_OWNER_PID=" | head -1 | sed 's/CAPTURED_OWNER_PID=//')
  135. if [[ "$owner_pid_value" == "" || "$owner_pid_value" == "__UNSET__" ]]; then
  136. pass "start-server.sh passes empty BRAINSTORM_OWNER_PID on Windows"
  137. else
  138. fail "start-server.sh passes empty BRAINSTORM_OWNER_PID on Windows" \
  139. "Expected empty or unset, got '$owner_pid_value'"
  140. fi
  141. rm -rf "$FAKE_NODE_DIR" "$TEST_DIR/session"
  142. else
  143. skip "start-server.sh passes empty BRAINSTORM_OWNER_PID" "not on Windows"
  144. fi
  145. # ========== Test 3: Auto-foreground detection on Windows ==========
  146. echo ""
  147. echo "--- Foreground Mode Detection ---"
  148. if [[ "$is_windows" == "true" ]]; then
  149. FAKE_NODE_DIR="$TEST_DIR/fake-bin"
  150. mkdir -p "$FAKE_NODE_DIR"
  151. cat > "$FAKE_NODE_DIR/node" <<'FAKENODE'
  152. #!/usr/bin/env bash
  153. echo "FOREGROUND_MODE=true"
  154. exit 0
  155. FAKENODE
  156. chmod +x "$FAKE_NODE_DIR/node"
  157. # Run WITHOUT --foreground flag — Windows should auto-detect
  158. captured=$(PATH="$FAKE_NODE_DIR:$PATH" bash "$START_SCRIPT" --project-dir "$TEST_DIR/session2" 2>/dev/null || true)
  159. if echo "$captured" | grep -q "FOREGROUND_MODE=true"; then
  160. pass "Windows auto-detects foreground mode"
  161. else
  162. fail "Windows auto-detects foreground mode" \
  163. "Expected foreground code path, output: $captured"
  164. fi
  165. rm -rf "$FAKE_NODE_DIR" "$TEST_DIR/session2"
  166. else
  167. skip "Windows auto-detects foreground mode" "not on Windows"
  168. fi
  169. # ========== Test 4: Server survives past 60-second lifecycle check ==========
  170. echo ""
  171. echo "--- Server Survival (lifecycle check) ---"
  172. mkdir -p "$TEST_DIR/survival"
  173. echo " Starting server (will wait ~75s to verify survival past lifecycle check)..."
  174. BRAINSTORM_DIR="$TEST_DIR/survival" \
  175. BRAINSTORM_HOST="127.0.0.1" \
  176. BRAINSTORM_URL_HOST="localhost" \
  177. BRAINSTORM_OWNER_PID="" \
  178. BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
  179. node "$SERVER_JS" > "$TEST_DIR/survival/.server.log" 2>&1 &
  180. SERVER_PID=$!
  181. if ! wait_for_server_info "$TEST_DIR/survival"; then
  182. fail "Server starts successfully" "Server did not write .server-info within 5 seconds"
  183. kill "$SERVER_PID" 2>/dev/null || true
  184. SERVER_PID=""
  185. else
  186. pass "Server starts successfully with empty OWNER_PID"
  187. SERVER_PORT=$(get_port_from_info "$TEST_DIR/survival")
  188. sleep 75
  189. if kill -0 "$SERVER_PID" 2>/dev/null; then
  190. pass "Server is still alive after 75 seconds"
  191. else
  192. fail "Server is still alive after 75 seconds" \
  193. "Server died. Log tail: $(tail -5 "$TEST_DIR/survival/.server.log" 2>/dev/null)"
  194. fi
  195. if http_check "$SERVER_PORT"; then
  196. pass "Server responds to HTTP after lifecycle check window"
  197. else
  198. fail "Server responds to HTTP after lifecycle check window" \
  199. "HTTP request to port $SERVER_PORT failed"
  200. fi
  201. if grep -q "owner process exited" "$TEST_DIR/survival/.server.log" 2>/dev/null; then
  202. fail "No 'owner process exited' in logs" \
  203. "Found spurious owner-exit shutdown in log"
  204. else
  205. pass "No 'owner process exited' in logs"
  206. fi
  207. kill "$SERVER_PID" 2>/dev/null || true
  208. wait "$SERVER_PID" 2>/dev/null || true
  209. SERVER_PID=""
  210. fi
  211. # ========== Test 5: Bad OWNER_PID causes shutdown (control) ==========
  212. echo ""
  213. echo "--- Control: Bad OWNER_PID causes shutdown ---"
  214. mkdir -p "$TEST_DIR/control"
  215. # Find a PID that does not exist
  216. BAD_PID=99999
  217. while kill -0 "$BAD_PID" 2>/dev/null; do
  218. BAD_PID=$((BAD_PID + 1))
  219. done
  220. BRAINSTORM_DIR="$TEST_DIR/control" \
  221. BRAINSTORM_HOST="127.0.0.1" \
  222. BRAINSTORM_URL_HOST="localhost" \
  223. BRAINSTORM_OWNER_PID="$BAD_PID" \
  224. BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
  225. node "$SERVER_JS" > "$TEST_DIR/control/.server.log" 2>&1 &
  226. CONTROL_PID=$!
  227. if ! wait_for_server_info "$TEST_DIR/control"; then
  228. fail "Control server starts" "Server did not write .server-info within 5 seconds"
  229. kill "$CONTROL_PID" 2>/dev/null || true
  230. CONTROL_PID=""
  231. else
  232. pass "Control server starts with bad OWNER_PID=$BAD_PID"
  233. echo " Waiting ~75s for lifecycle check to kill server..."
  234. sleep 75
  235. if kill -0 "$CONTROL_PID" 2>/dev/null; then
  236. fail "Control server self-terminates with bad OWNER_PID" \
  237. "Server is still alive (expected it to die)"
  238. kill "$CONTROL_PID" 2>/dev/null || true
  239. else
  240. pass "Control server self-terminates with bad OWNER_PID"
  241. fi
  242. if grep -q "owner process exited" "$TEST_DIR/control/.server.log" 2>/dev/null; then
  243. pass "Control server logs 'owner process exited'"
  244. else
  245. fail "Control server logs 'owner process exited'" \
  246. "Log tail: $(tail -5 "$TEST_DIR/control/.server.log" 2>/dev/null)"
  247. fi
  248. fi
  249. wait "$CONTROL_PID" 2>/dev/null || true
  250. CONTROL_PID=""
  251. # ========== Test 6: stop-server.sh cleanly stops the server ==========
  252. echo ""
  253. echo "--- Clean Shutdown ---"
  254. mkdir -p "$TEST_DIR/stop-test"
  255. BRAINSTORM_DIR="$TEST_DIR/stop-test" \
  256. BRAINSTORM_HOST="127.0.0.1" \
  257. BRAINSTORM_URL_HOST="localhost" \
  258. BRAINSTORM_OWNER_PID="" \
  259. BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
  260. node "$SERVER_JS" > "$TEST_DIR/stop-test/.server.log" 2>&1 &
  261. STOP_TEST_PID=$!
  262. echo "$STOP_TEST_PID" > "$TEST_DIR/stop-test/.server.pid"
  263. if ! wait_for_server_info "$TEST_DIR/stop-test"; then
  264. fail "Stop-test server starts" "Server did not start"
  265. kill "$STOP_TEST_PID" 2>/dev/null || true
  266. STOP_TEST_PID=""
  267. else
  268. bash "$STOP_SCRIPT" "$TEST_DIR/stop-test" >/dev/null 2>&1 || true
  269. sleep 1
  270. if ! kill -0 "$STOP_TEST_PID" 2>/dev/null; then
  271. pass "stop-server.sh cleanly stops the server"
  272. else
  273. fail "stop-server.sh cleanly stops the server" \
  274. "Server PID $STOP_TEST_PID is still alive after stop"
  275. kill "$STOP_TEST_PID" 2>/dev/null || true
  276. fi
  277. fi
  278. wait "$STOP_TEST_PID" 2>/dev/null || true
  279. STOP_TEST_PID=""
  280. # ========== Summary ==========
  281. echo ""
  282. echo "=== Results: $passed passed, $failed failed, $skipped skipped ==="
  283. if [[ $failed -gt 0 ]]; then
  284. exit 1
  285. fi
  286. exit 0