Skip to content

test: native_format's twin is a port by name, and its index arms reach the fetch (#1341) - #1531

Merged
jdatcmd merged 2 commits into
mainfrom
test/1341-native-format-twin
Oct 17, 2026
Merged

jdatcmd merged 2 commits into
mainfrom
test/1341-native-format-twin

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

For #1341's box "Shell arms added after 2026-09-09 with no pytest counterpart": native_format, one of its INCOMPLETE suites.

What changes

  • The twin is a port by name. It held 10 of the shell's 25 checks in one test. It now runs the shell's sequence by name, one check per test, and goes from 1 test to 25. compare_to_bash.py: 25 literal matches, 0 missing. native_format moves from INCOMPLETE to COMPLETE.

  • The metapage version is planted through pgcolumnar_debug_set_metapage_version. The hook is bound at test time, as the shell binds it and test_native_gap.py binds its own. The INCOMPLETE note called this Port the test suite to pytest, complete by 1.0-alpha4 #432's work, but it needs nothing Port the test suite to pytest, complete by 1.0-alpha4 #432 provides.

  • Every reading goes through query_error(). "Backend survives" is read on a new connection, as the shell's q reads it.

  • Part 4's index arms now reach the fetch path, in both twins. The query left the custom scan on, so pgcolumnar's own node served it, as part 5's comment already measured. With pgcolumnar_fetch_row's stamp check deleted, these three arms stayed green, refused by the scan-open guard instead:

    • "an index-scan fetch of a decoded column fails";
    • "the failure is the native-format-version guard";
    • "the native-format error names the offending version".

    Both twins now set pgcolumnar.enable_custom_scan = off for that query.

Evidence (PG17, each mutant built clean, red sets compared by name)

Mutant Shell Twin
none 28/28 25 passed
columnar_storage.c: the metapage major check off (if (false && meta->versionMajor != …)) 3 red: bumped read fails, the guard, names 99.0 the same 3
columnar_reader.c: pgcolumnar_fetch_row's PgColumnarCheckNativeFormatVersion removed, before the part-4 fix 2 red: the two range arms the same 2
the same mutant, after the fix 5 red: the two range arms + the three part-4 index arms the same 5

Clean on 15-19:

  • Shell: 28/28 on each.
  • Twin: 25 passed on each.
  • harness_selftest: 1247.
  • Collection: 956, which is cluster_tests 932 + 24.
  • Guards: 442.
  • test_harness_deps: 20.
  • Gate: rc=0.

No ledger change: native_format has no rows in the ledger.

🤖 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.

@jdatcmd Requesting changes at da558dbb. The port by name and the part-4 fix hold. But under four more mutants the twin reddens checks the shell keeps green, for two reasons the twin can fix.

What holds. A reviewer agent measured on PG17, each mutant built clean, src reverted between runs:

  • clean: shell 28/28, twin 25 passed; compare_to_bash.py 25 literal, 0 missing;
  • part 4 really reaches the fetch: with the custom scan on, SELECT v FROM fv WHERE id=42 plans Custom Scan (PgColumnarScan); off, Index Scan using fv_id on fv;
  • one check per test, failed reads through query_error().
mutant shell reds twin reds same?
yours: columnar_storage.c:138 major check off 3 (bumped read fails, guard, names 99.0) same 3 yes
yours: columnar_reader.c:4834 fetch_row check removed 5 same 5 yes
ours: columnar_vector.c:3194 agg-scan guard removed 3 (aggregate over future fails, stamp above, stamp below) same 3 yes
ours: columnar_vector.c:5438 group-agg guard removed none none yes, but nothing catches it
ours: columnar_reader.c:4230 columnarFetchFmtOk = false reset removed none 2: stamp above, stamp below no
ours: columnar_storage.c:139 ERROR → FATAL none all 25 error at fixture setup no
ours: columnar_metadata.c:2910 errmsg drops %d, errdetail still names 99 none 1: "the native-format error names the offending version" no
ours: columnar_storage.c:139 ERROR → WARNING none 3: bumped read fails, guard, names 99.0 no

1. One session where the shell uses one per statement (test_native_format.py:110, fixture c). The shell's err_of, sqlstate_of and q each start a new psql.

  • Fetch-check reset removed: the format check is cached per session. On the shared connection the stamp-1 fetch caches "ok" for fr, so the stamp-3 and stamp-0 fetches skip the check: twin 2 red, shell green.
  • FATAL: the finally: RESET {g} at :62-64 raises outside any try, the fixture aborts and all 25 error. Even with it guarded, every later reading on the dead connection would be a query_error().
  • Ask: run each _refusal / _four_paths probe on a connection of its own, as _alive already does, and guard the RESET.

2. The twin reads less of the error than the shell.

  • _says (:192) matches exc.diag.message_primary only. The shell's contains reads all of stderr, DETAIL included, which still names 99. Ask: match what the shell reads (primary plus detail, or str(exc)).
  • WARNING: the shell's err_of (native_format.sh:38-40) takes any stderr as the failure, a warning included. So the guard can drop to a warning, the table is misread, and the shell stays green. That predates this PR, but it's the shell that's wrong here. Ask: make err_of require ERROR:, so both read the same and both go red.

Not blocking. The group-agg guard removed reddens nothing in either harness, yet both docstrings say each arm drives PgColumnarBeginGroupAggScan. EXPLAIN shows the GROUP BY query does take the group-agg node, so an earlier guard must refuse it first. It predates the PR. Either narrow the claim or add an arm that reaches it.

🤖 Generated with Claude Code

@jdatcmd
jdatcmd force-pushed the test/1341-native-format-twin branch from da558db to daffef3 Compare October 17, 2026 19:38
@jdatcmd

jdatcmd commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD All four disagreements are fixed at daffef31, rebased over #1524 (bf1d89a2).

1. Every reading runs in a session of its own. This applies to the readings only; setup DDL stays on the fixture's connection. _one, _ordered, _stamp, _refusal and _four_paths each open a new session on the suite's schema, as the shell's psql calls do. No RESET is left to raise.

2. Both twins read a refusal the same way.

  • The shell's err_of now returns stderr only when it holds ERROR:. A WARNING or a FATAL reads as no refusal.
  • The twin's _refusal counts only an exception at ERROR severity. It returns the message and its detail, and _says matches over both, as contains reads all of stderr.

Measured on PG17, each mutant built clean. Red sets were compared by name, counting both the "a side is empty" and the VacuityError forms:

mutant shell twin same?
none 28/28 25 passed —
columnar_storage.c major check off 3 3 yes
columnar_reader.c fetch_row check removed 5 5 yes
columnarFetchFmtOk = false reset removed 0 0 yes
columnar_storage.c:139 ERROR → FATAL 3 3 yes: bumped read fails, guard, names 99.0
columnar_storage.c:139 ERROR → WARNING 3 3 yes, the same 3
errmsg drops %d, errdetail still names 99 0 0 yes: both read the detail
group-agg guard removed 0 0 yes

Not blocking, now narrowed. Both docstrings now say the GROUP BY query plans the group-agg node but does not prove its own check, because an earlier check refuses first.

Open gap, now in both twins. Neither twin catches the fetch-cache reset (columnarFetchFmtOk) any more. Each reading starts a new backend, so a per-session cache never carries across readings. An arm for it would have to read twice in one session, which neither twin does. I'll add it to #1341 rather than widen this PR.

A guard false positive. On the first matrix, test_failed_query_sentinel.py's scan (#1527) flagged _refusal's != "ERROR". The scan reads any string in an except handler that starts with "error" as minted failure text, and this one is only compared. The constant is now REFUSAL_SEVERITY at module level, with a comment saying why. I reran the FATAL and WARNING mutants on the final head: both still give the same 3 reds in both twins.

Matrix at daffef31:

🤖 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.

@jdatcmd Approved at daffef31. All four disagreements are gone. A reviewer agent re-ran the matrix on PG17, each mutant built clean, comparing red names:

mutant shell twin same
(a) columnarFetchFmtOk = false reset removed 0 0 yes
(b) columnar_storage.c:139 ERROR → FATAL 3 3 yes
(c) errmsg drops %d, detail still names 99 0 0 yes
(d) columnar_storage.c:139 ERROR → WARNING 3 3 yes
yours: metapage major check off 3 3 yes
yours: pgcolumnar_fetch_row's check removed 5 5 yes
  • Under (b) the twin no longer errors all 25; "backend survives" stays green in both, rightly, since only that session dies.
  • err_of can't miss a real ERROR: *"ERROR:"* matches anywhere in stderr, after a NOTICE or WARNING included. A FATAL or crash returns empty, and every arm reading it expects a refusal, so empty goes red, not vacuous.
  • REFUSAL_SEVERITY is an honest rename: it's only compared, never returned. test_failed_query_sentinel.py 15 passed; compare_to_bash.py 25 literal, 0 missing.

Non-blocking nits:

  1. test_native_format.py:48: FETCH_GUCS looks dead now that the RESET is gone.
  2. The _refusal docstring says a WARNING "is a failed reading here". A WARNING doesn't raise in psycopg, so it reads as ("ok", ""). Same outcome (red), different reason.
  3. _four_paths joins _refusal(...)[0] into one string, so a query_error() past the first position reads as a text mismatch, not a VacuityError. Still red, and the old code did the same with "?".
  4. err_of matches ERROR: anywhere while sqlstate_of anchors on ^ERROR:. Harmless.

Agreed on taking the fetch-cache arm to #1341.

🤖 Generated with Claude Code

jdatcmd and others added 2 commits October 17, 2026 20:34
…h the fetch (#1341)

The twin ported 10 of the shell's 25 checks in one test. It is now the
shell's sequence by name, one check per test (1 -> 25 tests), with the
metapage version planted through pgcolumnar_debug_set_metapage_version,
bound at test time as the shell and test_native_gap.py bind their hooks.
Every reading goes through query_error(); "backend survives" is read
on a new connection, as the shell's q reads it.

Porting it showed that part 4's three arms on the index query could not
see the path they name. The query left the custom scan on, so
pgcolumnar's own node served it: with pgcolumnar_fetch_row's stamp check
deleted, "an index-scan fetch of a decoded column fails" and both
message arms stayed green, refused by the scan-open guard instead. Both
twins now turn the custom scan off, as part 5 does, and that mutant
reddens all three, identically in both twins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ssion of its own (#1341)

Four mutants reddened checks in one harness and not the other.

- The twin read everything on one connection; the shell starts a psql per
  statement. The read paths cache a table's format check per session, so with
  that cache never reset the stamp-3 and stamp-0 fetches skipped the check in
  the twin only. A FATAL guard closed the shared connection, and all 25 tests
  errored at fixture setup. Every reading now runs in a session of its own.
- The twin matched the message only; the shell's `contains` reads all of
  stderr, the DETAIL included. The twin now reads the message and its detail.
- The shell's `err_of` took any stderr as the refusal, so a guard that fell to
  a WARNING, or a FATAL, kept its arms green. It now requires an ERROR, and the
  twin counts only an ERROR too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jdatcmd
jdatcmd force-pushed the test/1341-native-format-twin branch from daffef3 to 7a3bd98 Compare October 17, 2026 20:37
@jdatcmd

jdatcmd commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

@OffgridwithJD Restated over #1530 (a9e070c3) at 7a3bd98a. Your approval is on daffef31. Only cluster_tests moved.

  • Tree: git merge-tree --write-tree origin/main daffef31 gives 84eeecfc. Its diff against 7a3bd98a touches only test/pytest/expected_tests.txt: the markers are gone, main's comment lines are kept, and the value is restated. TESTS.md merged without a conflict, because this PR edits an existing section and adds none.
  • cluster_tests: 966 → 990 (+24), collected the CI way: 990.
  • Guards: 442 passed. test_harness_deps.py: 20 passed.
  • Ledger: no rows in this PR. The census is main's.

🤖 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.

@jdatcmd Re-approved at 7a3bd98a, restated over #1530 (a9e070c3).

  • Tree: git merge-tree --write-tree origin/main daffef31 = 84eeecfc; its diff against 7a3bd98a touches only test/pytest/expected_tests.txt.
  • Collection, the CI way (PG17): 990 = cluster_tests.
  • Guards: 442 passed. Census is main's (3085 / 1402 / 4487), ledger sorted.

🤖 Generated with Claude Code

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.

2 participants