Skip to content

Python (pytest) test framework + representative TAP→pytest conversions#9

Open
adunstan wants to merge 14 commits into
masterfrom
pytap/v4
Open

Python (pytest) test framework + representative TAP→pytest conversions#9
adunstan wants to merge 14 commits into
masterfrom
pytap/v4

Conversation

@adunstan

Copy link
Copy Markdown
Owner

Introduces the Python/pytest test framework (in-process libpq via ctypes, a
PostgresServer fixture layer, the pgtap plugin wired into the meson build) plus
a small representative sample of TAP→pytest conversions. Based on master.

Core framework

  • libpq ctypes layer + in-process Session
  • PostgresServer framework + pytest fixtures
  • pgtap plugin + meson wiring
  • SSL/LDAP/Kerberos/OAuth + pg_regress helpers
  • README for the pytest/session framework

Representative conversions (16 scripts)

Each converted script's Perl t/*.pl origin is removed in the same commit.

Suite Mode pytest Perl removed
src/bin/psql full dir 4 4
src/bin/pg_ctl full dir 4 4
src/interfaces/libpq full dir 6 6
src/test/recovery 001_stream_rep (physical rep) partial 1 1
src/test/subscription 001_rep_changes (logical rep) partial 1 1

Full directories flip the meson registration from tap to pytest entirely.
The two partial conversions add a pytest block and drop only the one converted
Perl entry; the remaining Perl tests in those directories (e.g.
recovery/002_archiving, subscription/002_types) are untouched. Coverage spans
interactive client, server lifecycle, libpq/Session networking, and physical &
logical replication.

CI

  • ci: run the pytest suite in CI — enables -Dpytest=enabled on all jobs and
    installs the needed deps (MacPorts/pip/pacman; ASan LD_PRELOAD accommodation).

This branch deliberately removes the Perl scripts it converts rather than
carrying a "temporarily disable the Perl TAP tests" commit. The pg_mkdir_p
concurrency fix is not included (already in master).

Validation

meson setup -Dpytest=enabled configures clean; meson registers exactly the
converted pytest tests with no dangling Perl references; the libpq suite runs
green (4 OK, 2 env-gated skips).

adunstan added 13 commits June 20, 2026 11:49
A ctypes binding of libpq (bindings, constants, OIDs, library discovery,
notification and result handling) plus a Session class providing synchronous,
asynchronous, pipeline, COPY-free NOTIFY/notice and non-blocking query
execution.  This lets the Python test suite run SQL in-process without forking
psql.
PostgresServer manages a cluster's lifecycle (initdb, start/stop/restart,
promote), configuration, in-process SQL, log inspection, backup/streaming/
archiving/restore, WAL helpers, replication-slot helpers and connect_ok/
connect_fails connection assertions.  PgBin runs client programs; the fixtures
(pg_bin, create_pg, pg, conn, bindir, libdir) build the common test objects and
tear them down automatically.

Author: Jelte Fennema-Nio <postgres@jeltef.nl>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
pgtap is a pytest plugin that emits TAP for the meson/prove harness when
TESTLOGDIR is set, and maps a whole-module skip to success.  The repository
pyproject.toml carries the pytest configuration.  meson gains a pytest feature
option and a kind=='pytest' test branch, so each directory can list pytest
suites beside its tap suites.  Includes the suite's own self-tests.

The root pyproject.toml also configures the suite's code quality gates:
black for formatting, and pylint and mypy for linting and type checking
(the dev-tooling dependency group lives in src/test/pytest/pyproject.toml;
none of it is needed to run the tests).  The whole suite is kept
black-clean, pylint 10.00/10 and mypy-clean.

Author: Jelte Fennema-Nio <postgres@jeltef.nl>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Helpers used by the heavier test suites: a pg_regress runner, an OpenSSL-backed
SSL server configurator, an slapd launcher, a stand-alone Kerberos KDC, and a
launcher for the mock OAuth provider.
Document the Python port of the Perl TAP suite: layout, how to run the
tests under meson and directly with pytest, the shared fixtures, and the
PostgresServer/Session/PgBin framework classes.
Replace the four src/bin/psql Perl TAP tests with their pytest
equivalents and switch the meson test registration from 'tap' to
'pytest'.  The interactive tab-completion and pager tests drive psql
through a pty via pexpect (the interactive_psql fixture in
pyt/conftest.py) and skip when pexpect, readline or a working "wc -l"
is unavailable.
Replace the four src/bin/pg_ctl Perl TAP tests with their pytest
equivalents and switch the meson test registration from 'tap' to
'pytest'.
Replace the six src/interfaces/libpq Perl TAP tests with their pytest
equivalents and switch the meson test registration from 'tap' to
'pytest', preserving the existing env and deps.
Convert the streaming-replication recovery test as a representative
multi-node physical-replication example.  Add a 'pytest' block to
src/test/recovery/meson.build for pyt/test_001_stream_rep.py and drop
the corresponding t/001_stream_rep.pl from the 'tap' list; the
remaining recovery Perl tests are unchanged.
Convert the logical-replication change-propagation test as a
representative multi-node logical-replication example.  Add a 'pytest'
block to src/test/subscription/meson.build for
pyt/test_001_rep_changes.py and drop the corresponding
t/001_rep_changes.pl from the 'tap' list; the remaining subscription
Perl tests are unchanged.
Enable the pytest suite (-Dpytest=enabled) on all jobs.

This needs pytest installed where the images do not already provide it:
via MacPorts on macOS (plus pexpect for the interactive psql tests, which
need a pty and so skip on Windows), via pip on the Windows VS image, and
via pacman on MinGW.

The AddressSanitizer job needs one accommodation: the suite loads libpq
in-process via ctypes, and dlopening an ASan-instrumented libpq into an
uninstrumented python aborts because the ASan runtime must come first in
the link order.  Preload the ASan runtime for the test step to satisfy
that; it is a no-op for the already-instrumented server binaries.
The SanityCheck job ran the TAP test pg_ctl/001_start_stop, which was
removed when src/bin/pg_ctl was converted to pytest (see "python tests:
convert the pg_ctl TAP suite to pytest").  Point MTEST_TARGET at the
pytest equivalent pg_ctl/test_001_start_stop and enable -Dpytest on the
SanityCheck setup (its minimal config uses --auto-features=disabled, so
pytest must be requested explicitly; the Linux image already provides
pytest).  Also update the now-stale "a tap test" comment.
The pytest suites were wired into the Meson build only, so the
autoconf/make build had no way to run them.  After the converted Perl
tests were removed, "make check" in the fully converted directories ran
prove against an empty t/ and failed.

Teach the make build to run pytest, mirroring the existing prove_check
machinery:

- configure: add --enable-pytest and locate pytest (falling back to
  "python -m pytest", as the Meson build does, for installations that
  ship only the module).
- src/Makefile.global.in: add enable_pytest, PYTEST and the
  pytest_check / pytest_installcheck recipes.  They set up the temporary
  install on PATH and put src/test/pytest on PYTHONPATH, then run
  "pytest pyt/test_*.py" from the test directory; the active pytest
  configuration comes from the repository-root pyproject.toml.
- Wire the converted directories' check targets: psql, pg_ctl and libpq
  (fully converted) run pytest_check; recovery and subscription (one
  test each converted) run both prove_check and pytest_check so their
  remaining Perl tests still run.
- CI: configure the Linux Autoconf job with --enable-pytest.

Validated with a VPATH autoconf build: "make check" in src/bin/psql and
src/interfaces/libpq runs the pytest suites to green TAP output.
…onvention

The converted pytest suites were named test_NNN_name.py, which required a
custom python_files = ["test_*.py"] pytest setting.  Rename them to
NNN_name.py instead, mirroring the t/NNN_name.pl Perl originals they replace
(e.g. t/001_basic.pl -> pyt/001_basic.py); this is the project's TAP file
naming convention and makes the meson/prove test names match the Perl ones
(psql/001_basic rather than psql/test_001_basic).

The framework's own self-tests under src/test/pytest/pyt have no Perl origin
and keep the pytest-default test_*.py names.  pyproject.toml's python_files
now lists both patterns ("[0-9]*.py" and "test_*.py"), and the make
pytest_check glob uses pyt/[0-9]*.py (which also excludes conftest.py).  Test
*function* names keep the test_ prefix, as pytest requires.

Updates the five meson test lists, src/Makefile.global.in, the CI SanityCheck
target and the README accordingly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant