Skip to content

Add TEST_SCHEMA test-harness switch #295

Add TEST_SCHEMA test-harness switch

Add TEST_SCHEMA test-harness switch #295

Workflow file for this run

name: CI
on:
push:
branches:
- master
pull_request:
env:
PGUSER: postgres
jobs:
# Cheap gate that lets the heavy jobs below skip themselves on commits
# that touch only docs. This must run on every push/pull_request (no
# paths-ignore on the workflow itself), otherwise the required
# all-checks-passed check would never report on doc-only pushes and get
# stuck Pending in branch protection.
#
# It also derives, from a SINGLE set of constants, the supported-PostgreSQL-
# major lists that the test / extension-update / stepwise jobs consume (see
# the "Derive ..." step). Because those lists live here and every heavy job
# already needs this job, adding a PG major is a one-line edit with no new job.
changes:
name: 🔍 Detect changes & derive PG matrix
runs-on: ubuntu-latest
permissions:
# Needed only by "Find the last commit where real code changed" below,
# to query the Actions API for a past run's conclusion/URL.
actions: read
outputs:
docs_only: ${{ steps.diff.outputs.docs_only }}
# Set when docs_only is true: whether there's a real "last tested" commit
# to report at all (false), or this entire PR/push chain has never
# touched anything but docs (true, docs_only_pr) -- see the "Find the
# last commit..." step below for how these are derived.
docs_only_pr: ${{ steps.last_tested.outputs.docs_only_pr }}
last_tested_sha: ${{ steps.last_tested.outputs.last_tested_sha }}
last_tested_conclusion: ${{ steps.last_tested.outputs.last_tested_conclusion }}
last_tested_url: ${{ steps.last_tested.outputs.last_tested_url }}
# Derived PG-major lists (see the "Derive ..." step for their meaning).
supported_pg: ${{ steps.pg.outputs.supported_pg }}
update_pg: ${{ steps.pg.outputs.update_pg }}
climb_pg: ${{ steps.pg.outputs.climb_pg }}
legacy_pg: ${{ steps.pg.outputs.legacy_pg }}
# The `test` job's full {pg, test_schema} matrix (see the "Derive ..."
# step for why this is a pure include list rather than a second bare
# `test_schema` list crossed against `pg`).
test_matrix: ${{ steps.pg.outputs.test_matrix }}
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
# Full history needed so BASE and HEAD below are both reachable
# for `git diff`.
fetch-depth: 0
- name: Compute per-push changed files
id: diff
run: |
if [ "${{ github.event_name }}" = "pull_request" ] && \
[ "${{ github.event.action }}" = "synchronize" ] && \
[ -n "${{ github.event.before }}" ]; then
# A push to an already-open PR: before/after give the true
# per-push diff, same as for a branch push.
BASE="${{ github.event.before }}"
HEAD="${{ github.event.after }}"
elif [ "${{ github.event_name }}" = "pull_request" ]; then
# First run for this PR (opened/reopened/etc, or synchronize
# without a usable before): fall back to the whole base...head
# diff.
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
BASE="${{ github.event.before }}"
HEAD="${{ github.event.after }}"
fi
echo "base=$BASE"
echo "head=$HEAD"
# Fail-safe: a missing HEAD, or an all-zeros BASE (e.g. a new
# branch's first push, where GitHub reports no prior commit),
# means we can't compute a real diff. Run the full matrix rather
# than risk skipping tests.
if [ -z "$HEAD" ] || [ -z "$BASE" ] || [[ "$BASE" =~ ^0+$ ]]; then
echo "docs_only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED=$(git diff --name-only "$BASE" "$HEAD" || echo __DIFF_FAILED__)
DOCS_ONLY=true
if [ "$CHANGED" = "__DIFF_FAILED__" ] || [ -z "$CHANGED" ]; then
DOCS_ONLY=false
else
while IFS= read -r f; do
if ! [[ "$f" =~ \.(md|asc)$ ]]; then
DOCS_ONLY=false
break
fi
done <<< "$CHANGED"
fi
echo "changed files:"
echo "$CHANGED"
echo "docs_only=$DOCS_ONLY" >> "$GITHUB_OUTPUT"
- name: Find the last commit where real code changed
# This push is docs-only, so the heavy jobs below are about to skip --
# meaning THIS run's checks don't cover the code currently on this
# branch. Someone reviewing before merge needs to know whether that
# code was actually fully tested somewhere, and whether it was green.
# Walk backward from HEAD (bounded by the PR's base, or a generous
# depth cap for a direct push) to the newest commit whose OWN diff
# from its parent touched something other than docs -- every commit
# strictly after that one only changed docs, so that commit's CI run
# is still the authoritative test of the code on this branch now.
if: steps.diff.outputs.docs_only == 'true'
id: last_tested
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
# actions/checkout defaults to checking out an EPHEMERAL MERGE
# COMMIT for pull_request events (refs/pull/N/merge), not the PR's
# real head commit -- `git rev-parse HEAD` would return that merge
# commit's SHA, and walking `^` from it follows the wrong (base
# branch) first parent, landing on an unrelated commit entirely
# (confirmed hitting exactly this in real CI). Use the actual PR
# head SHA directly instead, same event-type split already used in
# "Compute per-push changed files" above.
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
HEAD_SHA="$PR_HEAD_SHA"
else
HEAD_SHA="${{ github.sha }}"
fi
if [ -n "$PR_BASE_SHA" ] && ! [[ "$PR_BASE_SHA" =~ ^0+$ ]]; then
BOUNDARY=$(git merge-base "$PR_BASE_SHA" "$HEAD_SHA")
else
# Not a PR (e.g. a direct push to master): no PR base to bound
# the walk against, so cap it at a generous depth instead of
# walking arbitrarily far into history.
BOUNDARY=$(git rev-list --max-count=1 --skip=100 "$HEAD_SHA" 2>/dev/null || echo "$HEAD_SHA")
fi
commit="$HEAD_SHA"
last_code_commit=""
while [ "$commit" != "$BOUNDARY" ]; do
parent=$(git rev-parse "$commit^" 2>/dev/null) || break
changed=$(git diff --name-only "$parent" "$commit")
all_docs=true
while IFS= read -r f; do
[ -z "$f" ] && continue
if ! [[ "$f" =~ \.(md|asc)$ ]]; then all_docs=false; break; fi
done <<< "$changed"
if [ "$all_docs" = true ]; then
commit="$parent"
else
last_code_commit="$commit"
break
fi
done
if [ -z "$last_code_commit" ]; then
# Reached the PR's own base (or the depth cap) without ever
# finding a commit that changed code: this whole PR/push chain
# is docs-only, so there is no "last tested" run to point to.
echo "docs_only_pr=true" >> "$GITHUB_OUTPUT"
echo "This entire PR/push contains only documentation changes -- no code testing applies."
exit 0
fi
echo "docs_only_pr=false" >> "$GITHUB_OUTPUT"
echo "last_tested_sha=$last_code_commit" >> "$GITHUB_OUTPUT"
echo "last code-changing commit: $last_code_commit"
# Find that commit's most recent completed run of this same "CI"
# workflow (there can be other workflows -- e.g. Claude Code Review
# -- triggered on the same SHA, hence the name filter).
RUN_JSON=$(gh api "repos/$REPO/actions/runs?head_sha=$last_code_commit&status=completed" --jq \
'[.workflow_runs[] | select(.name == "CI")] | sort_by(.run_started_at) | last // empty' 2>/dev/null || echo "")
if [ -z "$RUN_JSON" ] || [ "$RUN_JSON" = "null" ]; then
echo "last_tested_conclusion=unknown" >> "$GITHUB_OUTPUT"
echo "last_tested_url=" >> "$GITHUB_OUTPUT"
echo "No completed CI run found for $last_code_commit"
else
echo "last_tested_conclusion=$(jq -r '.conclusion' <<<"$RUN_JSON")" >> "$GITHUB_OUTPUT"
echo "last_tested_url=$(jq -r '.html_url' <<<"$RUN_JSON")" >> "$GITHUB_OUTPUT"
fi
- name: Derive the supported-PostgreSQL-major lists
id: pg
run: |
# Spending 20+ lines to replace a handful of version references looks
# silly on the surface, but the point is CONSISTENCY: every job -- the
# fresh-install `test` matrix, the `extension-update-test` matrix and
# the stepwise climb -- derives its PostgreSQL set from this ONE source,
# so they cannot drift onto different version lists.
#
# SINGLE SOURCE OF TRUTH for the supported PostgreSQL majors. To add
# or drop a PG major, edit ONLY the three constants below; the test,
# extension-update-test and pg-upgrade-stepwise jobs all derive their
# version lists from them (adding the newest major is a one-line NEWEST
# bump). Do NOT hardcode a supported major in any job matrix or loop.
#
# NEWEST -- highest PostgreSQL major cat_tools is tested on.
# CURRENT_FLOOR -- oldest major the CURRENT extension version supports:
# the 0.2.3->0.3.0 update runs ALTER TYPE ... ADD
# VALUE, which cannot run in a pre-PG12 transaction.
# LEGACY_FLOOR -- oldest major the pre-0.2.2 install scripts still
# load on (PG11 added pg_attribute.attmissingval and
# PG12+ exposes the oid system column in SELECT *, both
# of which those old scripts trip over). Only the
# update and stepwise paths reach back this far.
NEWEST=18
CURRENT_FLOOR=12
LEGACY_FLOOR=10
# supported = CURRENT_FLOOR..NEWEST (newest-first). The FRESH-install
# matrix (test job) runs exactly these.
supported=$(seq "$NEWEST" -1 "$CURRENT_FLOOR")
# update = supported plus the legacy floor: the extension-update job
# additionally exercises the PG10-only pre-0.2.2 update scripts.
update="$supported $LEGACY_FLOOR"
# climb = LEGACY_FLOOR+1 .. NEWEST (ascending). The stepwise job starts
# one cluster on the legacy floor and binary-pg_upgrades through every
# later major in turn, so its targets are every major above the floor.
climb=$(seq $((LEGACY_FLOOR + 1)) "$NEWEST")
# Emit a JSON array from a list of ints, for the job matrices to
# consume with fromJSON (GitHub evaluates a literal dollar-brace
# expression even inside a run block, so none is written here).
json() { printf '%s\n' "$@" | paste -sd, - | sed 's/^/[/; s/$/]/'; }
echo "supported_pg=$(json $supported)" >> "$GITHUB_OUTPUT"
echo "update_pg=$(json $update)" >> "$GITHUB_OUTPUT"
echo "legacy_pg=$LEGACY_FLOOR" >> "$GITHUB_OUTPUT"
# Space-separated for direct iteration in the stepwise bash loop.
echo "climb_pg=$(echo $climb)" >> "$GITHUB_OUTPUT"
# test_matrix: the `test` job's ACTUAL matrix, one {pg, test_schema}
# object per combination, consumed via a pure `strategy.matrix.include`
# (no separate bare `pg:` list) so this is the ONLY source of truth for
# what that job runs -- see the `test` job for why a pure include list,
# rather than crossing a second `test_schema` list against `pg`, is
# required here: GitHub's matrix `include` MERGES an object into an
# existing combination that already matches all of the object's keys
# (rather than adding a new one), so pairing a `test_schema` value with
# a `pg` value already in a bare list would silently REPLACE that
# version's default (TEST_SCHEMA empty) run instead of adding a second,
# separate one. A pure include list sidesteps that entirely: every
# object here is its own job, unconditionally.
#
# Every supported major runs once with TEST_SCHEMA empty (the default --
# CREATE EXTENSION cat_tools with no schema targeting). Exactly one
# extra leg, on the newest major, additionally sets TEST_SCHEMA to a
# mixed-case name that REQUIRES SQL identifier quoting -- proving the
# make -> PGOPTIONS -> GUC -> psql quoted-identifier pipeline handles
# such a name correctly end to end (see test/install/load.sql for what
# TEST_SCHEMA does and does NOT do). Only one leg needs it: the quoting
# pipeline doesn't depend on the PostgreSQL major at all, so crossing it
# against every supported version would add CI cost with no added
# confidence.
QUOTING_SCHEMA='CatToolsSchema'
test_matrix=$(
{
for pg in $supported; do
jq -n --arg pg "$pg" '{pg: ($pg | tonumber), test_schema: ""}'
done
jq -n --arg pg "$NEWEST" --arg test_schema "$QUOTING_SCHEMA" \
'{pg: ($pg | tonumber), test_schema: $test_schema}'
} | jq -s -c .
)
echo "test_matrix=$test_matrix" >> "$GITHUB_OUTPUT"
# ===========================================================================
# Test strategy
#
# A cat_tools install can be arrived at several ways, each of which can break
# differently, so each is exercised by its own job below (the per-job comments
# carry the details; this is the big picture):
#
# test -- FRESH install: CREATE EXTENSION at the current
# version on every supported PostgreSQL, with
# TEST_SCHEMA empty (the baseline a brand-new user
# gets), PLUS one extra leg, on the newest major,
# with TEST_SCHEMA set to a quoting-requiring
# schema name.
# extension-update-test -- IN-PLACE update: CREATE EXTENSION at an OLD version
# then ALTER EXTENSION UPDATE (same PostgreSQL, no
# pg_upgrade).
# pg-upgrade-test -- BINARY pg_upgrade, SINGLE jump: install an OLD
# version on an OLD major, binary-upgrade the cluster
# straight to a NEWER major (skipping intermediate
# majors), then update the extension. Proves objects
# created on an old server work when read on a new one.
# pg-upgrade-stepwise -- BINARY pg_upgrade, EVERY major in sequence: one
# cluster climbing 10 -> 11 -> ... -> 18, exercising
# each individual major-to-major transition in turn.
#
# Supported update origins are 0.2.0, 0.2.1 and 0.2.2 (0.1.x is unsupported).
# 0.2.0 and 0.2.1 are BOTH tested as origins because ALTER EXTENSION UPDATE
# takes the shortest path: a 0.2.0 origin updates straight through the
# 0.2.0--0.2.2 script and never touches 0.2.1--0.2.2, so starting at 0.2.1 is
# the only way to exercise the 0.2.1--0.2.2 update script (spelled out at the
# pg-upgrade-test matrix).
#
# Two PostgreSQL-version floors shape the matrices:
# - The pre-0.2.2 install scripts (0.2.0 / 0.2.1) load ONLY on PG10: PG11
# added pg_attribute.attmissingval and PG12+ exposes the oid system column
# in SELECT *, both of which those old scripts trip over. So a 0.2.0 / 0.2.1
# origin can only start on PG10.
# - The current version needs PG12+: the 0.2.3->0.3.0 update runs
# ALTER TYPE ... ADD VALUE, which cannot run in a pre-PG12 transaction (and
# an extension update script is one).
#
# KEY invariant: every pg_upgrade leg CLIMBS to a PostgreSQL that supports the
# current version, then updates to the current version and runs the full suite
# -- no leg stops short. A PG10/11 origin simply HOLDS the extension at 0.2.3
# (the highest version reachable on those majors) until the cluster reaches
# PG12+, where it is updated to the current version.
# ===========================================================================
test:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
# {pg, test_schema} pairs from the single source in the changes job.
# A pure include list (no separate bare `pg:` list) -- see the
# "Derive ..." step's test_matrix comment for why.
include: ${{ fromJSON(needs.changes.outputs.test_matrix) }}
# Append the TEST_SCHEMA value to the check name only on the one leg that
# sets it, so the other legs' names are unchanged.
name: 🐘 PostgreSQL ${{ matrix.pg }}${{ matrix.test_schema != '' && format(' (TEST_SCHEMA={0})', matrix.test_schema) || '' }}
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@v6
- name: Install rsync and server headers
# postgresql-server-dev-NN provides catalog/pg_class.h, which
# test/gen-relkinds.sh reads for the relkind drift check in
# test/sql/relation__.sql.
run: apt-get install -y rsync postgresql-server-dev-${{ matrix.pg }}
- name: Test on PostgreSQL ${{ matrix.pg }}
run: |
# Fail if the relkind drift source is empty (headers missing): the
# drift check must actually run on every version, not pass silently.
make check-relkind-source
# verify-results is the real gate: base.mk declares `verify-results:
# test`, so this runs the suite via its `test` prerequisite and then
# checks the pgtap/regression.diffs. A bare `make test` is redundant here
# and would not gate anyway -- it never exits non-zero on regressions
# (pgxntool marks installcheck `.IGNORE`), so failures would pass silently.
# TEST_SCHEMA is empty for every leg except the one dedicated
# quoting-requiring-schema leg (see the changes job's test_matrix).
make verify-results TEST_SCHEMA="${{ matrix.test_schema }}"
# Style linter (https://github.com/Postgres-Extensions/linter, vendored at
# .vendor/linter). Deliberately checked out WITHOUT submodules -- `make
# lint` is the same command a developer runs locally, and lint.mk
# self-initializes the submodule on first use (see its comment). Using the
# exact same entry point here is what actually proves that self-init works,
# rather than papering over it with a submodules: true checkout. The
# linter's own test suite (fixtures + scanner edge cases) is that repo's
# own CI's job, not this one's. No PostgreSQL needed -- sql-lint is a
# standalone Perl script -- so this doesn't use the pgxn-tools container.
lint:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
name: 🧹 SQL Lint
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v6
- name: Lint SQL
run: make lint
# Proves cat_tools survives a BINARY pg_upgrade (in-place catalog migration to a
# newer PostgreSQL major), not just an in-place extension update. Each leg is a
# SINGLE jump: install an old cat_tools on an old cluster, plant a dependency
# guard, binary-pg_upgrade STRAIGHT to a newer major (skipping any intermediate
# majors), update the extension to the current version, then run the suite
# against the REAL migrated objects in "existing" mode. Catches view/catalog
# breakage that only surfaces when objects created on an old server are read on a
# new one. Every leg's new_pg supports the current version (PG12+); the
# version-by-version climb through EVERY major is pg-upgrade-stepwise.
pg-upgrade-test:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
include:
# old_pg=10 origins: the FULL real-world journey for a user on old
# PostgreSQL + old cat_tools. old_install is 0.2.0/0.2.1 -- the only
# versions that install on PG10 (PG11 added pg_attribute.attmissingval,
# PG12+ exposes oid in SELECT *, so 0.2.0/0.2.1 install ONLY on PG10).
# The BRIDGE update to 0.2.3, run on PG10 BEFORE pg_upgrade, makes the
# objects pg_upgrade-safe: the shipped 0.2.0->0.2.2 / 0.2.1->0.2.2
# scripts leave fresh-vs-update divergences (relhasoids/relhaspkey never
# stripped from _cat_tools.pg_class_v via the no-op `!= ANY` omit_column),
# and the 0.2.2->0.2.3 update repairs them (its pg_class_v DROP+CREATE
# rebuild strips the dropped columns). 0.2.3 is the highest version a
# PG10 cluster can hold (0.2.3->0.3.0 needs PG12+); the single jump lands
# on a PG12+ major, where the post-upgrade step updates 0.2.3 -> current.
#
# BOTH 0.2.0 and 0.2.1 appear as origins because ALTER EXTENSION UPDATE
# takes the SHORTEST path: a 0.2.0 origin updates straight through the
# 0.2.0--0.2.2 script and never exercises 0.2.1--0.2.2, so starting at
# 0.2.1 is the ONLY way to exercise the 0.2.1--0.2.2 update script.
- old_pg: "10"
new_pg: "18"
old_install: "0.2.0"
bridge_to: "0.2.3"
- old_pg: "10"
new_pg: "18"
old_install: "0.2.1"
bridge_to: "0.2.3"
# old_pg>=11: 0.2.0/0.2.1 do not install on PG11+, and such users are
# already on 0.2.2+. A FRESH 0.2.2 install already strips relhasoids/
# relhaspkey (fixed `!= ALL` omit_column), so it is pg_upgrade-safe with
# no bridge. pg_upgrade to a PG12+ major, then update to the current
# version.
- old_pg: "11"
new_pg: "12"
old_install: "0.2.2"
bridge_to: ""
- old_pg: "11"
new_pg: "18"
old_install: "0.2.2"
bridge_to: ""
- old_pg: "12"
new_pg: "13"
old_install: "0.2.2"
bridge_to: ""
- old_pg: "12"
new_pg: "18"
old_install: "0.2.2"
bridge_to: ""
# Include old_install in the name so matrix legs that differ only by origin
# version (e.g. the two 10 → 18 legs from 0.2.0 and 0.2.1) get distinct,
# unambiguous check names.
name: 🔄 Binary pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }} (from ${{ matrix.old_install }})
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
# Both clusters must use the same initdb options so pg_upgrade sees
# consistent settings (checksums, auth) on old and new clusters.
INITDB_OPTS: --data-checksums --auth trust
steps:
- name: Start PostgreSQL ${{ matrix.old_pg }}
run: pg-start ${{ matrix.old_pg }}
- name: Check out the repo
uses: actions/checkout@v6
- name: Install rsync
run: apt-get install -y rsync
- name: Recreate old cluster with data checksums enabled
run: .github/scripts/pg_upgrade_cluster recreate-old ${{ matrix.old_pg }}
- name: Install cat_tools into old cluster
run: make install
- name: Prepare the old cluster (install + bridge + dependency guard)
# prepare-old installs cat_tools at old_install and, when bridge_to is set
# (old_pg=10), ALTER EXTENSION UPDATEs to it BEFORE pg_upgrade -- the
# bridge that makes the views pg_upgrade-safe (see prepare-old in the
# script). It then plants + proves the dependency guard so the later
# existing-mode run cannot silently drop+reinstall and test a fresh
# install instead. The extension is always older than the current version,
# so the post-upgrade ALTER EXTENSION UPDATE always has real work to do.
run: >-
bin/test_existing prepare-old cat_tools_upgrade
"${{ matrix.old_install }}" "${{ matrix.bridge_to }}"
- name: Install PostgreSQL ${{ matrix.new_pg }}
run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }}
- name: Install cat_tools into new cluster
# PG_CONFIG must be specified explicitly: at this point both old and new
# PostgreSQL are installed, and the default pg_config on PATH may not be
# the new version's.
run: make install PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config
- name: Stop old cluster, binary pg_upgrade to PostgreSQL ${{ matrix.new_pg }}, start new cluster
run: .github/scripts/pg_upgrade_cluster upgrade ${{ matrix.old_pg }} ${{ matrix.new_pg }}
- name: Update the pg_upgraded extension to the current version
# Exercises ALTER EXTENSION UPDATE on genuinely pg_upgraded objects (the
# extension binary pg_upgrade just migrated), running the version-to-version
# scripts up to the current version. The 0.2.3 -> 0.3.0 script uses
# ALTER TYPE ... ADD VALUE, which is why every leg's new_pg is >= 12.
run: bin/test_existing update cat_tools_upgrade
- name: Run the suite against the pg_upgraded database (existing mode)
# run-suite asserts the version, re-proves the dependency guard still
# blocks a non-CASCADE drop (i.e. it survived pg_upgrade), then runs the
# suite against the REAL pg_upgraded + updated database via --use-existing
# (so pg_regress does not drop/recreate it) -- a plain fresh `make test`
# would silently test a fresh install instead of the migrated objects.
run: bin/test_existing run-suite cat_tools_upgrade
# Proves cat_tools survives EVERY individual major-to-major binary pg_upgrade
# transition, not just a single big jump (that is pg-upgrade-test): ONE cluster
# that starts on the oldest origin and climbs through every major in sequence.
# Installs 0.2.0 on PG10, bridges to 0.2.3 (the highest version a PG10 cluster
# can hold -- see the pg-upgrade-test matrix comment), plants the dependency
# guard, then binary-pg_upgrades 10->11->12->...->18 ONE major at a time. The
# extension is HELD at 0.2.3 for the 10->11 and 11->12 steps (PG11 cannot run
# the 0.2.3->0.3.0 update, which needs PG12+); on reaching PG12 it is updated
# 0.2.3 -> the current version, and from PG12 on the full suite runs in existing
# mode at EVERY major (12..18) against the REAL pg_upgraded objects, re-proving
# the guard survived each step. CI-heavy on purpose: it installs PostgreSQL 10
# through 18, performs 8 sequential pg_upgrades, and runs the suite 7 times.
pg-upgrade-stepwise:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
name: 🪜 Stepwise pg_upgrade 10 → 18 (from 0.2.0)
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
# Every pg_upgrade in the climb pairs two clusters that must share initdb
# options (checksums, auth), exactly as in pg-upgrade-test.
INITDB_OPTS: --data-checksums --auth trust
DB: cat_tools_stepwise
# Climb targets and starting (legacy) major, from the single source in the
# changes job -- so a new PG major joins the climb with no edit here.
CLIMB_PG: ${{ needs.changes.outputs.climb_pg }}
LEGACY_PG: ${{ needs.changes.outputs.legacy_pg }}
steps:
- name: Start PostgreSQL 10
run: pg-start 10
- name: Recreate the PG10 cluster with data checksums enabled
run: |
pg_ctlcluster 10 test stop
pg_dropcluster 10 test
# -p 5432: force the well-known port (see pg-upgrade-test for why).
pg_createcluster -p 5432 10 test -- $INITDB_OPTS
pg_ctlcluster 10 test start
pg_isready -t 30
- name: Check out the repo
uses: actions/checkout@v6
- name: Install rsync
# Must NOT install a newer server-dev yet: `make install` just below runs
# with the DEFAULT pg_config, which must still resolve to PG10. Each newer
# major's server-dev is installed inside the climb loop, at which point the
# default pg_config advances with it (the final-major suite reads that
# version's catalog header for the relkind drift check; see the test job).
run: apt-get install -y rsync
- name: Install cat_tools into the PG10 cluster
run: make install
- name: Prepare PG10 (install 0.2.0, bridge to 0.2.3, plant guard)
run: bin/test_existing prepare-old "$DB" 0.2.0 0.2.3
- name: Climb every major via binary pg_upgrade (10 → 11 → ... → 18)
# One sequential loop; each iteration binary-pg_upgrades the cluster from
# $old to $new (a single major step). cat_tools is a SQL-only extension, so
# pg_upgrade only needs its scripts present in the NEW cluster's sharedir:
# `make install PG_CONFIG=<new>` before each step (the $old cluster already
# has them, from the previous iteration or the PG10 install above). The
# extension is held at 0.2.3 until PG12, where it is updated to the current
# version; the suite then runs at every major from PG12 on (see the suite
# policy inside the loop). The per-step pg_upgrade invocation and log
# handling mirror pg-upgrade-test.
run: |
old=$LEGACY_PG
for new in $CLIMB_PG; do
echo "=== binary pg_upgrade PostgreSQL $old -> $new ==="
apt-get install -y postgresql-$new postgresql-server-dev-$new
# PG_CONFIG explicit: several majors are installed, so the default
# pg_config on PATH may not be $new's.
make install PG_CONFIG=/usr/lib/postgresql/$new/bin/pg_config
pg_ctlcluster $old test stop
pg_createcluster -p 5432 $new test -- $INITDB_OPTS
# PG17+ writes logs under the new datadir; older versions to CWD. Dump
# both on failure (same handling as pg-upgrade-test).
mkdir -p /tmp/pg_upgrade_logs
chown postgres:postgres /tmp/pg_upgrade_logs
su -c "cd /tmp/pg_upgrade_logs && /usr/lib/postgresql/$new/bin/pg_upgrade \
-b /usr/lib/postgresql/$old/bin \
-B /usr/lib/postgresql/$new/bin \
-d /var/lib/postgresql/$old/test \
-D /var/lib/postgresql/$new/test \
-o '-c config_file=/etc/postgresql/$old/test/postgresql.conf' \
-O '-c config_file=/etc/postgresql/$new/test/postgresql.conf'" postgres \
|| { find /tmp/pg_upgrade_logs \
/var/lib/postgresql/$new/test/pg_upgrade_output.d \
-name '*.log' 2>/dev/null | sort | xargs -r tail -n +1; exit 1; }
pg_ctlcluster $new test start
pg_isready -t 30
# Suite policy across the climb. We unfortunately CANNOT run the suite
# against the OLD version (0.2.3) at the pre-PG12 steps (10->11, 11->12):
# the suite matches the CURRENT version's objects and we do not maintain
# an old-version expected-output set. So those steps only verify that
# 0.2.3 SURVIVES each pg_upgrade -- i.e. the climb completing without
# error. On reaching PG12 (the floor for 0.2.3->0.3.0, which needs
# ALTER TYPE ... ADD VALUE) update once to the current version; from
# PG12 on the extension is at current, so run the full suite in existing
# mode at EVERY major (run-suite asserts the version and re-proves the
# guard survived this step's pg_upgrade).
if [ "$new" = 12 ]; then
bin/test_existing update "$DB"
fi
if [ "$new" -ge 12 ]; then
bin/test_existing run-suite "$DB"
fi
old=$new
done
# Proves the in-place extension update path: CREATE EXTENSION at an OLD cat_tools
# version then ALTER EXTENSION UPDATE (no pg_upgrade, same PostgreSQL). On PG12+
# it updates 0.2.2 -> current and runs the FULL suite against the updated
# database (same expected output as a fresh install, so an updated DB must behave
# identically). The PG10 leg only exercises the pre-0.2.2 update scripts, the
# sole version where they still load. Complements pg-upgrade-test, which covers
# the cross-major-version binary upgrade instead.
extension-update-test:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
# PG12+: exercise the WIDEST update path we support — CREATE EXTENSION at
# the 0.2.2 backward-compat floor, ALTER EXTENSION UPDATE to the CURRENT
# version, and run the full suite against the updated database. 0.2.2 is
# the floor because the 0.2.0/0.2.1 install scripts fail on PG11+/PG12+;
# PG12 is the PostgreSQL floor because the update runs
# `ALTER TYPE ... ADD VALUE`, which PG11 and below cannot run in an
# extension update script (lifted in PG12).
# PG10: the ONLY version where the pre-0.2.2 install scripts still load,
# so the only place the 0.2.0->0.2.2 and 0.2.1->0.2.2 update scripts and
# the 0.2.2->0.2.3 view rebuild on the broken path can be exercised. They
# target 0.2.2/0.2.3 (not the current version) and use no
# ALTER TYPE ... ADD VALUE, so they run on PG10. The PG10 leg runs only
# those legacy checks — not the current-version suite (the current version
# needs PG12+: the 0.2.3->0.3.0 update adds enum values via ALTER TYPE ...
# ADD VALUE, unrunnable in a pre-PG12 transaction). See the per-step `if`
# guards.
#
# Current-supported majors + the legacy PG10 floor, from the single
# source in the changes job (update_pg = supported_pg plus legacy_pg).
pg: ${{ fromJSON(needs.changes.outputs.update_pg) }}
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@v6
- name: Install rsync and server headers
# server-dev provides catalog/pg_class.h for the relkind drift check
# (see the "test" job); required so check-relkind-source below passes.
# PG10 runs only the legacy-script checks (no suite), so it needs no headers.
if: matrix.pg != '10'
run: apt-get install -y rsync postgresql-server-dev-${{ matrix.pg }}
- name: Install rsync
if: matrix.pg == '10'
run: apt-get install -y rsync
- name: Install cat_tools (all versions)
run: make install
- name: Test pre-0.2.2 update scripts + 0.2.2→0.2.3 rebuild (PG10 only)
# 0.2.0/0.2.1 install only on PG10; their update scripts target 0.2.2 (not
# the current version) and are otherwise never exercised. Both origins are
# checked because ALTER EXTENSION UPDATE takes the shortest path (see the
# pg-upgrade-test matrix): 0.2.0 goes via the 0.2.0--0.2.2 script, 0.2.1 via
# 0.2.1--0.2.2. update-check asserts each lands on 0.2.2. No suite runs (the
# current version needs PG12+).
#
# The rebuild_020/rebuild_021 checks exercise the REAL broken path end to
# end from BOTH pre-0.2.2 origins: a 0.2.0 (resp. 0.2.1) install on PG10
# leaves relhasoids in _cat_tools.pg_class_v (the buggy 0.2.0--0.2.2 /
# 0.2.1--0.2.2 omit_column no-op), and updating to 0.2.3 routes through
# 0.2.2--0.2.3 (shortest path <origin>--0.2.2 then 0.2.2--0.2.3), firing
# the conditional rebuild that strips relhasoids. 0.2.3 is the furthest
# PG10 can reach (0.2.3--0.3.0 needs PG12+). On PG12+ relhasoids never
# existed, so only PG10 exercises the rebuild. The psql assertion fails
# loudly if the rebuild did not fire (relhasoids still present) --
# complementing the stronger 10→18 pg_upgrade bridge legs. `$$` is escaped
# as `\$\$` so the shell passes literal dollar quotes through to psql.
if: matrix.pg == '10'
run: |
bin/test_existing update-check cat_tools_from_020 0.2.0 0.2.2
bin/test_existing update-check cat_tools_from_021 0.2.1 0.2.2
for origin in 020:0.2.0 021:0.2.1; do
db="cat_tools_rebuild_${origin%%:*}"
from="${origin##*:}"
bin/test_existing update-check "$db" "$from" 0.2.3
psql -d "$db" -v ON_ERROR_STOP=1 -c "DO \$\$ BEGIN IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid='_cat_tools.pg_class_v'::regclass AND attname='relhasoids' AND NOT attisdropped AND attnum>0) THEN RAISE EXCEPTION 'pg_class_v still exposes relhasoids after update through 0.2.2->0.2.3 -- rebuild did not fire'; END IF; END \$\$"
done
- name: Update 0.2.2 → current and run the suite (existing mode, PG12+)
# update-scenario creates a real database at 0.2.2, plants + proves the
# dependency guard, ALTER EXTENSION UPDATEs to the current version, and
# runs the suite against that updated database in existing mode (asserting
# the version and that the guard still blocks a drop). Reusing the SAME
# suite and expected output asserts the updated database behaves
# identically to a fresh install.
if: matrix.pg != '10'
run: bin/test_existing update-scenario cat_tools_update 0.2.2
pg-tle-test:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
# Current-supported majors, from the single source in the changes job
# (see its "Derive ..." step) -- same list the `test` job uses. This
# currently happens to line up with pg_tle 1.5.2's own supported range
# (12-18, see pgxntool/pgtle_versions.md) too, but that's a coincidence:
# if either range moves independently in the future, re-check they
# still overlap before assuming this job covers what it claims to.
# 1.5.2 itself is what AWS RDS for PostgreSQL 17 currently ships
# (RDS PG17.7+, our primary deployment target -- see
# https://docs.aws.amazon.com/AmazonRDS/latest/PostgreSQLReleaseNotes/postgresql-versions.html).
pg: ${{ fromJSON(needs.changes.outputs.supported_pg) }}
name: 🧩 pg_tle ${{ matrix.pg }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
PG_TLE_RELEASE: "1.5.2"
steps:
# A dedicated cluster, never shared with the other jobs in this
# workflow: pg_tle requires shared_preload_libraries and mixing
# pg_tle/non-pg_tle extension installs on one cluster can misbehave.
- name: Start PostgreSQL ${{ matrix.pg }}
run: pg-start ${{ matrix.pg }}
- name: Check out the repo
uses: actions/checkout@v6
- name: Install rsync
run: apt-get install -y rsync
- name: Install pgtap (test harness dependency)
# pgTAP is a filesystem-installed dependency of the TEST HARNESS, not
# part of what this job proves is pg_tle-only -- it's not being
# deployed via pg_tle here, and never will be. `bin/test_existing`'s
# pg_tle-mode run_suite calls `make testdeps`, which would otherwise
# filesystem-install pgTAP for the first time partway through the job
# and trip the contamination checks below (confirmed happening in a
# real CI run). Installing it explicitly here, before the baseline
# snapshot, makes it part of the accepted starting state -- like any
# other extension already on disk -- instead of needing a hardcoded
# exclude-by-name rule that would erode the whole point of diffing
# against a baseline.
run: make pgtap
- name: Snapshot filesystem extension control files (pre-pg_tle baseline)
# Whatever ships on disk by default (e.g. contrib, and now pgtap)
# before we install pg_tle. bin/assert_fs_clean's later checks diff
# against this, so they flag ANY extension that lands on disk instead
# of being registered via pg_tle -- not just cat_tools -- without
# hardcoding contrib/pgtap names.
run: bin/assert_fs_clean snapshot ${{ matrix.pg }} /tmp/control_baseline.txt
- name: Build and install pg_tle ${{ env.PG_TLE_RELEASE }}
# flex/bison/libkrb5-dev aren't in the pgxn-tools image; pg_tle's build
# needs them (guc-file.l, and clientauth.c includes gssapi.h).
run: |
apt-get install -y flex bison libkrb5-dev
git clone --branch v${{ env.PG_TLE_RELEASE }} --depth 1 https://github.com/aws/pg_tle.git /tmp/pg_tle
make -C /tmp/pg_tle install
- name: Enable pg_tle and restart PostgreSQL ${{ matrix.pg }}
run: |
echo "shared_preload_libraries = 'pg_tle'" >> /etc/postgresql/${{ matrix.pg }}/test/postgresql.conf
pg_ctlcluster ${{ matrix.pg }} test restart
pg_isready -t 30
- name: Register pg_tle + cat_tools against template1
# template1, not the ambient default db: pg_tle's registration catalog
# is per-database, and `createdb` only inherits it because it copies
# template1 by default. Every cat_tools database used below (the
# smoke-test db, and whatever bin/test_existing createdb's) is created
# AFTER this step specifically so it inherits both registrations.
run: |
psql -d template1 -c "CREATE EXTENSION pg_tle"
PGDATABASE=template1 make run-pgtle
- name: Verify no stray extension control files landed on the filesystem
# CRITICAL, and intentionally redundant with the cat_tools-specific
# check in the next step: a filesystem control file silently wins
# over a pg_tle-registered extension of the same name, which would
# make this whole job a false pass without ever raising an error.
# Confirmed hitting exactly this while researching pg_tle version
# compatibility (a stale filesystem cat_tools install shadowed the
# pg_tle-registered one under test). Run again after every step below
# that could plausibly write extension files to disk -- never trust a
# single check to catch everything.
run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt
- name: Install cat_tools purely via pg_tle (fresh install, no filesystem trace)
# cat_tools is never `make install`ed in this job, so a successful
# CREATE EXTENSION here can only be resolving through pg_tle's
# registration, not a control file on disk. Checked explicitly here
# too (not just via the comprehensive check above) as a guard
# specifically for the extension under test, in case that check's
# exclude-list logic has a bug.
run: |
test ! -e /usr/share/postgresql/${{ matrix.pg }}/extension/cat_tools.control
createdb cat_tools_smoke
psql -d cat_tools_smoke -c "CREATE EXTENSION cat_tools"
- name: Verify cat_tools works when deployed via pg_tle
run: |
INSTALLED=$(psql -d cat_tools_smoke -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'")
EXPECTED=$(make -s print-PGXNVERSION 2>/dev/null | sed -n 's/.*set to "\(.*\)"$/\1/p')
echo "installed=$INSTALLED expected=$EXPECTED"
if [ -z "$INSTALLED" ] || [ -z "$EXPECTED" ] || [ "$INSTALLED" != "$EXPECTED" ]; then
echo "FAIL: installed='$INSTALLED' expected='$EXPECTED'"; exit 1
fi
psql -d cat_tools_smoke -v ON_ERROR_STOP=1 -c "SELECT relname FROM cat_tools.pg_class_v LIMIT 1" > /dev/null
- 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
- name: Install PostgreSQL ${{ matrix.pg }} server dev headers
# Needed by `make check-relkind-source`, which bin/test_existing's
# run_suite calls -- every other job that reaches that check already
# installs this; pg-tle-test didn't need it until now because the
# smoke test above never runs the full suite.
run: apt-get install -y postgresql-server-dev-${{ matrix.pg }}
- name: Test the update path via pg_tle (full pgTAP suite, --use-existing)
# Exercises the SAME update path extension-update-test proves for a
# filesystem install (0.2.2 -> current), but entirely through pg_tle.
# bin/test_existing's update_scenario is UNMODIFIED from the
# filesystem case -- only run_suite's internals differ, gated by this
# one env var (see the TEST_EXISTING_DEPLOY comment in
# bin/test_existing) -- so this reuses the exact same update/upgrade
# code the other jobs use instead of duplicating it for pg_tle.
run: TEST_EXISTING_DEPLOY=pgtle bin/test_existing update-scenario cat_tools_update 0.2.2
- name: Verify no stray extension control files after the pg_tle update test
# Runs AFTER the complete update+full-suite flow, not before: the
# whole point of pg_tle-mode testing is proving NOTHING touched the
# filesystem THROUGHOUT that flow, not merely that the environment
# started clean. A check placed before this point could not catch a
# regression introduced by anything update-scenario itself does.
run: bin/assert_fs_clean verify ${{ matrix.pg }} /tmp/control_baseline.txt
pg-tle-upgrade-test:
needs: [changes]
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
include:
# Mirrors pg-upgrade-test's old_pg=12 legs (the only ones cleanly
# within pg_tle 1.5.2's own supported range, PG12-18) so the SAME PG
# version jumps are proven for both deployment methods. Unlike
# pg-upgrade-test, there is no bridge/old_install complexity here --
# pg_tle has no equivalent of the pre-0.2.2 filesystem install-script
# issues (those are about SELECT * over catalog columns in old SQL
# files, irrelevant to how the extension gets registered).
- old_pg: "12"
new_pg: "13"
- old_pg: "12"
new_pg: "18"
name: 🧩🔄 pg_tle binary pg_upgrade ${{ matrix.old_pg }} → ${{ matrix.new_pg }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
env:
# Both clusters must use the same initdb options so pg_upgrade sees
# consistent settings (checksums, auth) on old and new clusters.
INITDB_OPTS: --data-checksums --auth trust
PG_TLE_RELEASE: "1.5.2"
steps:
# A dedicated cluster pair, never shared with the other jobs in this
# workflow -- same reasoning as pg-tle-test.
- name: Start PostgreSQL ${{ matrix.old_pg }}
run: pg-start ${{ matrix.old_pg }}
- name: Check out the repo
uses: actions/checkout@v6
- name: Install rsync
run: apt-get install -y rsync
- name: Recreate old cluster with data checksums enabled
run: .github/scripts/pg_upgrade_cluster recreate-old ${{ matrix.old_pg }}
- name: Snapshot filesystem extension control files (old cluster baseline)
run: bin/assert_fs_clean snapshot ${{ matrix.old_pg }} /tmp/control_baseline_old.txt
- name: Build and install pg_tle ${{ env.PG_TLE_RELEASE }} on the old cluster
# flex/bison/libkrb5-dev aren't in the pgxn-tools image; pg_tle's build
# needs them (guc-file.l, and clientauth.c includes gssapi.h).
run: |
apt-get install -y flex bison libkrb5-dev
git clone --branch v${{ env.PG_TLE_RELEASE }} --depth 1 https://github.com/aws/pg_tle.git /tmp/pg_tle_old
make -C /tmp/pg_tle_old install
- name: Enable pg_tle and restart PostgreSQL ${{ matrix.old_pg }}
run: |
echo "shared_preload_libraries = 'pg_tle'" >> /etc/postgresql/${{ matrix.old_pg }}/test/postgresql.conf
pg_ctlcluster ${{ matrix.old_pg }} test restart
pg_isready -t 30
- name: Register pg_tle + cat_tools against template1 (old cluster)
run: |
psql -d template1 -c "CREATE EXTENSION pg_tle"
PGDATABASE=template1 make run-pgtle
- name: Verify no stray extension control files on the old cluster
run: bin/assert_fs_clean verify ${{ matrix.old_pg }} /tmp/control_baseline_old.txt
- name: Prepare the old cluster (install via pg_tle + dependency guard)
# A fresh 0.2.2 install (no bridge needed -- see pg-upgrade-test's own
# comment on why its old_pg>=11 legs don't need one), created via
# pg_tle only, then the same dependency guard bin/test_existing's
# other subcommands rely on to prove existing-mode never silently
# drops+reinstalls the extension.
run: |
test ! -e /usr/share/postgresql/${{ matrix.old_pg }}/extension/cat_tools.control
createdb cat_tools_tle_upgrade
psql -d cat_tools_tle_upgrade -c "CREATE EXTENSION cat_tools VERSION '0.2.2'"
bin/test_existing plant-guard cat_tools_tle_upgrade
- name: Install PostgreSQL ${{ matrix.new_pg }}
run: apt-get install -y postgresql-${{ matrix.new_pg }} postgresql-server-dev-${{ matrix.new_pg }}
- name: Install pgtap on the new cluster (test harness dependency)
# Must happen BEFORE the new-cluster baseline snapshot below, same
# reasoning as pg-tle-test's "Install pgtap" step: pgTAP is a
# filesystem-installed dependency of the test harness, unrelated to
# cat_tools's own pg_tle-only deployment story, so it needs to be part
# of the accepted starting state rather than flagged as contamination.
# PATH is set explicitly (not just relying on apt's update-alternatives
# switch from installing postgresql-${{ matrix.new_pg }} above) since
# `pgxn install` resolves pg_config via PATH, not via a passed-through
# PG_CONFIG make variable.
run: PATH=/usr/lib/postgresql/${{ matrix.new_pg }}/bin:$PATH make pgtap
- name: Snapshot filesystem extension control files (new cluster baseline)
run: bin/assert_fs_clean snapshot ${{ matrix.new_pg }} /tmp/control_baseline_new.txt
- name: Build and install pg_tle ${{ env.PG_TLE_RELEASE }} on the new cluster
# Only pg_tle's OWN framework is needed on the new binary tree -- NOT
# cat_tools. Binary pg_upgrade's schema-only restore recreates each
# object individually (binary_upgrade_create_empty_extension), and
# pg_tle's own catalog tables (holding cat_tools's registered SQL) are
# carried over as ordinary heap data during the physical copy phase --
# no re-registration needed on the new side. A SEPARATE clone
# (pg_tle_new, not pg_tle_old rebuilt) avoids installing a .so still
# compiled against the old cluster's PostgreSQL headers: pg_tle has C
# code, unlike cat_tools, so switching PG_CONFIG on an already-built
# tree without a fresh build would risk installing the wrong binary.
run: |
git clone --branch v${{ env.PG_TLE_RELEASE }} --depth 1 https://github.com/aws/pg_tle.git /tmp/pg_tle_new
make -C /tmp/pg_tle_new install PG_CONFIG=/usr/lib/postgresql/${{ matrix.new_pg }}/bin/pg_config
- name: Verify no stray extension control files on the new cluster (pre-upgrade)
run: bin/assert_fs_clean verify ${{ matrix.new_pg }} /tmp/control_baseline_new.txt
- name: Stop old cluster, create new cluster, enable pg_tle, binary pg_upgrade, start new cluster
run: .github/scripts/pg_upgrade_cluster upgrade ${{ matrix.old_pg }} ${{ matrix.new_pg }} "shared_preload_libraries = 'pg_tle'"
- name: Verify no stray extension control files after binary pg_upgrade
run: bin/assert_fs_clean verify ${{ matrix.new_pg }} /tmp/control_baseline_new.txt
- name: Update the pg_upgraded extension to the current version
# Exercises ALTER EXTENSION UPDATE on genuinely pg_upgraded objects
# registered via pg_tle. update_ext() is pure psql (no `make` calls),
# so it's identical regardless of deployment method -- no pg_tle-mode
# flag needed here, only for run-suite below.
run: bin/test_existing update cat_tools_tle_upgrade
- name: Run the suite against the pg_upgraded database via pg_tle (existing mode)
run: TEST_EXISTING_DEPLOY=pgtle bin/test_existing run-suite cat_tools_tle_upgrade
- name: Verify no stray extension control files after the pg_tle upgrade+update+suite run
# CRITICAL: must run AFTER the complete flow, not before -- same
# principle as pg-tle-test's final checkpoint. Proves nothing touched
# the filesystem across binary pg_upgrade, the extension update, AND
# the full pgTAP suite run, not merely that the environment started
# clean.
run: bin/assert_fs_clean verify ${{ matrix.new_pg }} /tmp/control_baseline_new.txt
# A single stable check name for use as a required status check in branch
# protection rules. Matrix jobs produce check names like "🐘 PostgreSQL 14"
# which would all need to be listed individually and updated whenever the
# matrix changes. This job passes if all others passed or were skipped (e.g.
# the heavy jobs gated off by the `changes` job on a docs-only push), and
# fails if any failed or were cancelled.
all-checks-passed:
needs: [changes, test, lint, pg-upgrade-test, pg-upgrade-stepwise, extension-update-test, pg-tle-test, pg-tle-upgrade-test]
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Verify all jobs are listed in needs
# Ensures this job won't silently ignore a newly-added job that was
# omitted from the needs list above.
run: |
DEFINED=$(python3 -c "
import yaml
with open('.github/workflows/ci.yml') as f:
w = yaml.safe_load(f)
print('\n'.join(sorted(j for j in w['jobs'] if j != 'all-checks-passed')))
")
NEEDED=$(echo '${{ toJson(needs) }}' | python3 -c "
import json, sys
print('\n'.join(sorted(json.load(sys.stdin))))
")
if [ "$DEFINED" != "$NEEDED" ]; then
echo "Some jobs are missing from all-checks-passed needs:"
diff <(echo "$DEFINED") <(echo "$NEEDED")
exit 1
fi
- name: Check all jobs passed or were skipped
run: |
if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more jobs failed or were cancelled"
exit 1
fi
- name: Summarize what this push actually tested
# This job's own checks report "skipped" for every heavy job on a
# docs-only push -- true for THIS push, but easy to mistake for "never
# tested" when just glancing at the PR's Checks tab. Make it explicit
# what's actually being vouched for. Reporting only -- does not affect
# pass/fail; see the "Check all jobs passed" step above for that gate.
run: |
if [ "${{ needs.changes.outputs.docs_only }}" != "true" ]; then
echo "✅ This push's own CI run tested the code (not a docs-only push)." >> "$GITHUB_STEP_SUMMARY"
elif [ "${{ needs.changes.outputs.docs_only_pr }}" = "true" ]; then
echo "📄 This entire PR/push contains only documentation changes -- no code testing applies." >> "$GITHUB_STEP_SUMMARY"
else
SHA="${{ needs.changes.outputs.last_tested_sha }}"
CONCLUSION="${{ needs.changes.outputs.last_tested_conclusion }}"
URL="${{ needs.changes.outputs.last_tested_url }}"
if [ "$CONCLUSION" = "success" ]; then ICON="✅"; else ICON="⚠️"; fi
if [ -n "$URL" ]; then
echo "$ICON This push only changed docs. The code was last fully tested at \`${SHA:0:7}\` -- [$CONCLUSION]($URL)" >> "$GITHUB_STEP_SUMMARY"
else
echo "⚠️ This push only changed docs, and no completed CI run was found for the last code-changing commit (\`${SHA:0:7}\`) -- verify manually." >> "$GITHUB_STEP_SUMMARY"
fi
fi
# vi: expandtab ts=2 sw=2