|
|
@@ -0,0 +1,351 @@
|
|
|
+#!/usr/bin/env bash
|
|
|
+# Windows lifecycle tests for the brainstorm server.
|
|
|
+#
|
|
|
+# Verifies that the brainstorm server survives the 60-second lifecycle
|
|
|
+# check on Windows, where OWNER_PID monitoring is disabled because the
|
|
|
+# MSYS2 PID namespace is invisible to Node.js.
|
|
|
+#
|
|
|
+# Requirements:
|
|
|
+# - Node.js in PATH
|
|
|
+# - Run from the repository root, or set SUPERPOWERS_ROOT
|
|
|
+# - On Windows: Git Bash (OSTYPE=msys*)
|
|
|
+#
|
|
|
+# Usage:
|
|
|
+# bash tests/brainstorm-server/windows-lifecycle.test.sh
|
|
|
+set -uo pipefail
|
|
|
+
|
|
|
+# ========== Configuration ==========
|
|
|
+
|
|
|
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
+REPO_ROOT="${SUPERPOWERS_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
|
+START_SCRIPT="$REPO_ROOT/skills/brainstorming/scripts/start-server.sh"
|
|
|
+STOP_SCRIPT="$REPO_ROOT/skills/brainstorming/scripts/stop-server.sh"
|
|
|
+SERVER_JS="$REPO_ROOT/skills/brainstorming/scripts/server.js"
|
|
|
+
|
|
|
+TEST_DIR="${TMPDIR:-/tmp}/brainstorm-win-test-$$"
|
|
|
+
|
|
|
+passed=0
|
|
|
+failed=0
|
|
|
+skipped=0
|
|
|
+
|
|
|
+# ========== Helpers ==========
|
|
|
+
|
|
|
+cleanup() {
|
|
|
+ # Kill any server processes we started
|
|
|
+ for pidvar in SERVER_PID CONTROL_PID STOP_TEST_PID; do
|
|
|
+ pid="${!pidvar:-}"
|
|
|
+ if [[ -n "$pid" ]]; then
|
|
|
+ kill "$pid" 2>/dev/null || true
|
|
|
+ wait "$pid" 2>/dev/null || true
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then
|
|
|
+ rm -rf "$TEST_DIR"
|
|
|
+ fi
|
|
|
+}
|
|
|
+trap cleanup EXIT
|
|
|
+
|
|
|
+pass() {
|
|
|
+ echo " PASS: $1"
|
|
|
+ passed=$((passed + 1))
|
|
|
+}
|
|
|
+
|
|
|
+fail() {
|
|
|
+ echo " FAIL: $1"
|
|
|
+ echo " $2"
|
|
|
+ failed=$((failed + 1))
|
|
|
+}
|
|
|
+
|
|
|
+skip() {
|
|
|
+ echo " SKIP: $1 ($2)"
|
|
|
+ skipped=$((skipped + 1))
|
|
|
+}
|
|
|
+
|
|
|
+wait_for_server_info() {
|
|
|
+ local dir="$1"
|
|
|
+ for _ in $(seq 1 50); do
|
|
|
+ if [[ -f "$dir/.server-info" ]]; then
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+ sleep 0.1
|
|
|
+ done
|
|
|
+ return 1
|
|
|
+}
|
|
|
+
|
|
|
+get_port_from_info() {
|
|
|
+ # Read the port from .server-info. Use grep/sed instead of Node.js
|
|
|
+ # to avoid MSYS2-to-Windows path translation issues.
|
|
|
+ grep -o '"port":[0-9]*' "$1/.server-info" | head -1 | sed 's/"port"://'
|
|
|
+}
|
|
|
+
|
|
|
+http_check() {
|
|
|
+ local port="$1"
|
|
|
+ node -e "
|
|
|
+ const http = require('http');
|
|
|
+ http.get('http://localhost:$port/', (res) => {
|
|
|
+ process.exit(res.statusCode === 200 ? 0 : 1);
|
|
|
+ }).on('error', () => process.exit(1));
|
|
|
+ " 2>/dev/null
|
|
|
+}
|
|
|
+
|
|
|
+# ========== Platform Detection ==========
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "=== Brainstorm Server Windows Lifecycle Tests ==="
|
|
|
+echo "Platform: ${OSTYPE:-unknown}"
|
|
|
+echo "MSYSTEM: ${MSYSTEM:-unset}"
|
|
|
+echo "Node: $(node --version 2>/dev/null || echo 'not found')"
|
|
|
+echo ""
|
|
|
+
|
|
|
+is_windows="false"
|
|
|
+case "${OSTYPE:-}" in
|
|
|
+ msys*|cygwin*|mingw*) is_windows="true" ;;
|
|
|
+esac
|
|
|
+if [[ -n "${MSYSTEM:-}" ]]; then
|
|
|
+ is_windows="true"
|
|
|
+fi
|
|
|
+
|
|
|
+if [[ "$is_windows" != "true" ]]; then
|
|
|
+ echo "NOTE: Not running on Windows/MSYS2 (OSTYPE=${OSTYPE:-unset})."
|
|
|
+ echo "Windows-specific tests will be skipped. Tests 4-6 still run."
|
|
|
+ echo ""
|
|
|
+fi
|
|
|
+
|
|
|
+mkdir -p "$TEST_DIR"
|
|
|
+
|
|
|
+SERVER_PID=""
|
|
|
+CONTROL_PID=""
|
|
|
+STOP_TEST_PID=""
|
|
|
+
|
|
|
+# ========== Test 1: OWNER_PID is empty on Windows ==========
|
|
|
+
|
|
|
+echo "--- Owner PID Resolution ---"
|
|
|
+
|
|
|
+if [[ "$is_windows" == "true" ]]; then
|
|
|
+ # Replicate the PID resolution logic from start-server.sh lines 104-112
|
|
|
+ TEST_OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ' || true)"
|
|
|
+ if [[ -z "$TEST_OWNER_PID" || "$TEST_OWNER_PID" == "1" ]]; then
|
|
|
+ TEST_OWNER_PID="$PPID"
|
|
|
+ fi
|
|
|
+ # The fix: clear on Windows
|
|
|
+ case "${OSTYPE:-}" in
|
|
|
+ msys*|cygwin*|mingw*) TEST_OWNER_PID="" ;;
|
|
|
+ esac
|
|
|
+
|
|
|
+ if [[ -z "$TEST_OWNER_PID" ]]; then
|
|
|
+ pass "OWNER_PID is empty on Windows after fix"
|
|
|
+ else
|
|
|
+ fail "OWNER_PID is empty on Windows after fix" \
|
|
|
+ "Expected empty, got '$TEST_OWNER_PID'"
|
|
|
+ fi
|
|
|
+else
|
|
|
+ skip "OWNER_PID is empty on Windows" "not on Windows"
|
|
|
+fi
|
|
|
+
|
|
|
+# ========== Test 2: start-server.sh passes empty BRAINSTORM_OWNER_PID ==========
|
|
|
+
|
|
|
+if [[ "$is_windows" == "true" ]]; then
|
|
|
+ # Use a fake 'node' that captures the env var and exits
|
|
|
+ FAKE_NODE_DIR="$TEST_DIR/fake-bin"
|
|
|
+ mkdir -p "$FAKE_NODE_DIR"
|
|
|
+ cat > "$FAKE_NODE_DIR/node" <<'FAKENODE'
|
|
|
+#!/usr/bin/env bash
|
|
|
+echo "CAPTURED_OWNER_PID=${BRAINSTORM_OWNER_PID:-__UNSET__}"
|
|
|
+exit 0
|
|
|
+FAKENODE
|
|
|
+ chmod +x "$FAKE_NODE_DIR/node"
|
|
|
+
|
|
|
+ captured=$(PATH="$FAKE_NODE_DIR:$PATH" bash "$START_SCRIPT" --project-dir "$TEST_DIR/session" --foreground 2>/dev/null || true)
|
|
|
+ owner_pid_value=$(echo "$captured" | grep "CAPTURED_OWNER_PID=" | head -1 | sed 's/CAPTURED_OWNER_PID=//')
|
|
|
+
|
|
|
+ if [[ "$owner_pid_value" == "" || "$owner_pid_value" == "__UNSET__" ]]; then
|
|
|
+ pass "start-server.sh passes empty BRAINSTORM_OWNER_PID on Windows"
|
|
|
+ else
|
|
|
+ fail "start-server.sh passes empty BRAINSTORM_OWNER_PID on Windows" \
|
|
|
+ "Expected empty or unset, got '$owner_pid_value'"
|
|
|
+ fi
|
|
|
+
|
|
|
+ rm -rf "$FAKE_NODE_DIR" "$TEST_DIR/session"
|
|
|
+else
|
|
|
+ skip "start-server.sh passes empty BRAINSTORM_OWNER_PID" "not on Windows"
|
|
|
+fi
|
|
|
+
|
|
|
+# ========== Test 3: Auto-foreground detection on Windows ==========
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "--- Foreground Mode Detection ---"
|
|
|
+
|
|
|
+if [[ "$is_windows" == "true" ]]; then
|
|
|
+ FAKE_NODE_DIR="$TEST_DIR/fake-bin"
|
|
|
+ mkdir -p "$FAKE_NODE_DIR"
|
|
|
+ cat > "$FAKE_NODE_DIR/node" <<'FAKENODE'
|
|
|
+#!/usr/bin/env bash
|
|
|
+echo "FOREGROUND_MODE=true"
|
|
|
+exit 0
|
|
|
+FAKENODE
|
|
|
+ chmod +x "$FAKE_NODE_DIR/node"
|
|
|
+
|
|
|
+ # Run WITHOUT --foreground flag — Windows should auto-detect
|
|
|
+ captured=$(PATH="$FAKE_NODE_DIR:$PATH" bash "$START_SCRIPT" --project-dir "$TEST_DIR/session2" 2>/dev/null || true)
|
|
|
+
|
|
|
+ if echo "$captured" | grep -q "FOREGROUND_MODE=true"; then
|
|
|
+ pass "Windows auto-detects foreground mode"
|
|
|
+ else
|
|
|
+ fail "Windows auto-detects foreground mode" \
|
|
|
+ "Expected foreground code path, output: $captured"
|
|
|
+ fi
|
|
|
+
|
|
|
+ rm -rf "$FAKE_NODE_DIR" "$TEST_DIR/session2"
|
|
|
+else
|
|
|
+ skip "Windows auto-detects foreground mode" "not on Windows"
|
|
|
+fi
|
|
|
+
|
|
|
+# ========== Test 4: Server survives past 60-second lifecycle check ==========
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "--- Server Survival (lifecycle check) ---"
|
|
|
+
|
|
|
+mkdir -p "$TEST_DIR/survival"
|
|
|
+
|
|
|
+echo " Starting server (will wait ~75s to verify survival past lifecycle check)..."
|
|
|
+
|
|
|
+BRAINSTORM_DIR="$TEST_DIR/survival" \
|
|
|
+BRAINSTORM_HOST="127.0.0.1" \
|
|
|
+BRAINSTORM_URL_HOST="localhost" \
|
|
|
+BRAINSTORM_OWNER_PID="" \
|
|
|
+BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
|
|
|
+ node "$SERVER_JS" > "$TEST_DIR/survival/.server.log" 2>&1 &
|
|
|
+SERVER_PID=$!
|
|
|
+
|
|
|
+if ! wait_for_server_info "$TEST_DIR/survival"; then
|
|
|
+ fail "Server starts successfully" "Server did not write .server-info within 5 seconds"
|
|
|
+ kill "$SERVER_PID" 2>/dev/null || true
|
|
|
+ SERVER_PID=""
|
|
|
+else
|
|
|
+ pass "Server starts successfully with empty OWNER_PID"
|
|
|
+
|
|
|
+ SERVER_PORT=$(get_port_from_info "$TEST_DIR/survival")
|
|
|
+
|
|
|
+ sleep 75
|
|
|
+
|
|
|
+ if kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
|
+ pass "Server is still alive after 75 seconds"
|
|
|
+ else
|
|
|
+ fail "Server is still alive after 75 seconds" \
|
|
|
+ "Server died. Log tail: $(tail -5 "$TEST_DIR/survival/.server.log" 2>/dev/null)"
|
|
|
+ fi
|
|
|
+
|
|
|
+ if http_check "$SERVER_PORT"; then
|
|
|
+ pass "Server responds to HTTP after lifecycle check window"
|
|
|
+ else
|
|
|
+ fail "Server responds to HTTP after lifecycle check window" \
|
|
|
+ "HTTP request to port $SERVER_PORT failed"
|
|
|
+ fi
|
|
|
+
|
|
|
+ if grep -q "owner process exited" "$TEST_DIR/survival/.server.log" 2>/dev/null; then
|
|
|
+ fail "No 'owner process exited' in logs" \
|
|
|
+ "Found spurious owner-exit shutdown in log"
|
|
|
+ else
|
|
|
+ pass "No 'owner process exited' in logs"
|
|
|
+ fi
|
|
|
+
|
|
|
+ kill "$SERVER_PID" 2>/dev/null || true
|
|
|
+ wait "$SERVER_PID" 2>/dev/null || true
|
|
|
+ SERVER_PID=""
|
|
|
+fi
|
|
|
+
|
|
|
+# ========== Test 5: Bad OWNER_PID causes shutdown (control) ==========
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "--- Control: Bad OWNER_PID causes shutdown ---"
|
|
|
+
|
|
|
+mkdir -p "$TEST_DIR/control"
|
|
|
+
|
|
|
+# Find a PID that does not exist
|
|
|
+BAD_PID=99999
|
|
|
+while kill -0 "$BAD_PID" 2>/dev/null; do
|
|
|
+ BAD_PID=$((BAD_PID + 1))
|
|
|
+done
|
|
|
+
|
|
|
+BRAINSTORM_DIR="$TEST_DIR/control" \
|
|
|
+BRAINSTORM_HOST="127.0.0.1" \
|
|
|
+BRAINSTORM_URL_HOST="localhost" \
|
|
|
+BRAINSTORM_OWNER_PID="$BAD_PID" \
|
|
|
+BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
|
|
|
+ node "$SERVER_JS" > "$TEST_DIR/control/.server.log" 2>&1 &
|
|
|
+CONTROL_PID=$!
|
|
|
+
|
|
|
+if ! wait_for_server_info "$TEST_DIR/control"; then
|
|
|
+ fail "Control server starts" "Server did not write .server-info within 5 seconds"
|
|
|
+ kill "$CONTROL_PID" 2>/dev/null || true
|
|
|
+ CONTROL_PID=""
|
|
|
+else
|
|
|
+ pass "Control server starts with bad OWNER_PID=$BAD_PID"
|
|
|
+
|
|
|
+ echo " Waiting ~75s for lifecycle check to kill server..."
|
|
|
+ sleep 75
|
|
|
+
|
|
|
+ if kill -0 "$CONTROL_PID" 2>/dev/null; then
|
|
|
+ fail "Control server self-terminates with bad OWNER_PID" \
|
|
|
+ "Server is still alive (expected it to die)"
|
|
|
+ kill "$CONTROL_PID" 2>/dev/null || true
|
|
|
+ else
|
|
|
+ pass "Control server self-terminates with bad OWNER_PID"
|
|
|
+ fi
|
|
|
+
|
|
|
+ if grep -q "owner process exited" "$TEST_DIR/control/.server.log" 2>/dev/null; then
|
|
|
+ pass "Control server logs 'owner process exited'"
|
|
|
+ else
|
|
|
+ fail "Control server logs 'owner process exited'" \
|
|
|
+ "Log tail: $(tail -5 "$TEST_DIR/control/.server.log" 2>/dev/null)"
|
|
|
+ fi
|
|
|
+fi
|
|
|
+
|
|
|
+wait "$CONTROL_PID" 2>/dev/null || true
|
|
|
+CONTROL_PID=""
|
|
|
+
|
|
|
+# ========== Test 6: stop-server.sh cleanly stops the server ==========
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "--- Clean Shutdown ---"
|
|
|
+
|
|
|
+mkdir -p "$TEST_DIR/stop-test"
|
|
|
+
|
|
|
+BRAINSTORM_DIR="$TEST_DIR/stop-test" \
|
|
|
+BRAINSTORM_HOST="127.0.0.1" \
|
|
|
+BRAINSTORM_URL_HOST="localhost" \
|
|
|
+BRAINSTORM_OWNER_PID="" \
|
|
|
+BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
|
|
|
+ node "$SERVER_JS" > "$TEST_DIR/stop-test/.server.log" 2>&1 &
|
|
|
+STOP_TEST_PID=$!
|
|
|
+echo "$STOP_TEST_PID" > "$TEST_DIR/stop-test/.server.pid"
|
|
|
+
|
|
|
+if ! wait_for_server_info "$TEST_DIR/stop-test"; then
|
|
|
+ fail "Stop-test server starts" "Server did not start"
|
|
|
+ kill "$STOP_TEST_PID" 2>/dev/null || true
|
|
|
+ STOP_TEST_PID=""
|
|
|
+else
|
|
|
+ bash "$STOP_SCRIPT" "$TEST_DIR/stop-test" >/dev/null 2>&1 || true
|
|
|
+ sleep 1
|
|
|
+
|
|
|
+ if ! kill -0 "$STOP_TEST_PID" 2>/dev/null; then
|
|
|
+ pass "stop-server.sh cleanly stops the server"
|
|
|
+ else
|
|
|
+ fail "stop-server.sh cleanly stops the server" \
|
|
|
+ "Server PID $STOP_TEST_PID is still alive after stop"
|
|
|
+ kill "$STOP_TEST_PID" 2>/dev/null || true
|
|
|
+ fi
|
|
|
+fi
|
|
|
+
|
|
|
+wait "$STOP_TEST_PID" 2>/dev/null || true
|
|
|
+STOP_TEST_PID=""
|
|
|
+
|
|
|
+# ========== Summary ==========
|
|
|
+
|
|
|
+echo ""
|
|
|
+echo "=== Results: $passed passed, $failed failed, $skipped skipped ==="
|
|
|
+
|
|
|
+if [[ $failed -gt 0 ]]; then
|
|
|
+ exit 1
|
|
|
+fi
|
|
|
+exit 0
|