Fixes #2923 - #2927
Fixes #2923#2927
Conversation
ViewerFleetTimerGuardTests.NoStoreRead_IsFiredByBothFleetTimers cuts OnRefreshTimerTick at its first `return;` and intersects only the region above it with OnOverviewTimerTick. Move the fan-out below that return and the region empties, the intersection empties with it, and the assertion is satisfied BECAUSE the regression happened. Measured: that mutation leaves all four of that class's facts green, and the firstReturn.Success staleness guard cannot see it because a relocation leaves the `return;` exactly where it was. #2907 named that move as a regression in the same breath as the duplication the pin does catch - it would stop the alert poll and the store-size read entirely while the Overview or a per-server tab is up, and the Overview is the tab that ships selected. ViewerFleetTimerFanOutPositionTests asserts the ordering instead: every fire-and-forget in the tick has a call above the first `return;`, the awaited RefreshVisibleAsync stays below it, and the pinned set is derived from the tick with the three names #2907 argues about asserted to still be in it. Read over CSharpSourceWalker's output (#2913/#2925) rather than a sixth private copy of the walk; brace-matching that output to find a method body is what its own summary says the output is for. A count-based control cannot prove this pin red - a relocation leaves every occurrence count invariant - so the control is the offset comparison itself, and a third fact carries it in CI by running the check twice over one relocated fixture: detected through the walker, missed over the same text raw, where the fixture's own comment prose supplies the above-the-return hit. Nine mutations, each verified applied before it was trusted (by offset for the relocations, by count for the rest) and compiled first, each failing a different assertion. The relocation now fails only the new position fact with all four old facts green; moving the visible-tab refresh up fails the ordering fact; renaming PollAlertsAsync fails the name-staleness guard; deleting both early-returns fails the boundary guard in both pins; neutering the walker's strip fails the control; and the four pre-existing facts each still go red on their own mutation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed this as a test-only change (no production code touched): Correctness — traced the test logic against the real
Lite/Darling parity — no concern. This pins WPF-viewer-specific dual-timer behavior ( Security / Performance — none applicable; this only reads known repo-relative file paths ( One thing I checked but didn't flag as an issue: No correctness, parity, or security issues found. |
#2923's pin arrived with its own BraceBalanced, making two: one here, one in ViewerFleetTimerGuardTests. Brace-matching is only correct over the walker's output - braces in prose and in literals are exactly what unbalances it, which is why blanking a hole's delimiters is already part of StripCommentsAndStrings' contract - so the walker is where it belongs, the same argument #2925 made for the walk itself. The two copies had already drifted textually: the newer one fused the decrement into its condition (`else if (c == '}' && --depth == 0)`) where the older one decrements in the block. Proven equivalent before picking one, at EVERY offset of all 190 viewer .cs files (2,836,304 positions, zero differences) rather than by reading them, with the differential harness positive-controlled against a third form that omits the decrement. Kept the older expanded form: it is the one on dev, and it matches StatementSpanFrom's style in the file it moves into. The only behavioural change is the ArgumentNullException.ThrowIfNull the walker's three other entry points already carry; no caller passes null. BodyAfterSignature stays put. It has one class's two call sites and no other caller in the repo, and its contract is specific to that pin's call-graph walk - it returns an expression-bodied member's expression WITHOUT braces so the walk can follow `RefreshAgAsync => LoadAsync();`. Moving a single-caller helper would grow the shared surface without removing a duplicate, which is the opposite of the reason BraceBalanced moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extends #2923's entry rather than adding a second one: it is one PR closing one issue, and the consolidation is part of the same diff against the base. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed. This PR is Darling-only test infrastructure (no T-SQL, no product code) closing #2923 by pinning the fan-out's position relative to the tab early-return, plus consolidating the two private Traced the new logic by hand against the actual
No correctness, security, or performance issues found. One purely cosmetic nit: the retained comment block in |
Adding the brace-matching sentence left one line at 132 characters against its neighbours' 100-107. Comment-only; raised by review on the PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Reviewed the diff (test-only: Findings: none blocking. What I checked:
Nothing else stood out as a correctness, parity, or security concern. |
ViewerScopedPaintGuardTests kept a private BraceBalanced whose doc comment asserted it was not a sixth copy of the walk #2913 consolidated. #2927 moved the helper into CSharpSourceWalker and pointed the other three viewer call sites at it, which made that claim false: this was the last private copy. The two implementations were compared rather than assumed equivalent. They agree at every offset of 872 source files (14,619,467 positions, zero differences) and differ only at a null input, where the shared one raises ArgumentNullException and the copy raised NullReferenceException. The shared behaviour is kept; neither call site can pass null. Both call sites now read CSharpSourceWalker.BraceBalanced, and the doc comment moves to Stripped, which is where this file discharges the walk's precondition.
ViewerScopedPaintGuardTests kept a private BraceBalanced whose doc comment asserted it was not a sixth copy of the walk #2913 consolidated. #2927 moved the helper into CSharpSourceWalker and pointed the other three viewer call sites at it, which made that claim false: this was the last private copy. The two implementations were compared rather than assumed equivalent. They agree at every offset of 872 source files (14,619,467 positions, zero differences) and differ only at a null input, where the shared one raises ArgumentNullException and the copy raised NullReferenceException. The shared behaviour is kept; neither call site can pass null. Both call sites now read CSharpSourceWalker.BraceBalanced, and the doc comment moves to Stripped, which is where this file discharges the walk's precondition.
Fixes #2923
Two things, both test-only: a pin that closes #2923's hole, and the consolidation of
BraceBalancedthat the second copy created.1. The hole
NoStoreRead_IsFiredByBothFleetTimerscutsOnRefreshTimerTickat its firstreturn;and intersects only the region ABOVE it withOnOverviewTimerTick. Move the fan-out below that return and the region empties, the intersection empties with it, and the assertion is satisfied because the regression happened.Measured, not assumed: that relocation leaves all four of
ViewerFleetTimerGuardTests' facts green. ItsAssert.True(firstReturn.Success, …)staleness guard cannot see it, because a relocation leaves thereturn;exactly where it was.#2907 named that move as a regression in the same breath as the duplication the pin does catch — it "would stop the alert poll and the store-size read entirely while the Overview or a per-server tab is up" — and the Overview is the tab that ships selected.
What the new pin asserts
Darling/Darling.Tests/ViewerFleetTimerFanOutPositionTests.cs, three facts:TheUnconditionalFanOut_IsFiredAboveTheTabEarlyReturn— every fire-and-forget in the tick has a call at an offset above the firstreturn;. The set is derived from the tick (so a fan-out added later is covered without anyone remembering this file), with a floor of 4 so it cannot hold over nothing, and the three names Two viewer fleet-timer store reads have no in-flight guard, and the Overview timer double-fires one of them #2907 argues about asserted to still be in that set so a rename is loud rather than vacuously green.TheVisibleTabRefresh_IsAwaitedBelowTheTabEarlyReturn— the other half of the ordering, and the reason the sibling pin's split means anything:RefreshVisibleAsyncstays below. Above it, both timers refresh the same grid every cycle at one interval.TheAboveTheReturnCheck_SeesARelocatedFanOut_OnlyThroughTheWalker— the control, in CI rather than in a commit message.No product code changed. No schema rung, no version bump.
The walker, and being exact about what it buys
Read over
CSharpSourceWalker.StripCommentsAndStrings(#2913/#2925) rather than a sixth private copy of the walk.Being precise, because #2923 overstates this: the tick's comment-only mentions are ONE each of
RefreshServerStatusAsync,PollAlertsAsyncandRefreshVisibleAsync, none ofRefreshStoreSizeAsyncand none ofreturn— the issue's 2/1/2/2/2 are the totals including the code. None is call-shaped, so raw and stripped agree on every call offset in this tick today and stripping does not change the current answer. What it buys is the next comment: those three mentions sit above the early-return, at offsets 328, 472 and 622, so one comment writtenPollAlertsAsync()instead ofPollAlertsAsyncturns prose into an above-the-return hit and the pin green on the regression. Fact 3 runs exactly that case both ways, and mutation 7 below confirms the honesty of the claim.The control, since a count is invariant by construction
A relocation leaves every occurrence count where it was — one
_ = RefreshServerStatusAsync();before, one after — so an anchor tally reports the mutation as applied whether it applied or not. The control is the offset comparison itself: every failure message carries the offsets, each relocation mutation was verified by offset through an independent raw-text instrument (Python, deliberately not the pin's own walker, so the thing under test is not its own control), and Fact 3 carries the same comparison in CI.2.
BraceBalanced, consolidatedThe new pin arrived with its own
BraceBalanced, making two — one here, one inViewerFleetTimerGuardTests. Brace-matching is only correct over the walker's output: braces in prose and in literals are exactly what unbalance it, which is already why blanking a hole's DELIMITERS is part ofStripCommentsAndStrings' contract. So it now lives inCSharpSourceWalkerasinternal static, with both private copies deleted and all three call sites pointing at it. Same argument #2925 made for the walk itself.They had diverged. Textually, not behaviourally: the newer copy fused the decrement into its condition (
else if (c == '}' && --depth == 0)) where the older decrements inside the block. Rather than read them and conclude, they were run against each other at every offset of all 190 viewer.csfiles — 2,836,304 positions, zero differences — with the differential harness positive-controlled against a third form that omits the decrement (reported as different, so the harness can see one). Kept the older expanded form: it is the one ondev, and it matchesStatementSpanFrom's style in the file it moves into. The only behavioural change is theArgumentNullException.ThrowIfNullthe walker's three other entry points already carry; no caller passes null.BodyAfterSignaturedeliberately stays put. It has one class's two call sites and no other caller in the repo, and its contract is specific to that pin's call-graph walk — it returns an expression-bodied member's expression without braces so the walk can followRefreshAgAsync => LoadAsync();. Moving a single-caller helper would grow the shared surface without removing a duplicate, which is the opposite of the reasonBraceBalancedmoved.RepoRootis out of scope. 43 files inDarling.Testsdeclare their own walk-up helper (27 named exactlyRepoRoot), so that one is settled convention rather than duplication.Red-first evidence
Nine mutations proved the pin; all seven that bear on these two classes were re-run after the consolidation, each from a tree verified clean beforehand (
pre-mutation dirty files: 0), each compilingDarling.Testsitself, each still failing the same assertion, with the count unmoved at 7.RefreshServerStatusAsync'sif (_statusRefreshInFlight)testEveryFleetTimerFanOutTarget_IsSingleFlightOnEveryPathToAStoreRead_ = RefreshServerStatusAsync();toOnOverviewTimerTick[]→['RefreshServerStatusAsync']NoStoreRead_IsFiredByBothFleetTimersOnRefreshTimerTickpassesreplayIfBusy: trueOnlyANonPeriodicCaller_AsksTheStatusRefreshToReplay_alertPollInFlight = false;out of itsfinallyEveryFleetTimerGuard_IsReleasedInAFinallyreturn;TheUnconditionalFanOut_…only — all four old facts GREENawait RefreshVisibleAsync();above itTheVisibleTabRefresh_…+NoStoreRead_…git diff+2,return text;TheAboveTheReturnCheck_…Two more from the original battery, not affected by the consolidation: renaming
PollAlertsAsyncacross the viewer fails the name-staleness guard; deleting both early-returns fails the boundary guard in both pins (3 facts).Mutation 5's message, and the offsets are byte-identical before and after the consolidation — which is how the move is shown behaviour-preserving through the pins rather than merely still-green, since the shared helper is what extracts that body:
Mutations 1–4 are what discharges "the existing four facts still assert what they claim" — each still goes red on its own mutation, after the refactor as before it.
Mutation 7 also confirms the honesty above: with the strip neutered, facts 1 and 2 and all four existing facts stay green, so the walker is protecting the next comment rather than fixing today's answer.
Three earlier mutation attempts failed to apply and were reported as such rather than counted —
await RefreshVisibleAsync();is not a unique anchor inMainWindow.xaml.cs(5 at one indent, 2 at another). That is the silent-non-application failure mode; it was loud only because the driver asserts anchor uniqueness before every edit.Verification scope
Darling.Testsisnet10.0-windowsand cannot run on macOS — CI is the arbiter. What ran locally: the realViewerFleetTimerGuardTests.cs,CSharpSourceWalker.csand the new file compiled from the repo by absolute path (never a copy) into a throwawaynet10.0xunit host withAssemblyName=Darling.Tests, so[CallerFilePath]resolves and the pins read the real viewer project. 7 tests, 4 existing + 3 new, 0 failed, each named in the run output, before and after the consolidation.Because this PR edits
CSharpSourceWalker.cs, a second host ran the pins that scan the tree:CSharpSourceWalkerTests(its per-construct witnesses and the repo-wide balance sweep),DocCommentHygieneTestsandFleetIdentifierScrubTests— 31 tests, 0 failed, with the host's output directory relocated inside the repo soDocCommentHygieneTests'AppContext.BaseDirectorywalk-up actually resolves and it genuinely scanned all three edited files.Darling.Tests.csprojand the Viewer project both build clean withEnableWindowsTargeting=true.All four committed blobs are pure LF, no BOM, no stray CR; the working tree is CRLF per
.gitattributes.CHANGELOG.mdstaged with a plaingit addas 2 insertions, then 1 changed line.🤖 Generated with Claude Code