diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dbfdb7..561a8fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,33 @@ jobs: - name: Test on PostgreSQL ${{ matrix.pg }}, across every TEST_SCHEMA value run: make test-schema-all + # Proves the in-place extension update path: CREATE EXTENSION at the + # oldest version we still ship a full install script for (0.9.6), then + # ALTER EXTENSION UPDATE (no pg_upgrade, same PostgreSQL), all within + # test/install/load.sql's own committed session (TEST_LOAD_SOURCE=update - + # see the Makefile) - then run the FULL suite against the updated + # database via the SAME expected output as a fresh install (see + # test/README.md for how the suite stays schema/load-mode invariant). + # No external script needed for this leg: unlike a real pg_upgrade + # (pg-upgrade-test), the update itself is just SQL, so test/install can + # do the whole thing inside one pg_regress invocation. + extension-update-test: + strategy: + matrix: + pg: [18, 17, 16, 15, 14, 13, 12, 11, 10] + name: ⬆️ Extension update test on PostgreSQL ${{ matrix.pg }} + runs-on: ubuntu-latest + container: pgxn/pgxn-tools + steps: + - name: Start PostgreSQL ${{ matrix.pg }} + run: pg-start ${{ matrix.pg }} + - name: Check out the repo + uses: actions/checkout@v4 + - name: Install count_nulls + run: make install + - name: Update 0.9.6 -> current and run the suite + run: make verify-results TEST_LOAD_SOURCE=update + pg-tle-test: strategy: matrix: diff --git a/Makefile b/Makefile index 89e001d..1b10f15 100644 --- a/Makefile +++ b/Makefile @@ -32,8 +32,8 @@ TEST_SCHEMA ?= export PGOPTIONS := $(PGOPTIONS) -c count_nulls.test_schema=$(TEST_SCHEMA) # Every TEST_SCHEMA value the suite is tested against. A single source so -# test-schema-all and CI (once collapsed - see the "why not a CI matrix" -# note below) can't silently drift onto different sets. +# test-schema-all/test-update-schema-all and CI can't silently drift onto +# different sets. TEST_SCHEMA_VALUES = "" Quoted # TEST_SCHEMA is deliberately NOT a CI matrix dimension: unlike PostgreSQL @@ -57,3 +57,35 @@ test-schema-all: echo "=== TEST_SCHEMA=$$schema ==="; \ $(MAKE) test TEST_SCHEMA="$$schema" || exit 1; \ done + +# TEST_LOAD_SOURCE selects how test/install/load.sql installs count_nulls +# for the WHOLE test run: +# - fresh (default): CREATE EXTENSION count_nulls (current version). +# - update: CREATE EXTENSION at the oldest version we still ship a full +# install script for (0.9.6), then ALTER EXTENSION UPDATE to current - +# committed, since test/install runs outside any per-test rolled-back +# transaction (see pgxntool/README.asc's Update & Upgrade (U&U) Testing +# section for why the commit matters). +# - existing: count_nulls is already installed (a real `pg_upgrade` run, +# external to this invocation) - test/install only asserts it's present +# and current, it does not drop/create/update anything. Meant to be run +# with CONTRIB_TESTDB= EXTRA_REGRESS_OPTS=--use-existing against a +# real database, not via a make wrapper here. +# +# "update" (this) is extension-level (ALTER EXTENSION UPDATE); "upgrade" is +# cluster-level (pg_upgrade) - 'existing' is how that axis is exercised. +# +# Propagated the same way as TEST_SCHEMA: via the count_nulls.test_load_mode +# GUC, exported unconditionally through PGOPTIONS, read without missing_ok. +TEST_LOAD_SOURCE ?= fresh +ifeq ($(filter $(TEST_LOAD_SOURCE),fresh update existing),) +$(error TEST_LOAD_SOURCE must be 'fresh', 'update' or 'existing', got '$(TEST_LOAD_SOURCE)') +endif +export PGOPTIONS := $(PGOPTIONS) -c count_nulls.test_load_mode=$(TEST_LOAD_SOURCE) + +# 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. +.PHONY: test-update +test-update: + $(MAKE) test TEST_LOAD_SOURCE=update diff --git a/test/install/load.sql b/test/install/load.sql index 031eafc..5d6d1bf 100644 --- a/test/install/load.sql +++ b/test/install/load.sql @@ -34,4 +34,65 @@ CREATE SCHEMA IF NOT EXISTS :"schema"; SET search_path = :"schema"; \endif +/* + * Mode selection: 'fresh' installs the current version directly; 'update' + * installs the oldest version we still ship a full script for (0.9.6) and + * runs ALTER EXTENSION UPDATE, committed (this file runs outside any + * per-test rolled-back transaction, unlike the old test/deps.sql approach - + * see pgxntool/README.asc's U&U section for why the commit matters); + * 'existing' asserts count_nulls is already installed (a real `pg_upgrade` + * run, external to this invocation) and touches nothing. + * + * Read without missing_ok, same reasoning as count_nulls.test_schema above. + */ +SELECT current_setting('count_nulls.test_load_mode') AS count_nulls_test_load_mode +\gset + +DO $$ +BEGIN + IF current_setting('count_nulls.test_load_mode') NOT IN ('fresh', 'update', 'existing') THEN + RAISE EXCEPTION + 'count_nulls.test_load_mode must be ''fresh'', ''update'' or ''existing'', got ''%''' + , current_setting('count_nulls.test_load_mode') + ; + END IF; +END +$$; + +SELECT :'count_nulls_test_load_mode' = 'update' AS count_nulls_update_mode +\gset +SELECT :'count_nulls_test_load_mode' = 'existing' AS count_nulls_existing_mode +\gset + +\if :count_nulls_existing_mode +/* + * Already installed by something external to this pg_regress invocation + * (a real pg_upgrade run - see the pg-upgrade-test CI job). Only assert + * it's present and at the current version; do NOT drop/create/update it - + * the whole point of this mode is testing the REAL migrated objects. + */ +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'); +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_installed IS DISTINCT FROM v_default THEN + RAISE EXCEPTION 'count_nulls installed at % but default_version is %', v_installed, v_default; + END IF; +END +$$; +\elif :count_nulls_update_mode +CREATE EXTENSION count_nulls VERSION '0.9.6'; +/* + * Suppress the "already installed, no update" NOTICE class of messages any + * update script might emit. + */ +SET client_min_messages = WARNING; +ALTER EXTENSION count_nulls UPDATE; +SET client_min_messages = NOTICE; +\else CREATE EXTENSION count_nulls; +\endif