Files
pulse/tests/integration/scripts/run-tests.sh
T
pulse-triage[bot] 57beb6010f test: reap isolated browser writers before credential cleanup
Shell-only signal traps could leave browser descendants writing sensitive state after cleanup, and simultaneous runs shared the same cookie cache. Give the Linux qualification runner an invocation-owned cookie root and a child-subreaper supervisor so catchable interruption waits for writers before removing owned artifacts. Preserve normal reports for inspection and fail closed if writers cannot be reaped. Focused fixtures cover all three signals, late detached writers, concurrent isolation and forced termination; no real-browser cleanup or release readiness is claimed.

Change-source: pulse-maintainer
2026-09-05 16:31:12 +01:00

330 lines
12 KiB
Bash
Executable File

#!/bin/bash
#
# Run Pulse integration tests with different suites
# Usage: ./run-tests.sh [suite]
# suite: all, core, diagnostic, perf, visual, multi-tenant, retired-trial-acquisition, cloud-hosting, cloud-lifecycle, demo-contract, evals, updates-api
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# The outer process owns credentials and waits for all browser descendants.
if [ "${1:-}" != --owned-run ]; then
exec python3 "$SCRIPT_DIR/owned-run.py" "${BASH_SOURCE[0]}" "$@"
fi
shift
SUITE="${1:-all}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "==================================="
echo "Pulse Update Integration Tests"
echo "==================================="
echo ""
cd "$TEST_ROOT"
REPO_ROOT="$(cd "$TEST_ROOT/../.." && pwd)"
# Unique per invocation, including simultaneous runs of the same checkout.
# Never inherit another runner's project, image tags or fixed host ports.
: "${PULSE_E2E_RUN_ID:?owned-run supervisor required}"
export PULSE_E2E_SERVER_IMAGE="pulse:$PULSE_E2E_RUN_ID"
export PULSE_E2E_MOCK_IMAGE="pulse-mock-github:$PULSE_E2E_RUN_ID"
export PULSE_E2E_SERVER_CONTAINER="$PULSE_E2E_RUN_ID-server"
export PULSE_E2E_MOCK_CONTAINER="$PULSE_E2E_RUN_ID-mock"
export PULSE_E2E_SEED_CONTAINER="$PULSE_E2E_RUN_ID-seed"
# Entitlement bootstrap and browser helpers use this older variable name.
# Do not let inherited live-runtime overrides redirect writes outside this run.
export PULSE_E2E_PULSE_CONTAINER="$PULSE_E2E_SERVER_CONTAINER"
unset PULSE_E2E_ENTITLEMENT_WRITE_COMMAND PULSE_E2E_BILLING_STATE_PATH
unset PULSE_E2E_CONTAINER_BILLING_PATH PULSE_E2E_SKIP_DOCKER
unset PLAYWRIGHT_BASE_URL
export PULSE_E2E_PORT=0 PULSE_E2E_AGENT_PORT=0 PULSE_E2E_MOCK_GITHUB_PORT=0
# Reports and browser artifacts must be as isolated as the containers.
# Override inherited paths: sharing these can erase another invocation's receipts.
export PULSE_E2E_REPORT_DIR="$TEST_ROOT/playwright-report/$PULSE_E2E_RUN_ID"
export PULSE_E2E_RESULTS_DIR="$TEST_ROOT/test-results/$PULSE_E2E_RUN_ID"
echo "Isolated integration project: $PULSE_E2E_RUN_ID"
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
COMPOSE_CMD=(docker compose)
else
COMPOSE_CMD=(docker-compose)
fi
compose() {
"${COMPOSE_CMD[@]}" -p "$PULSE_E2E_RUN_ID" -f docker-compose.test.yml "$@"
}
ensure_test_images() {
# A local tag may belong to another checkout. Always build from these
# sources; Docker can reuse unchanged layers, but tag existence is not
# evidence that the image includes the code we are qualifying.
echo "Building test images from: $REPO_ROOT"
docker build -t "$PULSE_E2E_MOCK_IMAGE" "$TEST_ROOT/mock-github-server"
# Select the source-built server stage explicitly: the final Dockerfile
# stage is a prebuilt agent requiring a release_payload context.
# Test image drops the release build tag so the suite can enable mock
# fixtures without a demo entitlement. A build failure must stop the run,
# never fall back to a previously tagged image.
docker build --target e2e_runtime -t "$PULSE_E2E_SERVER_IMAGE" --build-arg GO_BUILD_TAGS="" -f "$REPO_ROOT/Dockerfile" "$REPO_ROOT"
}
# EXIT also handles build/start/test failures; signals preserve failure status.
cleanup() {
compose down -v || true
docker image rm "$PULSE_E2E_SERVER_IMAGE" "$PULSE_E2E_MOCK_IMAGE" >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
teardown_suite() {
# run_suite is called in an || list, where Bash disables errexit.
# Stop the entire run if teardown fails: later suites must not inherit
# containers or volumes from a previous scenario.
if ! compose down -v; then
echo -e "${RED}❌ Failed to clean up test environment; stopping run${NC}"
exit 1
fi
}
# Function to run suite with specific mock config
run_suite() {
local name="$1"
local suite="$2"
local checksum_error="${3:-false}"
local network_error="${4:-false}"
local rate_limit="${5:-false}"
local stale_release="${6:-false}"
local multi_tenant_enabled="${7:-false}"
echo ""
echo -e "${YELLOW}Running: $name${NC}"
echo "-----------------------------------"
# Set environment variables
export MOCK_CHECKSUM_ERROR="$checksum_error"
export MOCK_NETWORK_ERROR="$network_error"
export MOCK_RATE_LIMIT="$rate_limit"
export MOCK_STALE_RELEASE="$stale_release"
export PULSE_MULTI_TENANT_ENABLED="$multi_tenant_enabled"
if [ "$multi_tenant_enabled" = "true" ]; then
export PULSE_E2E_ENTITLEMENT_PROFILE="multi-tenant"
else
unset PULSE_E2E_ENTITLEMENT_PROFILE
fi
export PULSE_E2E_PORT=0 PULSE_E2E_AGENT_PORT=0 PULSE_E2E_MOCK_GITHUB_PORT=0
# Start services
echo "Starting test environment..."
if ! compose up -d; then
echo -e "${RED}❌ Failed to start docker services${NC}"
compose logs
teardown_suite
return 1
fi
# Resolve Docker-allocated ports only after successful startup. A failed
# bind must never fall back to probing a different worktree's listener.
local binding
binding="$(compose port pulse-test 7655)" || { teardown_suite; return 1; }
[[ "$binding" =~ :([0-9]+)$ ]] || { teardown_suite; return 1; }
export PULSE_BASE_URL="http://127.0.0.1:${BASH_REMATCH[1]}"
local pulse_base_url="$PULSE_BASE_URL"
local health_url="$pulse_base_url/api/health"
binding="$(compose port pulse-test 7656)" || { teardown_suite; return 1; }
[[ "$binding" =~ :([0-9]+)$ ]] || { teardown_suite; return 1; }
export PULSE_E2E_AGENT_PORT="${BASH_REMATCH[1]}"
export PULSE_AGENT_BASE_URL="http://127.0.0.1:${BASH_REMATCH[1]}"
binding="$(compose port mock-github 8080)" || { teardown_suite; return 1; }
[[ "$binding" =~ :([0-9]+)$ ]] || { teardown_suite; return 1; }
export MOCK_GITHUB_URL="http://127.0.0.1:${BASH_REMATCH[1]}"
# Wait for services
echo "Waiting for services to be ready..."
local health_ok=0
for i in {1..60}; do
if curl -fsS "$health_url" >/dev/null 2>&1; then
health_ok=1
break
fi
sleep 1
done
# Check if the Pulse test container is actually running and reachable.
local pulse_running
pulse_running="$(docker inspect -f '{{.State.Running}}' "$PULSE_E2E_SERVER_CONTAINER" 2>/dev/null || true)"
if [ "$health_ok" -ne 1 ] || [ "$pulse_running" != "true" ]; then
echo -e "${RED}❌ Services failed to start${NC}"
compose ps
compose logs
teardown_suite
return 1
fi
if ! node ./scripts/apply-entitlement-profile.mjs; then
echo -e "${RED}❌ Failed to apply entitlement bootstrap${NC}"
compose logs
teardown_suite
return 1
fi
# Run tests
echo "Running tests..."
set +e
case "$suite" in
diagnostic)
npx playwright test "tests/00-diagnostic.spec.ts" --reporter=list
;;
core)
npx playwright test "tests/01-core-e2e.spec.ts" --reporter=list
;;
perf)
PULSE_E2E_PERF=1 npx playwright test "tests/02-navigation-perf.spec.ts" --project=chromium --reporter=list
;;
visual)
npx playwright test "tests/06-theme-visual.spec.ts" --project=chromium --reporter=list
;;
multi-tenant)
npx playwright test --config=playwright.multi-tenant-diagnostic.config.ts --project=chromium
;;
retired-trial-acquisition)
npx playwright test "tests/07-retired-trial-acquisition.spec.ts" --project=chromium --reporter=list
;;
cloud-hosting)
npx playwright test "tests/08-cloud-hosting.spec.ts" --project=chromium --reporter=list
;;
cloud-lifecycle)
npx playwright test "tests/09-cloud-billing-lifecycle.spec.ts" --project=chromium --reporter=list
;;
demo-contract)
npx playwright test "tests/53-demo-mode-commercial-boundary.spec.ts" --project=chromium --project=mobile-chrome --reporter=list
;;
evals)
node ./scripts/run-evals.mjs --mode deterministic
;;
updates-api)
(
cd "$REPO_ROOT"
UPDATE_API_BASE_URL="$pulse_base_url" \
go test ./internal/api -run 'TestHandleCheckUpdates|TestHandleApplyUpdate|TestHandleUpdateStatus' -count=1
)
;;
*)
echo "Unknown suite: $suite"
set -e
return 1
;;
esac
TEST_RESULT=$?
set -e
if [ $TEST_RESULT -eq 0 ]; then
echo -e "${GREEN}$name passed${NC}"
else
echo -e "${RED}$name failed${NC}"
fi
# Cleanup
echo "Cleaning up..."
teardown_suite
return $TEST_RESULT
}
# Run specific test suite or all tests
FAILED_TESTS=()
ensure_test_images
case "$SUITE" in
all)
echo "Running all suites..."
run_suite "Diagnostic Smoke" "diagnostic" || FAILED_TESTS+=("Diagnostic Smoke")
run_suite "Core E2E" "core" || FAILED_TESTS+=("Core E2E")
run_suite "Multi-tenant E2E" "multi-tenant" "false" "false" "false" "false" "true" || FAILED_TESTS+=("Multi-tenant E2E")
run_suite "Retired Trial Acquisition E2E" "retired-trial-acquisition" || FAILED_TESTS+=("Retired Trial Acquisition E2E")
run_suite "Cloud Hosting E2E" "cloud-hosting" || FAILED_TESTS+=("Cloud Hosting E2E")
run_suite "Cloud Billing Lifecycle E2E" "cloud-lifecycle" || FAILED_TESTS+=("Cloud Billing Lifecycle E2E")
run_suite "Public Demo Contract" "demo-contract" || FAILED_TESTS+=("Public Demo Contract")
run_suite "Navigation Performance" "perf" || FAILED_TESTS+=("Navigation Performance")
run_suite "Theme Visual Regression" "visual" || FAILED_TESTS+=("Theme Visual Regression")
run_suite "Update API Integration" "updates-api" || FAILED_TESTS+=("Update API Integration")
;;
diagnostic)
run_suite "Diagnostic Smoke" "diagnostic" || FAILED_TESTS+=("Diagnostic Smoke")
;;
core)
run_suite "Core E2E" "core" || FAILED_TESTS+=("Core E2E")
;;
perf)
run_suite "Navigation Performance" "perf" || FAILED_TESTS+=("Navigation Performance")
;;
visual)
run_suite "Theme Visual Regression" "visual" || FAILED_TESTS+=("Theme Visual Regression")
;;
multi-tenant)
run_suite "Multi-tenant E2E" "multi-tenant" "false" "false" "false" "false" "true" || FAILED_TESTS+=("Multi-tenant E2E")
;;
retired-trial-acquisition)
run_suite "Retired Trial Acquisition E2E" "retired-trial-acquisition" || FAILED_TESTS+=("Retired Trial Acquisition E2E")
;;
cloud-hosting)
run_suite "Cloud Hosting E2E" "cloud-hosting" || FAILED_TESTS+=("Cloud Hosting E2E")
;;
cloud-lifecycle)
run_suite "Cloud Billing Lifecycle E2E" "cloud-lifecycle" || FAILED_TESTS+=("Cloud Billing Lifecycle E2E")
;;
demo-contract)
run_suite "Public Demo Contract" "demo-contract" || FAILED_TESTS+=("Public Demo Contract")
;;
evals)
run_suite "Agentic Eval Pack (Deterministic)" "evals" || FAILED_TESTS+=("Agentic Eval Pack (Deterministic)")
;;
updates-api)
run_suite "Update API Integration" "updates-api" || FAILED_TESTS+=("Update API Integration")
;;
*)
echo "Unknown suite: $SUITE"
echo "Available suites: all, diagnostic, core, perf, visual, multi-tenant, retired-trial-acquisition, cloud-hosting, cloud-lifecycle, demo-contract, evals, updates-api"
exit 1
;;
esac
# Summary
echo ""
echo "==================================="
echo "Test Summary"
echo "==================================="
if [ ${#FAILED_TESTS[@]} -eq 0 ]; then
echo -e "${GREEN}✅ All tests passed!${NC}"
exit 0
else
echo -e "${RED}❌ Some tests failed:${NC}"
for test in "${FAILED_TESTS[@]}"; do
echo -e "${RED} - $test${NC}"
done
exit 1
fi