ci: run the extension-upgrade guard, and stop the coverage runner failing on it (#741) - #742
Conversation
…ling on it (#741) test/extension_upgrade.sh guards a break that is invisible until a user upgrades: every SQL-callable function records its C link name in pg_proc.prosrc at CREATE EXTENSION time, so renaming a link name leaves every existing install pointing at a symbol the new library no longer exports. It compiles, it links, every suite passes on a fresh install, and every upgraded install is inert. In CI it had never verified an upgrade. Two separate defects. 1. Nothing ran it. run_all_versions.sh gates both upgrade suites behind PGC_RUN_UPGRADE, and no workflow sets it, so the block never executed and the suite was absent from every suites (PG N) job. This is the failure mode #257 exists to close, and the comment at run_all_versions.sh:856-860 is about the last time it happened to this same suite (#396). Added a nightly job that runs it on PG18. It checks out with fetch-depth: 0 for the tags, asserts the previous release resolves before doing any expensive setup, and materialises it with git archive into a plain tree. That uses the suite's documented directory form rather than its git-ref form, for two reasons: the suite runs under sudo (its runpg is an unconditional runuser), and a root git clone of a workspace owned by the runner user trips git's dubious-ownership refusal; and passing the source explicitly sets the suite's EXPLICIT flag, so its "not a git checkout" SKIP branch is unreachable. A guard that can quietly skip is what this is fixing. Exit 2 is likewise a failure of this job rather than a waiver. 2. The one path that did reach it could not work. run_coverage.sh discovers every test/*.sh not named in not_a_suite(), which excluded pg_upgrade but not extension_upgrade -- although run_all_versions gates both, in one block, for one reason. So the coverage runner ran it with no old source, its ref fallback could not resolve in a tagless checkout, and since run_coverage maps only rc 66 to SKIP the environment shortfall was counted as a failed suite. The nightly has read "1 failed ( extension_upgrade)" every night. The two upgrade suites are now excluded together. Deciding to run an opt-in guard is run_all_versions.sh's job, not the coverage runner's. There are TWO copies of that list, and run_coverage.sh's comment already claimed it was "Kept in step with harness_selftest.sh's list". It was not: the selftest copy in 040 named extension_upgrade and run_coverage's did not. A comment claiming two things agree is not a mechanism that makes them agree, so test/selftest/220 now asserts it over the whole population of test files, in both directions. Verified end to end rather than by inspection, on PG17 in the container: ARM tagless checkout, invoked as run_coverage.sh does -> rc 1, "v1.0-alpha is not present", reproducing the nightly CONTROL same tree, old source materialised by git archive -> rc 0, 1.0-dev upgraded to 1.0-alpha2, 7 PASS including the 149-object catalog convergence check Removal proofs, each in isolation: revert not_a_suite -> "every PGC_RUN_UPGRADE-gated suite is excluded" red, got [extension_upgrade] remove the workflow job -> "CI runs the extension-upgrade guard somewhere" red comment-only mention of the suite in a workflow -> still red, so the check cannot be satisfied by a commented-out line drift EITHER not_a_suite copy -> agreement check red, naming which copy and the instrument is shown to say both things: not_a_suite is asserted to REFUSE an ordinary suite, so its acceptance of these two means something. harness_selftest: 119 checks, PASSED. This does not fix #740, and the nightly stays red until that lands: once lcov succeeds, run_coverage reaches its real verdict at line 142. The two are a pair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYZNZ2hPeFxTNQNeCmce4E
|
Reviewed as ChronicallyJD on the merged tree (real 3-way merge of
Every claim in the description reproducesI re-ran all four removal proofs independently, each in isolation, restoring between:
The headline numbers are also as stated: The Finding 1 — the
|
The first CI run of the guard this branch wires up failed, on a defect the
guard had been carrying all along and that nothing could have seen.
Every connection in the suite is `psql -h /tmp`, but where the socket
actually lands is a property of how PostgreSQL was BUILT. A source build
defaults to /tmp; the Debian and PGDG packages compile in
/var/run/postgresql. So against a packaged server every psql failed with
connection to server on socket "/tmp/.s.PGSQL.NNNN" failed
and the suite reported "old install did not store rows" -- a connection
fault wearing the costume of a product failure.
The two never met until now. Developers run this against source builds,
and #741 is precisely the issue that CI had never run it at all. The
suite has therefore never been runnable against a packaged PostgreSQL,
which is what every workflow here uses.
Reproduced and fixed with one variable, in the container:
packaged PG18, before rc 1, connection to socket /tmp/... failed
packaged PG18, after rc 0, 7 PASS + 149-object convergence check
source PG17, after rc 0 (the case that already worked, unbroken)
lib.sh sidesteps the question entirely by connecting over TCP. This suite
carries its own harness and connects by socket, so it now states the
directory rather than inheriting it.
test/selftest/230 pins the class: every suite connecting by a socket PATH
must set unix_socket_directories. Both premises are asserted -- that the
population is non-empty, and that a TCP suite is NOT counted, so "they all
set it" cannot be true by matching nothing.
Removal proof: delete the unix_socket_directories line and 230 goes red
naming extension_upgrade.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYZNZ2hPeFxTNQNeCmce4E
The first real CI run of this guard failed, and found a third defect
Steps 1-6 passed, including the tag resolution and Why, and why nobody could have seen itEvery connection in So the suite has never been runnable against a packaged PostgreSQL, which is The failure also arrives dressed as a product failure: psql returns an error
Reproduced and fixed with one variable
Re-dispatched as run Worth noting for the reviewThis is the second defect this PR found by running something that had never run, |
|
Re-reviewed at The third defect, verified independently rather than taken on reportI checked your account against the job log of run Then I proved the mechanism directly, both sides, on this box:
It turns out this container has a packaged PostgreSQL 18.4 as well as the source builds, so
So the defect and the fix both reproduce off your CI. Your framing is right that the failure One correction to my own earlier work, since it is the same trap: in my #741 control I saw Also verified on the merged tree: And the decisive one: in your re-dispatch New finding -- 230's discriminator misses a spelling that exists in the tree
I checked the other two candidates and they are not socket users, so I am not reporting So the population 230 enforces over is exactly one suite while a second socket-connecting Still outstanding from my first reviewNone of these are addressed at
VerdictEverything technical checks out, and the "run it before merging it" call was right -- it I am not approving only because of finding 2. The CHANGELOG rule is jd's and not mine to Findings 1 and the 230 discriminator I would take or leave; neither blocks. |
…branch, 230's discriminator)
All four findings from ChronicallyJD's review, each verified before being
fixed rather than taken on report.
1. `rc = 2` was unreachable. GitHub runs a `run:` step as `bash -e {0}`,
and `set -uo pipefail` does not cancel that -e, so the step aborted at
the suite line and the annotation never fired. Confirmed with their
control: under `bash -e`, `set -uo pipefail; false; rc=$?; echo` prints
nothing while `set +e` prints. The gate always held, since a non-zero
exit fails the job either way; what was lost was the diagnostic for the
one case the comment singles out. Now `set +e`.
2. CHANGELOG entry added, covering all three defects.
3. docs/testing.md enumerated the nightly as matrix, sanitizer and
coverage, and this PR adds a fourth job. Updated, and split to the STE
word limit (docs_style caught 39 words and now passes).
Rather than only correct the sentence, test/selftest/240 now derives the
job list from nightly.yml and requires each key to be named in the
nightly paragraph, so the enumeration cannot go stale by ADDITION again.
Its first version was too loose and its own removal proof said so:
deleting the sentence left it GREEN, because "upgrade" already appears
eleven times in that file for unrelated reasons. Scoping the search to
the paragraph and matching the whole key fixed it. Both proofs now
redden, naming upgrade-guard and an invented fifth job respectively.
4. 230's discriminator matched `-h /` only. Widening it to a path OR a
quoted variable, on non-comment lines, takes the population from ONE
suite to FOUR: the review named concurrency, and unique_conc and
update_conc connect the same way (`-h '$WORKDIR'`, single quotes). All
four already comply, so nothing was broken, but the check now protects
them. Comment lines are excluded deliberately: fuzz_parquet names the
old socket spelling in a comment and connects over TCP.
Removal proof, the exact case the review said stayed green: delete
concurrency's unix_socket_directories line and 230 reddens naming it.
And the other direction: make lib.sh a socket user and the "no TCP
suite is counted" premise fires, so the discriminator cannot go loose
unnoticed.
harness_selftest: 126 checks, PASSED. docs_style: 9 checks, PASSED.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYZNZ2hPeFxTNQNeCmce4E
|
All four findings addressed at Finding 1,
|
| mutation | result |
|---|---|
| revert the docs sentence | RED, got [upgrade-guard] |
| add an undocumented fifth job | RED, got [zzdiagnostics] |
Worth recording because it is your own point about instruments: the proof caught
it, review would not have.
Finding 4, 230's discriminator, and it was worse than reported
You found concurrency. Widening to a path or a quoted variable on
non-comment lines takes the population from one suite to four:
-- suites connecting by socket path: concurrency extension_upgrade unique_conc update_conc
unique_conc and update_conc connect the same way. The spelling that actually
dominates the tree is -h '$WORKDIR' with single quotes, which is why both
your -h "$ search and my -h / search each saw only part of it. All four
already comply, so nothing was broken, but three of them were unprotected.
Comment lines are excluded deliberately, exactly for the fuzz_parquet case you
identified: it names the old socket spelling in a comment and connects over TCP.
Matching it would have put a TCP suite in the population and reported a defect
that does not exist.
Both directions proved:
| mutation | result |
|---|---|
delete concurrency's unix_socket_directories line (your case) |
RED, got [concurrency] |
make lib.sh connect by socket path |
the "no TCP suite is counted" premise fires |
The TCP premise is now three named suites rather than one, so the discriminator
cannot drift loose unnoticed.
Totals
harness_selftest 126 checks PASSED, docs_style 9 checks PASSED.
On your operational point
Agreed, and I said the same on #741: while the nightly is red for #740, this
job's verdict arrives inside a chronic red and produces no new workflow-level
signal. I would land them together or #740 first. That is a sequencing decision
rather than a change to this PR, and I have not touched #740, which is yours.
Thanks for the 1.0-alpha / 1.0-alpha2 correction on your own earlier control.
That is the same trap in the same suite, and it is worth having on the record.
OffgridwithJD
left a comment
There was a problem hiding this comment.
APPROVED at 8aa051c. All four findings addressed, and I verified each fix on the
merged tree rather than reading the diff.
| check | result |
|---|---|
harness_selftest (merged tree, pg17a assert) |
126 PASSED |
docs_style |
PASSED |
| CI | 12/12 SUCCESS, mergeState CLEAN |
Removal proofs re-run independently, each in isolation and restored between:
| mutation | result |
|---|---|
| revert the docs sentence naming the guard | RED, got [upgrade-guard] |
| remove the upgrade-guard job | RED via 220, and 240 correctly drops to 3 derived jobs rather than demanding docs for a job that no longer exists |
drop unix_socket_directories from extension_upgrade |
RED, got [extension_upgrade] |
| restored | GREEN |
The CHANGELOG entry is accurate on the point I measured myself: /tmp for a source build,
/var/run/postgresql for the packages.
Two corrections to my own review
My finding 4 under-counted. I reported concurrency as the one suite escaping 230's
discriminator. Widening to a path or a quoted variable finds four --
concurrency, extension_upgrade, unique_conc, update_conc -- because I matched only
the double-quoted form and the tree's dominant spelling is -h '$WORKDIR'. All four set
unix_socket_directories, so there was never a live bug, but your population is right and
mine was not. The printed population beside the claim is what makes that visible, which is
the reason to print it.
Part of one of my removal proofs was my own artifact. When I reverted the docs
sentence I left a blank line mid-paragraph, which truncated 240's paragraph range early, so
my run reddened [suites upgrade-guard]. The suites half was my mutation, not the check.
The upgrade-guard half is the real proof and it stands.
On 240
Recording agreement with your note: the first version passing its own removal proof is the
interesting result, not the fix. "upgrade" appearing eleven times in that file for
unrelated reasons is exactly the too-loose failure that reads as success, and scoping to
the paragraph plus matching the whole job key is the right shape. Deriving the job list
from nightly.yml means a fifth job is covered the day it is added, which is what makes
this different from correcting the sentence.
Unchanged and disclosed
This does not fix #740, so the nightly stays red and this guard's verdict still arrives
inside that red. Your PR body says so. Once #740 lands, run_coverage reaches its real
verdict at line 142 and the workflow-level signal starts meaning something again.
Approving as OffgridwithJD (formerly ChronicallyJD -- same account, renamed today).
Merging is yours; I merge nothing.
Self-inflicted and caught by a guard written earlier today. The edit that replaced the chown block with the GCOV_PREFIX redirect selected the region between the chown comment and `SUITES=`, and that region also contained not_a_suite() and its comment. Both went with it. The consequence was not subtle: without not_a_suite the coverage runner discovers every test/*.sh, including pg_upgrade and extension_upgrade, which is exactly the defect #742 fixed. It turned the `suites` jobs red on all five majors, because harness_selftest runs inside them and test/selftest/220 asserts the two not_a_suite copies agree. So 220 caught a deletion its author made, one commit after writing it, which is the argument for the check rather than for me. Restored byte-identical to main, verified by diff. The branch's diff against main for this file is now purely additive, 80 insertions and 0 deletions, which is the cheap check that would have caught this at the time: an edit meant to ADD a redirect had no business removing lines. harness_selftest: 132 checks, PASSED. run_coverage in the hostile configuration still returns 33 counters and builds a report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYZNZ2hPeFxTNQNeCmce4E
Closes #741.
test/extension_upgrade.shguards the break that is invisible until a userupgrades. In CI it had never verified an upgrade. Two separate defects, fixed
separately.
1. Nothing ran it
No workflow set
PGC_RUN_UPGRADE, so the block atrun_all_versions.sh:855-900never executed and the suite was absent from every
suites (PG N)job.Added a nightly
upgrade-guardjob on PG18. Three details are deliberate:fetch-depth: 0, becauseactions/checkoutfetches no tags by default and thesuite's ref fallback then cannot resolve. That is the whole of defect 2's
symptom.
tagless checkout must be a loud red, never a skip, since "the old source was
not available" is exactly the state that made this guard look like it was
running when it was not.
git archiveinto a plain tree and passedexplicitly, i.e. the suite's documented directory form. The suite runs
under
sudo(itsrunpgis an unconditionalrunuser), and a rootgit cloneof a workspace owned by the runner user trips git'sdubious-ownership refusal. Passing it explicitly also sets the suite's
EXPLICITflag, so its "not a git checkout" SKIP branch is unreachable fromhere. Exit 2 is a failure of this job rather than a waiver, for the same
reason.
2. The one path that did reach it could not work
run_coverage.shdiscovers everytest/*.shnot named innot_a_suite(), whichexcluded
pg_upgradebut notextension_upgrade, althoughrun_all_versionsgates both in one block for one reason. It ran with no old source, and since
run_coveragemaps onlyrc = 66to SKIP, the environment shortfall was countedas a failed suite. Hence
1 failed ( extension_upgrade)every night.Both upgrade suites are now excluded together. Deciding to run an opt-in guard is
run_all_versions.sh's job, not the coverage runner's.The comment was already false
There are two copies of that list, and
run_coverage.sh's comment said it was"Kept in step with harness_selftest.sh's list". It was not: the selftest copy in
040already namedextension_upgrade. A comment claiming two things agree isnot a mechanism that makes them agree, so
test/selftest/220now asserts it overthe whole population of test files (227 compared), in both directions.
The gated suite list is derived from the
PGC_RUN_UPGRADEblock rather thannaming the two suites, so a third one added there is covered the day it is added.
Verified end to end, not by inspection
On PG17 in the container, one variable between the arms:
run_coverage.shdoesv1.0-alpha is not presentgit archiveThe ARM reproduces the nightly's failure exactly. The CONTROL upgrades 1.0-dev to
1.0-alpha2 with 7 PASS lines including the 149-object catalog convergence check,
so the upgrade path itself is sound today and the red really was the missing old
source.
Removal proofs, each in isolation
not_a_suite[extension_upgrade]not_a_suitecopyAnd the instrument is shown to say both things:
not_a_suiteis asserted toREFUSE an ordinary suite, so its acceptance of these two means something.
harness_selftest: 119 checks, PASSED.What this does not do
lcovsucceeds,run_coveragereaches its real verdict at line 142. The twoare a pair, and neither alone reaches green.
run_coverage.shthe exit-2 SKIP contract thatrun_all_versions.shhonours. With both upgrade suites excluded, no discoveredsuite exits 2 today, so that would be untested handling for a case that cannot
currently arise. Worth doing if a suite ever needs it.
pg_upgradecross-major pair matrix, which is the otherhalf of
PGC_RUN_UPGRADEand a much larger job.🤖 Generated with Claude Code
https://claude.ai/code/session_01EYZNZ2hPeFxTNQNeCmce4E