test(transport): row 74 — gauge asserts become delta-over-scoped-action, not absolute
BundleSessionObservabilityTests' two gauge facts asserted absolute values (Assert.Equal(0, baseline), Assert.Equal(2, ReadGauge())) against what is a single process-global provider slot — the same latent shape as the row-32 QueueDepthGaugeTests flake, correct only while this file is the slot's sole registrant. Converted to the delta-over-scoped-action pattern the row-66 rejection-counter facts in the same file already use: a new ReadGaugeDelta helper measures how far the instrument MOVED across an action driven by this test's own store, so an inherited non-zero baseline cancels out. Two assertions got stronger in the process. The post-clear check was Assert.Equal(0, ReadGauge()) — "the instrument reports zero", which any unregistered slot satisfies; it is now "opening more sessions on the detached store moves the gauge by nothing", which is the actual claim. And the compare-and-clear fact gained a negative control: the superseded store's opens must NOT reach the instrument, so a slot still pointing at oldStore can no longer satisfy the assertion by coincidence when both stores hold equal counts. Fail-before (temporary probe, not committed): with a foreign provider returning 7 registered before the service starts, the OLD body fails `Assert.Equal(0, baseline)` with `Expected: 0 / Actual: 7`; the NEW delta body passes the identical scenario. Honest residual documented on the helper: a gauge has one slot and last-writer-wins, so a registrant that clobbers it BETWEEN the two reads is unrecoverable read-side — that needs row-32's mutual exclusion. It cannot arise today (this is the only file in the assembly constructing a BundleSessionEvictionService, and test assemblies are separate processes); a second such file should bring the collection treatment with it. Transport.Tests: 180/180 green.
This commit is contained in:
+77
-16
@@ -215,30 +215,39 @@ public sealed class BundleSessionObservabilityTests
|
||||
public async Task EvictionService_RegistersTheGaugeProvider_AndClearsItOnStop()
|
||||
{
|
||||
// The eviction service is Transport's only lifecycle seam, so it owns the
|
||||
// process-global gauge slot. The gauge is read as a delta against a baseline
|
||||
// because the instrument is a process-wide static.
|
||||
// process-global gauge slot. Every assertion here is a DELTA over a scoped action
|
||||
// against this test's own store (register row 74) — see ReadGaugeDelta.
|
||||
var clock = new TestClock(DateTimeOffset.UtcNow);
|
||||
var store = new BundleSessionStore(Options.Create(new TransportOptions()), clock, NullLogger<BundleSessionStore>.Instance);
|
||||
var service = new BundleSessionEvictionService(store, NullLogger<BundleSessionEvictionService>.Instance);
|
||||
|
||||
var baseline = ReadGauge();
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
try
|
||||
{
|
||||
store.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30)));
|
||||
store.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30)));
|
||||
// While registered, this store's opens are what the gauge reports.
|
||||
var whileRegistered = ReadGaugeDelta(() =>
|
||||
{
|
||||
store.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30)));
|
||||
store.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30)));
|
||||
});
|
||||
|
||||
Assert.Equal(2, ReadGauge());
|
||||
Assert.Equal(2, whileRegistered);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
// After the clear the gauge falls back to reporting 0, not through a dead store.
|
||||
Assert.Equal(0, ReadGauge());
|
||||
Assert.Equal(0, baseline);
|
||||
// After the clear the gauge no longer reads through this store: opening more
|
||||
// sessions on it moves the instrument by nothing. Stated as a delta this is a
|
||||
// STRONGER claim than the old `Assert.Equal(0, ReadGauge())` — that one merely
|
||||
// said "the instrument reports zero", which any unregistered slot satisfies,
|
||||
// and which a foreign registrant would have failed for reasons unrelated to
|
||||
// the clear.
|
||||
var afterClear = ReadGaugeDelta(() =>
|
||||
store.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30))));
|
||||
|
||||
Assert.Equal(0, afterClear);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -257,29 +266,81 @@ public sealed class BundleSessionObservabilityTests
|
||||
await newService.StartAsync(CancellationToken.None);
|
||||
try
|
||||
{
|
||||
newStore.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30)));
|
||||
|
||||
// Late stop of the superseded instance.
|
||||
// Late stop of the superseded instance, then prove the NEWER store is still
|
||||
// the one driving the gauge — by moving it and measuring the movement.
|
||||
await oldService.StopAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(1, ReadGauge());
|
||||
var newStoreStillDrivesIt = ReadGaugeDelta(() =>
|
||||
newStore.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30))));
|
||||
|
||||
Assert.Equal(1, newStoreStillDrivesIt);
|
||||
|
||||
// Negative control on the same window: the superseded store is detached, so
|
||||
// its opens must not reach the instrument. Without this, a provider slot left
|
||||
// pointing at oldStore would still satisfy the assertion above by coincidence
|
||||
// whenever both stores happened to hold equal counts.
|
||||
var oldStoreIsDetached = ReadGaugeDelta(() =>
|
||||
oldStore.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30))));
|
||||
|
||||
Assert.Equal(0, oldStoreIsDetached);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await newService.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
Assert.Equal(0, ReadGauge());
|
||||
var afterClear = ReadGaugeDelta(() =>
|
||||
newStore.Open(SessionExpiringAt(clock.GetUtcNow().AddMinutes(30))));
|
||||
|
||||
Assert.Equal(0, afterClear);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Runs <paramref name="act"/> between two observations of the open-import-sessions
|
||||
/// gauge and returns how far the instrument MOVED across it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Register row 74. These tests used to assert absolute gauge values —
|
||||
/// <c>Assert.Equal(0, baseline)</c>, <c>Assert.Equal(2, ReadGauge())</c> — against
|
||||
/// what is a single process-global provider slot
|
||||
/// (<c>SetImportSessionCountProvider</c> / <c>ClearImportSessionCountProvider</c>).
|
||||
/// That is the same shape as the register row 32 flake in
|
||||
/// <c>QueueDepthGaugeTests</c>: correct only for as long as this file is the slot's
|
||||
/// sole registrant, and silently wrong the day a sibling suite writes it. A delta
|
||||
/// carries no assumption about what the slot held on entry, so a non-zero inherited
|
||||
/// baseline cancels out.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Honest residual, and the reason this is a delta rather than a full fix: unlike
|
||||
/// <see cref="ReadRejectionCounter"/> — where every measurement in the window is
|
||||
/// driven synchronously by this test's own <c>Open</c> call, making it genuinely
|
||||
/// immune — a gauge has ONE slot and last-writer-wins. If a concurrent registrant
|
||||
/// overwrote the slot BETWEEN the two reads, no read-side technique could recover;
|
||||
/// that case needs mutual exclusion on the slot (row 32's serialized collection).
|
||||
/// It cannot arise today: this is the only file in the assembly that constructs a
|
||||
/// <c>BundleSessionEvictionService</c>, and test assemblies run in separate
|
||||
/// processes. A second such file should bring the collection treatment with it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="act">The action to measure across.</param>
|
||||
/// <returns>The gauge's movement while <paramref name="act"/> ran.</returns>
|
||||
private static long ReadGaugeDelta(Action act)
|
||||
{
|
||||
var before = ReadGauge();
|
||||
act();
|
||||
return ReadGauge() - before;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces one synchronous observation of the open-import-sessions gauge and returns
|
||||
/// the measured value. Mirrors the MeterListener helper used by the Communication
|
||||
/// and StoreAndForward telemetry suites.
|
||||
/// and StoreAndForward telemetry suites. Private to <see cref="ReadGaugeDelta"/> by
|
||||
/// convention — see its remarks for why no test reads this absolutely.
|
||||
/// </summary>
|
||||
/// <returns>The gauge's current value, or 0 if it never reported.</returns>
|
||||
private static long ReadGauge()
|
||||
|
||||
Reference in New Issue
Block a user