#!/usr/bin/env bash set -euo pipefail : "${EVOBGP_TEST_DATABASE_URL:?PostgreSQL integration tests require EVOBGP_TEST_DATABASE_URL}" # The Gitea job container does not publish the service onto its own loopback. # Wait until the DSN host accepts TCP so a slow postmaster is not a test failure. python3 - <<'PY' import os import socket import time from urllib.parse import urlparse raw = os.environ["EVOBGP_TEST_DATABASE_URL"] parsed = urlparse(raw) host = parsed.hostname or "127.0.0.1" port = parsed.port or 5432 deadline = time.time() + 90 last = "no attempt" while time.time() < deadline: try: with socket.create_connection((host, port), timeout=2): print(f"postgres tcp open at {host}:{port}", flush=True) raise SystemExit(0) except OSError as exc: last = str(exc) time.sleep(2) raise SystemExit(f"postgres not accepting TCP at {host}:{port} after 90s: {last}") PY assert_passes() { local result_file="$1" package="$2" label="$3" shift 3 python3 - "$result_file" "$package" "$label" "$@" <<'PY' import json import sys path, package, label = sys.argv[1:4] required = set(sys.argv[4:]) passed = set() skipped = set() failed = set() package_passed = False unexpected_packages = set() with open(path, encoding="utf-8") as events: for line in events: event = json.loads(line) if event.get("Package") != package: unexpected_packages.add(event.get("Package", "")) continue action = event.get("Action") test = event.get("Test") if action == "pass" and test: passed.add(test) elif action == "skip" and test: skipped.add(test) elif action == "fail": failed.add(test or "") elif action == "pass" and not test: package_passed = True missing = required - passed if failed or skipped or missing or unexpected_packages or not package_passed: raise SystemExit( f"{label}: required PostgreSQL tests did not pass: " f"missing={sorted(missing)}, skipped={sorted(skipped)}, " f"failed={sorted(failed)}, unexpected_packages={sorted(unexpected_packages)}, " f"package_passed={package_passed}" ) print(f"{label}: all {len(required)} required PostgreSQL integration tests passed, 0 skipped") PY } db_result="$(mktemp)" repo_result="$(mktemp)" trap 'rm -f "$db_result" "$repo_result"' EXIT # Run packages serially against the ephemeral database. Migration concurrency is # exercised explicitly inside the isolated-schema DB tests. go test -race -count=1 -p 1 -json -run '^TestPostgresMigration.*Integration$' ./internal/db | tee "$db_result" assert_passes "$db_result" evobgp/internal/db 'DB migrations' \ TestPostgresMigrationsIntegration \ TestPostgresMigrationConcurrentApplyIntegration \ TestPostgresMigrationRollbackIntegration \ TestPostgresMigrationIdempotentIntegration \ TestPostgresMigrationLockCancellationIntegration go test -race -count=1 -p 1 -json -run 'Integration$' ./internal/repository | tee "$repo_result" assert_passes "$repo_result" evobgp/internal/repository 'Repository' \ TestPostgresPingIntegration \ TestPostgresModuleCRUDIntegration \ TestPostgresFirewallClientCreateAndGetIntegration \ TestEnsurePrefixSnapshotFillsEmptyExistingIntegration \ TestEnsurePrefixSnapshotIdempotentIntegration \ TestPostgresPruneUnreferencedPrefixSnapshotsIntegration \ TestPostgresEstimateRevisionPruneIntegration \ TestJobAuditClaimAndRestartIntegration \ TestJobAuditIdempotencyRowIntegration \ TestJobQueueRetryBoundsAndFailureIntegration \ TestJobQueueFreshSeedIntegration \ TestDeploymentLeaseGenerationAndRestartIntegration \ TestTelemetryLeaseSnapshotAndDiscoveryIntegration \ TestPostgresModuleSnapshotAtomicRMWMaxPoolOneIntegration \ TestPostgresModuleSnapshotAdvisoryWaitCancellationIntegration \ TestPostgresModuleSnapshotPartialWriteRollbackIntegration \ TestPostgresModuleSnapshotKilledBackendRollsBackIntegration \ TestPostgresModuleSnapshotMethodsRespectCancellationIntegration