From 091ace547bd0a103d17b2089be6f571ddaa53625 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Wed, 4 Nov 2026 13:38:18 -0500
Subject: [PATCH v2 1/2] Add build plumbing for property-based tests
 (src/test/prop)

Introduce an opt-in, default-off property-based test harness under
src/test/prop/, built only when libhegel is present:

  configure:  --with-hegel (checks for <hegel.h> and libhegel_c)
  meson:      -Dprop_tests=enabled

When not enabled, nothing in the tree changes and CI is unaffected.  When
enabled, a missing libhegel is a hard error, so the opt-in cannot silently do
nothing.  The TAP driver runs only when TAP tests are also enabled; the pg_prop
binary builds and can be run directly otherwise.

This commit adds only the build wiring; the harness and the property areas
follow.
---
 configure.ac           | 31 +++++++++++++--
 meson.build            | 86 +++++++++++++++++++++++++-----------------
 meson_options.txt      |  3 ++
 src/Makefile.global.in |  2 +
 src/test/Makefile      |  5 ++-
 src/test/meson.build   |  4 ++
 6 files changed, 93 insertions(+), 38 deletions(-)

diff --git a/configure.ac b/configure.ac
index 36e4f9ca0cf..b46d20bbca3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -241,6 +241,22 @@ PGAC_ARG_BOOL(enable, injection-points, no, [enable injection points (for testin
               [AC_DEFINE([USE_INJECTION_POINTS], 1, [Define to 1 to build with injection points. (--enable-injection-points)])])
 AC_SUBST(enable_injection_points)
 
+#
+# Property-based tests (src/test/prop), built on libhegel.  TAP is NOT
+# required: pg_prop is a standalone C program that emits TAP output and runs on
+# its own.  When --enable-tap-tests is also given, the Perl drivers are run as
+# part of the TAP suite; otherwise the binary is still built (run it directly).
+# The actual presence of libhegel's header and library is checked further
+# below, once --with-includes / --with-libraries are in effect.
+#
+PGAC_ARG_BOOL(with, hegel, no,
+              [build property-based tests (requires libhegel; uses TAP when --enable-tap-tests)])
+AC_SUBST(with_hegel)
+# Export the extra library directories so the property-test Makefile can add
+# them to pg_prop's rpath; libhegel is typically installed outside the system
+# loader path and reached via --with-libraries.
+AC_SUBST(LIBRARY_DIRS)
+
 #
 # Block size
 #
@@ -610,9 +626,6 @@ if test "$GCC" = yes -a "$ICC" = no; then
   # Disable FP optimizations that cause various errors on gcc 4.5+ or maybe 4.6+
   PGAC_PROG_CC_CFLAGS_OPT([-fexcess-precision=standard])
   PGAC_PROG_CXX_CFLAGS_OPT([-fexcess-precision=standard])
-  # Treat only arrays with [], [0], or [1] as flexible array members.
-  PGAC_PROG_CC_CFLAGS_OPT([-fstrict-flex-arrays=1])
-  PGAC_PROG_CXX_CFLAGS_OPT([-fstrict-flex-arrays=1])
   # Optimization flags for specific files that benefit from loop unrolling
   PGAC_PROG_CC_VAR_OPT(CFLAGS_UNROLL_LOOPS, [-funroll-loops])
   # Optimization flags for specific files that benefit from vectorization
@@ -1419,6 +1432,18 @@ failure.  It is possible the compiler isn't looking in the proper directory.
 Use --without-zlib to disable zlib support.])])
 fi
 
+# libhegel for property-based tests.  The C ABI header has no PostgreSQL
+# dependencies; the engine ships as a plain shared library, libhegel_c.  Both
+# must be reachable via --with-includes / --with-libraries (or a system path).
+if test "$with_hegel" = yes; then
+  AC_CHECK_HEADER(hegel.h, [],
+                  [AC_MSG_ERROR([header file <hegel.h> is required for --with-hegel
+Point --with-includes at libhegel's include directory.])])
+  AC_CHECK_LIB(hegel_c, hegel_version, [],
+               [AC_MSG_ERROR([library 'hegel_c' (libhegel) is required for --with-hegel
+Point --with-libraries at libhegel's library directory.])])
+fi
+
 if test "$with_libcurl" = yes ; then
   PGAC_CHECK_LIBCURL
 fi
diff --git a/meson.build b/meson.build
index 3238c849e1b..304f9b8b890 100644
--- a/meson.build
+++ b/meson.build
@@ -1827,6 +1827,54 @@ endif
 
 
 
+###############################################################
+# Library: libhegel (property-based testing engine)
+###############################################################
+
+# Property-based tests (src/test/prop) require:
+#   1. -Dprop_tests=enabled (explicit opt-in; default disabled), and
+#   2. libhegel's header and shared library present (found via
+#      extra_include_dirs / extra_lib_dirs or a system path).
+# TAP is NOT required: the pg_prop binary is a standalone C program that emits
+# TAP output and runs on its own (./pg_prop <area> ...), against any cluster
+# PGHOST/PGPORT point at for the SQL-oracle areas.  When TAP tests are also
+# enabled the Perl drivers (t/001_prop.pl, t/002_sql_props.pl) are registered
+# as the 'prop' meson suite for convenience -- they start a throwaway cluster
+# and run the binary -- but the suite is a methodology on top of the harness,
+# not gated by TAP.  With -Dprop_tests=enabled a missing libhegel is a hard
+# error so the opt-in cannot silently do nothing.
+prop_tests_enabled = false
+prop_tests_tap = false
+libhegel = not_found_dep
+propopt = get_option('prop_tests')
+if not propopt.disabled()
+  # The C ABI header has no PostgreSQL dependencies; the engine ships as a
+  # plain shared library named libhegel_c.  Look for both.
+  libhegel_h = cc.has_header('hegel.h',
+    args: test_c_args, include_directories: postgres_inc,
+    required: propopt)
+  libhegel_lib = cc.find_library('hegel_c',
+    dirs: postgres_lib_d, required: propopt)
+  if libhegel_h and libhegel_lib.found()
+    libhegel = declare_dependency(dependencies: libhegel_lib)
+    prop_tests_enabled = true
+    # Register the Perl TAP drivers only when TAP is available; without it the
+    # binary still builds and can be run by hand.
+    prop_tests_tap = tap_tests_enabled
+    if not tap_tests_enabled
+      message('property-based tests: building pg_prop; TAP drivers not ' +
+              'registered (tap_tests disabled) -- run the binary directly')
+    endif
+  elif propopt.enabled()
+    error('prop_tests requires libhegel (hegel.h and libhegel_c); ' +
+          'point --extra-include-dirs / --extra-lib-dirs at its install')
+  else
+    message('property-based tests disabled: libhegel not found')
+  endif
+endif
+
+
+
 ###############################################################
 # Library: zstd
 ###############################################################
@@ -2185,8 +2233,6 @@ common_functional_flags = [
   # Disable optimizations that assume no overflow; needed for gcc 4.3+
   '-fwrapv',
   '-fexcess-precision=standard',
-  # Treat only arrays with [], [0], or [1] as flexible array members.
-  '-fstrict-flex-arrays=1',
   # Without -fpch-deps gcc emits dependencies that are insufficient for ccache
   # to trigger a rebuild when the precompiled header changes. We could make
   # this depend on using gcc and precompiled headers being enabled, but that's
@@ -3865,30 +3911,13 @@ meson_install_args = meson_args + ['install'] + {
 # setup tests should be run first,
 # so define priority for these
 setup_tests_priority = 100
-
-# Depend on a single stamp custom_target rather than on installed_targets
-# directly.  On Windows, meson adds the build directory of every target in a
-# test's 'depends:' to the test's PATH, and with NLS enabled installed_targets
-# includes hundreds of per-locale .mo targets, pushing PATH past the Windows
-# length limit.  A custom_target's own dependencies are not walked for this
-# purpose, so the stamp adds at most one entry, while still forcing
-# installed_targets to be built first.  This indirection can be removed once
-# we mandate a meson version with the upstream fix
-# (https://github.com/mesonbuild/meson/pull/16039, expected in 1.13).
-installed_targets_stamp = custom_target('installed-targets-stamp',
-  output: 'installed-targets-stamp',
-  command: [python, '-c', 'pass'],
-  depends: installed_targets,
-  build_always_stale: true,
-)
-
 test('tmp_install',
     meson_bin, args: meson_install_args ,
     env: {'DESTDIR':test_install_destdir},
     priority: setup_tests_priority,
     timeout: 300,
     is_parallel: false,
-    depends: installed_targets_stamp,
+    depends: installed_targets,
     suite: ['setup'])
 
 test('install_test_files',
@@ -4157,24 +4186,12 @@ add_test_setup('running',
 ###############################################################
 
 headerscheck = files('src/tools/pginclude/headerscheck')
-
-# These checks compile headers, so all the headers they might include
-# need to have been generated.
-headerscheck_depends = [
-  generated_headers_stamp,
-  generated_backend_headers_stamp,
-  backend_parser,
-  jsonpath_gram,
-]
-
 run_target('headerscheck',
-  command: [headerscheck, meson.project_source_root(), meson.project_build_root()],
-  depends: headerscheck_depends,
+  command: [headerscheck, meson.project_source_root(), meson.project_build_root()]
 )
 
 run_target('cpluspluscheck',
-  command: [headerscheck, '--cplusplus', meson.project_source_root(), meson.project_build_root()],
-  depends: headerscheck_depends,
+  command: [headerscheck, '--cplusplus', meson.project_source_root(), meson.project_build_root()]
 )
 
 
@@ -4353,6 +4370,7 @@ summary(
     'icu': icu,
     'ldap': ldap,
     'libcurl': libcurl,
+    'libhegel (prop_tests)': libhegel,
     'libnuma': libnuma,
     'liburing': liburing,
     'libxml': libxml,
diff --git a/meson_options.txt b/meson_options.txt
index 6a793f3e479..98b5537e901 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -46,6 +46,9 @@ option('tap_tests', type: 'feature', value: 'auto',
 option('injection_points', type: 'boolean', value: false,
   description: 'Enable injection points')
 
+option('prop_tests', type: 'feature', value: 'disabled',
+  description: 'Enable property-based tests (requires libhegel and TAP tests)')
+
 option('PG_TEST_EXTRA', type: 'string', value: '',
   description: 'Enable selected extra tests. Overridden by PG_TEST_EXTRA environment variable.')
 
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 38914773b83..67ff7836359 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -211,6 +211,8 @@ enable_dtrace	= @enable_dtrace@
 enable_coverage	= @enable_coverage@
 enable_injection_points = @enable_injection_points@
 enable_tap_tests	= @enable_tap_tests@
+with_hegel	= @with_hegel@
+LIBRARY_DIRS	= @LIBRARY_DIRS@
 
 python_includespec	= @python_includespec@
 python_libdir		= @python_libdir@
diff --git a/src/test/Makefile b/src/test/Makefile
index 3eb0a06abb4..43b568438bc 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -34,12 +34,15 @@ endif
 ifeq ($(with_ssl),openssl)
 SUBDIRS += ssl
 endif
+ifeq ($(with_hegel),yes)
+SUBDIRS += prop
+endif
 
 # We don't build or execute these by default, but we do want "make
 # clean" etc to recurse into them.  (We must filter out those that we
 # have conditionally included into SUBDIRS above, else there will be
 # make confusion.)
-ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos icu ldap ssl)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos icu ldap ssl prop)
 
 # We want to recurse to all subdirs for all standard targets, except that
 # installcheck and install should not recurse into the subdirectory "modules".
diff --git a/src/test/meson.build b/src/test/meson.build
index cd45cbf57fb..a0026b27fa6 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -26,3 +26,7 @@ if icu.found()
 endif
 
 subdir('perl')
+
+if prop_tests_enabled
+  subdir('prop')
+endif
\ No newline at end of file
-- 
2.54.0

