test(export): census the range guards in Logging/Export so they cannot drift apart - #671
Conversation
…t drift apart The SCPI producers have a structural census pinning the shape of every inline range guard in one file. Nothing did that anywhere else, and the guards elsewhere have already drifted: some throw the two-argument ArgumentOutOfRangeException and so report no ActualValue, and CsvExporter composes its ParamName instead of using nameof. Adds a companion census over Logging/Export, where the new guards have been landing. It walks all three guards in the folder and compares ParamName, ActualValue and message against the table, then reads the source files and asserts the table matches every throw site it finds, so a guard added without a census entry fails rather than going quietly uncovered. CsvExporter's composed "options.AverageWindow" ParamName is pinned as-is and documented as deliberate rather than normalised: it is part of the public contract and carries more information than a bare nameof(options). A structural check keeps it honest — the root segment must still name a real parameter and the tail a real member of its type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoAdd a guard census for Logging/Export range checks to prevent drift
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1.
|
…erload Qodo review, both findings taken: - The source scan counted any line mentioning ArgumentOutOfRangeException and skipped only `//` and `*`-prefixed lines. A `catch`, a `typeof`, or a mention inside a `/* */` block would have failed the census for a change that added no guard at all, and a drift test that cries wolf gets switched off. It now matches `throw new ArgumentOutOfRangeException` and the `ArgumentOutOfRangeException.ThrowIf*` helpers only, over comment-stripped lines that carry block-comment state across the file. - Type.GetMethod(name) would throw AmbiguousMatchException the day an overload is added, saying nothing about what to do. Resolution is now by enumeration with an explicit message telling the reader the census must name the overload. Re-ran the added-guard mutation against the tightened scan: still red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Both findings taken, fixed in e4ab807. 1. Guard scan matches non-throws — agreed, and it was the more serious of the two. The scan counted any line mentioning 2. Reflection lookup brittle — agreed. Full suite green on net9.0 and net10.0 under |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit e4ab807 |
|
Qodo-clean, CI green — ready for review. Independently re-verified at head
One thing reviewers should weigh before merging: the merge order with #668 has a real cost. Confirmed against #668 at head Not merging. |
The census reads the source rather than trusting its own table, which is what it is for -- and it caught the first guard to land after it was written. #668 added a range guard to SdCardLogSampleSource's constructor while this branch was open, so the scan found a throw site with no entry: Expected: "CsvExporter.cs: 1; LiveCsvRecording.cs: 2" Actual: "CsvExporter.cs: 1; LiveCsvRecording.cs: 2; SdCardLogSampleSource.cs: 1" The new guard is in a constructor, so GuardSite holds a MethodBase now instead of a MethodInfo. Nothing else changes: ParamName is still resolved against the throwing member's parameter list, so the structural check covers the constructor on the same terms as the two methods. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 59211a6 |
|
Qodo-clean, CI green — ready for review, now at head The earlier ready note was for
No production code touched, so bench validation doesn't apply. |
The current head looks ready to merge from the review perspective. The diff is test-only and the census now covers the four export range-guard sites, including |
What was wrong
When Core rejects an out-of-range argument, what the caller actually gets back is inconsistent. Some guards throw the two-argument
ArgumentOutOfRangeException, which leavesActualValuenull — so the value that was rejected simply isn't reported — while others use the three-argument form and do report it. Nobody notices, because nothing checks.ScpiMessageProducergot a census in #660 that locks its nine guards into one shape, but that's one file, and the new guards aren't landing there: #657 added two inLogging/Export, and #668 added another while this branch was open.So the region that's guaranteed uniform is not the region that's growing, and a caller writing
catch (ArgumentOutOfRangeException ex)can't rely onex.ActualValuebeing populated.How it was fixed
A companion census over
Logging/Export— the folder where the recent guards actually landed, four sites today. It invokes each guard and asserts the exactParamName,ActualValueand message the caller sees, and that the message is a sentence ending in a period. Then it reads the folder's source and asserts the table matches everyArgumentOutOfRangeExceptionsite it finds, per file, so a guard added without a census entry fails loudly instead of being quietly uncovered.The thing a reviewer is most likely to want to argue with:
CsvExportercomposes itsParamNameas"options.AverageWindow"where everything else uses a plainnameof. That's the one real divergence in scope, and I pinned it as-is rather than normalising it, with a comment saying why —ParamNameis observable, a caller can filter on it, and the composed form says which option was rejected wherenameof(options)wouldn't. To stop that becoming a loophole, the census resolves everyParamNamestructurally: the first segment must name a real parameter of the throwing member and the tail a real public member of that parameter's type, so a rename that leaves the string behind fails.The divergent guards outside this folder (
DigitalChannel,AnalogChannel,HidLibraryTransport,TimestampProcessor, the two SD-card files) are out of scope and stay recorded in #664.Verification
The census can fail, and I checked, by mutating production code and reverting:
LiveCsvRecording'sbufferCapacityguard — i.e. drifting it into the shape six other guards in the repo already have — turns the census red onActualValue, while every other test in the export test classes stays green, includingLiveCsvRecordingTests' ownbufferCapacitytest. That's the point: the existing per-site tests assert type andParamNameonly, so this drift is invisible to them.CsvExporterturns the source scan red, naming the file.SdCardLogSampleSourcewith a constructor guard while this branch was open, the scan went red naming it, and59211a6adds the row (wideningGuardSitefromMethodInfotoMethodBaseso a constructor censuses on the same terms).Full suite green on net9.0 and net10.0 under
-warnaserror: 3982 passing, 0 failing, plusDaqifi.Mcp.Tests. Bench validation doesn't apply — test-only change, no production code touched.closes #664Not merging — for review.
🤖 Generated with Claude Code