#!/usr/bin/env bash # Verify that crashpad_handler is active when zenserver (release build) starts. # # This test: # 1. Launches zenserver from the release build. # 2. Waits for the HTTP health endpoint to become ready. # 3. Checks that a crashpad_handler child process is running. # 4. Checks that the startup log contains "sentry initialized" (not a failure). # # Usage: # ./scripts/test_linux/crashpad-test.sh [path-to-zenserver] # # If no path is given, defaults to build/linux/x86_64/release/zenserver # relative to the repository root. # # The test exits 0 on success, 1 on failure. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" ZENSERVER_BINARY="${1:-$REPO_ROOT/build/linux/x86_64/release/zenserver}" CRASHPAD_HANDLER="$(dirname "$ZENSERVER_BINARY")/crashpad_handler" PORT=18558 # Use a non-default port so we don't collide with a running zenserver DATA_DIR="$(mktemp -d)" STDOUT_FILE="$DATA_DIR/stdout.log" ZENSERVER_PID="" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' PASSED=0 FAILED=0 pass() { echo -e " ${GREEN}PASS${NC} $1" (( PASSED++ )) || true } fail() { echo -e " ${RED}FAIL${NC} $1" (( FAILED++ )) || true } cleanup() { set +e if [ -n "$ZENSERVER_PID" ] && kill -0 "$ZENSERVER_PID" 2>/dev/null; then kill "$ZENSERVER_PID" 2>/dev/null wait "$ZENSERVER_PID" 2>/dev/null fi rm -rf "$DATA_DIR" } trap cleanup EXIT # ── Preflight ──────────────────────────────────────────────────────────────── echo "" echo "==============================" echo " Crashpad active check" echo "==============================" echo "" if [ ! -f "$ZENSERVER_BINARY" ]; then echo -e "${RED}ERROR: zenserver binary not found: $ZENSERVER_BINARY${NC}" echo " Build with: xmake config -m release && xmake build zenserver" exit 1 fi if [ ! -f "$CRASHPAD_HANDLER" ]; then echo -e "${RED}ERROR: crashpad_handler not found alongside zenserver: $CRASHPAD_HANDLER${NC}" echo " It should be copied there automatically by the build." exit 1 fi echo "zenserver: $ZENSERVER_BINARY" echo "crashpad_handler: $CRASHPAD_HANDLER" echo "port: $PORT" echo "data dir: $DATA_DIR" echo "" # ── Start zenserver ────────────────────────────────────────────────────────── # zenserver runs in the foreground until SIGINT/SIGTERM. Launch in background # so we can poll its health endpoint and inspect child processes. "$ZENSERVER_BINARY" \ --port="$PORT" \ --data-dir="$DATA_DIR" \ > "$STDOUT_FILE" 2>&1 & ZENSERVER_PID=$! echo "Started zenserver (pid $ZENSERVER_PID), waiting for health endpoint..." # ── Wait for health endpoint ───────────────────────────────────────────────── READY=false for i in $(seq 1 40); do if curl -sf "http://localhost:$PORT/health" > /dev/null 2>&1; then READY=true break fi sleep 0.5 done if [ "$READY" = false ]; then echo -e "${RED}ERROR: zenserver did not become ready within 20 seconds${NC}" if [ -f "$STDOUT_FILE" ]; then echo "" echo "--- stdout ---" cat "$STDOUT_FILE" fi exit 1 fi echo "Server is ready." # Give the server a moment to finish startup logging (sentry init log # message is emitted after the health endpoint comes up). sleep 1 echo "" # ── Test 1: crashpad_handler process is running for our data dir ───────────── # # sentry-native starts crashpad_handler with --database pointing at # $DATA_DIR/.sentry-native. The process re-parents itself out of zenserver's # process tree, so we look for it by its command-line arguments rather than # by parent PID. HANDLER_PID="$(pgrep -f "crashpad_handler.*${DATA_DIR}" 2>/dev/null | head -1 || true)" if [ -n "$HANDLER_PID" ]; then pass "crashpad_handler is running (pid $HANDLER_PID) with database in our data dir" else fail "crashpad_handler process not found — sentry_init may have failed or sentry is disabled" fi # ── Test 2: No sentry_init failure in startup log ──────────────────────────── # # The "sentry initialized" success message is logged at INFO level under the # sentry-sdk log category, which is filtered to Warn by default — so it won't # appear in normal stdout. We check for the absence of the failure message # instead (which IS at Warn level and would appear if sentry_init failed). if grep -q "sentry_init returned failure" "$STDOUT_FILE" 2>/dev/null; then ERRMSG="$(grep "sentry_init returned failure" "$STDOUT_FILE" | head -1)" fail "sentry_init reported failure: $ERRMSG" else pass "No sentry_init failure message in startup log" fi # ── Test 3: ldd sanity — crashpad_handler must not need libc++.so.1 ────────── MISSING_LIBCXX="$(ldd "$CRASHPAD_HANDLER" 2>/dev/null | grep "libc++\.so\.1" | grep "not found" || true)" if [ -n "$MISSING_LIBCXX" ]; then fail "crashpad_handler has an unsatisfied libc++.so.1 dependency (static linking patch not applied)" elif ldd "$CRASHPAD_HANDLER" 2>/dev/null | grep -q "libc++\.so\.1"; then fail "crashpad_handler links libc++.so.1 dynamically — it should be statically linked" else pass "crashpad_handler has no dynamic libc++.so.1 dependency" fi # ── Summary ────────────────────────────────────────────────────────────────── echo "" echo "==============================" printf " Passed: " echo -e "${GREEN}${PASSED}${NC}" printf " Failed: " if [ "$FAILED" -gt 0 ]; then echo -e "${RED}${FAILED}${NC}" else echo -e "${GREEN}${FAILED}${NC}" fi echo "==============================" echo "" if [ "$FAILED" -gt 0 ]; then if [ -f "$STDOUT_FILE" ]; then echo "--- stdout ---" cat "$STDOUT_FILE" fi exit 1 fi