docs/testing.md tells a reader to run a suite by naming it. Thirty of those invocations fail, because 103 of the harness scripts do not carry the executable bit. CI never meets this, so nothing has reported it.
What happens
$ test/run_all_versions.sh /usr/local/pg18a/bin/pg_config
bash: test/run_all_versions.sh: Permission denied
$ test/parquet_export.sh /usr/local/pg18a/bin/pg_config
bash: test/parquet_export.sh: Permission denied
Both are the exact command docs/testing.md gives (lines 156 and 27). test/smoke.sh, which is mode 100755, runs.
Who this affects, and who it does not
A person following the documentation, on a fresh clone. That is the whole of it.
CI and the version matrix are unaffected, and it is worth being explicit about why, because it is the reason this has been invisible:
.github/workflows/ci.yml:485 and nightly.yml:188 both invoke it as bash test/run_all_versions.sh ....
run_all_versions.sh runs each suite as bash "$builddir/test/${s}.sh" "$pgc" (lines 712 and 749), so a suite's own mode never matters inside the matrix.
So every green run in the project's history is honest. The gap is between the documentation and a shell.
The measurement
At 0e4884c1, counted from the ref rather than a working tree:
|
100755 |
100644 |
test/*.sh |
153 |
95 |
test/*.py |
4 |
8 |
30 documented invocations name one of those 103 files: 29 in docs/testing.md (lines 13, 20-24, 26-28, 33, 51-55, 57, 58, 60-65, 156, 188, 189, 217, 272, 412) and one in docs/limitations.md:79. Two further mentions — docs/administration.md:422 and CONTEXT.md:166 — are prose references, not commands, and are fine.
Cause: two different situations, not one
I searched the whole history of test/ for mode transitions. There is exactly one 100755 -> 100644 change:
56ae5f8eb 2026-08-16 perf: iterate needed columns in the batch fold, not all natts
test/run_all_versions.sh 100755 -> 100644
That commit is mine. It touched four files, added one line to SUITES, and took the bit off run_all_versions.sh on the way past — no part of the change had anything to do with it. run_all_versions.sh was added executable on 2026-07-19 in 1d1b31b and stayed that way for four weeks until I broke it. test/native_batch_fold_projection.sh, added in that same commit, was created 100644.
The other 102 were born 100644 and never regressed. Spot-checked at their own add commits: parquet_export.sh (b6a3b30), native_agg.sh (85b6f29), hardening.sh (d2cb212), native_batch_fold_projection.sh (56ae5f8) — all 100644 from the first commit that introduced them.
So this is not one accident. It is one regression plus a long-standing habit of adding suites without the bit, and nothing anywhere notices either.
Why nothing caught it
test/harness_selftest.sh has no mode check — no chmod, no 100755, no -x test anywhere in it. The suite that exists to pin harness invariants does not pin this one, which is why a mode could drift out of a perf commit unremarked and why 102 files could arrive without it.
Suggested fix, in this order
chmod +x the 103 files and commit the mode change alone, so the diff is reviewable as exactly that.
- Add a
harness_selftest check: every test/*.sh and test/*.py that docs/testing.md names must be executable. Run it over the tree before committing step 1 and it flags 103, which is the false-positive budget measured rather than assumed; after step 1 it flags none.
- Decide the rule and write it in
CONTEXT.md beside the "register every suite in SUITES" line, since that is where someone adding a suite is already reading.
An alternative to 1 and 2 is to change the 30 documented invocations to bash test/..., matching what CI does. I would rather not: docs/testing.md presents these as commands to type, a reader will type them, and chmod +x is the smaller change to make the documentation true.
Happy to send the PR for any or all three; step 2 is the part that stops it recurring, and it is the part I would not want left out.
Found while gating #851, whose two new files (test/parquet_export_stats.sh, test/parquet_stats.py) are both correctly 100755. My own gate script died on run_all_versions.sh before it ran a single suite, which is how I noticed.
docs/testing.mdtells a reader to run a suite by naming it. Thirty of those invocations fail, because 103 of the harness scripts do not carry the executable bit. CI never meets this, so nothing has reported it.What happens
Both are the exact command
docs/testing.mdgives (lines 156 and 27).test/smoke.sh, which is mode 100755, runs.Who this affects, and who it does not
A person following the documentation, on a fresh clone. That is the whole of it.
CI and the version matrix are unaffected, and it is worth being explicit about why, because it is the reason this has been invisible:
.github/workflows/ci.yml:485andnightly.yml:188both invoke it asbash test/run_all_versions.sh ....run_all_versions.shruns each suite asbash "$builddir/test/${s}.sh" "$pgc"(lines 712 and 749), so a suite's own mode never matters inside the matrix.So every green run in the project's history is honest. The gap is between the documentation and a shell.
The measurement
At
0e4884c1, counted from the ref rather than a working tree:test/*.shtest/*.py30 documented invocations name one of those 103 files: 29 in
docs/testing.md(lines 13, 20-24, 26-28, 33, 51-55, 57, 58, 60-65, 156, 188, 189, 217, 272, 412) and one indocs/limitations.md:79. Two further mentions —docs/administration.md:422andCONTEXT.md:166— are prose references, not commands, and are fine.Cause: two different situations, not one
I searched the whole history of
test/for mode transitions. There is exactly one100755 -> 100644change:That commit is mine. It touched four files, added one line to
SUITES, and took the bit offrun_all_versions.shon the way past — no part of the change had anything to do with it.run_all_versions.shwas added executable on 2026-07-19 in1d1b31band stayed that way for four weeks until I broke it.test/native_batch_fold_projection.sh, added in that same commit, was created 100644.The other 102 were born 100644 and never regressed. Spot-checked at their own add commits:
parquet_export.sh(b6a3b30),native_agg.sh(85b6f29),hardening.sh(d2cb212),native_batch_fold_projection.sh(56ae5f8) — all 100644 from the first commit that introduced them.So this is not one accident. It is one regression plus a long-standing habit of adding suites without the bit, and nothing anywhere notices either.
Why nothing caught it
test/harness_selftest.shhas no mode check — nochmod, no100755, no-xtest anywhere in it. The suite that exists to pin harness invariants does not pin this one, which is why a mode could drift out of a perf commit unremarked and why 102 files could arrive without it.Suggested fix, in this order
chmod +xthe 103 files and commit the mode change alone, so the diff is reviewable as exactly that.harness_selftestcheck: everytest/*.shandtest/*.pythatdocs/testing.mdnames must be executable. Run it over the tree before committing step 1 and it flags 103, which is the false-positive budget measured rather than assumed; after step 1 it flags none.CONTEXT.mdbeside the "register every suite inSUITES" line, since that is where someone adding a suite is already reading.An alternative to 1 and 2 is to change the 30 documented invocations to
bash test/..., matching what CI does. I would rather not:docs/testing.mdpresents these as commands to type, a reader will type them, andchmod +xis the smaller change to make the documentation true.Happy to send the PR for any or all three; step 2 is the part that stops it recurring, and it is the part I would not want left out.
Found while gating #851, whose two new files (
test/parquet_export_stats.sh,test/parquet_stats.py) are both correctly 100755. My own gate script died onrun_all_versions.shbefore it ran a single suite, which is how I noticed.