#!/usr/bin/env bash # Run the dockerized integration tests in isolation: builds the app image, starts it on a # random port, runs the suite against it, then tears everything down and exits with the # suite's exit code. # # With COVERAGE=1 (see script/integration-coverage) the app runs under Cloverage # (docker-compose.coverage.yml); after the suite finishes the app is stopped gracefully so # it writes its report, which is copied to target/coverage/integration. set -euo pipefail cd "$(dirname "$0")/.." root=$PWD source script/lib/docker.sh # Picks a random port in [lo, hi] that's currently free on this host; retries on # collision since another process could grab it first. free_port_in_range() { local lo=$1 hi=$2 port attempt for attempt in $(seq 20); do port=$((lo + RANDOM % (hi - lo + 1))) # A successful connect means something is already listening. if ! (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then echo "$port" return 0 fi done echo "no free port found in range $lo-$hi" >&2 return 1 } port=$(free_port_in_range 30000 40000) echo "[test] starting vibe-app on random port $port${COVERAGE:+ (under coverage instrumentation)}" compose=(docker compose) if [[ -n "${COVERAGE:-}" ]]; then compose+=(-f docker-compose.yml -f docker-compose.coverage.yml) fi use_builder cd integration-test export VIBE_PORT=$port teardown() { local status=$? trap - EXIT "${compose[@]}" down -v || true exit "$status" } trap teardown EXIT trap 'exit 130' INT trap 'exit 143' TERM # Build the app image with buildx directly instead of through compose: compose delegates # builds to bake, which fails on the CI runner with "failed to find target default". The # image name matches `image:` in docker-compose.yml, so `run` below finds it and never # builds. DEPS_ALIASES must match build.args in docker-compose.coverage.yml. docker buildx build --load \ --build-arg "DEPS_ALIASES=run${COVERAGE:+:cov}" \ -t vibe-app-integration \ -f "$root/Dockerfile" "$root" # `run` returns the test container's own exit code; `up --abort-on-container-exit` can leak # the app's SIGTERM exit (143) during teardown instead. status=0 "${compose[@]}" --ansi never run --rm integration-test || status=$? if [[ -n "${COVERAGE:-}" ]]; then # SIGTERM is what makes Cloverage write its report, so stop (not kill) the app and pull # the report out of the stopped container before `down` removes it. out_dir=$root/target/coverage echo "[test] stopping vibe-app to flush coverage report" "${compose[@]}" stop -t 120 vibe-app || true rm -rf "$out_dir/integration" mkdir -p "$out_dir" if ! "${compose[@]}" cp vibe-app:/app/target/coverage/integration "$out_dir" \ || [[ ! -f $out_dir/integration/lcov.info ]]; then echo "[test] WARNING: no integration coverage report was produced" fi fi exit "$status"