You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[TEST] Behavioral coverage for the three PostgreSQL engine gates (reconcile, snapshot_now, analyze_now) #2230
The three engine gates added in #2213's round-3 fix (the long-query XE reconcile early-return, the snapshot_now dispatch predicate, and analyze_now's honest-tombstone arm) are covered by source-scanning pins and a live rig run (9 sweeps, zero engine-blind dispatches), but none has a BEHAVIORAL test that drives a PostgreSQL runtime through the gate and asserts the short-circuit.
What a test needs
analyze_now is the tractable one: fabricate the worker (GetUninitializedObject idiom, see CollectorMemoryKnobTests.SweepGate tests) with a live _postgres, a ServerLoopState with a PostgreSql runtime, invoke RunAnalyzeNowAsync, assert the analysis_state row carries the engine tombstone (not the generic insufficient_data) and the CommandOutcome is the success shape.
The reconcile and snapshot gates need the same fabrication plus a fake runner; the observable is absence (no connection attempt), which wants a counting seam.
From #2213 round 3 (reviewer finding 7); filed per the deferral rule during release week.
Partially delivered — one of the three gates, not all three. Recording that here rather than closing it, because the issue asks for three and #2237 covered one.
Darling/Darling.Tests/PostgresEngineGateBehaviorTests.cs (merged in #2237, a076f49f) carries two facts, both on analyze_now — the one this issue called "the tractable one":
The second is what makes the first mean anything: without a SQL Server arm, a test asserting "the pass didn't run" passes just as well if the pass never runs for anybody.
Still uncovered behaviorally:
The long-query XE reconcile early-return. Needs a fabricated worker whose reconcile path can be driven without an XE session on the other end.
The snapshot_now dispatch predicate.
Both remain covered only by the source-scanning pins and the live rig run from #2213's round 3 (9 sweeps, zero engine-blind dispatches) — which is evidence the gates work, but not a test that fails if someone removes one.
The reconcile gate is covered behaviorally — and my earlier note here was half wrong, which is the interesting part. I had written that this gate "observes an ABSENCE (no connection attempted), which needs a counting seam that does not exist yet." It doesn't: DarlingXeSessions.ReconcileLongQueryCompletionsAsync carries its own engine precondition ("belt to the worker's braces") and is public static, so an ungated call throws where a gated one returns. No seam, no live store, no network.
Pinned:
fact
asserts
PG target, enabled: true
returns, no throw
PG target, enabled: false
returns, no throw — the gate sits ahead of that branch, so ungated the false arm would try to DROP a session over the same impossible connection
SQL Server engine, same connection string
throws Keyword not supported: 'host'
The third row is what makes the first two non-vacuous: the engine is the only variable, so the test can't pass because connection handling changed for some unrelated reason. runner is passed null in the PG arms deliberately — reaching it would mean the gate didn't fire.
Remaining: snapshot_now
This is the one that genuinely needs what I wrongly claimed for the reconcile gate. Unlike the other two it has no public precondition of its own to call, and its observable is a true absence — the dispatch predicate simply doesn't hand a PostgreSQL target to a SQL-Server-shaped collector, so a passing test looks identical to a test that never dispatched anything.
Two ways to make it observable, neither free:
A counting seam — have the dispatch path report what it dispatched (a counter, or an injectable dispatch delegate the test can record). Cheapest observable, but it widens a production surface purely to be watched.
Assert through the store — drive a sweep against a PostgreSQL target and assert no SQL-Server-shaped collector wrote a row for that server_id. No production change, and it exercises the real path, but it needs a live store and it asserts an absence, which is only as strong as the list of tables it checks.
(2) is more honest about what the gate promises; (1) is easier to keep green. Leaving this open rather than picking one, since it's a design choice about widening a surface for testability.
3 of 3, closing.#2237 covered analyze_now, #2245 the reconcile early-return, and #2261 (merged) snapshot_now.
The last one needed neither trick the other two used. analyze_now asserts a presence in analysis_state; the reconcile gate exploits a public precondition that throws when ungated. snapshot_now's loop already reports collectorsRun in its outcome JSON, and every run it makes writes itself to collection_log — so the gate's effect is a count and a set of rows, with nothing to infer.
What made it a small test rather than a 49-collector one: schedule overrides disable everything except wait_stats, SQL-Server-only and dispatched through the same loop, so the two arms differ in the engine and nothing else.
arm
collectorsRun
collection_log
PostgreSQL target
0
empty
SQL Server target, same overrides
1
wait_stats
Asserting the row's absence rather than "not SUCCESS" is the point: a gate that dispatched and then failed would also avoid SUCCESS, while having connected to a PostgreSQL host as SQL Server. And the SQL Server arm is load-bearing rather than decorative — without it the gate assertion passes just as well against a snapshot that stopped dispatching anything at all, since "ran zero collectors" is also what a broken loop reports.
Both ran for real against the live store rather than skipping: 1 s for the SQL arm, 149 ms for the PostgreSQL one — the split the design predicts, since only the SQL arm pays for a connect attempt.
Note for whoever reads this next: my first version pointed the connection at an unresolvable .invalid hostname, which made the timing a property of CI's DNS resolver. Review caught it; it now uses the suite's existing Host=127.0.0.1;Port=1 idiom, and the identity host and the connection host are deliberately different values for different reasons.
Activity
Round-3 review fixes: mirror the EnsureAllAsync self-gate, recursive …
Merge pull request #2237 from erikdarlingdata/test/2230-pg-engine-gat…
erikdarlingdata commentedon Aug 13, 2026
Partially delivered — one of the three gates, not all three. Recording that here rather than closing it, because the issue asks for three and #2237 covered one.
Darling/Darling.Tests/PostgresEngineGateBehaviorTests.cs(merged in #2237,a076f49f) carries two facts, both onanalyze_now— the one this issue called "the tractable one":AnalyzeNow_AgainstAPostgresTarget_WritesTheEngineTombstone_AndDoesNotRunThePassAnalyzeNow_AgainstASqlServerTarget_DoesNotTakeTheEngineGateThe second is what makes the first mean anything: without a SQL Server arm, a test asserting "the pass didn't run" passes just as well if the pass never runs for anybody.
Still uncovered behaviorally:
snapshot_nowdispatch predicate.Both remain covered only by the source-scanning pins and the live rig run from #2213's round 3 (9 sweeps, zero engine-blind dispatches) — which is evidence the gates work, but not a test that fails if someone removes one.
Leaving this open at 1/3.
Cover the reconcile engine gate behaviorally, no seam required after all
Merge pull request #2245 from erikdarlingdata/test/2230-reconcile-gat…
erikdarlingdata commentedon Aug 13, 2026
2 of 3 now, via #2245 (merged).
The reconcile gate is covered behaviorally — and my earlier note here was half wrong, which is the interesting part. I had written that this gate "observes an ABSENCE (no connection attempted), which needs a counting seam that does not exist yet." It doesn't:
DarlingXeSessions.ReconcileLongQueryCompletionsAsynccarries its own engine precondition ("belt to the worker's braces") and ispublic static, so an ungated call throws where a gated one returns. No seam, no live store, no network.Pinned:
enabled: trueenabled: falseKeyword not supported: 'host'The third row is what makes the first two non-vacuous: the engine is the only variable, so the test can't pass because connection handling changed for some unrelated reason.
runneris passednullin the PG arms deliberately — reaching it would mean the gate didn't fire.Remaining:
snapshot_nowThis is the one that genuinely needs what I wrongly claimed for the reconcile gate. Unlike the other two it has no public precondition of its own to call, and its observable is a true absence — the dispatch predicate simply doesn't hand a PostgreSQL target to a SQL-Server-shaped collector, so a passing test looks identical to a test that never dispatched anything.
Two ways to make it observable, neither free:
server_id. No production change, and it exercises the real path, but it needs a live store and it asserts an absence, which is only as strong as the list of tables it checks.(2) is more honest about what the gate promises; (1) is easier to keep green. Leaving this open rather than picking one, since it's a design choice about widening a surface for testability.
Merge pull request #2261 from erikdarlingdata/test/2230-snapshot-now-…
erikdarlingdata commentedon Aug 14, 2026
3 of 3, closing. #2237 covered
analyze_now, #2245 the reconcile early-return, and #2261 (merged)snapshot_now.The last one needed neither trick the other two used.
analyze_nowasserts a presence inanalysis_state; the reconcile gate exploits a public precondition that throws when ungated.snapshot_now's loop already reportscollectorsRunin its outcome JSON, and every run it makes writes itself tocollection_log— so the gate's effect is a count and a set of rows, with nothing to infer.What made it a small test rather than a 49-collector one: schedule overrides disable everything except
wait_stats, SQL-Server-only and dispatched through the same loop, so the two arms differ in the engine and nothing else.collectorsRuncollection_logwait_statsAsserting the row's absence rather than "not SUCCESS" is the point: a gate that dispatched and then failed would also avoid SUCCESS, while having connected to a PostgreSQL host as SQL Server. And the SQL Server arm is load-bearing rather than decorative — without it the gate assertion passes just as well against a snapshot that stopped dispatching anything at all, since "ran zero collectors" is also what a broken loop reports.
Both ran for real against the live store rather than skipping: 1 s for the SQL arm, 149 ms for the PostgreSQL one — the split the design predicts, since only the SQL arm pays for a connect attempt.
Note for whoever reads this next: my first version pointed the connection at an unresolvable
.invalidhostname, which made the timing a property of CI's DNS resolver. Review caught it; it now uses the suite's existingHost=127.0.0.1;Port=1idiom, and the identity host and the connection host are deliberately different values for different reasons.Behavioral coverage for the analyze_now PostgreSQL engine gate (erikd…
Cover the snapshot_now engine gate behaviorally — the last of erikdar…