fix(cli): count a test's blocking rate in sessions, not retry attempts - #205
Merged
Conversation
BlockingRateOf divided blocking failures by failed executions. Both counts were per attempt, so a retry-masked session — one the test failed several times and then passed — put several failures in the denominator and none in the numerator. Four failed attempts in one green session and one failure in a red one scored 0.20 where the occasions say 0.50: the more a test retried, the less blocking it looked, and it ranked below a test that fails once per build. The term carries 0.20 of the impact weight and sits directly beneath RunFrequencyOf, which #176 already moved to sessions, so the two disagreed about their unit inside one scorer. The numerator was also asking the wrong question. _sessionsWithFinalFailures is session-wide — true when any test ended the session red — so a test whose every failure was masked scored 1.00 as long as a neighbour failed finally in the same session, the opposite of the separation the method's own remark claims. Both counts are now sessions: the sessions the test ended red, read off its deciding attempt via RunsOf, over the sessions it failed in at all. A failed run implies a failed attempt in that session, so the ratio needs no clamping. _sessionsWithFinalFailures and its only feeder, SessionOutcomes.HasFinalFailure, are removed. Closes #181. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TMQcZLBGFTd8wz3mcfn9vc
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 2 files with indirect coverage changes 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #181.
The problem
TestIndex.BlockingRateOfdivided blocking failures by failed executions. Both counts were per attempt, so a retry-masked session — one the test failed several times and then passed, leaving the session green — contributed several to the denominator and none to the numerator. A test that fails four attempts in one masked session and once in a red session scored 1/5 = 0.20 where the occasions say 1/2 = 0.50. The more a test retried, the less blocking it appeared. The term carries 0.20 of the impact weight and sits directly beneathRunFrequencyOf, which #176 moved to sessions for this exact reason, so the two disagreed about their unit inside one scorer. It was the last per-attempt counter left in the impact formula after #176 and #179.Reviewing it turned up a second defect in the same expression, not named in the issue. The numerator asked
_sessionsWithFinalFailures.Contains(...)— a session-wide set fed bySessionOutcomes.HasFinalFailure, true when any test ended the session red. A test whose every failure was masked by a retry therefore scored a blocking rate of 1.00 as long as some neighbour failed finally in the same session, which is the exact opposite of the "masked by a retry" separation the method's own remark claims to make.What changed
Both counts are sessions now:
ExecutionsOfand advancing only when a failure comes from a session the last one did not. Executions arrive grouped by session, so this is the same adjacency check, and the same reasoning, as thesessionsRunInincrement inBuild.RunsOf(added in fix(cli): count sessions, not executions, in every arm gate and evidence floor #179). Blocking means this test stopped the build, not that the build stopped.A failed run implies a failed attempt in that session, so the numerator cannot outrun the denominator and the ratio needs no clamping.
_sessionsWithFinalFailuresand its only feeder,SessionOutcomes.HasFinalFailure, are gone;TallykeepsFinalOutcomesalive forSessionView.ARunAgreesWithSessionOutcomesWhenAttemptsArriveOutOfOrdernow pins the same invariant againstTally.Before/after over a real store
The issue asks for this on its own rather than riding along with an unrelated change, since impact scores and therefore finding order move.
The repo's
./.xpingstore (SampleApp.XUnit, 45 runs) is byte-identical —diff <(jq -S '.findings' before.json) <(jq -S '.findings' after.json)is empty. That store records no retries at all (every execution isattemptNumber: 1), so nothing in it can distinguish the two counts. It confirms only that nothing else moved.To exercise the fix end to end I built a second store from six runs of
samples/SampleApp.MSTest, which carriesFlakyTest_PassesOnRetry([Retry(3)], fails attempt 1 and passes attempt 2 every time) alongside the always-failing and timing-out tests that end each session red:FlakyTest_PassesOnRetrynever ends a session red — its failures are always masked — but the sessions are red because of other tests, so it scored a blocking rate of 1.00 and collected the full 0.20. It now scores 0.00, both of its findings drop 0.20 of impact,RetryMaskedfalls out of the high band, andFlakyTest_RaceCondition— which genuinely does end 2 of 6 sessions red, and whose blocking rate is 1.00 before and after — moves up past it. Ranks 1–3 are unchanged.That store demonstrates the second defect. The retry-dilution direction, where a heavily retried test's impact goes up, has no fixture in
samples/(nothing there retries and then still fails), so it rests on the unit test below.Acceptance
BlockingRateCountsSessionsRatherThanAttemptsreproduces the issue's own arithmetic: 0.20 before, 0.50 after.AMaskedFailureIsNotBlockingEvenWhenAnotherTestFailedTheSession: 1.00 before, 0.00 after.ATestThatEndsEverySessionRedBlocksEveryTimeItFails,ATimeoutCountsAsAFailureAndAsABlock, and the two zero cases) pass either way — they are guards, not pins.Verification
932 CLI tests pass;
Xping.Sdk.slnbuilds clean with no warnings from the removed members.🤖 Generated with Claude Code
https://claude.ai/code/session_01TMQcZLBGFTd8wz3mcfn9vc