Skip to content

Commit 4ab954a

Browse files
authored
[tests] For testing, run Postgres from Docker (#177)
Instead of assuming a local installation of Postgres (which is a lot more intrusive), just assume the existence of Docker, and run Postgres inside a Docker container. This allows running a clean Docker container in every test, which will unblock wider testing with Postgres. This also reduces the dependencies for running the test suite from requiring Postgres to only requiring Docker, which should be fine to assume since we already use Docker for a bunch of stuff in LNT. Finally, this also moves the installation.shtest from being a unit test to something that we only run in the CI. This was a slow test which required the network and really didn't belong in a Lit test. This patch was created with Claude's assistance but I did review everything, made manual tweaks, etc.
1 parent b997656 commit 4ab954a

File tree

8 files changed

+170
-98
lines changed

8 files changed

+170
-98
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Check installation
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
- push
8+
- pull_request
9+
10+
jobs:
11+
check-installation:
12+
runs-on: ubuntu-latest
13+
name: "Python ${{ matrix.python-version }}"
14+
15+
strategy:
16+
matrix:
17+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
18+
fail-fast: false
19+
20+
steps:
21+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
22+
- name: Set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
- name: Install PostgreSQL development headers
27+
run: sudo apt-get install -y libpq-dev
28+
- name: Check client installation
29+
run: |
30+
python -m venv .venv-client
31+
.venv-client/bin/python -m pip install -r requirements.client.txt
32+
.venv-client/bin/lnt create lnt-client-instance
33+
- name: Check server installation
34+
run: |
35+
python -m venv .venv-server
36+
.venv-server/bin/python -m pip install -r requirements.server.txt
37+
.venv-server/bin/lnt create lnt-server-instance

.github/workflows/tox.yaml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ on:
1010
jobs:
1111
build:
1212
runs-on: ubuntu-latest
13-
name: "Python ${{ matrix.python-version }}, Postgres=${{ matrix.with-postgres }}"
13+
name: "Python ${{ matrix.python-version }}"
1414

1515
strategy:
1616
matrix:
1717
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
18-
with-postgres: [true, false]
1918
fail-fast: false
2019

2120
steps:
@@ -30,11 +29,6 @@ jobs:
3029
run: |
3130
python -m pip install --upgrade pip
3231
pip install tox tox-gh-actions
33-
- name: Install PostgreSQL
34-
if: ${{ matrix.with-postgres }}
35-
uses: tj-actions/install-postgresql@a889ed6c6fa05022333ed4101295bb1d604f97a8 # v3.2.0
36-
with:
37-
postgresql-version: 17
3832
- name: Tox flake8
3933
run: tox -e flake8
4034
- name: Tox docs

docs/developer_guide.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ virtual environment and installing the development dependencies::
1919
pip install ".[dev]"
2020

2121
This will install the current version of the package, along with the dependencies
22-
required for development (``lit``, ``filecheck``, etc). Note that ``curl`` and
23-
``jq`` are also required for running the tests.
22+
required for development (``lit``, ``filecheck``, etc). Note that ``curl``, ``jq``
23+
and ``docker`` are also required for running the tests.
2424

2525
Running LNT's Regression Tests
2626
------------------------------

tests/SharedInputs/postgres_wrapper.sh

Lines changed: 0 additions & 54 deletions
This file was deleted.

tests/lit.cfg

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import os
44
import platform
5-
import shutil
65

76
import lit.formats
87
import lit.util
@@ -39,20 +38,13 @@ config.environment['I'] = ""
3938
src_root = os.path.join(config.test_source_root, '..')
4039
config.substitutions.append(('%src_root', src_root))
4140
config.substitutions.append(('%{src_root}', src_root))
42-
config.substitutions.append(('%{shared_inputs}', os.path.join(
43-
src_root, 'tests', 'SharedInputs')))
41+
config.substitutions.append(('%{shared_inputs}', os.path.join(config.test_source_root, 'SharedInputs')))
42+
config.substitutions.append(('%{utils}', os.path.join(config.test_source_root, 'utils')))
4443
config.substitutions.append(('%{test_exec_root}', config.test_exec_root))
4544

4645
if lit_config.params.get('long', None):
4746
config.available_features.add('long')
4847

49-
# Enable postgres database support testing if the postgres binary can be found.
50-
# Note that you do not need to setup an actual server, the tests will create
51-
# temporary instances on demand.
52-
postgres = shutil.which('postgres')
53-
if postgres is not None:
54-
config.available_features.add('postgres')
55-
5648
# Enable tidylib testing. This requires pytidylib and tidy-html5.
5749
if lit_config.params.get('tidylib', None):
5850
config.substitutions.append(('%{tidylib}', '--use-tidylib'))

tests/lnttool/PostgresDB.shtest

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
# REQUIRES: postgres
21
# RUN: rm -rf "%t.install"
3-
# RUN: %{shared_inputs}/postgres_wrapper.sh "%t.install" bash %s "%t.install" postgresql://pgtest@localhost:9100 "%{shared_inputs}"
2+
# RUN: mkdir -p "%t.install"
3+
# RUN: %{utils}/with_postgres.sh %t.log bash %s "%t.install" "%{shared_inputs}"
44
set -eux
55

66
TESTDIR="$1"
7-
PGURL="$2"
8-
SHARED_INPUTS="$3"
9-
dropdb --if-exists --maintenance-db="${PGURL}/postgres" lnt_regr_test_PostgresDB
10-
createdb --maintenance-db="${PGURL}/postgres" lnt_regr_test_PostgresDB
7+
SHARED_INPUTS="$2"
118

12-
lnt create "${TESTDIR}/instance" --db-dir ${PGURL} --default-db lnt_regr_test_PostgresDB
9+
lnt create "${TESTDIR}/instance" --db-dir ${LNT_TEST_DB_URI} --default-db ${LNT_TEST_DB_NAME}
1310

1411
# Import a test set.
1512
lnt import "${TESTDIR}/instance" "${SHARED_INPUTS}/sample-a-small.plist" --show-sample-count

tests/utils/installation.shtest

Lines changed: 0 additions & 18 deletions
This file was deleted.

tests/utils/with_postgres.sh

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env bash
2+
#
3+
# with_postgres.sh <LOG_FILE> <command> [args...]
4+
#
5+
# Start a fresh PostgreSQL container, run the wrapped command, then stop and
6+
# remove the container. The following environment variables are exported before
7+
# the command runs:
8+
#
9+
# LNT_TEST_DB_URI Base connection URL (no database), e.g. postgresql://postgres@127.0.0.1:PORT
10+
# LNT_TEST_DB_NAME Name of a ready-to-use database ("lnt_test")
11+
#
12+
# Example:
13+
# with_postgres.sh /tmp/my.log bash my_test.sh "${LNT_TEST_DB_URI}" "${LNT_TEST_DB_NAME}"
14+
#
15+
set -euo pipefail
16+
17+
if ! command -v docker > /dev/null 2>&1; then
18+
echo "error: Could not find 'docker' -- Docker is required to run the tests"
19+
exit 1
20+
fi
21+
22+
# ---------------------------------------------------------------------------
23+
# Arguments
24+
# ---------------------------------------------------------------------------
25+
if [ $# -lt 2 ]; then
26+
echo 1>&2 "usage: $(basename "$0") <LOG_FILE> <command> [args...]"
27+
exit 1
28+
fi
29+
30+
LOG_FILE="$1"
31+
shift
32+
33+
if [ -f "${LOG_FILE}" ]; then
34+
echo 1>&2 "error: Log file ${LOG_FILE} already exists"
35+
exit 1
36+
fi
37+
38+
# ---------------------------------------------------------------------------
39+
# Unique container name — allows parallel test runs without collision.
40+
# ---------------------------------------------------------------------------
41+
CONTAINER_NAME="lnt_pg_$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-')"
42+
43+
# ---------------------------------------------------------------------------
44+
# Cleanup — always stop and remove the container, success or failure.
45+
# ---------------------------------------------------------------------------
46+
cleanup() {
47+
local exit_code=$?
48+
echo "Stopping container ${CONTAINER_NAME} ..."
49+
docker stop "${CONTAINER_NAME}" > /dev/null 2>&1 || true
50+
docker rm "${CONTAINER_NAME}" > /dev/null 2>&1 || true
51+
exit "${exit_code}"
52+
}
53+
trap cleanup EXIT
54+
55+
# ---------------------------------------------------------------------------
56+
# Start the container in the background and bind a random port from the host
57+
# to the container's 5432 port. Stream the logs of the container into the
58+
# specified log file -- useful for debugging.
59+
#
60+
# Key flags:
61+
# POSTGRES_HOST_AUTH_METHOD=trust no password needed
62+
# --tmpfs /var/lib/postgresql/data store data in RAM — faster, no cleanup needed
63+
# ---------------------------------------------------------------------------
64+
echo "Starting container ${CONTAINER_NAME} ..."
65+
docker run \
66+
--detach \
67+
--name "${CONTAINER_NAME}" \
68+
--publish "127.0.0.1::5432" \
69+
--env POSTGRES_HOST_AUTH_METHOD=trust \
70+
--tmpfs /var/lib/postgresql/data \
71+
postgres:17-alpine \
72+
> /dev/null 2>&1
73+
74+
echo "Streaming PostgreSQL server logs into ${LOG_FILE}"
75+
docker logs --follow "${CONTAINER_NAME}" >> "${LOG_FILE}" 2>&1 &
76+
77+
# ---------------------------------------------------------------------------
78+
# Discover the host port that Docker assigned.
79+
# `docker port` output: "n.n.n.n:PORT"
80+
# ---------------------------------------------------------------------------
81+
HOST_PORT=$(docker port "${CONTAINER_NAME}" 5432/tcp | head -1 | sed 's/.*://')
82+
if [ -z "${HOST_PORT}" ]; then
83+
echo 1>&2 "error: could not determine host port for container ${CONTAINER_NAME}"
84+
docker logs "${CONTAINER_NAME}" 1>&2
85+
exit 1
86+
fi
87+
88+
export LNT_TEST_DB_URI="postgresql://postgres@127.0.0.1:${HOST_PORT}"
89+
echo "PostgreSQL available at: ${LNT_TEST_DB_URI}"
90+
91+
# ---------------------------------------------------------------------------
92+
# Wait for PostgreSQL to accept connections (up to 30 seconds).
93+
# pg_isready is available inside the official postgres image.
94+
#
95+
# NOTE: We use '-h localhost' to check via TCP rather than the Unix socket.
96+
# The official postgres image runs a temporary server during initdb that
97+
# listens only on the Unix socket (listen_addresses=''). Without '-h localhost',
98+
# pg_isready can succeed against that temp server, then createdb fails when
99+
# the socket disappears during the transition to the real server.
100+
# ---------------------------------------------------------------------------
101+
MAX_TRIES=30
102+
for i in $(seq 1 "${MAX_TRIES}"); do
103+
if docker exec "${CONTAINER_NAME}" pg_isready --quiet -h localhost; then
104+
echo "PostgreSQL ready after ${i} attempt(s)."
105+
break
106+
fi
107+
if [ "${i}" -eq "${MAX_TRIES}" ]; then
108+
echo 1>&2 "error: PostgreSQL did not become ready after ${MAX_TRIES}s."
109+
docker logs "${CONTAINER_NAME}" 1>&2
110+
exit 1
111+
fi
112+
sleep 1
113+
done
114+
115+
# ---------------------------------------------------------------------------
116+
# Create a default database for tests to use.
117+
# ---------------------------------------------------------------------------
118+
docker exec "${CONTAINER_NAME}" createdb --username=postgres lnt_test
119+
export LNT_TEST_DB_NAME="lnt_test"
120+
121+
# ---------------------------------------------------------------------------
122+
# Run the wrapped command.
123+
# ---------------------------------------------------------------------------
124+
eval "$@"

0 commit comments

Comments
 (0)