mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 02:28:59 +00:00
Harden managed dev runtime ownership
This commit is contained in:
parent
0c7a6ebf31
commit
736c2efa87
3 changed files with 311 additions and 58 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
# Run Pulse hot-dev in a managed background session (macOS/Linux).
|
# Run Pulse hot-dev in a managed background session (macOS/Linux).
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./scripts/hot-dev-bg.sh start
|
# ./scripts/hot-dev-bg.sh start [--takeover]
|
||||||
# ./scripts/hot-dev-bg.sh stop
|
# ./scripts/hot-dev-bg.sh stop
|
||||||
# ./scripts/hot-dev-bg.sh restart
|
# ./scripts/hot-dev-bg.sh restart
|
||||||
# ./scripts/hot-dev-bg.sh status
|
# ./scripts/hot-dev-bg.sh status
|
||||||
|
|
@ -38,13 +38,46 @@ is_port_listening() {
|
||||||
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
|
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_port() {
|
listener_pids() {
|
||||||
local port="$1"
|
local port="$1"
|
||||||
local timeout_seconds="${2:-60}"
|
lsof -nP -t -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
process_group_id() {
|
||||||
|
local pid="$1"
|
||||||
|
ps -o pgid= -p "${pid}" 2>/dev/null | tr -d '[:space:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
process_command() {
|
||||||
|
local pid="$1"
|
||||||
|
ps -o command= -p "${pid}" 2>/dev/null | sed 's/^[[:space:]]*//'
|
||||||
|
}
|
||||||
|
|
||||||
|
listener_is_managed() {
|
||||||
|
local port="$1"
|
||||||
|
local session_pid="$2"
|
||||||
|
local listener_pid
|
||||||
|
|
||||||
|
[[ -n "${session_pid}" ]] || return 1
|
||||||
|
|
||||||
|
while IFS= read -r listener_pid; do
|
||||||
|
[[ -n "${listener_pid}" ]] || continue
|
||||||
|
if [[ "$(process_group_id "${listener_pid}")" == "${session_pid}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(listener_pids "${port}")
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_managed_listener() {
|
||||||
|
local port="$1"
|
||||||
|
local session_pid="$2"
|
||||||
|
local timeout_seconds="${3:-60}"
|
||||||
local checks=$((timeout_seconds * 2))
|
local checks=$((timeout_seconds * 2))
|
||||||
|
|
||||||
while (( checks > 0 )); do
|
while (( checks > 0 )); do
|
||||||
if is_port_listening "${port}"; then
|
if listener_is_managed "${port}" "${session_pid}"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
|
|
@ -64,11 +97,61 @@ is_running() {
|
||||||
kill -0 "${pid}" 2>/dev/null
|
kill -0 "${pid}" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe_listener() {
|
||||||
|
local port="$1"
|
||||||
|
local session_pid="${2:-}"
|
||||||
|
local found=0
|
||||||
|
local listener_pid pgid owner command
|
||||||
|
|
||||||
|
while IFS= read -r listener_pid; do
|
||||||
|
[[ -n "${listener_pid}" ]] || continue
|
||||||
|
found=1
|
||||||
|
pgid="$(process_group_id "${listener_pid}")"
|
||||||
|
owner="unmanaged"
|
||||||
|
if [[ -n "${session_pid}" && "${pgid}" == "${session_pid}" ]]; then
|
||||||
|
owner="managed"
|
||||||
|
fi
|
||||||
|
command="$(process_command "${listener_pid}")"
|
||||||
|
log "Port ${port}: ${owner} listener pid=${listener_pid} pgid=${pgid:-unknown} cmd=${command:-unknown}"
|
||||||
|
done < <(listener_pids "${port}")
|
||||||
|
|
||||||
|
if [[ "${found}" -eq 1 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
has_any_listener() {
|
||||||
|
local port="$1"
|
||||||
|
local listener_pid
|
||||||
|
|
||||||
|
while IFS= read -r listener_pid; do
|
||||||
|
[[ -n "${listener_pid}" ]] || continue
|
||||||
|
return 0
|
||||||
|
done < <(listener_pids "${port}")
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
has_unmanaged_listeners() {
|
||||||
|
local session_pid="${1:-}"
|
||||||
|
local port
|
||||||
|
|
||||||
|
for port in "${FRONTEND_DEV_PORT}" "${PULSE_DEV_API_PORT}"; do
|
||||||
|
if has_any_listener "${port}" && ! listener_is_managed "${port}" "${session_pid}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
require_python() {
|
require_python() {
|
||||||
command -v python3 >/dev/null 2>&1 || fail "python3 is required"
|
command -v python3 >/dev/null 2>&1 || fail "python3 is required"
|
||||||
}
|
}
|
||||||
|
|
||||||
start_bg() {
|
start_bg() {
|
||||||
|
local takeover="${1:-false}"
|
||||||
require_python
|
require_python
|
||||||
mkdir -p "$(dirname "${PID_FILE}")"
|
mkdir -p "$(dirname "${PID_FILE}")"
|
||||||
touch "${LOG_FILE}"
|
touch "${LOG_FILE}"
|
||||||
|
|
@ -81,6 +164,18 @@ start_bg() {
|
||||||
# Clear stale PID file before fresh start.
|
# Clear stale PID file before fresh start.
|
||||||
rm -f "${PID_FILE}"
|
rm -f "${PID_FILE}"
|
||||||
|
|
||||||
|
if has_unmanaged_listeners ""; then
|
||||||
|
if [[ "${takeover}" != "true" ]]; then
|
||||||
|
log "Unmanaged listeners are already using the dev ports."
|
||||||
|
describe_listener "${FRONTEND_DEV_PORT}" "" || true
|
||||||
|
describe_listener "${PULSE_DEV_API_PORT}" "" || true
|
||||||
|
fail "Refusing to take over unmanaged listeners. Stop them first or rerun with: ./scripts/hot-dev-bg.sh start --takeover"
|
||||||
|
fi
|
||||||
|
log "Taking over existing unmanaged listeners on the dev ports."
|
||||||
|
describe_listener "${FRONTEND_DEV_PORT}" "" || true
|
||||||
|
describe_listener "${PULSE_DEV_API_PORT}" "" || true
|
||||||
|
fi
|
||||||
|
|
||||||
local pid
|
local pid
|
||||||
pid="$(
|
pid="$(
|
||||||
FRONTEND_DEV_HOST="${FRONTEND_DEV_HOST}" \
|
FRONTEND_DEV_HOST="${FRONTEND_DEV_HOST}" \
|
||||||
|
|
@ -120,15 +215,15 @@ PY
|
||||||
printf "%s\n" "${pid}" > "${PID_FILE}"
|
printf "%s\n" "${pid}" > "${PID_FILE}"
|
||||||
|
|
||||||
log "Started hot-dev (pid: ${pid})"
|
log "Started hot-dev (pid: ${pid})"
|
||||||
log "Waiting for backend port ${PULSE_DEV_API_PORT}..."
|
log "Waiting for managed backend listener on port ${PULSE_DEV_API_PORT}..."
|
||||||
if ! wait_for_port "${PULSE_DEV_API_PORT}" 90; then
|
if ! wait_for_managed_listener "${PULSE_DEV_API_PORT}" "${pid}" 90; then
|
||||||
log "Backend did not start. Showing recent logs:"
|
log "Backend did not start. Showing recent logs:"
|
||||||
tail -n 80 "${LOG_FILE}" || true
|
tail -n 80 "${LOG_FILE}" || true
|
||||||
fail "hot-dev startup failed"
|
fail "hot-dev startup failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! wait_for_port "${FRONTEND_DEV_PORT}" 90; then
|
if ! wait_for_managed_listener "${FRONTEND_DEV_PORT}" "${pid}" 90; then
|
||||||
log "Backend is up, but frontend port ${FRONTEND_DEV_PORT} is not listening yet."
|
log "Backend is up, but a managed frontend listener on port ${FRONTEND_DEV_PORT} is not ready yet."
|
||||||
log "Check logs with: ./scripts/hot-dev-bg.sh logs"
|
log "Check logs with: ./scripts/hot-dev-bg.sh logs"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -168,10 +263,10 @@ stop_bg() {
|
||||||
}
|
}
|
||||||
|
|
||||||
status_bg() {
|
status_bg() {
|
||||||
|
local managed_pid=""
|
||||||
if is_running; then
|
if is_running; then
|
||||||
local pid
|
managed_pid="$(cat "${PID_FILE}")"
|
||||||
pid="$(cat "${PID_FILE}")"
|
log "Running (pid: ${managed_pid})"
|
||||||
log "Running (pid: ${pid})"
|
|
||||||
else
|
else
|
||||||
log "Not running"
|
log "Not running"
|
||||||
fi
|
fi
|
||||||
|
|
@ -195,6 +290,15 @@ status_bg() {
|
||||||
[[ -n "${backend_code}" ]] || backend_code="000"
|
[[ -n "${backend_code}" ]] || backend_code="000"
|
||||||
log "Frontend HTTP: ${frontend_code}"
|
log "Frontend HTTP: ${frontend_code}"
|
||||||
log "Backend HTTP: ${backend_code}"
|
log "Backend HTTP: ${backend_code}"
|
||||||
|
|
||||||
|
describe_listener "${FRONTEND_DEV_PORT}" "${managed_pid}" || true
|
||||||
|
describe_listener "${PULSE_DEV_API_PORT}" "${managed_pid}" || true
|
||||||
|
|
||||||
|
if [[ -z "${managed_pid}" ]] && has_unmanaged_listeners ""; then
|
||||||
|
log "Detected unmanaged dev listeners. hot-dev-bg is not managing the current runtime."
|
||||||
|
elif [[ -n "${managed_pid}" ]] && has_unmanaged_listeners "${managed_pid}"; then
|
||||||
|
log "Detected split ownership: managed session is running, but one or more dev ports are owned by another process."
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
logs_bg() {
|
logs_bg() {
|
||||||
|
|
@ -207,7 +311,7 @@ usage() {
|
||||||
Usage: $(basename "$0") <command>
|
Usage: $(basename "$0") <command>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
start
|
start [--takeover]
|
||||||
stop
|
stop
|
||||||
restart
|
restart
|
||||||
status
|
status
|
||||||
|
|
@ -216,10 +320,31 @@ EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
case "${1:-}" in
|
local command="${1:-}"
|
||||||
start) start_bg ;;
|
local flag="${2:-}"
|
||||||
|
local takeover="false"
|
||||||
|
|
||||||
|
if [[ "${flag}" == "--takeover" ]]; then
|
||||||
|
takeover="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${command}" in
|
||||||
|
start)
|
||||||
|
if [[ -n "${flag}" && "${flag}" != "--takeover" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
start_bg "${takeover}"
|
||||||
|
;;
|
||||||
stop) stop_bg ;;
|
stop) stop_bg ;;
|
||||||
restart) stop_bg; start_bg ;;
|
restart)
|
||||||
|
if [[ -n "${flag}" && "${flag}" != "--takeover" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
stop_bg
|
||||||
|
start_bg "${takeover}"
|
||||||
|
;;
|
||||||
status) status_bg ;;
|
status) status_bg ;;
|
||||||
logs) logs_bg ;;
|
logs) logs_bg ;;
|
||||||
*) usage; exit 1 ;;
|
*) usage; exit 1 ;;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
# Environment Variables:
|
# Environment Variables:
|
||||||
# HOT_DEV_USE_PROD_DATA=true Use /etc/pulse for data (sessions, config, etc.)
|
# HOT_DEV_USE_PROD_DATA=true Use /etc/pulse for data (sessions, config, etc.)
|
||||||
# HOT_DEV_USE_PRO=true Build Pro binary (default: true if module available)
|
# HOT_DEV_USE_PRO=true Build Pro binary (default: true if module available)
|
||||||
|
# HOT_DEV_ALLOW_OSS_FALLBACK=true Allow fallback to OSS backend when Pro build fails
|
||||||
# PULSE_MOCK_MODE=true Render mock UI data while keeping real metrics history intact
|
# PULSE_MOCK_MODE=true Render mock UI data while keeping real metrics history intact
|
||||||
# PULSE_DATA_DIR=/path Override data directory
|
# PULSE_DATA_DIR=/path Override data directory
|
||||||
# PULSE_DEV_API_PORT=7655 Backend API port (default: 7655)
|
# PULSE_DEV_API_PORT=7655 Backend API port (default: 7655)
|
||||||
|
|
@ -70,6 +71,41 @@ check_dependencies() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
build_backend_binary() {
|
||||||
|
local build_output
|
||||||
|
PULSE_REPOS_DIR="${PULSE_REPOS_DIR:-${DEFAULT_PULSE_REPOS_DIR}}"
|
||||||
|
PRO_MODULE_DIR="${PULSE_REPOS_DIR}/pulse-enterprise"
|
||||||
|
|
||||||
|
if [[ -d "${PRO_MODULE_DIR}" ]] && [[ ${HOT_DEV_USE_PRO:-true} == "true" ]]; then
|
||||||
|
log_info "Building Pro binary (includes persistent audit logging)..."
|
||||||
|
if build_output=$(cd "${PRO_MODULE_DIR}" && go build -buildvcs=false -o "${ROOT_DIR}/pulse" ./cmd/pulse-enterprise 2>&1); then
|
||||||
|
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
||||||
|
PRO_BUILD_SUCCESS=true
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
||||||
|
if [[ ${HOT_DEV_ALLOW_OSS_FALLBACK:-false} != "true" ]]; then
|
||||||
|
log_error "Pro build failed; refusing to fall back to the OSS backend."
|
||||||
|
log_error "Fix pulse-enterprise or set HOT_DEV_ALLOW_OSS_FALLBACK=true to override this explicitly."
|
||||||
|
PRO_BUILD_SUCCESS=false
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_warn "Pro build failed; HOT_DEV_ALLOW_OSS_FALLBACK=true so hot-dev is falling back to the OSS backend."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if build_output=$(cd "${ROOT_DIR}" && go build -o pulse ./cmd/pulse 2>&1); then
|
||||||
|
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
||||||
|
PRO_BUILD_SUCCESS=false
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
||||||
|
PRO_BUILD_SUCCESS=false
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# --- Configuration & Environment ---
|
# --- Configuration & Environment ---
|
||||||
|
|
||||||
load_env_file() {
|
load_env_file() {
|
||||||
|
|
@ -273,24 +309,10 @@ cd "${ROOT_DIR}"
|
||||||
mkdir -p internal/api/frontend-modern/dist
|
mkdir -p internal/api/frontend-modern/dist
|
||||||
touch internal/api/frontend-modern/dist/index.html
|
touch internal/api/frontend-modern/dist/index.html
|
||||||
|
|
||||||
# Check if Pro module is available and use it for full audit logging support
|
# Check if Pro module is available and use it for full audit logging support.
|
||||||
# Use PULSE_REPOS_DIR env var or default to the parent directory that contains sibling repos.
|
# Use PULSE_REPOS_DIR env var or default to the parent directory that contains sibling repos.
|
||||||
PULSE_REPOS_DIR="${PULSE_REPOS_DIR:-${DEFAULT_PULSE_REPOS_DIR}}"
|
if ! build_backend_binary; then
|
||||||
PRO_MODULE_DIR="${PULSE_REPOS_DIR}/pulse-enterprise"
|
exit 1
|
||||||
if [[ -d "${PRO_MODULE_DIR}" ]] && [[ ${HOT_DEV_USE_PRO:-true} == "true" ]]; then
|
|
||||||
log_info "Building Pro binary (includes persistent audit logging)..."
|
|
||||||
cd "${PRO_MODULE_DIR}"
|
|
||||||
go build -buildvcs=false -o "${ROOT_DIR}/pulse" ./cmd/pulse-enterprise 2>/dev/null || {
|
|
||||||
log_warn "Pro build failed, falling back to standard binary"
|
|
||||||
cd "${ROOT_DIR}"
|
|
||||||
go build -o pulse ./cmd/pulse
|
|
||||||
}
|
|
||||||
cd "${ROOT_DIR}"
|
|
||||||
# Note: PULSE_AUDIT_DIR is set after PULSE_DATA_DIR is determined below
|
|
||||||
PRO_BUILD_SUCCESS=true
|
|
||||||
else
|
|
||||||
PRO_BUILD_SUCCESS=false
|
|
||||||
go build -o pulse ./cmd/pulse
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
FRONTEND_PORT=${PULSE_DEV_API_PORT}
|
FRONTEND_PORT=${PULSE_DEV_API_PORT}
|
||||||
|
|
@ -565,34 +587,11 @@ log_info "Starting backend file watcher..."
|
||||||
log_info "🔄 Change detected: $(basename "$changed_file")"
|
log_info "🔄 Change detected: $(basename "$changed_file")"
|
||||||
log_info "Rebuilding backend..."
|
log_info "Rebuilding backend..."
|
||||||
|
|
||||||
# Use the same build logic as the initial build
|
# Use the same build logic as the initial build.
|
||||||
local build_success=0
|
if build_backend_binary; then
|
||||||
local build_output
|
|
||||||
PULSE_REPOS_DIR="${PULSE_REPOS_DIR:-${DEFAULT_PULSE_REPOS_DIR}}"
|
|
||||||
PRO_MODULE_DIR="${PULSE_REPOS_DIR}/pulse-enterprise"
|
|
||||||
if [[ -d "${PRO_MODULE_DIR}" ]] && [[ ${HOT_DEV_USE_PRO:-true} == "true" ]]; then
|
|
||||||
log_info "Building Pro binary..."
|
|
||||||
if build_output=$(cd "${PRO_MODULE_DIR}" && go build -buildvcs=false -o "${ROOT_DIR}/pulse" ./cmd/pulse-enterprise 2>&1); then
|
|
||||||
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
|
||||||
build_success=1
|
|
||||||
else
|
|
||||||
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $build_success -eq 0 ]]; then
|
|
||||||
if build_output=$(go build -o pulse ./cmd/pulse 2>&1); then
|
|
||||||
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
|
||||||
build_success=1
|
|
||||||
else
|
|
||||||
printf "%s\n" "${build_output}" | grep -v "^#" || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $build_success -eq 1 ]]; then
|
|
||||||
restart_backend
|
restart_backend
|
||||||
else
|
else
|
||||||
log_error "✗ Build failed!"
|
log_error "✗ Build failed; keeping the current backend process running."
|
||||||
fi
|
fi
|
||||||
log_info "Watching for changes..."
|
log_info "Watching for changes..."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
129
scripts/tests/test-hot-dev-bg.sh
Executable file
129
scripts/tests/test-hot-dev-bg.sh
Executable file
|
|
@ -0,0 +1,129 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Smoke test for scripts/hot-dev-bg.sh managed ownership diagnostics.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
HOT_DEV_BG="${ROOT_DIR}/scripts/hot-dev-bg.sh"
|
||||||
|
|
||||||
|
if [[ ! -x "${HOT_DEV_BG}" ]]; then
|
||||||
|
echo "hot-dev-bg.sh not found or not executable at ${HOT_DEV_BG}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
server_pids=()
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local pid
|
||||||
|
for pid in "${server_pids[@]:-}"; do
|
||||||
|
kill "${pid}" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local desc="$1"
|
||||||
|
local haystack="$2"
|
||||||
|
local needle="$3"
|
||||||
|
|
||||||
|
if [[ "${haystack}" == *"${needle}"* ]]; then
|
||||||
|
echo "[PASS] ${desc}"
|
||||||
|
else
|
||||||
|
echo "[FAIL] ${desc}" >&2
|
||||||
|
echo "Expected to find: ${needle}" >&2
|
||||||
|
((failures++))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_free_port() {
|
||||||
|
python3 - <<'PY'
|
||||||
|
import socket
|
||||||
|
|
||||||
|
sock = socket.socket()
|
||||||
|
sock.bind(("127.0.0.1", 0))
|
||||||
|
print(sock.getsockname()[1])
|
||||||
|
sock.close()
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
start_http_server() {
|
||||||
|
local port="$1"
|
||||||
|
python3 -m http.server "${port}" --bind 127.0.0.1 >/dev/null 2>&1 &
|
||||||
|
local pid=$!
|
||||||
|
server_pids+=("${pid}")
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test_status_without_runtime() {
|
||||||
|
local frontend_port backend_port output
|
||||||
|
frontend_port="$(pick_free_port)"
|
||||||
|
backend_port="$(pick_free_port)"
|
||||||
|
if [[ "${backend_port}" == "${frontend_port}" ]]; then
|
||||||
|
backend_port="$(pick_free_port)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
output="$(
|
||||||
|
FRONTEND_DEV_HOST=127.0.0.1 \
|
||||||
|
FRONTEND_DEV_PORT="${frontend_port}" \
|
||||||
|
PULSE_DEV_API_HOST=127.0.0.1 \
|
||||||
|
PULSE_DEV_API_PORT="${backend_port}" \
|
||||||
|
"${HOT_DEV_BG}" status
|
||||||
|
)"
|
||||||
|
|
||||||
|
assert_contains "status reports no managed runtime" "${output}" "[hot-dev-bg] Not running"
|
||||||
|
assert_contains "status reports idle frontend port" "${output}" "[hot-dev-bg] Frontend port ${frontend_port} is not listening"
|
||||||
|
assert_contains "status reports idle backend port" "${output}" "[hot-dev-bg] Backend port ${backend_port} is not listening"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_detects_unmanaged_listeners() {
|
||||||
|
local frontend_port backend_port status_output start_output
|
||||||
|
frontend_port="$(pick_free_port)"
|
||||||
|
backend_port="$(pick_free_port)"
|
||||||
|
if [[ "${backend_port}" == "${frontend_port}" ]]; then
|
||||||
|
backend_port="$(pick_free_port)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_http_server "${frontend_port}"
|
||||||
|
start_http_server "${backend_port}"
|
||||||
|
|
||||||
|
status_output="$(
|
||||||
|
FRONTEND_DEV_HOST=127.0.0.1 \
|
||||||
|
FRONTEND_DEV_PORT="${frontend_port}" \
|
||||||
|
PULSE_DEV_API_HOST=127.0.0.1 \
|
||||||
|
PULSE_DEV_API_PORT="${backend_port}" \
|
||||||
|
"${HOT_DEV_BG}" status
|
||||||
|
)"
|
||||||
|
|
||||||
|
assert_contains "status reports unmanaged frontend listener" "${status_output}" "[hot-dev-bg] Port ${frontend_port}: unmanaged listener"
|
||||||
|
assert_contains "status reports unmanaged backend listener" "${status_output}" "[hot-dev-bg] Port ${backend_port}: unmanaged listener"
|
||||||
|
assert_contains "status explains unmanaged runtime ownership" "${status_output}" "[hot-dev-bg] Detected unmanaged dev listeners. hot-dev-bg is not managing the current runtime."
|
||||||
|
|
||||||
|
start_output="$(
|
||||||
|
set +e
|
||||||
|
FRONTEND_DEV_HOST=127.0.0.1 \
|
||||||
|
FRONTEND_DEV_PORT="${frontend_port}" \
|
||||||
|
PULSE_DEV_API_HOST=127.0.0.1 \
|
||||||
|
PULSE_DEV_API_PORT="${backend_port}" \
|
||||||
|
"${HOT_DEV_BG}" start 2>&1
|
||||||
|
printf '\nexit_code=%s' "$?"
|
||||||
|
)"
|
||||||
|
|
||||||
|
assert_contains "start refuses unmanaged takeover by default" "${start_output}" "Refusing to take over unmanaged listeners."
|
||||||
|
assert_contains "start returns failure for unmanaged takeover" "${start_output}" "exit_code=1"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
test_status_without_runtime
|
||||||
|
test_detects_unmanaged_listeners
|
||||||
|
|
||||||
|
if (( failures > 0 )); then
|
||||||
|
echo "Total failures: ${failures}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "hot-dev-bg ownership diagnostics smoke tests passed."
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue