Skip to content

fix(cli): count the tests a metric could not measure, not only the candidates the report dropped (#185) - #207

Merged
xping-admin merged 1 commit into
mainfrom
fix/185-provider-declines
Sep 7, 2026
Merged

fix(cli): count the tests a metric could not measure, not only the candidates the report dropped (#185)#207
xping-admin merged 1 commit into
mainfrom
fix/185-provider-declines

Conversation

@xping-admin

@xping-admin xping-admin commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #185.

The problem

AnalysisResult.ExcludedLowEvidence was incremented in exactly one place — FindingCoordinator's reporting floor — so it counted only candidates a provider had already offered. Every decline inside a provider was invisible, and that is where most of them happen.

The sharpest case, and the one the issue was filed from: a test whose runs all recorded a zero median normalises nothing, so DurationProvider can compute neither of its statistics and declines both. That test then landed silently inside healthy, which tells a reader it was looked at and is fine. It was not looked at. This is #167's fourth acceptance criterion — the one PR #184 explicitly deferred for want of a mechanism.

The shape of the fix

Per kind, and never totalled. Summing across kinds counts one test once per question its data could not answer; intersecting them collapses to nothing, because a test's pass and fail are always readable; and either total would move with --kind, which is the one thing a count of what could not be measured must not do. Per kind it cannot move — the six providers own disjoint kind sets.

Two reasons, not one. Examination.NotPosed is deleted in favour of AwaitingRuns and Unreadable. Deleting it turned all six call sites into compile errors and forced each to declare which it was. The question at every site is whether another run of the same shape fixes it: Duration's 7/3 arm floors empty as the store fills, a zero median never will. Folding both into one figure would have repeated the issue's own category error one level down.

ProviderReport gains a per-kind NotMeasured tally beside its family size, filtered by --kind with the same condition the family and the candidates get. Every provider fills it.

Acceptance, checked against a real store

criterion result
a zero-median test is accounted for DurationProvider.Unstable killed it at the dispersion gate, because RobustDispersion.Of answers 0 both for a sample too short to have a spread and for one whose median is not positive — an unmeasured test read as a perfectly steady one. The preconditions now run before that gate.
the tally tells the two reasons apart awaitingRuns vs unreadable, published separately per kind
the number does not depend on which providers ran DurationRegression: {awaitingRuns: 0, unreadable: 3} is identical in the full report and under --kind DurationRegression

The sample suite now shows what it was hiding — this previously printed 16 healthy and a green No findings.:

no findings | 16 tests | 16 healthy
* nothing to measure: concurrency 16

```
* Nothing reportable yet: 1 kind had nothing to measure.
```

Judgement calls

  • healthy is unchanged. Subtracting the unmeasured would make a headline count move with --kind. SummaryDto.Healthy now documents that it means "no finding was raised", not "checked and fine".
  • Only the unreadable half is rendered. The counts line already says "awaiting more runs" in a different unit — tests here, candidates there — and two waiting figures on one screen is the confusion this change exists to remove. awaitingRuns is in the JSON.
  • SharedFailure and BrokenFixture publish nothing rather than zero. They are counted in signature groups, not tests. Absence says the kind keeps no such tally; a published zero says it looked and could read everything.
  • The three retry kinds share one figure, taken at the precondition they all read. Exhausted ?? Deepening ?? Masked stops at the first kind that fires, so whether a later one was measurable is a question the algorithm never asks; answering it for the tally alone would mean running all three on every fingerprint.
  • ParallelSensitiveProvider's test == null stays a tested fingerprint, unlike the same condition elsewhere: CochranArmitage.Of has already run by then, so the comparison genuinely was made and the family has to say so.

Contract

Schema 1.141.15. summary.notMeasured is keyed by the kind as findings[].kind and --kind spell it:

"notMeasured": {
  "DurationRegression": { "awaitingRuns": 63, "unreadable": 32 },
  "ParallelSensitive": { "awaitingRuns": 0, "unreadable": 108 }
}

docs/internals/finding-populations.md gains a section recording the per-kind decisions, per that file's own rule.

Testing

964 CLI tests pass (24 new), 901 SDK Core tests pass, solution builds clean under TreatWarningsAsErrors + AnalysisMode=All. Two runs over an unchanged store still serialise byte-identically.

New tests include the acceptance case (ATestWhoseRunsAllRecordedAZeroMedianIsCountedRatherThanSilentlyCalledHealthy), its negative (a steady test is counted nowhere), --kind stability, floor-vs-tally exclusivity, and one coverage pair per provider.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TMQcZLBGFTd8wz3mcfn9vc

…ndidates the report dropped (#185)

`ExcludedLowEvidence` was incremented in exactly one place — the coordinator's
own reporting floor — so it counted only candidates a provider had already
offered. Every decline inside a provider was invisible, and that is where most
of them happen. A test whose runs all recorded a zero median normalises
nothing, so `DurationProvider` could compute neither of its statistics and
declined both; the test then landed silently inside `healthy`, which tells a
reader it was looked at and is fine. It was not looked at.

`ProviderReport` gains a per-kind `NotMeasured` tally beside its family size,
and every provider fills it. `Examination.NotPosed` is deleted in favour of
`AwaitingRuns` and `Unreadable`, which turned all six call sites into compile
errors and forced each to say which it was — the two are different news and
only the first is answered by waiting. Six baseline runs where the comparison
needs seven empties as the store fills; a zero median never will.

Per kind and never totalled. Summing counts one test once per question its data
could not answer, intersecting collapses to nothing because pass and fail are
always readable, and either total would move with `--kind` — which is the one
thing a count of what could not be measured must not do.

`DurationProvider.Unstable` is where the acceptance criterion lives:
`RobustDispersion.Of` answers zero both for a sample too short to have a spread
and for one whose median is not positive, so the dispersion gate was reading an
unmeasured test as a perfectly steady one. The preconditions are now computed
before it.

`healthy` is deliberately unchanged. Subtracting the unmeasured would make a
headline count move with `--kind`; `SummaryDto.Healthy` now documents that it
means "no finding was raised" rather than "checked and fine".

`SharedFailure` and `BrokenFixture` publish nothing rather than zero — they are
counted in signature groups, not tests. The three retry kinds share one figure,
taken at the precondition they all read, because the chain that picks between
them never asks whether a later kind was measurable.

Rendered as one line above the fence naming the three largest kinds, and
published per kind as `summary.notMeasured`. Schema 1.14 -> 1.15.
`docs/internals/finding-populations.md` records the per-kind decisions, per
that file's own rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMQcZLBGFTd8wz3mcfn9vc

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The contract and rendering changes are consistently implemented across providers/coordinator/envelope (with schema bump) and are backed by comprehensive targeted tests for both JSON and text output behavior.

Pull request overview

This PR fixes a reporting blind spot in the CLI’s analysis summary by adding a per-finding-kind “not measured” tally (split into awaitingRuns vs unreadable) so tests a provider cannot compute metrics for no longer vanish into “healthy”, and the JSON/report output can explain coverage gaps per metric kind.

Changes:

  • Extend the provider/coordinator contract to carry per-kind NotMeasured counts, and project them into the JSON envelope as summary.notMeasured (schema 1.141.15).
  • Update CLI text rendering to surface an “nothing to measure” line (unreadable-only) and ensure empty reports don’t incorrectly read as success when all questions were unanswerable.
  • Add broad unit test coverage across providers, coordinator behavior (including --kind filtering stability), and output rendering/contract.
File summaries
File Description
tests/Xping.Cli.Tests/Report/VanishedProviderTests.cs Adds tests asserting Vanished publishes awaiting-runs vs unreadable semantics (unreadable structurally zero).
tests/Xping.Cli.Tests/Report/TimeSensitiveProviderTests.cs Adds tests for window-wide “no recorded clock” behavior and for “measured but no finding” not being counted.
tests/Xping.Cli.Tests/Report/ShareableOutputTests.cs Verifies rendered output includes (or omits) the “nothing to measure” line and ordering/truncation rules.
tests/Xping.Cli.Tests/Report/RetryProviderTests.cs Adds tests that retry kinds share one not-measured figure and do not count readable uneventful tests.
tests/Xping.Cli.Tests/Report/ReportEnvelopeTests.cs Updates schema assertion to 1.15 and checks summary.notMeasured presence.
tests/Xping.Cli.Tests/Report/ParallelSensitiveProviderTests.cs Adds tests for unreadable-only counting when concurrency never varies and for “measured but no finding”.
tests/Xping.Cli.Tests/Report/FindingCoordinatorTests.cs Adds tests for --kind stability of the not-measured tally, floor-vs-tally exclusivity, and provider-failure behavior.
tests/Xping.Cli.Tests/Report/FailureModeProviderTests.cs Adds tests for outage windows being counted as awaiting runs and for avoiding double-reporting clustered failures.
tests/Xping.Cli.Tests/Report/DurationProviderTests.cs Adds acceptance coverage for the zero-median/unmeasurable duration case and related non-counted/measured cases.
tests/Xping.Cli.Tests/Commands/CliSurfaceTests.cs Updates schema assertions to 1.15.
src/Xping.Cli/Report/Rendering/TextReportRenderer.cs Renders an unreadable-only “nothing to measure” line and improves empty-report messaging when kinds were unanswerable.
src/Xping.Cli/Report/Providers/VanishedProvider.cs Tracks and publishes per-kind not-measured counts for Vanished (awaiting-runs only).
src/Xping.Cli/Report/Providers/TimeSensitiveProvider.cs Publishes not-measured counts; distinguishes awaiting-runs vs unreadable at decline sites and handles “no clocks” window-wide.
src/Xping.Cli/Report/Providers/RetryProvider.cs Publishes a shared not-measured count for all three retry kinds at their shared precondition.
src/Xping.Cli/Report/Providers/ParallelSensitiveProvider.cs Publishes not-measured counts and reclassifies “insufficient concurrency variation” as unreadable.
src/Xping.Cli/Report/Providers/IFindingProvider.cs Introduces NotMeasuredCount, expands ProviderReport to include per-kind not-measured tallies, and refines Examination outcomes.
src/Xping.Cli/Report/Providers/FailureModeProvider.cs Publishes shared not-measured counts for per-test failure-mode kinds while keeping group-based kinds absent.
src/Xping.Cli/Report/Providers/DurationProvider.cs Adds per-kind not-measured tracking and preconditions so “unmeasurable duration” cases are counted rather than silently treated as healthy.
src/Xping.Cli/Report/FindingCoordinator.cs Aggregates and filters provider NotMeasured tallies per kind alongside candidates/family sizes, returning them in AnalysisResult.
src/Xping.Cli/Report/Contract/ReportEnvelope.cs Bumps schema version to 1.15 and adds summary.notMeasured + DTOs with clarified healthy meaning.
src/Xping.Cli/Report/Contract/EnvelopeBuilder.cs Projects AnalysisResult.NotMeasured into the envelope with stable enum-declaration ordering and correct key spelling.
docs/internals/finding-populations.md Documents per-kind not-measured semantics, reasons, and why certain kinds publish nothing.
docs/cli/command-reference.md Updates JSON contract docs and example to include summary.notMeasured and schema 1.15.
Review details
  • Files reviewed: 23/23 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.70588% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/Xping.Cli/Report/Providers/IFindingProvider.cs 85.71% 2 Missing and 1 partial ⚠️
src/Xping.Cli/Report/FindingCoordinator.cs 77.77% 2 Missing ⚠️
src/Xping.Cli/Report/Providers/DurationProvider.cs 92.30% 1 Missing and 1 partial ⚠️
.../Xping.Cli/Report/Providers/FailureModeProvider.cs 96.00% 1 Missing ⚠️
...c/Xping.Cli/Report/Rendering/TextReportRenderer.cs 96.15% 0 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
src/Xping.Cli/Report/Contract/EnvelopeBuilder.cs 95.69% <100.00%> (+0.29%) ⬆️
src/Xping.Cli/Report/Contract/ReportEnvelope.cs 100.00% <100.00%> (ø)
....Cli/Report/Providers/ParallelSensitiveProvider.cs 94.87% <100.00%> (+0.16%) ⬆️
src/Xping.Cli/Report/Providers/RetryProvider.cs 97.91% <100.00%> (-0.42%) ⬇️
...ping.Cli/Report/Providers/TimeSensitiveProvider.cs 95.80% <100.00%> (+0.78%) ⬆️
src/Xping.Cli/Report/Providers/VanishedProvider.cs 100.00% <100.00%> (ø)
.../Xping.Cli/Report/Providers/FailureModeProvider.cs 96.36% <96.00%> (+0.08%) ⬆️
...c/Xping.Cli/Report/Rendering/TextReportRenderer.cs 83.60% <96.15%> (+2.07%) ⬆️
src/Xping.Cli/Report/FindingCoordinator.cs 97.56% <77.77%> (-1.13%) ⬇️
src/Xping.Cli/Report/Providers/DurationProvider.cs 97.30% <92.30%> (-0.48%) ⬇️
... and 1 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xping-admin
xping-admin merged commit a2d081f into main Sep 7, 2026
3 checks passed
@xping-admin
xping-admin deleted the fix/185-provider-declines branch September 7, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enhancement(cli): a provider that declines for want of data leaves no trace — the excluded tally only counts the coordinator's own drops

2 participants