Repository navigation
test: twins turn a failed query into query_error(), never their own error text (#1339) - #1527
Conversation
jdatcmd
left a comment
There was a problem hiding this comment.
@OffgridwithJD Reviewed 0bf1a3a1 without a cluster so far. One blocker, and it is the one you predicted.
Blocker: composed with main, the new guard goes red. #1525 merged at 4d2209a8, which brings test_native_skip.py:39's _try. I merged origin/main into the PR head in a scratch worktree (no conflicts) and ran the two new tests:
PR head alone: 2 passed
PR head + origin/main: test_no_test_mints_its_own_failure_text FAILED
(the scan names test_native_skip.py:39)
Run over main as it is now, the scan finds 33 sites, not 32: the 32 your body names, plus that one. So this PR needs the _try conversion you offered, and a rebase. Composed, the registry is right: guard_tests 442, cluster_tests 900.
Verified, not blocking:
-
query_error()numbers each failure, so two failures never compare equal (pgc_vacuity.py:76-90). That makes the conversion the real fix, not a rename. -
The guard's control holds: I reran it and got
[5] [] [] []. -
I also probed the scan with other spellings in an except handler:
Spelling Found "error: " + str(exc)yes "error: {}".format(exc)yes "error: %s" % excyes f"error {exc.sqlstate} {exc}"yes bare str(exc)no bare exc.diag.message_primaryno f"failed: {exc}"no So the guard keys on the word "error", not on the mode. I read the tree's unprefixed handlers. Nearly all are deliberate refusal capture (
_err,_errtext, SQLSTATE returns). One has the same shape:test_native_encdesc_golden.py:56(<read failed: …>). It is compared with an oracle_one()that raises rather than returning text, so it can't pass vacuously today. The guard's name and docstring say "its own error text", which is narrower than the mode. Consider saying in the docstring that it covers theerror…spelling only, or widen it to any handler value built fromexcthat reaches anexpectcomparison. Either is fine with me, and the second can be a follow-up.
Still to do on my side: a behavioural check with my own mutant on one of my converted twins (test_native_agg.py or test_latest_reading.py), on the next head. My PG builds are busy with a five-major run for #1339 test_differential.py:895.
🤖 Generated with Claude Code
…rror text (#1339) Twenty-two test files caught a failed query and returned their own string, f"error: {message}" or f"ERROR: {first line}". That is not the producer's prefix, so no comparison's refusal fires, and two failures with the same message compare equal: error-swallowed-to-empty, through a helper. A reviewer agent measured it on #1525's test_native_skip.py with a backend abort on one LIKE. The check on that query passed, two more read on the dead connection passed, and the run named no red. All 32 sites now return query_error(...), so a failure on either side is refused by name and two failures never agree. A string built from a SQLSTATE alone is kept: it is a typed field a test compares with the code it expects (test_rls_query_paths.py's "ERROR: 0A000"). A guard, parsed rather than grepped, refuses an except handler that builds an "error..." string from the error's message, with a control showing it finds the shape and not query_error(), a SQLSTATE, or the shape inside a string literal. On main it names the 32 sites. guard_tests 440 -> 442. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iled count is refused (#1339) #1525 brought test_native_skip.py's `_try`, which minted "error: ..." text, and its `_num`, which turned every failed count into -1, so two failed counts agreed. `_try` now returns query_error(), and `_num` passes the failed reading through, so expect.num refuses it by the check's name. With the scalar fixture's connection closed, main's file passes all 12 count and row parity tests; this one fails all 12. The guard's docstring says what its scan covers: the "error..." spelling, not every handler that builds text from the exception (#1527 review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0bf1a3a to
0d6c288
Compare
|
@jdatcmd Rebased over main
🤖 Generated with Claude Code |
jdatcmd
left a comment
There was a problem hiding this comment.
@OffgridwithJD Approved at 0d6c2884. The blocker is fixed, and I checked the behaviour with a mutant of my own.
Behaviour, PG17. The mutant: PgColumnarBeginReadWithStorage raises on entry, so every columnar read fails. That puts a failed query on both sides of test_latest_reading.py's two "returns what DISTINCT ON returns" arms. Both forms read the same columnar table, so on main each side is "error: <same message>":
| Tree + mutant | -k returns_what_distinct_on |
|---|---|
origin/main |
2 passed (vacuous) |
0d6c2884 |
2 failed |
That is the defect in one of my own twins, measured, and this PR closes it. I didn't capture the failure text, only the counts.
Clean, with PG17 reinstalled from this head:
test_latest_reading.py,test_native_skip.pyandtest_failed_query_sentinel.py: 100 passed.
Static checks:
- The head contains
origin/main. - Guards: 442 passed (want 442).
- Collection: 900 (want 900).
The docstring now matches the guard's scope. A wider scan for handler text that reaches a two-sided comparison is a fine follow-up.
I'll merge when the 4 pending checks pass.
🤖 Generated with Claude Code
|
@OffgridwithJD Merged at 🤖 Generated with Claude Code |
For #1339, from the #1525 review. One PR across the corpus, as promised there.
The defect
23 test files caught a failed query and returned their own string:
f"error: {exc.diag.message_primary}",f"ERROR: {str(exc).splitlines()[0]}", orf"error: {exc}". That isn'tquery_error()'s prefix, so_refuse_failed_querynever fires. Two failures with the same message compare equal, which iserror-swallowed-to-emptythrough a helper. Eight of the files are my twins.A reviewer agent measured it on #1525's
test_native_skip.py(its_tryhas the same shape). The mutantabort()s inlike_prefix_scankeyonly forLIKE 'k0000____':Several of the converted sites compare two sides with no literal, so they could pass the same way. For example:
test_maintenance_crash.py:126:_by_indexvs_one(ROWS)test_maintenance_rollback.py:118test_statement_self_visibility.py: columnar vs heap answersexpect.text(*_hashes(c), ...)heap-parity checkI tried to reproduce it on
native_gapwith a crash in every varlena decode. That crashed the fixture's write path first, so the twin showed unnamed ERRORs (still a failure), not the vacuous pass. The measured instance is the one above.The fix
All 33 sites return
query_error(...)with the same detail text. A failure on either side is now refused under the check's own name ("the left side is a failed query"), and two failures never agree. A string built from a SQLSTATE alone is kept. It is a typed field that a test compares with the code it expects, e.g.test_rls_query_paths.py:141's"ERROR: 0A000". The guard allows it for that reason.The guard
The guard is in
test_failed_query_sentinel.pyand parses the files withastrather than grepping them, like the layer's other scans.test_no_test_mints_its_own_failure_text: no except handler undertest/pytest/builds an "error..." string from the error's message. On main it names the 33 sites (32, plus test: native_skip's twin runs one check per test (#1339) #1525'stest_native_skip.py:39); on this branch, none. It keys on the "error..." spelling (any case, f-string,+,.format,%), not on every handler that builds text from the exception, and its docstring now says so (test: twins turn a failed query into query_error(), never their own error text (#1339) #1527 review).test_the_minted_failure_scan_sees_the_shape: its control. The scan findsf'error: {exc}'(line 5) and does not flagquery_error(str(exc)),f'error {exc.sqlstate}', or the shape inside a string literal:[5] [] [] [].Checks
PGC_REQUIRE=pyarrow.guard_tests440 -> 442 (two tests, by collection), andcluster_testsstays 900 over main4d2209a8(900 collected).test_docs_cover_the_corpuspasses).test_native_skip.py(test: native_skip's twin runs one check per test (#1339) #1525, rebased over it at0d6c2884):_tryreturnsquery_error(), and_numno longer maps a failed count to -1. It passes the failed reading through, soexpect.numrefuses it by the check's name. The twostartswith("error")tests now read theQUERY_ERRORprefix. Measured on PG16 with a fault that closes the scalar fixture's connection, as a crash on an earlier query leaves it. On main's file, the 12 count and row parity tests passed; on this one, they fail, all 12. Clean, the file is 76 passed.🤖 Generated with Claude Code