Skip to content

Serialize the live classes that share the store, document the ones that do not (#1776) - #1783

Merged
erikdarlingdata merged 7 commits into
devfrom
feature/1776-live-postgres-collection-sweep
Jul 28, 2026
Merged

erikdarlingdata merged 7 commits into
devfrom
feature/1776-live-postgres-collection-sweep

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

Closes #1776.

I verified the issue's classification against the source rather than applying it, and it moved in both directions: one aggressor was missed, and three of the six were substring false positives.

Corrected classification

Class Issue said Verified Action
StoreConfigProviderTests shared shared attribute
DarlingEndpointToggleCliTests shared shared attribute
ExcludedDatabasesStoreLiveTests shared shared attribute
DarlingComposeTests not listed shared, runs DDL split + attribute
DarlingAlertTuningKnobsTests not listed own database (ScratchPostgres) marker
DarlingDeliveryModeTests not listed own database (ScratchPostgres) marker
DarlingManagedPostgresTests "own cluster" never reads DARLING_TEST_PG marker, corrected reason
DarlingStoreUpgradeTests "own cluster" never reads DARLING_TEST_PG marker, corrected reason
DarlingPgRuntimeVersionPinTests "probes the bundle" never reads DARLING_TEST_PG marker, corrected reason
PayloadDimensionTests not listed prose mention only out of scope, like AlertEngineTests

The missed aggressor is the significant one. DarlingComposeTests.RunComposedPanel_OldWindow_AgainstPlainPostgres_RunsCleanOnRaw connects straight to the shared database and runs PgMigrations.MigrateAsync against it — DDL, concurrently with the sixty-odd serialized classes. That makes it the most aggressive of the four, and it was not on the list.

Three of the six were a substring artifact. DarlingManagedPostgresTests, DarlingStoreUpgradeTests and DarlingPgRuntimeVersionPinTests read DARLING_TEST_PGRUNTIME (and _OLD / _NEWZIP) — different variables that merely share the prefix with DARLING_TEST_PG. They never touch the shared store at all. "Do not serialize them" was the right answer reached for the wrong reason, so the comment on each records which reason actually applies; otherwise the next sweep re-derives the same false positive.

The judgement call on DarlingComposeTests

It does not take the attribute directly. The class holds 127 tests, 126 of them pure, and dragging all of them into the serialized collection to protect one live test is exactly the "slow the suite for no safety gain" the issue warns about. The live test is split into DarlingComposeLivePostgresTests, which is the shape 44 other files in this project already use (counted, not estimated): a ...SurfaceAndSqlTests / ...LivePostgresTests pair. The parent class's doc comment already claimed "the live compile-and-run round-trip is exercised against a real store elsewhere" — that is now true rather than aspirational.

Why the rollback comments did not make those classes safe

Two of the three serialized classes documented themselves as "transaction-rolled-back so they never clobber a shared dev store's singleton config rows", which reads like an exemption and is not one. Rolling back protects the data, not the concurrency: an uncommitted write still holds its row locks for the life of the transaction and still fires the config_version bump trigger. That is recorded on each class, because the next reader will otherwise draw the same wrong conclusion the comment invites.

The meta-test

LivePostgresCollectionHygieneTests requires every class that reads the shared store to either carry [Collection("live-postgres")] or record an #1776 own-store exemption. It demands a decision, not the attribute — a class that mints its own database genuinely should not serialize.

Two details that matter:

  • It matches the quoted literal "DARLING_TEST_PG", not a substring. That is what keeps DARLING_TEST_PGRUNTIME out, and it also keeps out doc-comment prose (which writes the name bare), so a class that merely describes the variable is not dragged in. Matching the way the issue's original sweep did would make this test permanently red on three innocent classes.
  • It fails rather than skips when it cannot find the tree, following DocCommentHygieneTests: a guard that silently skips is a guard that silently stops guarding.

The failure message names each offender with file and line and spells out both remedies, including the recommendation to split rather than serialize a mostly-pure class.

Mutation

# Mutation Result
M4 Remove [Collection("live-postgres")] from StoreConfigProviderTests RED: StoreConfigProviderTests.cs:35 StoreConfigProviderTests, followed by the "Pick one, deliberately" guidance

Worth noting what M4 also proved: the explanatory comment left on the serialized classes begins #1776: while the exemption marker is #1776 own-store, and the guard correctly refused to accept the former as an exemption. The two markers do not collide.

Verification

  • Darling.Tests full suite against a live PostgreSQL 18 + TimescaleDB 2.28.1 rig: 3649 passed, 0 failed, 7 skipped (base plus the one new meta-test; the compose split moves tests between classes without changing the count).
  • Meta-test green on the fixed tree, red under M4.
  • Build: dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug -t:Rebuild — 0 Warning(s), 0 Error(s).

No production code is touched; this is test infrastructure only.

🤖 Generated with Claude Code

erikdarlingdata and others added 3 commits July 27, 2026 23:42
…at do not (#1776)

Verified the issue's classification rather than applying it, and it moved in
both directions.

FOUR classes share the DARLING_TEST_PG database and now carry
[Collection("live-postgres")]: StoreConfigProviderTests,
DarlingEndpointToggleCliTests, ExcludedDatabasesStoreLiveTests, and the live
compose round-trip. The issue listed three; DarlingComposeTests was missed and
is the most aggressive of them, since it runs PgMigrations.MigrateAsync (DDL)
against the shared store.

It does NOT take the attribute directly. The class holds 127 tests, 126 of
them pure, and serializing all of them to protect one is the slowdown the
issue itself warns against. The live test is split into
DarlingComposeLivePostgresTests, the shape roughly thirty files here already
use. The parent's doc claimed "the live round-trip is exercised elsewhere",
which is now true.

THREE of the issue's six were substring false positives. DarlingManagedPostgresTests,
DarlingStoreUpgradeTests and DarlingPgRuntimeVersionPinTests never read
DARLING_TEST_PG at all - they read DARLING_TEST_PGRUNTIME (and _OLD /
_NEWZIP), different variables that merely share the prefix. "Do not serialize"
was the right answer for the wrong reason, so the recorded reason says which.

TWO more were missed, both exempt: DarlingAlertTuningKnobsTests and
DarlingDeliveryModeTests reach DARLING_TEST_PG only to CREATE and DROP their
own database via ScratchPostgres. PayloadDimensionTests mentions the variable
in prose only, like AlertEngineTests, and is out of scope.

The meta-test matches the QUOTED literal, so DARLING_TEST_PGRUNTIME and doc-
comment prose cannot match, and demands a decision rather than the attribute:
either [Collection("live-postgres")] or an "#1776 own-store" comment. The
failure message spells out both options and points at the split as the
preferred shape for a mostly-pure class.

Recorded on the three serialized classes: rolling the writes back protects the
DATA, not the CONCURRENCY - an uncommitted write still holds its row locks and
still fires the config_version bump trigger. That is why the existing
"transaction-rolled-back" comments did not make them safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The project is flat today, so this is equivalent - but TopDirectoryOnly means
the first test file someone puts in a subfolder escapes the rule silently,
which is the exact failure mode the test exists to prevent.

Verified while checking for holes: the quoted literal appears nowhere else in
the repo, and [Collection] groups classes within an assembly, so scoping to
this project is correct rather than lazy. Both facts are now recorded in the
code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
erikdarlingdata added a commit that referenced this pull request Jul 28, 2026
Verified rather than estimated: 44 files declare a *LivePostgresTests class.
The original figure understated it, and an inaccurate number in a rationale is
still an inaccurate number.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified rather than estimated: 44 files declare a *LivePostgresTests class.
The original figure understated it, and an inaccurate number in a rationale is
still an inaccurate number.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@erikdarlingdata
erikdarlingdata force-pushed the feature/1776-live-postgres-collection-sweep branch from 78afcc8 to 81ad2cf Compare July 28, 2026 04:06
erikdarlingdata and others added 3 commits July 28, 2026 00:08
…gres-collection-sweep

# Conflicts:
#	CHANGELOG.md
#1783)

Reviewer addendum: the guard worked only because every ScratchPostgres
consumer happens to duplicate the DARLING_TEST_PG read at its own call site. A
routine DRY refactor moving that read into the helper would have blinded it for
every consumer at once, silently - the exact failure it exists to prevent.

A ScratchPostgres.CreateAsync reference now counts as a shared-store touch,
because it is one: the helper connects to the DARLING_TEST_PG server and issues
CREATE DATABASE on it. The rule follows REACHING the store rather than the
spelling, so the legitimate refactor survives instead of being quietly
forbidden.

Matched with the trailing .CreateAsync( for the same reason the variable is
matched quoted: prose says 'through ScratchPostgres' and a test method is named
...AgainstScratchPostgres, neither of which is a store access, and the helper's
own file declares Task<ScratchPostgres> CreateAsync( and new ScratchPostgres(,
so it does not match itself.

Watched red: with the marker removed AND the env-var read made indirect - zero
DARLING_TEST_PG literals left in the class, i.e. the post-refactor state - the
guard still flagged DarlingAlertTuningKnobsTests.cs:27 via the new trigger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gres-collection-sweep

# Conflicts:
#	CHANGELOG.md
@erikdarlingdata
erikdarlingdata merged commit 07a8e2c into dev Jul 28, 2026
4 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/1776-live-postgres-collection-sweep branch July 28, 2026 04:48
erikdarlingdata added a commit that referenced this pull request Jul 28, 2026
Resolved to get CI to run at all, not as an arm-time step: a CONFLICTING PR
has no merge ref, and pull_request workflows build the merge ref -- so Build
and Claude Auto Review could not start on this PR while the conflict stood,
while other branches' runs kept firing normally. That is what made this look
like an Actions outage for ~25 minutes.

CHANGELOG.md was the only conflict, and only in the link-ref block. Resolved
KEEP-BOTH with no re-sorting: this branch's #1665/#1788 refs and dev's
#1781/#1783/#1786/#1792 refs all retained, in the order they appeared. No
entry text on either side was touched.

Everything else auto-merged. Verified rather than assumed, because the
dangerous case here is textual cleanliness hiding a semantic break: full
solution rebuild 0 warnings / 0 errors, Darling suite 3549 passed / 0 failed.
The one real collision this class of merge produced -- #1776's new live-store
hygiene guard versus this branch's new own-store live test -- was found the
same way and fixed in 59f877a before this merge.

Co-Authored-By: Claude Fable 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.

1 participant