From 7dfa63d456510e50eedb3a1de7cf58da96a4a95c Mon Sep 17 00:00:00 2001 From: Anthonin Bonnefoy Date: Wed, 15 Jul 2026 08:13:32 +0200 Subject: Add a protocol trace test harness Create a new test harness in src/test/trace similar to regress: It runs SQL scripts and check if the result matches the expected output. On top of psql's normal output, it also captures protocol trace using PQTRACE and compare the expected trace. The schedule is currently empty. Trace tests will be added in follow-on commits. --- doc/src/sgml/regress.sgml | 6 ++ meson.build | 5 +- src/Makefile.global.in | 16 +++++ src/test/Makefile | 3 +- src/test/meson.build | 1 + src/test/trace/Makefile | 43 ++++++++++++ src/test/trace/meson.build | 38 ++++++++++ src/test/trace/trace_main.c | 128 ++++++++++++++++++++++++++++++++++ src/test/trace/trace_schedule | 8 +++ 9 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/test/trace/Makefile create mode 100644 src/test/trace/meson.build create mode 100644 src/test/trace/trace_main.c create mode 100644 src/test/trace/trace_schedule diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index c74941bfbf2..c6471653300 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -226,6 +226,12 @@ make check-world -j8 >/dev/null located in src/test/isolation. + + + Protocol trace regression tests, + located in src/test/trace. + + Tests for crash recovery and physical replication, diff --git a/meson.build b/meson.build index d05634fef0d..67fdf50df02 100644 --- a/meson.build +++ b/meson.build @@ -3968,7 +3968,7 @@ foreach test_dir : tests t = test_dir[kind] - if kind in ['regress', 'isolation', 'ecpg'] + if kind in ['regress', 'isolation', 'ecpg', 'trace'] if kind == 'regress' runner = pg_regress fallback_dbname = 'regression_@0@' @@ -3978,6 +3978,9 @@ foreach test_dir : tests elif kind == 'ecpg' runner = pg_regress_ecpg fallback_dbname = 'ecpg_regression_@0@' + elif kind == 'trace' + runner = pg_trace_regress + fallback_dbname = 'trace_regression_@0@' endif test_group = test_dir['name'] diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 3279ea87eff..46f88de6f50 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -737,6 +737,22 @@ pg_isolation_regress_installcheck = \ --bindir='$(bindir)' \ $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS) +pg_trace_regress_check = \ + echo "\# +++ trace check in $(subdir) +++" && \ + $(with_temp_install) \ + $(top_builddir)/src/test/trace/pg_trace_regress \ + --temp-instance=./tmp_check \ + --inputdir=$(srcdir) \ + --bindir= \ + $(TEMP_CONF) \ + $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS) +pg_trace_regress_installcheck = \ + echo "\# +++ trace install-check in $(subdir) +++" && \ + $(top_builddir)/src/test/trace/pg_trace_regress \ + --inputdir=$(srcdir) \ + --bindir='$(bindir)' \ + $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS) + ########################################################################## # # Customization diff --git a/src/test/Makefile b/src/test/Makefile index 3eb0a06abb4..097563864f8 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -20,7 +20,8 @@ SUBDIRS = \ postmaster \ recovery \ regress \ - subscription + subscription \ + trace ifeq ($(with_icu),yes) SUBDIRS += icu diff --git a/src/test/meson.build b/src/test/meson.build index cd45cbf57fb..4c79f679201 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -2,6 +2,7 @@ subdir('regress') subdir('isolation') +subdir('trace') subdir('authentication') subdir('postmaster') diff --git a/src/test/trace/Makefile b/src/test/trace/Makefile new file mode 100644 index 00000000000..0d136afb780 --- /dev/null +++ b/src/test/trace/Makefile @@ -0,0 +1,43 @@ +# +# Makefile for trace tests +# + +PGFILEDESC = "pg_trace_regress - trace test driver" +PGAPPICON = win32 + +subdir = src/test/trace +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +override CPPFLAGS := -I$(srcdir) -I$(libpq_srcdir) \ + -I$(srcdir)/../regress $(CPPFLAGS) + +all: pg_trace_regress$(X) + +install: all installdirs + $(INSTALL_PROGRAM) pg_trace_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_trace_regress$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)' + +uninstall: + rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_trace_regress$(X)' + +submake-regress: + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o + +pg_regress.o: | submake-regress + rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/pg_regress.o . + +pg_trace_regress$(X): trace_main.o pg_regress.o $(WIN32RES) + $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@ + +clean distclean: + rm -f pg_trace_regress$(X) trace_main.o pg_regress.o + rm -rf $(pg_regress_clean_files) + +installcheck: all + $(pg_trace_regress_installcheck) --schedule=$(srcdir)/trace_schedule + +check: all + $(pg_trace_regress_check) --schedule=$(srcdir)/trace_schedule diff --git a/src/test/trace/meson.build b/src/test/trace/meson.build new file mode 100644 index 00000000000..37362d9fa7c --- /dev/null +++ b/src/test/trace/meson.build @@ -0,0 +1,38 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# pg_regress_c helpfully provided by regress/meson.build + +trace_sources = pg_regress_c + files( + 'trace_main.c', +) + +if host_system == 'windows' + trace_sources += rc_bin_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_trace_regress', + '--FILEDESC', 'pg_trace_regress - libpq trace test driver',]) +endif + +pg_trace_regress = executable('pg_trace_regress', + trace_sources, + c_args: pg_regress_cflags, + include_directories: pg_regress_inc, + dependencies: [frontend_code, libpq], + kwargs: default_bin_args + { + 'install_dir': dir_pgxs / 'src/test/trace', + }, +) +bin_targets += pg_trace_regress + +tests += { + 'name': 'trace', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'trace': { + 'schedule': files('trace_schedule'), + 'test_kwargs': { + 'priority': 40, + 'timeout': 1000, + }, + 'dbname': 'trace_regression', + }, +} diff --git a/src/test/trace/trace_main.c b/src/test/trace/trace_main.c new file mode 100644 index 00000000000..a818215856f --- /dev/null +++ b/src/test/trace/trace_main.c @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------- + * + * trace_main --- protocol trace regression test for libpq + * + * Runs the same sql test scripts as pg_regress_main, but compares the + * libpq protocol trace produced via PQTRACE against an expected trace + * file instead of comparing psql's textual output. + * + * Copyright (c) 2026, PostgreSQL Global Development Group + * + * src/test/trace/trace_main.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include "lib/stringinfo.h" +#include "libpq-fe.h" +#include "pg_regress.h" + +/* + * start a psql test process for specified file (including redirection), + * with PQTRACE enabled, and return process ID + */ +static PID_TYPE +trace_start_test(const char *testname, + _stringlist **resultfiles, + _stringlist **expectfiles, + _stringlist **tags) +{ + PID_TYPE pid; + char infile[MAXPGPATH]; + char outfile[MAXPGPATH]; + char expectfile[MAXPGPATH]; + char traceresultfile[MAXPGPATH]; + char traceexpectfile[MAXPGPATH]; + StringInfoData psql_cmd; + char *appnameenv; + + /* + * Look for files in the output dir first, consistent with a vpath search. + */ + snprintf(infile, sizeof(infile), "%s/sql/%s.sql", + outputdir, testname); + if (!file_exists(infile)) + snprintf(infile, sizeof(infile), "%s/sql/%s.sql", + inputdir, testname); + + snprintf(outfile, sizeof(outfile), "%s/results/%s.out", + outputdir, testname); + + snprintf(traceresultfile, sizeof(traceresultfile), "%s/results/%s.trace", + outputdir, testname); + + snprintf(traceexpectfile, sizeof(traceexpectfile), "%s/expected/%s.trace", + expecteddir, testname); + if (!file_exists(traceexpectfile)) + snprintf(traceexpectfile, sizeof(traceexpectfile), "%s/expected/%s.trace", + inputdir, testname); + + snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", + expecteddir, testname); + if (!file_exists(expectfile)) + snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", + inputdir, testname); + + add_stringlist_item(resultfiles, outfile); + add_stringlist_item(expectfiles, expectfile); + + add_stringlist_item(resultfiles, traceresultfile); + add_stringlist_item(expectfiles, traceexpectfile); + + initStringInfo(&psql_cmd); + + if (launcher) + appendStringInfo(&psql_cmd, "%s ", launcher); + + appendStringInfo(&psql_cmd, + "\"%s%spsql\" -X -q -d \"%s\" %s < \"%s\" > \"%s\" 2>&1", + bindir ? bindir : "", + bindir ? "/" : "", + dblist->str, + "-v HIDE_NOTIFICATIONS=on", + infile, + outfile); + + appnameenv = psprintf("pg_trace_regress/%s", testname); + setenv("PGAPPNAME", appnameenv, 1); + pfree(appnameenv); + + /* Suppress timestamps and mask varying data, for reproducible traces */ + setenv("PQTRACE", traceresultfile, 1); + setenv("PQTRACEFLAGS", psprintf("%d", PQTRACE_SUPPRESS_TIMESTAMPS | PQTRACE_REGRESS_MODE), 1); + + pid = spawn_process(psql_cmd.data); + + if (pid == INVALID_PID) + { + fprintf(stderr, _("could not start process for test %s\n"), + testname); + exit(2); + } + + unsetenv("PGAPPNAME"); + unsetenv("PQTRACE"); + unsetenv("PQTRACEFLAGS"); + + pfree(psql_cmd.data); + + return pid; +} + +static void +trace_init(int argc, char **argv) +{ + /* set default regression database name */ + add_stringlist_item(&dblist, "trace_regression"); +} + +int +main(int argc, char *argv[]) +{ + return regression_main(argc, argv, + trace_init, + trace_start_test, + NULL /* no postfunc needed */ ); +} diff --git a/src/test/trace/trace_schedule b/src/test/trace/trace_schedule new file mode 100644 index 00000000000..d08de8e3cfd --- /dev/null +++ b/src/test/trace/trace_schedule @@ -0,0 +1,8 @@ +# ---------- +# src/test/trace/trace_schedule +# +# Each test here is run through psql with PQTRACE enabled, and the +# resulting protocol trace is compared against expected/.trace. +# ---------- + +test: -- 2.50.1 (Apple Git-155)