Repository navigation
Carry the retention live tests' evidence into their failure messages (Fixes #2818) - #2887
Conversation
…ixes #2818) Both live retention tests passed logger: null to EnsureRetentionPoliciesAsync, so the per-relation catch discarded all seventeen warning messages and a nightly's one 'expected 17, got 0' was untriageable by construction. Pass the CapturingTestLogger #2128 added for exactly this shape (#1564), fold Joined into every count assertion, and capture the EnsureContinuousAggregatesAsync pass too, since a swallowed CAGG failure is a plausible upstream cause of the same symptom. Proven both ways against live TimescaleDB 2.29: the injected all-relations-throw shape now names the actual Postgres error per relation (TS001: not a hypertable), and the healthy path applies 17/17, idempotent, with zero warnings captured. The shared logger's empty-case placeholder stops claiming to be about purges; nothing asserts on that literal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MX6HyjsuDCs15qGB2rh4Gy
| @@ -1375,7 +1390,9 @@ FROM timescaledb_information.jobs AS j | |||
| Assert.Equal(("21 days", true), (before.Armed.DropAfter, before.Armed.Scheduled)); | |||
|
|
|||
| /* THE measured claim: the sweep converges both onto the constant, preserving everything else. */ | |||
| Assert.Equal(RetentionPolicyCount, await TimescaleSupport.EnsureRetentionPoliciesAsync(connection, null, ct)); | |||
| var converged = await TimescaleSupport.EnsureRetentionPoliciesAsync(connection, retentionLog, ct); | |||
| Assert.True(converged == RetentionPolicyCount, | |||
| $"the convergence pass should count all {RetentionPolicyCount} policies, got {converged}; {retentionLog.Joined}"); | |||
|
|
|||
| var after = new | |||
| { | |||
| @@ -1391,7 +1408,9 @@ FROM timescaledb_information.jobs AS j | |||
| Assert.Equal(before.Armed.NextStart, after.Armed.NextStart); | |||
|
|
|||
| /* Idempotence: a third sweep finds nothing distinct from the constants and moves nothing. */ | |||
| Assert.Equal(RetentionPolicyCount, await TimescaleSupport.EnsureRetentionPoliciesAsync(connection, null, ct)); | |||
| var settled17 = await TimescaleSupport.EnsureRetentionPoliciesAsync(connection, retentionLog, ct); | |||
| Assert.True(settled17 == RetentionPolicyCount, | |||
| $"the idempotent third sweep should count all {RetentionPolicyCount} policies, got {settled17}; {retentionLog.Joined}"); | |||
There was a problem hiding this comment.
Minor diagnostic-quality nit: retentionLog is a single CapturingTestLogger shared across the CAGG call and all three EnsureRetentionPoliciesAsync passes in this test, and CapturingTestLogger has no reset — Joined just keeps growing. Unlike the simpler first test, this fixture deliberately keeps query_stats HELD for the whole test (line 1335 comment), so every pass logs a LogWarning("... HELD PAUSED ...") for it, plus EnsureRetentionPoliciesAsync always logs an Information summary line even on full success (TimescaleSupport.cs:2286).
By the time settled17's assertion could fire, Joined would contain the accumulated benign HELD/summary lines from created17 and converged as well — the real evidence for whichever pass actually failed gets buried in expected noise from the earlier passes, which cuts against the PR's own goal of a message that "names the actual Postgres error" without a round of triage.
Since CapturingTestLogger has no clear/reset, consider instantiating a fresh one before each EnsureRetentionPoliciesAsync call (as the CAGG-failure-can-explain-it comment in the first test already implies for that call+the immediately following one), so each assertion's Joined reflects only its own pass.
|
Reviewed. This is a well-scoped, test-only fix ( One diagnostic-quality nit left as an inline comment on No security, performance, or Lite/Darling parity concerns found. |
#2887) CapturingTestLogger has no reset, and EnsureRetentionPoliciesAsync always logs an Information summary even on full success. The convergence fixture also keeps query_stats HELD for its entire duration, so every pass logs a benign HELD-PAUSED warning for it too. Sharing one logger across passes meant a later pass's real failure would be buried under the earlier passes' accumulated, already-explained noise — cutting directly against this PR's own goal of a message that names the actual cause without a round of triage. Each EnsureRetentionPoliciesAsync call now gets a fresh logger. Verified against the exact HELD fixture shape: pass-to-pass line count stays flat (2-3 lines) rather than growing, confirming isolation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MX6HyjsuDCs15qGB2rh4Gy
|
Good catch — fixed in 3d1dcd1. Every |
|
Reviewed. This is a tightly-scoped, test-only change to
One non-blocking observation, not a defect: several other live test files ( |
…t-evidence # Conflicts: # CHANGELOG.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed. This is a test-only change in Checked:
No correctness, parity, security, or performance concerns found. LGTM. |
…t-evidence # Conflicts: # CHANGELOG.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReviewTest-only change (Darling.Tests + CapturingTestLogger + CHANGELOG), no shipped-code paths touched. Went through it against CONTRIBUTING.md conventions and the correctness/parity/security/perf checklist. Correctness — Sound. The Parity — No concern. TimescaleDB retention/CAGG policies are Darling-only (Lite uses DuckDB); there's no Lite counterpart to keep in sync here. Security — Nothing in scope; test-only, no new external input, no new file/network/process surface. Performance — N/A; gated live tests behind Minor/non-blocking: the CHANGELOG's new Note: I wasn't able to run No blocking issues found. |
The defect
Both live retention tests passed
logger: nulltoEnsureRetentionPoliciesAsync, so its per-relation catch — correct for the service, where one bad relation must not abort the other sixteen — discarded every warning message. When the nightly failed once with expected 17, got 0, all seventeen discarded messages were the diagnosis and the bare count was the only survivor. It was written off as a flake because it could not be anything else. Same family as #2801/#2816/#2826: a failure that destroys the one piece of information needed to act on it.The fix
Test-side only. #2818 assumed no capturing
ILoggerexisted inDarling.Tests;CapturingTestLoggerhas existed since #2128, added by #1564 for the purge E2Es after the identical null-logger shape. Both tests now create one, pass it toEnsureContinuousAggregatesAsyncand everyEnsureRetentionPoliciesAsynccall (a swallowed CAGG failure is a plausible upstream cause of the same all-relations-throw symptom), and foldJoinedinto each count assertion — the fourAssert.Equalcount checks becomeAssert.Truewith evidence-carrying messages, theDarlingRetentionTestshouse pattern. The shared logger's empty-case placeholder generalizes from(no purge log lines)to(no log lines captured); nothing asserts on that literal.Proven both ways against live TimescaleDB 2.29 (PG17), running the SHIPPED methods
Injected the all-relations-throw shape (migrated store, no hypertables/CAGGs):
applied=0— everything the assertion could sayapplied=0plus one captured warning per relation, first line:Warning: Retention policy for query_stats (4 days) failed - … TS001: "query_stats" is not a hypertable or a continuous aggregateHealthy path unchanged: 69/69 hypertables, 20/20 CAGGs, 17 applied, 17 idempotent, zero warnings captured.
No product behavior change, no migration rung, no schema bump. The next occurrence names its cause in the CI log on the first failure.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MX6HyjsuDCs15qGB2rh4Gy