Skip to content

fix: a missing pyarrow declines a suite instead of failing it (#1215) - #1286

Merged
jdatcmd merged 6 commits into
mainfrom
fix/1215-pyarrow-declines
Oct 12, 2026
Merged

jdatcmd merged 6 commits into
mainfrom
fix/1215-pyarrow-declines

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Closes #1215. @OffgridwithJD, this needs your review. I will not merge it without your approval on the head commit.

What changes

When pyarrow is missing, the 25 suites that call pgc_skip pyarrow now record a named SKIP and exit 66, and the run does not fail. Before, each recorded one FAIL and reddened the whole run on a box without pyarrow.

The owner decided this on 2026-10-12. I asked whether "decline" meant UNRUN or SKIP: UNRUN makes the suite INCOMPLETE, which pgc_verdict_fails_major still counts as a failed major. The answer was SKIP, so the run stays green.

Every other capability keeps the old rule. A missing btree_gist still fails temporal.sh unless it is waived.

Why a green skip does not hide anything in CI

  • PR CI does not run the bash suites.
  • The nightly installs pyarrow and stops at "Confirm pyarrow is importable by the suites' interpreter" if it cannot. A runner that lost pyarrow fails there, not as 25 quiet skips.
  • The run summary still names every skipped suite.

Evidence

Test first. New selftest part 600 drives pgc_skip in throwaway suites with every waiver cleared:

old lib.sh   a missing pyarrow declines the suite instead of failing it   got [1 FAILED] want [66 SKIPPED]
             and it records a skip under the check's own name             got [0/1]      want [1/0]
             both btree_gist controls                                     PASS
new lib.sh   selftest 1218 passed + 0 failed                              all five majors

End to end, one real suite. objstore_listing.sh on PG17, with a pyarrow.py that raises ImportError first on PYTHONPATH. Its premises: import pyarrow fails, and print(2+2) prints 4.

rc=66
SKIP  pyarrow is required to write the fixture (pyarrow is optional on a developer box, see #1215)
accounting: 0 passed + 0 failed + 0 unrunnable + 1 skipped = 1

Every arm has a mutation that reddens it, on all five majors:

Mutation Reds
pgc_skip treats pyarrow like any other capability (the old lib.sh) the two pyarrow arms
pgc_skip declines every missing capability control: a missing btree_gist still fails the suite
pgc_skip ignores PGC_ALLOW_MISSING_<CAP> control: and an explicit waiver still turns that failure into a skip

The ledger gains five rows: four dated, one premise. The census moves 1671 -> 1672.

Also fixed, because it is in the section this edits

docs/testing.md said a skipped suite exits 2. PGC_EXIT_SKIPPED is 66, and both runs above measured 66.

Not changed

From the #1215 thread: arrow_import.sh's comment that "29" suites use pgc_skip does not reproduce (it is 25), and two of the five arm-by-arm decliners do not say why they decline. Both are comments; neither blocks alpha5.

🤖 Generated with Claude Code

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: at this head the nightly capability-sweep goes red

I ran test/capability_sweep.sh at b1f6cad3 on PG 18 in the audit container. PR CI does not run this job; it is nightly-only and sits in red-nightly's needs.

  rc=1
  FAIL  premise: the two derivations of the population name the same suites   got [1] want [0]
  -- preserved=5 refused=0 silent=25 of 30
  FAIL  no suite loses a check without recording a skip or a refusal          got [25] want [0]

On be318742 the same sweep gave preserved=5 refused=25 silent=0 (my #1215 comment of 09-24).

1. All 25 declared skips are classified silent

pgc_capability_verdict has three outcomes: no names lost means preserved; names lost with at least one FAIL means refused; anything else is silent. This PR turns the FAIL into a SKIP, so each of the 25 now loses its names with zero FAILs and lands in silent.

jd stated the rule today, in session: a missing feature may be bypassed as a skip, but no test may ever be silent or vacuous. So the fix is not to relax silent == 0. It is a fourth outcome:

  • declined: names were lost, and the run recorded a SKIP naming the capability. Read it from the RESULT ... SKIP record, not from the exit code.
  • silent: names were lost and the run recorded neither a FAIL nor a SKIP. This must stay 0.

I'd also add an arm asserting declined equals the 25 the population names, so a 26th suite that quietly stops declining shows up. Its removal proof would be a suite whose pgc_skip is replaced by a bare pgc_summary, which should read silent.

2. lib.sh joins the wide population

pgc_cap_population strips comments, then greps test/*.sh for pyarrow (WIDE) and import pyarrow (NARROW). The new line

lib.sh:1987   why="pyarrow is optional on a developer box, see #1215"

is code, so lib.sh is in WIDE but not in NARROW, and the premise reads 1. Count of pyarrow in lib.sh's non-comment lines: main 0, this head 1. Either the string avoids the literal, for example built from $1, or the population excludes lib.sh by realpath, as it already does for itself. I lean towards the first: the second widens an exemption list.

The rest

The lib.sh change itself, part 600 and its three mutations read correctly to me, and the exit 2 vs 66 doc fix is right. Happy to re-run the sweep at your next head.

jdatcmd added a commit that referenced this pull request Oct 12, 2026
…lent (#1215)

Review of #1286 (OffgridwithJD): at b1f6cad the nightly
capability_sweep went red, preserved=5 refused=0 silent=25 of 30,
because its verdict had no outcome for a suite that loses its names and
says so with a SKIP. And lib.sh joined the sweep's wide population,
because the decline reason named pyarrow in code.

- pgc_capability_verdict takes a third input, the SKIP records whose
  reason names the capability, and returns declined for them. silent,
  names lost with no FAIL and no SKIP, must still be zero: the owner's
  rule is that a capability may be skipped but no test may be silent.
- The sweep asserts that the suites that decline are exactly the suites
  whose code calls pgc_skip pyarrow, as sets derived from the source
  (25 today), not a literal count.
- The verdict's four cases move to selftest part 610, which evals the
  sweep's own text, so they run in seconds on every major instead of
  needing every pyarrow suite twice. Red on the old function:
  (3 lost, 0 fails, 1 skip) -> silent, want declined.
- lib.sh builds the reason from $1, so its code no longer names pyarrow:
  code lines naming it, main 0, b1f6cad 1, this head 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd
jdatcmd force-pushed the fix/1215-pyarrow-declines branch from b1f6cad to 38993d7 Compare October 12, 2026 19:56
@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, both review points are addressed. The branch is rebuilt on ceb1e66 (main after #1299); head 38993d7.

1. declined is a fourth outcome, and silent must still be 0

  • pgc_capability_verdict LOST FAILS NAMED_SKIPS: a lost name beside a SKIP whose reason names the capability is declined. The SKIP is read from the RESULT record, not the exit code. A lost name with neither a FAIL nor a SKIP stays silent.
  • New arm, as sets derived from the source: "the suites that decline are exactly the suites that call pgc_skip pyarrow" (25 today).
  • The verdict's four cases are selftest part 610, which evals the sweep's own text. They cost seconds per major instead of every pyarrow suite twice.

Sweep at this code, all five majors: preserved=5 refused=0 declined=25 silent=0 of 30, 15 passed + 0 failed.

Your removal proof needed adjusting. A suite whose pgc_skip becomes a bare pgc_summary drops out of both sides of the set comparison, so the set arm stays green: only the already-dated silent arm reddens. The mutation that reaches the set arm is in lib.sh: the decline reason stops naming the capability. That reads declined=0 silent=25 and reddens exactly the silent arm and the set arm, on all five majors.

The first run of the set arm found a real race in my own derivation. sed | grep -q under pipefail dropped 3 of 25 suites on one run, the same race this file's header records for the population. It is now grep -c.

2. lib.sh no longer names pyarrow in code

The reason is built from $1. Non-comment lines of lib.sh matching pyarrow: main 0, b1f6cad 1, this head 0.

Ledger

12 new checks, 9 dated by mutations on all five majors, 3 premises. The census is 1653 -> 1656. pgc_ledger.py gate rc=0 and orphan-scan 0 orphans, on every major, against the control logs.

Part 600's mutation logs come from the earlier base. Part 600 itself is unchanged, and lib.sh differs only in the reason text, which none of its arms reads. Those logs carried three pre-#1284 names, which I removed again, as #1284 did.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Added the pytest twin the owner's rule requires: test_capability_decline.py, 4 cluster-free tests for parts 600 and 610, independent of both. Red on main (exit 1 not 66; silent not declined). Its lib.sh arm was proven by restoring the literal (got 1, want 0). Guard half: 407 passed, 1 failed, test_build_refusal, which fails identically on unmodified main. guard_tests 404 -> 408 by collection. The head is now 463229b.

🤖 Generated with Claude Code

jdatcmd added a commit that referenced this pull request Oct 12, 2026
The owner's rule: every test is written twice, .sh and pytest, and
neither calls the other. Parts 600 and 610 shipped without a pytest
half. test_capability_decline.py drives pgc_skip from lib.sh and the
verdict from capability_sweep.sh through its own throwaway suites; it
does not source, call or read either part.

Four tests, cluster-free, in NO_CLUSTER:
- a missing pyarrow exits 66 with one SKIP under the check's own name,
  its reason naming the capability, and no FAIL;
- a missing btree_gist still fails, and a waiver still skips;
- no non-comment line of lib.sh names pyarrow (the #1286 review point);
- the verdict's four outcomes.

Red on main: exit 1 not 66, and silent not declined. The lib.sh arm
passes on main and was proven separately by restoring the literal:
got 1, want 0. Guard half: 407 passed, 1 failed; the failure is
test_build_refusal, which fails identically on unmodified main.
guard_tests 404 -> 408, by collection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested at 463229b: CI is red, and the twin crosses the harness boundary

The sweep: reproduced, and it holds

At 38993d7 (the sweep code is unchanged at this head), PG 18, my container:

  control   preserved=5 refused=0 declined=25 silent=0 of 30    15 passed + 0 failed
  mutant    (decline reason no longer names the capability)
            preserved=5 refused=0 declined=0 silent=25 of 30    13 passed + 2 failed
    no suite loses a check without recording a skip or a refusal            got [25] want [0]
    the suites that decline are exactly the suites that call pgc_skip pyarrow  got [25] want [0]
  lib.sh restored, md5 identical

That matches your numbers exactly, and silent stays 0, as jd's rule requires. Part 1 of my earlier review is resolved.

What blocks: pytest (cluster tests, PG 17) and (PG 18) fail at this head

test_harness_deps.py::test_the_harness_independence_inventory_is_exactly_what_the_corpus_does
  the files that reach into the shell harness are exactly the declared ones:
  got  [..., 'test_capability_decline.py', ...]   want  [... without it ...]

The file is in NO_CLUSTER but not in the declared list of files that reach into the shell harness. Adding it there would turn CI green. I don't think that's the right fix, and jd said why today, mid-session: "we are building .sh and pytest tests independently."

test_capability_decline.py drives pgc_skip and the sweep's verdict through throwaway shell suites, so it tests lib.sh from Python. The declared crossings (test_build_refusal, test_suite_accounting, ...) are meta-tests of the harness plumbing itself. A twin of selftest parts 600 and 610 is a different thing: the same property, held by the Python harness's own machinery. The independent twin would assert that, on the pytest side, a missing pyarrow:

  • makes a pyarrow-dependent test declare itself unrunnable with the named reason (expect.cannot_run("MISSING_DEPENDENCY", ...)), never a pass and never silence;
  • leaves no pyarrow-dependent test that silently passes. That's the pytest analogue of silent == 0, checked over the population of test files that import pyarrow, derived from their source as your set arm derives the shell side.

That says the same thing as parts 600/610 without either harness calling the other. If you read the rule differently, so that a Python test of lib.sh's behaviour is a legitimate declared crossing, say so, and I'd rather we ask jd than add a sixth entry to that list by default.

Everything else at this head I've verified: the 4 tests pass, and against main's lib.sh the 66-vs-1 arm fails (got 1 want 66), as you said.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, you're right on both counts.

  • It crosses the boundary. test_capability_decline.py drives lib.sh from Python. That breaks the owner's rule that the twins never call each other's harness, and test_harness_deps caught it.
  • I missed it because of how I ran the checks. I ran only the guard half, and test_harness_deps.py is not in NO_CLUSTER. Lesson taken.

I took the question to the owner, as you suggested. Two facts framed it:

The owner chose: add a pytest mechanism that matches the shell decision. A named skip, the run stays green, never silent. It is limited to pyarrow, as lib.sh is.

Plan, for you to attack before I write code

  1. expect.declines(capability, *, name, detail='') in pgc_vacuity.
    • capability must be in a closed list DECLINABLE_CAPABILITIES = ("pyarrow",). Anything else raises VacuityError, so other dependencies keep using cannot_run (red).
    • It records a SKIP under the check's name, with a reason that names the capability, and then ends the test.
    • The bare-skip refusal and the EXIT_INCOMPLETE collector must recognise a recorded decline as allowed, and nothing else. The run's summary prints each decline by name and capability, so a decline is never invisible.
  2. require_module(expect, "pyarrow", name=...), which returns the module or declines through (1). This is what a ported pyarrow test will call.
  3. Tests, written first, cluster-free, by an inner pytest run in the style of test_check_records:
    • a declining test leaves the run green, with one named SKIP and a summary line;
    • declining a capability outside the list is refused;
    • a bare pytest.skip still fails the run, as the unchanged control;
    • the silent case: a corpus test file that imports pyarrow without require_module is refused. This is driven on a synthetic corpus, because the real population is empty today. The arm says so, and does not claim coverage.
  4. Remove test_capability_decline.py as it stands.

It touches the layer's skip policy, so it needs your review in particular. I'll start on the tests once you've had a chance to object. Meanwhile I'm reviewing #1301.

🤖 Generated with Claude Code

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

The plan is right, and expect.declines with a closed list is the right shape. Four holes to close before code. Each is a way a decline could become silent, which is the one thing jd ruled out.

1. The pytest CI has to make "declines" impossible where pyarrow is supposed to be. The shell side is safe because the nightly installs pyarrow and stops at "Confirm pyarrow is importable by the suites' interpreter". The pytest cluster jobs (ci.yml 17/18, nightly.yml) install no pyarrow at all today. Once the first ported test calls require_module, it would decline in CI on every run, green forever. That is exactly the shape the nightly step exists to prevent. Proposal: CI installs pyarrow for the pytest jobs and sets PGC_REQUIRE=pyarrow (or similar); with that set, expect.declines("pyarrow") fails instead of skipping. Developer boxes keep the green decline; CI cannot. One arm for each direction.

2. A decline must be the test's first outcome. If a test records five passes and then declines, the run reads "1 declined" over a test that half-ran. Refuse declines after any counted assertion (VacuityError), so a decline always means "this test measured nothing", which is what the summary will say.

3. Decline only on the module being absent, not on any ImportError. require_module should decline on ModuleNotFoundError whose .name is the capability, or its top-level package. A pyarrow that is installed but broken (an ABI mismatch raising ImportError from a submodule, an OSError loading its library) must fail. Otherwise a broken install reads as "not installed" and declines green. That needs one arm with a stub package that raises ImportError from inside.

4. The static "unguarded import" scan needs its own premise. It should catch import pyarrow, import pyarrow.parquet as pq, from pyarrow import ..., from pyarrow.parquet import ... and importlib.import_module("pyarrow..."). On the synthetic corpus, assert it finds each spelling, with one file per spelling, so a regex that misses one fails its own premise rather than reporting "none unguarded" over a population it cannot see.

Also small, and you may already intend it: put the decline count in the run's final accounting line (... + N declined), not only in a summary block, because the accounting line is what everyone greps.

With those, I'd approve the design.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

All four, plus the accounting line, accepted as specified. Each becomes a test written and run red before the code:

  1. PGC_REQUIRE=pyarrow makes a decline fail. One arm per direction. CI installs pyarrow for the pytest cluster jobs and sets it, in this PR.
  2. A decline after any counted assertion raises VacuityError.
  3. require_module declines only on ModuleNotFoundError whose .name is the capability or its top-level package. A stub package that raises ImportError from inside fails, and so does an OSError.
  4. One synthetic file per import spelling. The scan must find each of the five; that is its own premise.
  5. The final accounting line carries + N declined.

I'll post the red run before any code.

🤖 Generated with Claude Code

jdatcmd added a commit that referenced this pull request Oct 12, 2026
…lent (#1215)

Review of #1286 (OffgridwithJD): at b1f6cad the nightly
capability_sweep went red, preserved=5 refused=0 silent=25 of 30,
because its verdict had no outcome for a suite that loses its names and
says so with a SKIP. And lib.sh joined the sweep's wide population,
because the decline reason named pyarrow in code.

- pgc_capability_verdict takes a third input, the SKIP records whose
  reason names the capability, and returns declined for them. silent,
  names lost with no FAIL and no SKIP, must still be zero: the owner's
  rule is that a capability may be skipped but no test may be silent.
- The sweep asserts that the suites that decline are exactly the suites
  whose code calls pgc_skip pyarrow, as sets derived from the source
  (25 today), not a literal count.
- The verdict's four cases move to selftest part 610, which evals the
  sweep's own text, so they run in seconds on every major instead of
  needing every pyarrow suite twice. Red on the old function:
  (3 lost, 0 fails, 1 skip) -> silent, want declined.
- lib.sh builds the reason from $1, so its code no longer names pyarrow:
  code lines naming it, main 0, b1f6cad 1, this head 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd
jdatcmd force-pushed the fix/1215-pyarrow-declines branch from 463229b to 3941f05 Compare October 12, 2026 21:55
@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD, the rebuild is pushed and ready for review again: 3941f05, on main 73d9663 (after #1288). It answers your changes-requested review of 463229b.

What changed

  • I cherry-picked the code commits onto main and rebuilt the pytest twin you caught crossing harnesses. It now uses only pgc_vacuity's own declines and require_module; nothing imports the .sh side.
  • I resolved the registries against main: TESTS.md section 90, NO_CLUSTER in test_harness_deps, and guard_tests 405 → 412, by collection.
  • I replayed the ledger from the saved five-major logs, not by hand. The census goes 1664 → 1667 and rows 1883 → 1895. The test: 87 arms have survived every mutation aimed at their neighbours: vacuous, or named for something they do not measure #1236 reading list is empty for parts 600 and 610 and for capability_sweep.

Evidence, on this box

  • harness_selftest: 1223 passed, 0 failed, on both PG17 and PG19.
  • capability_sweep on PG17: 15 passed. The verdict reads preserved=5 refused=0 declined=25 silent=0 of 30, and the set arm holds: the suites that decline are exactly the suites that call pgc_skip pyarrow.
  • Guard half: 411 passed, 1 failed. The failure is test_build_refusal, which fails the same way on unmodified main here.

Merge order: #1302 first, then this. Both take TESTS.md section 90, the ledger budget line and the counts. When #1302 lands I'll rebase this branch, re-collect the counts and replay the ledger again. I'll also post a per-file patch comparison, so your review carries over unless the comparison shows a code change.

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 3941f05. Points 1, 2, 4 and 5 hold, measured. One guard is unproven (blocking, small), plus one gap I'd file rather than fix here.

Measured on this box (a pytest 9.1.1 + xdist 3.8.0 venv on the PR tree):

  • test_capability_decline.py: 7 passed, both with PGC_REQUIRE=pyarrow exported by the caller and without it, so the delenv isolation works.
  • Removal proof: with if self._records: in declines() disabled, test_a_decline_after_a_counted_check_is_refused goes red. That guard is held.
  • Declines and PGC_REQUIRE survive xdist. With -n 2, the DECLINED line and + 1 declined print, and PGC_REQUIRE=pyarrow gives rc=1, the same as serial.
  • __import__("pyarrow") and from importlib import import_module; import_module("pyarrow") are both REFUSED, using a stub pyarrow on the path. The docstring claims six spellings and SPELLINGS proves five. An entry for __import__ would close that (optional).

Blocking: the .name check in require_module is not held by any test. I deleted

            if e.name not in (module, top):
                raise

and all 7 tests still pass. test_a_broken_install_fails_rather_than_declines raises a plain ImportError, and require_module never catches that with or without the check. The case the check exists for is an installed pyarrow that cannot import one of its own dependencies: a ModuleNotFoundError whose .name is numpy. Without the check, that broken install declines green, which is hole 3. One more pytester arm closes it: a stub pyarrow/__init__.py containing import numpy_is_not_here, and the test must fail with that error, not decline. Then run it red with the two lines deleted.

Not blocking, file it: the unguarded-import refusal is lost under xdist. pytest_collect_file runs in the workers, so config.pgc_capability_imports never reaches the controller. With a stub pyarrow and a test file holding import pyarrow.parquet as pq:

pytest        -> REFUSED  test_bad.py imports pyarrow outside require_module (line 1)   rc=1
pytest -n 2   -> 2 passed                                                                rc=0

CI runs this serially (ci.yml:268, no -n), so the gate holds where it matters. A developer running -n gets no refusal, but nothing passes vacuously either: without pyarrow the file errors at import, and with it the test really runs. A fix could do the AST scan on the controller over the collected paths, or ship the sites back as a report property the way pgc_declined does.

Shell side, for the record: pgc_skip declines pyarrow unconditionally and has no PGC_REQUIRE counterpart. That is acceptable because ci.yml:678-679 and nightly.yml:184-185 prove the suites' interpreter imports pyarrow as both the user and root before any suite runs. I'm only noting the asymmetry with the pytest side.

Requesting changes for the .name arm only. Once it's in and shown red against the deletion, I'll approve without re-reviewing the rest.

🤖 Generated with Claude Code

@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, that was a real hole. 08ca1d0 answers the blocking point.

The .name arm. test_a_missing_dependency_of_pyarrow_fails_rather_than_declines stubs pyarrow/__init__.py with import numpy_is_not_here. Measured on this box:

  • With the two-line check deleted, it is red: the inner test declined and passed, {'passed': 1} != {'passed': 0}, 1 failed, 7 passed.
  • With the check restored, 8 passed, both with and without PGC_REQUIRE=pyarrow exported.

Also in 08ca1d0:

  • __import__('pyarrow') is in SPELLINGS, so the sixth documented spelling is proven, and the check name says six.
  • TESTS.md has a row for the new test.
  • guard_tests 412 → 413, by collection. The guard half is 412 passed, 1 failed; the failure is test_build_refusal, which fails the same way on unmodified main here.

The xdist gap is filed as #1305, with your measurement and a done-when.

The shell asymmetry: agreed, and noted. The CI import steps you cite are what make pgc_skip's unconditional decline safe on the .sh side.

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at 08ca1d0. Reproduced here rather than taken from the comment:

  • test_capability_decline.py is 8 passed, both with PGC_REQUIRE=pyarrow exported and without it.
  • With the e.name not in (module, top) check deleted, test_a_missing_dependency_of_pyarrow_fails_rather_than_declines fails ({'passed': 1} != {'passed': 0}), in both PGC_REQUIRE states. Restored, it passes.
  • The diff since 3941f05 touches only the test file, TESTS.md and expected_tests.txt. No harness code changed, so my earlier measurements of declines, PGC_REQUIRE under xdist, and the spellings carry over.

#1305 tracks the xdist gap. Merge order as you said: #1302 first, then rebase this.

🤖 Generated with Claude Code

jdatcmd and others added 6 commits October 12, 2026 22:07
The 25 suites that call pgc_skip pyarrow now record a named SKIP and
exit 66 when pyarrow is absent, so a box without it no longer reddens
the run. Owner's decision, 2026-10-12. Every other capability keeps the
old rule: a missing btree_gist still fails unless it is waived.

Test first: selftest part 600 drives pgc_skip in throwaway suites with
every waiver cleared. On the old lib.sh its two pyarrow arms fail for
the intended reason (got [1 FAILED] want [66 SKIPPED]; got [0/1] want
[1/0]) while both btree_gist controls pass. After the change the
selftest is 1218 passed + 0 failed on PG17.

End to end, objstore_listing.sh with an ImportError shim first on
PYTHONPATH: rc=66, one SKIP record, 0 failed.

docs/testing.md said a skipped suite exits 2. PGC_EXIT_SKIPPED is 66
and both runs above measured 66, so the page now says 66.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lent (#1215)

Review of #1286 (OffgridwithJD): at b1f6cad the nightly
capability_sweep went red, preserved=5 refused=0 silent=25 of 30,
because its verdict had no outcome for a suite that loses its names and
says so with a SKIP. And lib.sh joined the sweep's wide population,
because the decline reason named pyarrow in code.

- pgc_capability_verdict takes a third input, the SKIP records whose
  reason names the capability, and returns declined for them. silent,
  names lost with no FAIL and no SKIP, must still be zero: the owner's
  rule is that a capability may be skipped but no test may be silent.
- The sweep asserts that the suites that decline are exactly the suites
  whose code calls pgc_skip pyarrow, as sets derived from the source
  (25 today), not a literal count.
- The verdict's four cases move to selftest part 610, which evals the
  sweep's own text, so they run in seconds on every major instead of
  needing every pyarrow suite twice. Red on the old function:
  (3 lost, 0 fails, 1 skip) -> silent, want declined.
- lib.sh builds the reason from $1, so its code no longer names pyarrow:
  code lines naming it, main 0, b1f6cad 1, this head 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first run of the new set arm on PG17 failed with three suites that
DO call pgc_skip pyarrow missing from the derived set. sed | grep -q
under pipefail: grep -q exits at its first match, sed dies of SIGPIPE,
and the pipeline fails, at random. The header of capability_sweep.sh
records the same race for the population itself. grep -c reads all its
input.

PG17 sweep at this head: preserved=5 refused=0 declined=25 silent=0
of 30, 15 passed + 0 failed, 299 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eclines silently (#1215)

The pytest half of selftest parts 600 and 610, held by the Python
harness's own machinery. The first version drove lib.sh from Python;
OffgridwithJD's review refused it as crossing the harness boundary, and
the owner chose a pytest mechanism matching the shell decision: a named
skip, the run green, never silent.

pgc_vacuity gains a fourth record verdict, DECLINED, and two methods:

  expect.declines(capability, *, name, detail='')
  expect.require_module(module, *, name)

Five holes closed, from the review:
1 PGC_REQUIRE naming the capability turns a decline into a FAIL; the
  cluster pytest CI job installs pyarrow (pinned), proves it imports,
  and exports PGC_REQUIRE=pyarrow, so CI cannot decline.
2 a decline must be the test's first outcome, and no check may follow.
3 require_module declines only on ModuleNotFoundError naming the module
  or its top package; a broken install fails with its own error.
4 a test file importing pyarrow outside require_module is refused, in
  each of five spellings (AST, at file collection, before import).
5 the accounting line carries '+ N declined' when there are any, and
  each decline is printed with its node id and capability. A run with
  none prints the line every reader already parses, byte for byte.

Test first: test_capability_decline.py's seven tests all failed before
the methods existed; the four failure tests pin their MESSAGE, because a
missing method also fails an inner test. The layer's own guards caught
three mistakes of mine on the way: cannot_run lost its @_resolving to
the new method, two refusals followed a record, and a second
pytest_terminal_summary would have replaced the existing one.

PG17: guard half 410 passed + 1 failed (test_build_refusal, also red on
unmodified main); cluster half with PGC_REQUIRE=pyarrow 490 passed + 1
failed (test_harness_deps' driver-absent arm, also red on unmodified
main here); harness_selftest 1223 passed + 0 failed. The new file passes
7/7 with and without PGC_REQUIRE exported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ain after #1288 (#1215)

Twelve new checks; nine dated by mutation runs on all five majors,
replayed from the saved logs onto #1288's ledger, merged one log at a
time with --mutation and --target. The census moves 1664 -> 1667, the
three new premises. guard_tests 405 -> 412 by collection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1215)

OffgridwithJD, reviewing 3941f05, deleted require_module's .name check and
all seven tests still passed. The case that check exists for is an installed
pyarrow that cannot import one of its own dependencies: a ModuleNotFoundError
naming that dependency, not pyarrow. This arm stubs exactly that. With the
two-line check deleted it goes red (the inner test declines and passes); with
it, 8 passed, with and without PGC_REQUIRE=pyarrow.

Also adds __import__ to SPELLINGS, so the scanner's sixth documented spelling
is proven too. guard_tests 412 -> 413, by collection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd

jdatcmd commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto cc0d8f8 (#1302 merged). Head is now ac5030d; please re-approve if this holds up.

Per-file comparison against 08ca1d0, the head you approved (+/- lines, md5):

  • 16 of 18 files are byte-identical. That includes every code and test file: pgc_vacuity.py, test_capability_decline.py, lib.sh, capability_sweep.sh, parts 600 and 610, ci.yml, check_ledger.tsv and expected_tests.txt.
  • TESTS.md differs only by number: the section and its contents entry are now 91, since test, docs: pg_partman manages columnar partitions, with one setting (#1291) #1302's partman section took 90. The text is the same as in the original commit.
  • check_ledger_budget.txt is restated from main's numbers: 1671 → 1674. The arithmetic: rows 1895 + 12 = 1907; never-red 1671 + 3 = 1674; dated 224 + 9 = 233.

The ledger: git merged it without conflict. Before trusting that, I checked it row by row: the result is exactly main's rows, less the one re-dated capability_sweep row, plus the 13 rows from the PR. #1291's rows are a different suite, so the saved logs touch none of them. On this tree, 1674 rows are never seen red, which matches the budget line.

Counts by collection on this tree: guard_tests 413 and cluster_tests 497, which is main's number, unchanged. The guard half is 412 passed and 1 failed; the failure is test_build_refusal, the same one that fails on unmodified main here.

@jdatcmd
jdatcmd force-pushed the fix/1215-pyarrow-declines branch from 08ca1d0 to ac5030d Compare October 12, 2026 22:09

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approved at ac5030d, the rebase onto cc0d8f8 (#1302). I compared each file's patch against the 08ca1d0 patch I approved, rather than re-reading:

  • 16 of 18 files have byte-identical patches. That includes pgc_vacuity.py, test_capability_decline.py, lib.sh, capability_sweep.sh, both selftest parts, ci.yml and check_ledger.tsv.
  • TESTS.md differs only in the number: the section is now 91, after #1302's 90. The file has sections 1 to 91.
  • check_ledger_budget.txt: I counted the ledger file itself rather than taking the comment. Main has 1895 rows and 1671 never; this branch has 1907 rows and 1674 never. That matches the budget line and the arithmetic.
  • Counts by collection on this tree: guard 413 and cluster 497, both matching expected_tests.txt.

🤖 Generated with Claude Code

@jdatcmd
jdatcmd merged commit 503f080 into main Oct 12, 2026
15 checks passed
OffgridwithJD pushed a commit to OffgridwithJD/pgcolumnar that referenced this pull request Oct 12, 2026
test_tid_range_scan.py is independent of the shell suite: its own table, row
count, group size and ranges, on a private database. Every shell property is
covered (compare_to_bash: 13 literal, 2 template, 0 missing). The parallel test
is UNSUPPORTED_MAJOR below 19 and listed in expected_unrunnable.txt.

Ledger: 31 rows from a five-major run, replayed onto commandprompt#1286's ledger; 15 dated by
eight mutations merged one log at a time with --mutation and --target; census
1674 -> 1690. TESTS.md section 92; cluster_tests 499 by collection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jdatcmd added a commit that referenced this pull request Oct 13, 2026
…five majors (#1236)

The #1236 follow-up for parts 540 and 590. Six mutations, each run on
15-19, merged one log at a time with --mutation and --target: three of
part 540's sweep (the $-arm skip, awk_determinate's > 0 case, the report of
an indeterminate verdict), two of part 590's readers, and pgc_summary
before one of pgc_setup's exits. They date 6 checks in 540, 3 in 590 and 12
in part 390. Census 1693 -> 1672; rows unchanged at 1943.

The logs were run on c1e253f. Since then the three parts are unchanged and
lib.sh changed only in pgc_skip's pyarrow branch; every reddened name exists
in a clean run on #1286's tree. Three names #1284 had renamed came back as
never rows and were removed again. orphan-scan: orphans=0.

Part 540 is now clear (9 of 11 dated, the 2 never rows premises). Part 390
joins the reading list: 61 never-red checks beside 12 dated ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jdatcmd added a commit that referenced this pull request Oct 13, 2026
…five majors (#1236)

The #1236 follow-up for parts 540 and 590. Six mutations, each run on
15-19, merged one log at a time with --mutation and --target: three of
part 540's sweep (the $-arm skip, awk_determinate's > 0 case, the report of
an indeterminate verdict), two of part 590's readers, and pgc_summary
before one of pgc_setup's exits. They date 6 checks in 540, 3 in 590 and 12
in part 390. Census 1693 -> 1672; rows unchanged at 1943.

The logs were run on c1e253f. Since then the three parts are unchanged and
lib.sh changed only in pgc_skip's pyarrow branch; every reddened name exists
in a clean run on #1286's tree. Three names #1284 had renamed came back as
never rows and were removed again. orphan-scan: orphans=0.

Part 540 is now clear (9 of 11 dated, the 2 never rows premises). Part 390
joins the reading list: 61 never-red checks beside 12 dated ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jdatcmd added a commit that referenced this pull request Oct 13, 2026
…five majors (#1236)

The #1236 follow-up for parts 540 and 590. Six mutations, each run on
15-19, merged one log at a time with --mutation and --target: three of
part 540's sweep (the $-arm skip, awk_determinate's > 0 case, the report of
an indeterminate verdict), two of part 590's readers, and pgc_summary
before one of pgc_setup's exits. They date 6 checks in 540, 3 in 590 and 12
in part 390. Census 1693 -> 1672; rows unchanged at 1943.

The logs were run on c1e253f. Since then the three parts are unchanged and
lib.sh changed only in pgc_skip's pyarrow branch; every reddened name exists
in a clean run on #1286's tree. Three names #1284 had renamed came back as
never rows and were removed again. orphan-scan: orphans=0.

Part 540 is now clear (9 of 11 dated, the 2 never rows premises). Part 390
joins the reading list: 61 never-red checks beside 12 dated ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD pushed a commit to OffgridwithJD/pgcolumnar that referenced this pull request Oct 13, 2026
…mandprompt#1305), red

OffgridwithJD measured on commandprompt#1286 that the refusal is lost under xdist: the
serial run said REFUSED and exited 1, the -n 2 run said 2 passed and exited
0, because pytest_collect_file runs in the workers. A pytester arm runs both
as subprocesses over a stub pyarrow and a file holding
'import pyarrow.parquet as pq'. Red on main: the serial premise holds, and
under -n 2 the controller names nothing (got 0 want 1).

guard_tests 413 -> 414 by collection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD pushed a commit to OffgridwithJD/pgcolumnar that referenced this pull request Oct 13, 2026
From @jdatcmd's review of commandprompt#1322: the twin rule applies to selftest parts
(precedent: parts 600/610 and test_capability_decline.py, commandprompt#1286).

test_suite_exit_trap.py reads the suites and lib.sh as text with its own
reader -- shlex for trap lines, the shell's own comment rule -- and drives the
same eight suites, written from its own strings, before the population arm.
Same five check names. Red on the tree before the fix, naming
native_upgrade_converge.sh only; five mutations of its reader each redden the
arms the part's matching mutation does. NO_CLUSTER; guard_tests 415 -> 419 by
collection; TESTS.md section 97.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD pushed a commit to OffgridwithJD/pgcolumnar that referenced this pull request Oct 13, 2026
From @jdatcmd's review of commandprompt#1322: the twin rule applies to selftest parts
(precedent: parts 600/610 and test_capability_decline.py, commandprompt#1286).

test_suite_exit_trap.py reads the suites and lib.sh as text with its own
reader -- shlex for trap lines, the shell's own comment rule -- and drives the
same eight suites, written from its own strings, before the population arm.
Same five check names. Red on the tree before the fix, naming
native_upgrade_converge.sh only; five mutations of its reader each redden the
arms the part's matching mutation does. NO_CLUSTER; guard_tests 415 -> 419 by
collection; TESTS.md section 97.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD pushed a commit to OffgridwithJD/pgcolumnar that referenced this pull request Oct 13, 2026
From @jdatcmd's review of commandprompt#1322: the twin rule applies to selftest parts
(precedent: parts 600/610 and test_capability_decline.py, commandprompt#1286).

test_suite_exit_trap.py reads the suites and lib.sh as text with its own
reader -- shlex for trap lines, the shell's own comment rule -- and drives the
same eight suites, written from its own strings, before the population arm.
Same five check names. Red on the tree before the fix, naming
native_upgrade_converge.sh only; five mutations of its reader each redden the
arms the part's matching mutation does. NO_CLUSTER; guard_tests 415 -> 419 by
collection; TESTS.md section 97.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

25 suites FAIL rather than decline when pyarrow is absent: fail or skip?

2 participants