| #!/usr/bin/env bash |
| # |
| # with_postgres.sh <LOG_FILE> <command> [args...] |
| # |
| # Start a fresh PostgreSQL container, run the wrapped command, then stop and |
| # remove the container. The following environment variables are exported before |
| # the command runs: |
| # |
| # LNT_TEST_DB_URI Base connection URL (no database), e.g. postgresql://postgres@127.0.0.1:PORT |
| # LNT_TEST_DB_NAME Name of a ready-to-use database ("lnt_test") |
| # |
| # Example: |
| # with_postgres.sh /tmp/my.log bash my_test.sh "${LNT_TEST_DB_URI}" "${LNT_TEST_DB_NAME}" |
| # |
| set -euo pipefail |
| |
| # --------------------------------------------------------------------------- |
| # Prerequisites |
| # --------------------------------------------------------------------------- |
| if ! command -v docker > /dev/null 2>&1; then |
| echo 1>&2 "error: Could not find 'docker' -- Docker is required to run the tests" |
| exit 1 |
| fi |
| |
| if [ $# -lt 2 ]; then |
| echo 1>&2 "usage: $(basename "$0") <LOG_FILE> <command> [args...]" |
| exit 1 |
| fi |
| |
| LOG_FILE="$1" |
| shift |
| |
| if [ -f "${LOG_FILE}" ]; then |
| echo 1>&2 "error: Log file ${LOG_FILE} already exists" |
| exit 1 |
| fi |
| |
| # --------------------------------------------------------------------------- |
| # Unique container name — allows parallel test runs without collision. |
| # --------------------------------------------------------------------------- |
| CONTAINER_NAME="lnt_pg_$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-')" |
| |
| # --------------------------------------------------------------------------- |
| # Start the container in the background and bind a random port from the host |
| # to the container's 5432 port. |
| # |
| # Key flags: |
| # POSTGRES_HOST_AUTH_METHOD=trust no password needed |
| # --tmpfs /var/lib/postgresql store data in RAM — faster, no cleanup needed |
| # --------------------------------------------------------------------------- |
| echo 1>&2 "Starting container ${CONTAINER_NAME} ..." |
| docker run \ |
| --detach \ |
| --name "${CONTAINER_NAME}" \ |
| --publish "127.0.0.1::5432" \ |
| --env POSTGRES_HOST_AUTH_METHOD=trust \ |
| --tmpfs /var/lib/postgresql \ |
| postgres:18-alpine \ |
| > /dev/null 2>&1 |
| |
| # --------------------------------------------------------------------------- |
| # Cleanup — always stop and remove the container, success or failure. |
| # Registered immediately after docker run so that failures during the |
| # readiness wait or createdb don't leak the container. |
| # --------------------------------------------------------------------------- |
| cleanup() { |
| local exit_code=$? |
| echo "Stopping container ${CONTAINER_NAME} ..." |
| docker stop "${CONTAINER_NAME}" > /dev/null 2>&1 || true |
| docker rm "${CONTAINER_NAME}" > /dev/null 2>&1 || true |
| exit "${exit_code}" |
| } |
| trap cleanup EXIT |
| |
| echo 1>&2 "Streaming PostgreSQL server logs into ${LOG_FILE}" |
| docker logs --follow "${CONTAINER_NAME}" >> "${LOG_FILE}" 2>&1 & |
| |
| # --------------------------------------------------------------------------- |
| # Discover the host port that Docker assigned. |
| # --------------------------------------------------------------------------- |
| HOST_PORT=$(docker port "${CONTAINER_NAME}" 5432/tcp | head -1 | sed 's/.*://') |
| if [ -z "${HOST_PORT}" ]; then |
| echo 1>&2 "error: could not determine host port for container ${CONTAINER_NAME}" |
| docker logs "${CONTAINER_NAME}" 1>&2 |
| exit 1 |
| fi |
| |
| echo 1>&2 "PostgreSQL available at: postgresql://postgres@127.0.0.1:${HOST_PORT}" |
| |
| # --------------------------------------------------------------------------- |
| # Wait for PostgreSQL to accept connections (up to 30 seconds). |
| # |
| # NOTE: We use '-h localhost' to check via TCP rather than the Unix socket. |
| # The official postgres image runs a temporary server during initdb that |
| # listens only on the Unix socket (listen_addresses=''). Without '-h localhost', |
| # pg_isready can succeed against that temp server, then createdb fails when |
| # the socket disappears during the transition to the real server. |
| # --------------------------------------------------------------------------- |
| MAX_TRIES=30 |
| for i in $(seq 1 "${MAX_TRIES}"); do |
| if docker exec "${CONTAINER_NAME}" pg_isready --quiet -h localhost; then |
| echo 1>&2 "PostgreSQL ready after ${i} attempt(s)." |
| break |
| fi |
| if [ "${i}" -eq "${MAX_TRIES}" ]; then |
| echo 1>&2 "error: PostgreSQL did not become ready after ${MAX_TRIES}s." |
| docker logs "${CONTAINER_NAME}" 1>&2 |
| exit 1 |
| fi |
| sleep 1 |
| done |
| |
| # --------------------------------------------------------------------------- |
| # Create a default database for tests to use. |
| # --------------------------------------------------------------------------- |
| docker exec "${CONTAINER_NAME}" createdb --username=postgres lnt_test |
| |
| # --------------------------------------------------------------------------- |
| # Export the connection details and run the wrapped command. |
| # --------------------------------------------------------------------------- |
| export LNT_TEST_DB_URI="postgresql://postgres@127.0.0.1:${HOST_PORT}" |
| export LNT_TEST_DB_NAME="lnt_test" |
| |
| "$@" |