Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
3d1006a
python tests: add the libpq ctypes layer and in-process Session
adunstan Jun 6, 2026
714445b
python tests: add the PostgresServer framework and pytest fixtures
adunstan Jun 6, 2026
476fc84
python tests: add the pgtap plugin and wire pytest into the meson build
adunstan Jun 6, 2026
2b88cfe
python tests: add SSL, LDAP, Kerberos, OAuth and pg_regress helpers
adunstan Jun 6, 2026
1073811
python tests: pytest suites for the bin/ client tools
adunstan Jun 6, 2026
8e0e7b7
python tests: pytest suites for the backup and verify tools
adunstan Jun 6, 2026
1dce165
python tests: pytest suites for pg_dump and pg_upgrade
adunstan Jun 6, 2026
ce67f77
python tests: pytest suite for src/test/recovery
adunstan Jun 6, 2026
f1a67b9
python tests: pytest suite for src/test/subscription
adunstan Jun 6, 2026
df693ea
python tests: pytest suites for contrib modules
adunstan Jun 6, 2026
4371d39
python tests: pytest suites for src/test/modules
adunstan Jun 6, 2026
3275dc6
python tests: pytest suites for the SSL and authentication tests
adunstan Jun 6, 2026
7291824
python tests: pytest suites for libpq, ecpg, psql, pgbench and others
adunstan Jun 6, 2026
8ac51ef
python tests: add README for the pytest/session framework
adunstan Jun 8, 2026
7fc8594
python tests: make the pytest suite survive the sanitizer/32-bit CI jobs
adunstan Jun 8, 2026
5bf0513
python tests: install pytest on the macOS and Windows CI jobs
adunstan Jun 8, 2026
71f7315
python tests: listen on TCP on Windows, like the Perl harness
adunstan Jun 8, 2026
121fda9
python tests: add Windows/TCP guards to socket-sensitive tests
adunstan Jun 8, 2026
5bade80
python tests: refresh 004_load_balance_dns skip rationale
adunstan Jun 8, 2026
3bcf43e
python tests: implement own_host and un-skip the DNS load-balance test
adunstan Jun 8, 2026
78df847
python tests: treat empty PG_TEST_*_MODE as the default, like Perl
adunstan Jun 8, 2026
122351e
ci: enable pytest on the macOS and Windows jobs
adunstan Jun 8, 2026
4691246
python tests: fix wait_for_catchup for multiple matching replication …
adunstan Jun 8, 2026
06c6083
python tests: drop Perl references from framework comments
adunstan Jun 8, 2026
2456d02
python tests: match wait_for_catchup by name, with a walreceiver fall…
adunstan Jun 8, 2026
384f53f
python tests: pre-create the data directory before initdb
adunstan Jun 8, 2026
d49187c
python tests: fix the two macOS pytest failures
adunstan Jun 8, 2026
bcf48d5
python tests: tolerate vanishing files in the low-level backup copy
adunstan Jun 8, 2026
8d3521d
Make pg_mkdir_p tolerant of a concurrent directory creation
adunstan Jun 8, 2026
222ea91
python tests: put test data directories under TESTDATADIR, like the T…
adunstan Jun 8, 2026
a9e4e1a
python tests: give each test function its own data directory under TE…
adunstan Jun 8, 2026
c7da710
python tests: include the server log when pg_ctl start fails
adunstan Jun 9, 2026
af03486
python tests: create short temp directories with an inheritable ACL o…
adunstan Jun 9, 2026
e2f1f7c
python tests: capture program output via files, not pipes
adunstan Jun 9, 2026
ff0739a
python tests: locate and load libpq on Windows
adunstan Jun 9, 2026
ae4561f
python tests: normalize newlines in captured command output
adunstan Jun 9, 2026
03b0e5e
python tests: fix WAL archive and restore commands on Windows
adunstan Jun 9, 2026
e445ebd
python tests: root initdb test data dirs at the harness directory
adunstan Jun 9, 2026
31aaa76
python tests: send backend signals with pg_ctl kill, portably
adunstan Jun 9, 2026
f06e8af
python tests: double the backslashes in the Windows archive/restore c…
adunstan Jun 9, 2026
ef6fb12
python tests: create directory links as junctions on Windows
adunstan Jun 9, 2026
ab27af3
python tests: reconnect freshly after the crash in test_041
adunstan Jun 9, 2026
7b8c488
python tests: skip unix-permission checks on Windows (initdb, pg_ctl)
adunstan Jun 9, 2026
4e77f28
python tests: run connection/auth assertions through psql, not in-pro…
adunstan Jun 9, 2026
1ae7537
python tests: root pytest's tmp_path under the harness directory
adunstan Jun 9, 2026
596757b
python tests: wait deterministically for the crash restart in test_041
adunstan Jun 9, 2026
fd94837
python tests: retry the cached connection past server startup/recovery
adunstan Jun 9, 2026
a607e6a
ci: temporarily disable the Perl TAP tests
adunstan Jun 9, 2026
c51071a
python tests: make test_041 crash wait robust (fix the previous attempt)
adunstan Jun 9, 2026
bae8982
python tests: also retry past "not yet accepting connections"
adunstan Jun 9, 2026
000426b
ci: also disable Perl TAP tests on the Linux meson jobs
adunstan Jun 9, 2026
2c0fd97
python tests: fix Unix-only os/signal usage on Windows
adunstan Jun 9, 2026
26af445
python tests: use a short external pg_wal path in the rewind symlink …
adunstan Jun 9, 2026
e6acbec
python tests: run connect_ok's psql quietly (-q)
adunstan Jun 9, 2026
101afe3
python tests: stringify command items when formatting failure messages
adunstan Jun 9, 2026
d7c0828
python tests: skip unix-permission and non-UTF8 cases on Windows (pg_…
adunstan Jun 9, 2026
84aa00a
python tests: make test_025's archive_command script run on Windows
adunstan Jun 9, 2026
a6777e2
python tests: skip more unix-permission checks on Windows
adunstan Jun 9, 2026
43b230c
python tests: forward-slash cert/key paths embedded in SSL conninfo
adunstan Jun 9, 2026
035cdae
python tests: wait for the injection point before waking it in test_0…
adunstan Jun 9, 2026
f1585f9
python tests: stop caching a shared per-node libpq session
adunstan Jun 9, 2026
0479dba
python tests: fix tests that relied on the removed shared session
adunstan Jun 9, 2026
1e1c891
python tests: fix three Windows authentication test failures
adunstan Jun 10, 2026
ee5af5b
python tests: use forward slashes for service-file paths in test_006_…
adunstan Jun 10, 2026
84cd476
python tests: fix log offset on Windows (CRLF vs byte size)
adunstan Jun 10, 2026
e449192
python tests: pass password explicitly for in-process connect in test…
adunstan Jun 10, 2026
c41c858
python tests: connect via psql, not in-process libpq, in test_006_ser…
adunstan Jun 10, 2026
d27888d
python tests: handle Windows path separator in test_001_extension_con…
adunstan Jun 10, 2026
08de766
python tests: write files without newline translation (append_to_file)
adunstan Jun 10, 2026
be7b10d
python tests: forward-slash WAL paths (and short tablespace) for pg_w…
adunstan Jun 10, 2026
19db7de
python tests: forward-slash shell command paths in basebackup_to_shel…
adunstan Jun 10, 2026
e810c90
python tests: skip test_010_dump_connstr on Windows
adunstan Jun 10, 2026
7705a45
python tests: remove tablespace junctions with rmdir on Windows
adunstan Jun 10, 2026
f0d560f
python tests: fix pg_ctl start/stop test on Windows
adunstan Jun 10, 2026
eb0f94e
python tests: gate test_012_collation on with_icu, not a catalog query
adunstan Jun 10, 2026
146777a
python tests: forward-slash backup path in pg_verifybackup test_002_a…
adunstan Jun 10, 2026
7c37caa
python tests: forward-slash pgbench --file script paths
adunstan Jun 10, 2026
3c97035
python tests: accept either path separator in pgbench log file name c…
adunstan Jun 10, 2026
46be30a
python tests: forward-slash sslkeylogfile path in ssl test_001
adunstan Jun 10, 2026
2e0258e
python tests: handle non-preserved tablespace junction in pg_combineb…
adunstan Jun 10, 2026
5984dc3
python tests: forward-slash the broken-WAL path in pg_waldump test_001
adunstan Jun 10, 2026
79c213b
python tests: let pg_upgrade clusters listen on localhost TCP on Windows
adunstan Jun 10, 2026
4fef1c0
python tests: forward-slash pg_waldump --path archive arguments on Wi…
adunstan Jun 10, 2026
41d542d
python tests: skip permission-based pg_verifybackup corruption cases …
adunstan Jun 10, 2026
08a7be8
python tests: run pg_upgrade without a node PGHOST in pg_upgrade test…
adunstan Jun 10, 2026
8983517
python tests: guard the non-UTF8 path join in pg_basebackup test_010 …
adunstan Jun 10, 2026
4ff8630
python tests: match tablespace LOCATION separators flexibly in pg_dum…
adunstan Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
26 changes: 22 additions & 4 deletions .github/workflows/pg-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ env:
-Dplperl=enabled
-Dplpython=enabled
-Dpltcl=enabled
-Dpytest=enabled
-Dreadline=enabled
-Dssl=openssl
-Dtap_tests=enabled
-Dtap_tests=disabled
-Dzlib=enabled
-Dzstd=enabled

Expand Down Expand Up @@ -521,6 +522,7 @@ jobs:
--pkg-config-path /usr/lib/i386-linux-gnu/pkgconfig/ \
-DPERL=perl5.40-i386-linux-gnu \
-Dlibnuma=disabled \
-Dtap_tests=disabled \
build

- name: Build
Expand Down Expand Up @@ -607,6 +609,7 @@ jobs:
-Duuid=e2fs \
--buildtype=debug \
-Dllvm=enabled \
-Dtap_tests=disabled \
build

- name: Build
Expand All @@ -617,6 +620,15 @@ jobs:

- name: Test world
shell: *su_postgres_shell
# The pytest suite loads libpq in-process via ctypes. Here libpq is
# AddressSanitizer-instrumented, and ASan must come first in the link
# order; dlopening it into an otherwise uninstrumented python aborts
# with "ASan runtime does not come first". Preload the ASan runtime
# for the test run to satisfy that (a no-op for the already-instrumented
# server/client binaries). Scoped to this step so the build is
# unaffected; detect_leaks is already disabled via ASAN_OPTIONS.
env:
ADDITIONAL_SETUP: export LD_PRELOAD="$(gcc -print-file-name=libasan.so)"
run: *meson_test_world_cmd

- *linux_collect_cores_step
Expand Down Expand Up @@ -659,6 +671,8 @@ jobs:
openssl
p5.34-io-tty
p5.34-ipc-run
py312-pexpect
py312-pytest
python312
tcl
zstd
Expand Down Expand Up @@ -815,8 +829,9 @@ jobs:
-Dldap=enabled
-Dplperl=enabled
-Dplpython=enabled
-Dpytest=enabled
-Dssl=openssl
-Dtap_tests=enabled
-Dtap_tests=disabled

defaults:
run:
Expand Down Expand Up @@ -902,9 +917,11 @@ jobs:
- name: Install dependencies
shell: pwsh
run: |
# meson is not preinstalled on windows-2022. Install via pip
# meson is not preinstalled on windows-2022. Install via pip.
# pytest enables the Python test suite (pexpect is omitted: it needs
# a pty, which Windows lacks, and the interactive tests importorskip).
echo ::group::pip
python -m pip install --upgrade meson
python -m pip install --upgrade meson pytest
if (!$?) { throw 'cmdfail' }
echo ::endgroup::

Expand Down Expand Up @@ -1042,6 +1059,7 @@ jobs:
${MINGW_PACKAGE_PREFIX}-meson \
${MINGW_PACKAGE_PREFIX}-perl \
${MINGW_PACKAGE_PREFIX}-pkgconf \
${MINGW_PACKAGE_PREFIX}-python-pytest \
${MINGW_PACKAGE_PREFIX}-readline \
${MINGW_PACKAGE_PREFIX}-zlib \
${MINGW_PACKAGE_PREFIX}-zstd
Expand Down
10 changes: 10 additions & 0 deletions contrib/amcheck/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,14 @@ tests += {
't/006_verify_gin.pl',
],
},
'pytest': {
'tests': [
'pyt/test_001_verify_heapam.py',
'pyt/test_002_cic.py',
'pyt/test_003_cic_2pc.py',
'pyt/test_004_verify_nbtree_unique.py',
'pyt/test_005_pitr.py',
'pyt/test_006_verify_gin.py',
],
},
}
219 changes: 219 additions & 0 deletions contrib/amcheck/pyt/test_001_verify_heapam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Copyright (c) 2021-2026, PostgreSQL Global Development Group

"""Test amcheck's verify_heapam against deliberately corrupted heap pages."""

import os
import re
import struct


# Regexes matching the various line-pointer-corruption checks in
# verify_heapam.c, hit by corrupt_first_page on both little-endian and
# big-endian architectures.
_HEAP_CORRUPTION_RES = [
re.compile(
r"line pointer redirection to item at offset \d+ "
r"precedes minimum offset \d+"
),
re.compile(
r"line pointer redirection to item at offset \d+ "
r"exceeds maximum offset \d+"
),
re.compile(r"line pointer to page offset \d+ is not maximally aligned"),
re.compile(
r"line pointer length \d+ is less than the minimum "
r"tuple header size \d+"
),
re.compile(
r"line pointer to page offset \d+ with length \d+ "
r"ends beyond maximum page offset \d+"
),
]


def relation_filepath(node, session, relname):
"""Return the filesystem path for the named relation."""
pgdata = node.data_dir
rel = session.query_oneval(f"SELECT pg_relation_filepath('{relname}')")
assert rel is not None, f"path not found for relation {relname}"
return os.path.join(pgdata, rel)


def fresh_test_table(session, relname):
"""(Re)create and populate a test table of the given name."""
return session.do(
f"""
DROP TABLE IF EXISTS {relname} CASCADE;
CREATE TABLE {relname} (a integer, b text);
ALTER TABLE {relname} SET (autovacuum_enabled=false);
ALTER TABLE {relname} ALTER b SET STORAGE external;
INSERT INTO {relname} (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
BEGIN;
SAVEPOINT s1;
SELECT 1 FROM {relname} WHERE a = 42 FOR UPDATE;
UPDATE {relname} SET b = b WHERE a = 42;
RELEASE s1;
SAVEPOINT s1;
SELECT 1 FROM {relname} WHERE a = 42 FOR UPDATE;
UPDATE {relname} SET b = b WHERE a = 42;
COMMIT;
"""
)


def fresh_test_sequence(session, seqname):
"""Create a test sequence of the given name."""
return session.do(
f"""
DROP SEQUENCE IF EXISTS {seqname} CASCADE;
CREATE SEQUENCE {seqname}
INCREMENT BY 13
MINVALUE 17
START WITH 23;
SELECT nextval('{seqname}');
SELECT setval('{seqname}', currval('{seqname}') + nextval('{seqname}'));
"""
)


def advance_test_sequence(session, seqname):
"""Call SQL functions to increment the sequence."""
return session.query_oneval(f"SELECT nextval('{seqname}')")


def set_test_sequence(session, seqname):
"""Call SQL functions to set the sequence."""
return session.query_oneval(f"SELECT setval('{seqname}', 102)")


def reset_test_sequence(session, seqname):
"""Call SQL functions to reset the sequence."""
return session.do(f"ALTER SEQUENCE {seqname} RESTART WITH 51")


def corrupt_first_page(node, session, relname):
"""Stop the node, corrupt the first page of the relation, restart it."""
relpath = relation_filepath(node, session, relname)

session.close()
node.stop()

with open(relpath, "r+b") as fh:
# Corrupt some line pointers. The values are chosen to hit the
# various line-pointer-corruption checks in verify_heapam.c
# on both little-endian and big-endian architectures.
fh.seek(32)
fh.write(
struct.pack(
"<6L",
0xAAA15550,
0xAAA0D550,
0x00010000,
0x00008000,
0x0000800F,
0x001E8000,
)
)

node.start()
session.reconnect()


def detects_corruption(session, function, *res):
"""Assert that verify_heapam output matches each corruption regex."""
result = session.query_tuples(f"SELECT * FROM {function}")
for regex in res:
assert regex.search(result), \
f"expected /{regex.pattern}/ in:\n{result}"


def detects_heap_corruption(session, function):
"""Assert verify_heapam reports the expected heap corruption messages."""
detects_corruption(session, function, *_HEAP_CORRUPTION_RES)


def detects_no_corruption(session, function):
"""Assert verify_heapam reports no corruption (empty output)."""
result = session.query_tuples(f"SELECT * FROM {function}")
assert result == "", f"expected no corruption, got:\n{result}"


def check_all_options_uncorrupted(session, relname):
"""Check various options are stable and report no corruption.

The relname *must* be an uncorrupted table, or this will fail.
"""
for stop in ("true", "false"):
for check_toast in ("true", "false"):
for skip in ("'none'", "'all-frozen'", "'all-visible'"):
for startblock in ("NULL", "0"):
for endblock in ("NULL", "0"):
opts = (
f"on_error_stop := {stop}, "
f"check_toast := {check_toast}, "
f"skip := {skip}, "
f"startblock := {startblock}, "
f"endblock := {endblock}"
)
detects_no_corruption(
session, f"verify_heapam('{relname}', {opts})"
)


def test_001_verify_heapam(create_pg):
#
# Test set-up
#
node = create_pg("test", start=False, initdb_extra=["--no-data-checksums"])
node.append_conf("autovacuum=off")
node.start()
session = node.session()

session.do("CREATE EXTENSION amcheck")

#
# Check a table with data loaded but no corruption, freezing, etc.
#
fresh_test_table(session, "test")
check_all_options_uncorrupted(session, "test")

#
# Check a corrupt table
#
fresh_test_table(session, "test")
corrupt_first_page(node, session, "test")
detects_heap_corruption(session, "verify_heapam('test')")
detects_heap_corruption(session, "verify_heapam('test', skip := 'all-visible')")
detects_heap_corruption(session, "verify_heapam('test', skip := 'all-frozen')")
detects_heap_corruption(session, "verify_heapam('test', check_toast := false)")
detects_heap_corruption(
session, "verify_heapam('test', startblock := 0, endblock := 0)"
)

#
# Check a corrupt table with all-frozen data
#
fresh_test_table(session, "test")
session.do("VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test")
detects_no_corruption(session, "verify_heapam('test')")
corrupt_first_page(node, session, "test")
detects_heap_corruption(session, "verify_heapam('test')")
detects_no_corruption(session, "verify_heapam('test', skip := 'all-frozen')")

#
# Check a sequence with no corruption. The current implementation of
# sequences doesn't require its own test setup, since sequences are really
# just heap tables under-the-hood. To guard against future implementation
# changes made without remembering to update verify_heapam, we create and
# exercise a sequence, checking along the way that it passes corruption
# checks.
#
fresh_test_sequence(session, "test_seq")
check_all_options_uncorrupted(session, "test_seq")
advance_test_sequence(session, "test_seq")
check_all_options_uncorrupted(session, "test_seq")
set_test_sequence(session, "test_seq")
check_all_options_uncorrupted(session, "test_seq")
reset_test_sequence(session, "test_seq")
check_all_options_uncorrupted(session, "test_seq")
Loading