From 1008f982054c0d003e56ab982a7a9fb88076a751 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Mon, 19 Oct 2026 19:40:22 +0000 Subject: [PATCH 1/2] test: native_upgrade_converge's twin compares each fixture with what its tag shipped, and CI fetches the tags (#1341) The last arms of the pair: the fixture-sweep premise, and for each released fixture on disk (all but the synthetic 1.0-alpha) that its blob id equals v:pgcolumnar--.sql's. The twin compares blob ids as the shell does. With no git repository or no release tags it cannot run (UNMET_PRECONDITION), where the shell skips, rather than pass. actions/checkout fetches no tags, so these arms skipped on every CI run, in both harnesses. The suites and pytest-cluster jobs now fetch the v1.0-alpha* tags at depth 1 (checked on a --depth=1 --no-tags clone: the four tags arrive and each fixture's blob matches). The shell named the check through a variable, `check "$_fx_name"`, which the twin grader cannot read, so it saw only the premise as missing and a port without the per-tag comparison would have graded COMPLETE. The name is now written in the call. The pair moves from INCOMPLETE to COMPLETE. Measured on PG17, both harnesses, names compared: - clean: shell 31/31, twin 12 passed - a fixture drifted from its tag (a line appended to 1.0-alpha3's): that one check red in each - a stale local tag (v1.0-alpha3 moved to v1.0-alpha2's commit): that one red in each - a clone with no tags, and a tree with no .git: the shell skips the three, and the twin reports the same three UNRUN (exit 67), with the same reason in each harness Clean, the shell gives 31/31 and the twin 12 passed on each of 15-19. The suite has no ledger rows, so the gate is unchanged (rc=0 with the tag checks passing). cluster_tests 1025 -> 1029. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 16 +++++++++ test/native_upgrade_converge.sh | 15 ++++---- test/pytest/TESTS.md | 4 ++- test/pytest/expected_tests.txt | 4 ++- test/pytest/test_compare_to_bash.py | 7 +--- test/pytest/test_native_upgrade_converge.py | 40 +++++++++++++++++++++ 6 files changed, 72 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 609953660..0512c9031 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,6 +168,14 @@ jobs: steps: - uses: actions/checkout@v4 + # THE RELEASE TAGS, FOR native_upgrade_converge'S FIXTURE ARMS (#1341). Each + # released fixture must be byte-identical to what its tag shipped, compared by + # blob id against `v:pgcolumnar--.sql`. actions/checkout + # fetches no tags, so these arms used to skip on every CI run. Depth 1 is + # enough: only the file at each tagged commit is read. + - name: fetch the release tags, for the fixture-blob comparisons + run: git fetch --depth=1 origin '+refs/tags/v1.0-alpha*:refs/tags/v1.0-alpha*' + - name: Add the PGDG repository and install PostgreSQL ${{ matrix.pg }} with its headers run: | set -euo pipefail @@ -557,6 +565,14 @@ jobs: git fetch --depth=1 origin \ "+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}" + # THE RELEASE TAGS, FOR native_upgrade_converge'S FIXTURE ARMS (#1341). Each + # released fixture must be byte-identical to what its tag shipped, compared by + # blob id against `v:pgcolumnar--.sql`. actions/checkout + # fetches no tags, so these arms used to skip on every CI run. Depth 1 is + # enough: only the file at each tagged commit is read. + - name: fetch the release tags, for the fixture-blob comparisons + run: git fetch --depth=1 origin '+refs/tags/v1.0-alpha*:refs/tags/v1.0-alpha*' + # The repository and the package lists first, on their own, because the # cache key below is derived from the versions apt resolves and cannot be # computed before the lists exist. diff --git a/test/native_upgrade_converge.sh b/test/native_upgrade_converge.sh index 454900137..78b4b1443 100755 --- a/test/native_upgrade_converge.sh +++ b/test/native_upgrade_converge.sh @@ -219,23 +219,26 @@ _fx_git() { git -C "$HERE/.." "$@" 2>/dev/null; } if ! _fx_git rev-parse --git-dir >/dev/null; then _fx_why="no git repository in the tree under test" elif [ -z "$(_fx_git tag -l 'v1.0-alpha*')" ]; then - # CI checks out at depth 1 with no tags, so this cannot run there. It runs - # locally and in the five-major release gate, which is where a fixture is - # captured and therefore where it can be captured wrongly. + # actions/checkout fetches no tags, so CI fetches the release tags in a step + # of its own for these arms (#1341). A local checkout without them still + # lands here, and the arms skip rather than compare against nothing. _fx_why="no release tags in this checkout" else _fx_why="" fi +# The name is written out in each call rather than held in a variable: the twin +# grader reads a check's name from the call, and `check "$_fx_name"` was invisible +# to it, so a port could drop this arm and still grade complete. for v in $_FX_TAGGED; do - _fx_name="the $v fixture is byte-identical to what v$v shipped" if [ -n "$_fx_why" ]; then - check_skip "$_fx_name" "SKIP $_fx_name ($_fx_why)" "$_fx_why" + check_skip "the $v fixture is byte-identical to what v$v shipped" \ + "SKIP the $v fixture is byte-identical to what v$v shipped ($_fx_why)" "$_fx_why" continue fi _fx_tag="$(_fx_git rev-parse "v$v:pgcolumnar--$v.sql" || echo "no such path at v$v")" _fx_fix="$(_fx_git hash-object "$HERE/fixtures/pgcolumnar--$v.sql" || echo "fixture missing")" - check "$_fx_name" "$_fx_fix" "$_fx_tag" + check "the $v fixture is byte-identical to what v$v shipped" "$_fx_fix" "$_fx_tag" done # THE ARM THAT CATCHES THE QUIET HALF. Staging unconditionally fixes it; this diff --git a/test/pytest/TESTS.md b/test/pytest/TESTS.md index ab5ab84d8..ae1bcf83f 100644 --- a/test/pytest/TESTS.md +++ b/test/pytest/TESTS.md @@ -7531,7 +7531,7 @@ Part of the pytest twin of `test/drop_cleanup.sh` (#1265, ported for #1341): its ## 143. test_native_upgrade_converge.py: every release upgrades to a fresh catalog, and the window before it -The pytest twin of `test/native_upgrade_converge.sh` (#1341), by the shell's names, except its release-tag checks, so the pair is declared INCOMPLETE. `ALTER EXTENSION pgcolumnar UPDATE` must bring a database on any released version to a catalog identical to a fresh install of the current `default_version`: every function's definition, ACL and comment, and every relation, column, type and foreign-data wrapper. The released starting points are the install scripts in `test/fixtures`. They are staged into the extension directory once for the module, under a lock that makes a second stager wait, and whatever was there is put back after. A backup already there means an earlier run died, so it holds the original and is kept. +The pytest twin of `test/native_upgrade_converge.sh` (#1341), every check by the shell's name, so the pair is declared COMPLETE. `ALTER EXTENSION pgcolumnar UPDATE` must bring a database on any released version to a catalog identical to a fresh install of the current `default_version`: every function's definition, ACL and comment, and every relation, column, type and foreign-data wrapper. The released starting points are the install scripts in `test/fixtures`. They are staged into the extension directory once for the module, under a lock that makes a second stager wait, and whatever was there is put back after. A backup already there means an earlier run died, so it holds the original and is kept. Between installing a new library and running the update, a database runs the new library over its previous release's catalog (#1341 item 66). Reads and writes must work there. `DROP TABLE`, `TRUNCATE` and `pgcolumnar.vacuum()` need a catalog table the old one lacks, so they must be refused with 42P01, change nothing, and carry a hint naming the update. After the update all three succeed on the same rows. @@ -7544,6 +7544,8 @@ Between installing a new library and running the update, a database runs the new | `test_the_staged_fixtures_are_the_committed_ones` | the control's `default_version`; no backup was already staged; every fixture was staged, and each staged script is the committed fixture | | `test_each_release_upgrades_to_a_fresh_catalog` | parametrized by the four fixtures: a fresh install of the target has more than 100 objects to compare, the upgrade applies, the extension is at the target, and its catalog equals the fresh install's | | `test_the_upgrade_window` | at the previous catalog: rows written, deleted and read; the three operations refused with the hint and nothing changed; after the update the rows are intact and all three succeed | +| `test_the_fixture_sweep_found_fixtures` | the fixtures on disk, less the synthetic `1.0-alpha`, are not an empty list: an empty sweep would compare nothing and report clean | +| `test_each_fixture_is_what_its_tag_shipped` | parametrized by those fixtures: each one's blob id equals `v:pgcolumnar--.sql`'s; with no git repository or no release tags it cannot run (UNMET_PRECONDITION) rather than pass. CI fetches the tags for it | ## 144. test_fk_referencing.py: a foreign key referencing a columnar table is refused where it is chosen diff --git a/test/pytest/expected_tests.txt b/test/pytest/expected_tests.txt index 6b01c6e7c..71063c813 100644 --- a/test/pytest/expected_tests.txt +++ b/test/pytest/expected_tests.txt @@ -858,4 +858,6 @@ guard_tests 442 # tests, one per check, by collection over the cluster files. # cluster_tests 1018 -> 1025 for #1341, test_native_upgrade_converge.py ported by name except its # release-tag arms (1 -> 8 tests), by collection over the cluster files. -cluster_tests 1025 +# cluster_tests 1025 -> 1029 for #1341, test_native_upgrade_converge.py's release-tag arms (the +# fixture sweep premise and one test per tagged fixture: 1 + 3), by collection over the cluster files. +cluster_tests 1029 diff --git a/test/pytest/test_compare_to_bash.py b/test/pytest/test_compare_to_bash.py index 88f4f7038..22634568d 100644 --- a/test/pytest/test_compare_to_bash.py +++ b/test/pytest/test_compare_to_bash.py @@ -168,6 +168,7 @@ "native_skip", "native_toasted_write", "native_truncate", + "native_upgrade_converge", "native_vacuum_race", "native_varlena_bound", "native_vecskip", @@ -226,12 +227,6 @@ # about the PORT rather than a licence: the standing arm does not grade it, so the # reason is the only thing standing between a declared gap and a forgotten one. INCOMPLETE = { - # #1341 ported every arm but the release-tag ones: each tagged fixture is - # byte-identical to what its tag shipped. CI checks out without tags, so the shell - # skips them there with check_skip, and this layer has no verdict for an arm that - # runs on one checkout and not another. - "native_upgrade_converge": "every arm is ported except the release-tag sweep and " - "the per-tag fixture-blob comparisons", # #1341 item 29 ported the arms four fixes added with shell arms only. Each # pair below holds that fix's arms and nothing else of its shell suite. "inheritance": "the #1259 arms (a partitioned columnar parent is refused with " diff --git a/test/pytest/test_native_upgrade_converge.py b/test/pytest/test_native_upgrade_converge.py index d1f220b17..4c2276325 100644 --- a/test/pytest/test_native_upgrade_converge.py +++ b/test/pytest/test_native_upgrade_converge.py @@ -306,6 +306,46 @@ def test_the_staged_fixtures_are_the_committed_ones(staging, expect): "every staged install script is the committed fixture, not a leftover") +# The fixtures to compare against their tags, derived from the fixtures on disk as +# the shell derives them, so a fixture added at the next cycle-open is checked +# without anyone listing it. 1.0-alpha is synthetic: v1.0-alpha shipped +# pgcolumnar--1.0-dev.sql, so no tag holds a pgcolumnar--1.0-alpha.sql. +TAGGED = sorted(p.name[len("pgcolumnar--"):-len(".sql")] for p in FIXTURES.glob("pgcolumnar--*.sql") + if p.name != "pgcolumnar--1.0-alpha.sql") + + +def _git(*args): + """-> git's stripped stdout in the repository under test, or None when it fails.""" + r = subprocess.run(["git", "-C", str(REPO), *args], capture_output=True, text=True) + return r.stdout.strip() if r.returncode == 0 else None + + +def test_the_fixture_sweep_found_fixtures(expect): + # An empty sweep would leave nothing below to compare and report clean. + expect.text("yes" if TAGGED else "no", "yes", + "premise: the fixture sweep found fixtures to compare against their tags") + + +@pytest.mark.parametrize("v", TAGGED) +def test_each_fixture_is_what_its_tag_shipped(v, expect): + """Blob ids, not a diff: a blob id is a lookup that no similarity heuristic can + distort. The local tag is trusted, as in the shell; if this fails and the fixture + looks right, check the tag with `git ls-remote origin refs/tags/v`.""" + if _git("rev-parse", "--git-dir") is None: + why = "no git repository in the tree under test" + elif not _git("tag", "-l", "v1.0-alpha*"): + why = "no release tags in this checkout" + else: + why = "" + if why: + expect.cannot_run("UNMET_PRECONDITION", why, + name=f"the {v} fixture is byte-identical to what v{v} shipped") + return + tag = _git("rev-parse", f"v{v}:pgcolumnar--{v}.sql") or query_error(f"no such path at v{v}") + fixture = _git("hash-object", str(FIXTURES / f"pgcolumnar--{v}.sql")) or query_error("fixture missing") + expect.text(fixture, tag, f"the {v} fixture is byte-identical to what v{v} shipped") + + @pytest.mark.parametrize("from_", VERSIONS) def test_each_release_upgrades_to_a_fresh_catalog(pgc_cluster, staging, reference, from_, expect): extdir, _, _ = staging From 80054fccb87cd258b1187cf860fc0d34fcf298b2 Mon Sep 17 00:00:00 2001 From: "Joshua D. Drake" Date: Mon, 19 Oct 2026 19:51:00 +0000 Subject: [PATCH 2/2] ci: the nightly suites and pytest-cluster jobs fetch the release tags too (#1341) The same step as ci.yml's, so native_upgrade_converge's fixture-blob arms run in the nightly shell matrix and the nightly pytest job rather than skip (shell) or end incomplete (twin). The step goes after each checkout's own `with:` block, and every job's steps equal the base's apart from it (compared as parsed YAML). The fetch leaves a fetch-depth 2 checkout's HEAD~1 resolvable, which the suites job's ledger gate needs (measured on a --depth=2 clone). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/nightly.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 02c8b54b4..3d48bdca2 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -90,6 +90,15 @@ jobs: with: fetch-depth: 2 + # THE RELEASE TAGS, FOR native_upgrade_converge'S FIXTURE ARMS (#1341). Each + # released fixture must be byte-identical to what its tag shipped, compared by + # blob id against `v:pgcolumnar--.sql`. actions/checkout + # fetches no tags, so these arms used to skip on every CI run. Depth 1 is + # enough: only the file at each tagged commit is read, and it leaves the + # checkout's own history as it was (HEAD~1 still resolves at fetch-depth 2). + - name: fetch the release tags, for the fixture-blob comparisons + run: git fetch --depth=1 origin '+refs/tags/v1.0-alpha*:refs/tags/v1.0-alpha*' + # The packages are ~102 MB, of which ~91 MB is the LLVM toolchain that # postgresql-server-dev-N depends on. That download is what stalls: every # failure of this step sat at the 600s bound while every success finished @@ -727,6 +736,15 @@ jobs: steps: - uses: actions/checkout@v4 + # THE RELEASE TAGS, FOR native_upgrade_converge'S FIXTURE ARMS (#1341). Each + # released fixture must be byte-identical to what its tag shipped, compared by + # blob id against `v:pgcolumnar--.sql`. actions/checkout + # fetches no tags, so these arms used to skip on every CI run. Depth 1 is + # enough: only the file at each tagged commit is read, and it leaves the + # checkout's own history as it was (HEAD~1 still resolves at fetch-depth 2). + - name: fetch the release tags, for the fixture-blob comparisons + run: git fetch --depth=1 origin '+refs/tags/v1.0-alpha*:refs/tags/v1.0-alpha*' + - name: Add the PGDG repository and install PostgreSQL ${{ matrix.pg }} with its headers run: | set -euo pipefail