Repository navigation
test: recovery gets its pytest twin, and each crash proves its own recovery (#1341 item 25) - #1443
Conversation
jdatcmd
left a comment
There was a problem hiding this comment.
Approving c76d6d1.
At the head, PG17: recovery.sh 29/29; test_recovery.py 29 checks, 3 tests.
I checked your two test-side claims with my own mutants of the tests, applied to both twins, each confirmed to parse, and restored from git after the run:
| mutant | shell | pytest |
|---|---|---|
M1: every crash after the first is a clean pg_ctl stop -m fast |
25/29: s2, s3, s4 and s5 "recovery ran", each got [no recovery since the crash] |
s2, s4 and s5 "recovery ran", (1 before, 1 after); s3 is not reached, because test 1 stops at s2 |
| M2: s2's uncommitted INSERT targets a table that doesn't exist | 28/29: only premise: s2's INSERT ran in its open transaction before the crash, got [0] want [1] |
the same premise, got 0 want 1 |
So each crash now proves its own recovery, and scenario 2 proves that its INSERT ran. Before this PR, both of those failures would have passed.
I didn't repeat your three WAL-record mutants; I took them from your table. The census at this head (3556 / 2636 / 920) matches the budget paragraph, and suites_not_covered is 221.
… can fail (#1341 item 23) test_unique_conc.py is the twin of unique_conc.sh, every check by the shell's name, on a private cluster at unique_lock_buckets 100003 (fixed at server start). T1 is held mid-INSERT by a second row waiting in cq_block; T2 runs in a thread; every barrier polls pg_stat_activity for a lock wait. Porting it found four shell arms that could not fail: 3, 4 (differing key), 5 (outside the predicate) and 6 (NULLS DISTINCT). When T2 did block, the server's 60s lock_timeout aborted it as the 60s bounded wait ran out, and the waiter count read 0: a mutant that hashes only a key's first column left the shell at 33/33. In both twins T2 now runs those arms and the lock-off controls with a 5s lock_timeout, and each asserts nothing waits and its INSERT returned in a live transaction. A wait the next check asserts no longer records a second, fixed-name failure. The 23505 arms assert the SQLSTATE (psql VERBOSITY verbose), not the index name alone. The suite uses lib.sh's check, pgc_fail and pgc_summary instead of a local recorder, so the twin grader can read it and the log has an accounting line. The PG < 15 skip of NULLS NOT DISTINCT is gone: no supported major takes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ons of columnar_unique.c (#1341) 33 rows from five-major runs (33/33 on each of 15-19). Dated on PG18 by: the per-key lock never taken (18), the lock taken with the setting off (2), every key in one bucket (3), only the first key column hashed (1, shared), the partial predicate ignored (2) and a NULL key locked under NULLS DISTINCT (2). suites_not_covered 224 -> 223; the census is 3507 rows, 2607 never, 900 dated, restated on main 45eaa19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th pgc_summary (#1341) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…key (#1341) conflict() ran the whole race before the test asserted anything. With no key lock taken, T2 did not wait, T1 then stalled behind T2's uncommitted duplicate, and each test's first red was "a bounded wait for a standalone sentinel completed", not the arm the mutant breaks. conflict() now returns the waiter count with T1 held, the test asserts it, and settle() finishes the race. Measured on PG18: under the no-lock mutant the six tests now go red on their "serializes" arm (got 0 want 1), in 62s rather than 422s; on the head, 33/33. The sessions close holder first, so a T1 still paused when a test stops is released before its own connection closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hes the subtransaction discard (#1341 item 24) test_update_conc.py asserts every shell check by name; compare_to_bash grades it as covering all 31 (28 checks and the three bounded-wait names), and it joins COMPLETE. update_conc.sh moves onto lib.sh's check, pgc_fail and pgc_summary, so it leaves suites_without_accounting. Its savepoint arm could not reach the discard it claims: the inner UPDATE completes, executor-end flushes it under the inner subtransaction, and with either discard in pgcolumnar_subxact_callback removed it stays green. A new arm fails a statement partway inside the savepoint, with the scan order asserted as its premise, and goes red under each (0/|1 and 2/10,99|3). cluster_tests 627 -> 638 by collection; TESTS.md gets section 135. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions (#1341) 28 rows from five-major runs (28/28 on each of 15-19). Dated on PG18 by, in columnar_row_lock.c: the flush-time serialization skipped, the committed-delete conflict never raised, and the re-read from the statement snapshot (10 arms each, the same 10); the row lock skipped during a unique-index UPDATE (1); the self-modify guard removed (1); and in pgcolumnar_subxact_callback, either discard skipped (the 2 failed-statement savepoint arms). suites_not_covered 223 -> 222; the census is 3535 rows, 2621 never, 914 dated, restated on main 45eaa19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…covery (#1341 item 25) test_recovery.py asserts every shell check by name; compare_to_bash grades it as covering all 29, and it joins COMPLETE. Two of the shell's premises could not fail. "s4/s5 recovery ran" counted redo lines over the whole server log, so once scenario 1 had recovered they passed whether or not their own crash happened: with every crash after the first skipped, the suite stayed 26/26, and so did it with the VM clear's WAL removed as well. Each crash now notes the count, and recovery ran when the restart added a line; s2 and s3 gain the same arm. And scenario 2 waited `sleep 5` for its uncommitted INSERT: with the INSERT made to fail it passed whole, having nothing to roll back. It now waits for the session to sleep with an xid assigned, and asserts that as its premise. cluster_tests 638 -> 641 by collection; TESTS.md gets section 136. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en WAL records (#1341) 29 rows from five-major runs (29/29 on each of 15-19). Dated on PG18 by a WAL record not written for: a stripe data page (10), the metapage's reserved-offset advance (5, all among the 10) and the visibility-map clear (1). The five "recovery ran" arms and the s2 premise redden only under harness mutations and stay never. suites_not_covered 222 -> 221; the census is 3564 rows, 2639 never, 925 dated, restated on main 45eaa19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c76d6d1 to
676313b
Compare
jdatcmd
left a comment
There was a problem hiding this comment.
Re-approving 676313b, a bookkeeping-only restate of c76d6d1, which I approved.
- Identical per-file patches: recovery.sh, test_recovery.py, the ledger rows, test_compare_to_bash.py and TESTS.md (masked).
- The census at this head matches the budget paragraph: checks_never_observed_red 2639, and
cluster_testsis 641.
I'll merge the chain in order once each head is green.
|
@OffgridwithJD, your chain has merged: #1438 (90a49ea), #1441 (6f12e37) and #1443 (061747f). Main is 061747f. I've left the #1341 boxes for 23, 24 and 25 to you, since 25 still has two suites to go. Open from me: #1445, the last #1339 High-entry fix, and my proposal on #1339 for |
#1341 item 25, its first suite: committed columnar rows survive an immediate stop and redo, and an uncommitted transaction leaves nothing.
recovery.shhad no pytest twin. This PR adds one, every check by the shell's name, and seeds the suite into the ledger. The port found two premises in the shell that could not fail, and they're fixed in both twins. Item 25's other two suites,native_backend_crash.shandcorruption.sh, come in their own PRs.Stacked on #1441, which is stacked on #1438. Merge them in that order; this branch is #1441's head plus two commits.
What changes
test/pytest/test_recovery.py, the twin. It has three tests on a private cluster, which it crashes withpg_ctl -m immediate, astest_maintenance_crash.pydoes.compare_to_bash.pygrades it as covering all 29 shell names, and it joinsCOMPLETE.sleep 5, and nothing checked the INSERT.recoveryis seeded with 29 rows from five-major runs, and 11 of the 29 rows dated by the mutants below.suites_not_coveredgoes 222 → 221, and the census is 3527 + 29 = 3556 rows, 2618 + 18 = 2636 never and 909 + 11 = 920 dated, matching the tsv. The gate gives rc=0 against origin/main.Red first
Each mutation was run one at a time on PG18, through both twins, with the
.somd5 printed every time.Source mutations (each one makes a WAL record go unwritten):
recovery.shtest_recovery.pycolumnar_storage.c:511corrupt encoded chunk):237zstd decompression failed,got [714] want [0])columnar_visibilitymap.c:143s4 post-crash deleted block stays not-all-visible:got [t] want [f]:98Harness mutations, for the two premises:
recovery.shtest_recovery.pypremise: s2's INSERT ran …:got [0] want [1], where scenario 2 was green beforegot 0 want 1The harness reds observe the suite rather than the code, so the ledger doesn't date those six rows. They arrive
never, along with the row counts, the heap counts and s4's other arms: 18 in all.Runs
recovery.shtest_recovery.pyAlso measured: the guard half (438 tests), harness_selftest 1245/1245 and docs_style.
cluster_testsgoes 624 → 627 by collection, and TESTS.md gets §134.🤖 Generated with Claude Code