diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af34700..91c117f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -340,8 +340,9 @@ jobs: # when registered through AWS pg_tle's database-backed catalog instead of # a filesystem .control file - relevant for pg_tle-based deployment # targets (e.g. AWS RDS). Orthogonal to the fresh/update/upgrade axis - # above: this job only covers a fresh install via pg_tle, not update/ - # upgrade through it (a further step, tracked separately). + # above: this job covers both a fresh install AND the 0.9.6 -> current + # update path via pg_tle (not a binary pg_upgrade through it - that's a + # further step, tracked separately). pg-tle-test: needs: [changes] if: needs.changes.outputs.docs_only != 'true' @@ -462,6 +463,33 @@ jobs: fi - name: Verify no stray extension control files after the fresh-install smoke test run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt pg_tle.control + - name: Update 0.9.6 -> current and run the suite, purely via pg_tle (existing mode) + # A SECOND, separate scratch database (created after the template1 + # registration above, so it inherits both registrations, same + # reasoning as count_nulls_smoke above) - proves the extension-UPDATE + # path, not just a fresh install, also never touches the filesystem + # when deployed via pg_tle. update-scenario creates the database, + # CREATE EXTENSIONs at 0.9.6, plants + proves the dependency guard, + # ALTER EXTENSION UPDATEs to current, then runs the full pgTAP suite + # against that real updated database in existing mode - no changes + # needed to that flow itself, it's already pg_tle-transparent once + # the target database descends from the pg_tle-registered template1. + # + # TEST_EXISTING_DEPLOY=pgtle is the one piece of new plumbing this + # needed in bin/test_existing: without it, run_suite's normal + # `make test`/`make verify-results` would silently `make install` + # count_nulls onto the filesystem (pgxntool's `test` target + # unconditionally depends on `install`), defeating the entire point + # of this job. In pgtle mode it instead calls `make testdeps` + + # `make installcheck` directly (neither depends on `install`) and + # runs pgxntool/verify-results-pgtap.sh's pass/fail check directly - + # see bin/test_existing's TEST_EXISTING_DEPLOY comment for the full + # reasoning. + run: | + test ! -e /usr/share/postgresql/${{ matrix.pg }}/extension/count_nulls.control + TEST_EXISTING_DEPLOY=pgtle bin/test_existing update-scenario count_nulls_pgtle_update "" 0.9.6 + - name: Verify no stray extension control files after the update-path test + run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt pg_tle.control # A single stable check name for use as a required status check in branch # protection rules. Matrix jobs produce check names like diff --git a/Makefile b/Makefile index d9a9e1d..44672ce 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,41 @@ export PGOPTIONS := $(PGOPTIONS) -c count_nulls.test_load_mode=$(TEST_LOAD_SOURC TEST_SCHEMA ?= export PGOPTIONS := $(PGOPTIONS) -c count_nulls.test_schema=$(TEST_SCHEMA) +# TEST_EXISTING_DEPLOY: in 'existing' mode (TEST_LOAD_SOURCE=existing), how +# was the extension actually deployed onto the cluster before this run +# started? Unlike TEST_LOAD_SOURCE/TEST_SCHEMA above, this does not select or +# change any install behavior - it only tells test/deps.sql's existing-mode +# assertion where to cross-check "what does the cluster consider count_nulls's +# current version" against the actually-installed extversion: +# - filesystem (default): a real .control file is on disk (a real `make +# install`, or a pg_upgrade'd cluster carrying one over) - cross-check +# against pg_available_extensions.default_version, which reads .control +# files directly off disk. +# - pgtle: count_nulls was registered purely through pg_tle's +# database-backed catalog (see the pg-tle-test CI job), never touching +# the filesystem. pg_available_extensions does NOT see pg_tle +# registrations at all - it only ever reads .control files off disk - so +# its default_version comes back NULL for a pg_tle-only extension even +# though a version-less CREATE EXTENSION resolves correctly through +# pg_tle. pg_tle ships its own separate, non-integrated analog instead: +# pgtle.available_extensions(), a C function whose own doc comment in +# pg_tle's tleextension.c says "The system view pg_available_extensions +# provides a user interface to this SRF" - i.e. pg_tle's SRF is modeled +# on pg_available_extensions, but pg_tle never hooks or populates the +# real view itself. In this mode, cross-check against pg_tle's SRF +# instead. +# +# Deliberately the SAME env var name bin/test_existing already reads (to +# decide between `make test`/`verify-results` and `make testdeps`/ +# `installcheck`), not a second variable: it's already exported to any `make` +# invocation bin/test_existing spawns as a child process, so no extra +# plumbing is needed to get it here. +TEST_EXISTING_DEPLOY ?= filesystem +ifeq ($(filter $(TEST_EXISTING_DEPLOY),filesystem pgtle),) +$(error TEST_EXISTING_DEPLOY must be 'filesystem' or 'pgtle', got '$(TEST_EXISTING_DEPLOY)') +endif +export PGOPTIONS := $(PGOPTIONS) -c count_nulls.test_existing_deploy=$(TEST_EXISTING_DEPLOY) + # Convenience wrapper: `make test-update` == `make test TEST_LOAD_SOURCE=update`. # Must recurse (a fresh $(MAKE)) rather than depend on `test`, so the # parse-time TEST_LOAD_SOURCE conditional above re-evaluates with update set. diff --git a/bin/test_existing b/bin/test_existing index 7e1b448..2d8b122 100755 --- a/bin/test_existing +++ b/bin/test_existing @@ -46,6 +46,28 @@ # non-CASCADE DROP EXTENSION fails, and actively PROVE that (see # bin/test_existing.sql/assert_guard.sql): if the drop unexpectedly succeeds, # this script fails CI rather than silently passing. +# +# TEST_EXISTING_DEPLOY (optional environment variable, read by run_suite): +# unset/filesystem (default): count_nulls was deployed the normal way (a +# real `make install`). run_suite uses `make test` / `make verify-results` +# as it always has. +# pgtle: count_nulls was deployed purely through pg_tle's database-backed +# catalog (see the pg-tle-test CI job), never `make install`ed onto the +# filesystem. pgxntool's `test` target unconditionally depends on +# `install` (TEST_DEPS += install installcheck in base.mk), so naively +# calling `make test` here would silently `make install` count_nulls onto +# disk, creating a filesystem count_nulls.control that would shadow the +# pg_tle registration on any FUTURE version-less CREATE EXTENSION in the +# same cluster - defeating the entire point of a pg_tle-only proof, and +# doing so without ever raising an error. In this mode run_suite instead +# calls `make testdeps` + `make installcheck` directly (neither depends on +# `install` - see base.mk's `testdeps: pgtap` and +# `installcheck: $(TEST_RESULT_FILES) ...`), then runs +# pgxntool/verify-results-pgtap.sh directly against the results - the same +# check `make verify-results` runs, but the script itself takes only +# TESTOUT as an argument and has no dependency on the `test` target, so +# calling it directly gets the same pass/fail logic without the +# `install` side effect. set -euo pipefail # Run from the repository root (where `make` works and test paths resolve), @@ -213,8 +235,21 @@ run_suite() { # would re-run FRESH (against a new throwaway DB) instead of # verifying THIS existing database. local existing_args="TEST_LOAD_SOURCE=existing TEST_SCHEMA=$schema CONTRIB_TESTDB=$db EXTRA_REGRESS_OPTS=--use-existing PGXNTOOL_ENABLE_TEST_BUILD=no" - make test $existing_args - make verify-results $existing_args + if [ "${TEST_EXISTING_DEPLOY:-}" = pgtle ]; then + # pg_tle mode: see the TEST_EXISTING_DEPLOY comment in the file header. + # `make test`/`make verify-results` are NOT used here because `test` + # unconditionally depends on `install` (a real filesystem install) - the + # whole point of this mode is proving count_nulls is never touched on + # disk. `testdeps` and `installcheck` carry no such dependency. + make testdeps $existing_args + make installcheck $existing_args + local testout + testout=$(make -s print-TESTOUT $existing_args 2>/dev/null | sed -n 's/.*set to "\(.*\)"$/\1/p') + pgxntool/verify-results-pgtap.sh "$testout" + else + make test $existing_args + make verify-results $existing_args + fi } usage() { diff --git a/test/deps.sql b/test/deps.sql index b86701a..ed39c14 100644 --- a/test/deps.sql +++ b/test/deps.sql @@ -87,17 +87,48 @@ SET client_min_messages = NOTICE; * real extension matching this run's TEST_SCHEMA (or the default location, * if empty) before getting here, so the rest of the suite sees the same * schema regardless of mode. + * + * The "current version" half of that assertion (v_default below) has two + * sources depending on count_nulls.test_existing_deploy (see the + * TEST_EXISTING_DEPLOY comment in the Makefile): + * - filesystem (default): pg_available_extensions.default_version, read + * straight from a real .control file on disk. + * - pgtle: count_nulls was registered purely through pg_tle's + * database-backed catalog, never touching the filesystem. + * pg_available_extensions does NOT see pg_tle registrations at all - it + * only ever reads .control files off disk - so it comes back NULL here + * even though CREATE EXTENSION correctly resolves the default version + * through pg_tle. pg_tle ships its own separate, non-integrated analog + * for this: pgtle.available_extensions() (see pg_tle's tleextension.c, + * which documents pg_available_extensions as merely modeled on this SRF, + * not backed by it). Use that instead when running under pg_tle, rather + * than weakening the check for the filesystem case. */ DO $$ DECLARE v_installed text := (SELECT extversion FROM pg_extension WHERE extname = 'count_nulls'); - v_default text := (SELECT default_version FROM pg_available_extensions WHERE name = 'count_nulls'); + v_deploy text := current_setting('count_nulls.test_existing_deploy'); + v_default text; BEGIN IF v_installed IS NULL THEN RAISE EXCEPTION 'count_nulls.test_load_mode=existing but count_nulls is not installed'; END IF; + + IF v_deploy = 'pgtle' THEN + SELECT default_version INTO v_default + FROM pgtle.available_extensions() WHERE name = 'count_nulls'; + ELSIF v_deploy = 'filesystem' THEN + SELECT default_version INTO v_default + FROM pg_available_extensions WHERE name = 'count_nulls'; + ELSE + RAISE EXCEPTION + 'count_nulls.test_existing_deploy must be ''filesystem'' or ''pgtle'', got ''%''' + , v_deploy + ; + END IF; + IF v_installed IS DISTINCT FROM v_default THEN - RAISE EXCEPTION 'count_nulls installed at % but default_version is %', v_installed, v_default; + RAISE EXCEPTION 'count_nulls installed at % but default_version (deploy=%) is %', v_installed, v_deploy, v_default; END IF; END $$;