test(sdcard): direct tests for the text log line reader's byte counter and lease discipline - #665
Conversation
…r and lease discipline SdCardTextLineReader is the line pump every CSV and JSON log line passes through, and it had no tests of its own. The parser fixtures reach it only with small, seekable, zero- positioned MemoryStreams and then assert on the samples that come out the far end, so they never look at the byte count it reports and never take its forward-only branch. Adds 13 direct cases covering the byte counter (seekable position vs. estimate, the terminator, blank lines counting even though they are dropped), where reading starts, that the caller's stream is left open, cancellation, the source overload's lease release on both normal and early exit, and ToAsyncEnumerable's order and laziness. Tests only; no production change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoAdd direct tests for SdCardTextLineReader byte counting and source lease release
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can switch off images and animations for a plain-text comment |
|
Qodo-clean, CI green — ready for review. Independently re-verified on head
Separately, the |
The coverage-gap maintenance routine produced this. It is safe because it adds one new test file and changes no production code at all —
git diffagainst main touches nothing undersrc/Daqifi.Core/.What was wrong
SdCardTextLineReaderis the line pump that every line of a downloaded CSV or JSON log passes through on its way to a parser, and it had no tests of its own. Each line it hands over carries a running byte count, and that count is what a caller turns into a download progress bar — so if it drifts, a user watching a large log parse sees a bar that stalls, jumps, or finishes early, and nothing in the suite notices.Nothing noticed because the CSV and JSON parser fixtures reach this class only along one narrow path: a small, seekable
MemoryStreampositioned at byte zero, enumerated once, asserted on by the samples that come out the far end. They never read the byte count at all, and they never take the forward-only branch. That left the interesting half of the class unpinned:Position; a forward-only one reports an estimate built from line lengths. Either branch could be deleted and every existing test would still pass.How it was fixed
Adds
SdCardTextLineReaderTests.cs— 13 cases over the twoReadLinesAsyncoverloads andToAsyncEnumerable. Tests only, no production change; the reader behaves correctly on every edge probed, so no bug was found and no issue opened.The byte-count cases are built so the two branches produce visibly different numbers. A 20-byte payload fits in one
StreamReaderbuffer, so a seekable stream is already at EOF when the first line arrives and all three lines report20; the same payload read forward-only reports6, 12, 20. Neither branch can be substituted for the other without a failure.Mutation evidence — eleven mutations of
SdCardTextLineReader.cs, each applied from a pristine copy and reverted after. All 13 cases die in at least one:CountsSkippedBlankLinesTowardTheEstimateestimatedBytes += line.Length(drop the terminator)CanSeekReportsTheStreamsRealPositionNotTheEstimatestream.Position, ignoreCanSeekDropsBlankAndWhitespaceOnlyLines,OnAStreamOfNothingButBlankLines,CountsSkippedBlankLines…leaveOpen: falseLeavesTheCallersStreamOpen+ both lease casesStartsAtTheStreamsCurrentPositionNotAtZeroctfromReadLineAsyncWithAnAlreadyCancelledToken_Throwsawait usingReleasesTheLease…casesToAsyncEnumerablewalks its source eagerlyDoesNotTouchTheSourceUntilItIsEnumeratedToAsyncEnumerablereverses its sourcePreservesOrderAndContentWhat a reviewer might push back on
Two cases were written and then deliberately removed rather than shipped, because neither could be made to fail:
Assert.Empty) survived all eleven mutations — there is no realistic way to break the reader that makes a zero-byte stream yield a line, so it was carrying no information. The blank-lines-only case covers the same "yields nothing" shape and is killed by mutation E.SdCardParseSource.Open()'s contract rather than this class's — already pinned bySdCardParseSourceTestsfrom test(sdcard): direct tests for SdCardParseSource's rewind, lease latch and stream ownership #654.The seekable-stream case does depend on the whole payload fitting in one
StreamReaderbuffer. That is stated in a comment on the test; the assertion is on the reader's contract (report the stream's real position), and the buffering only determines what that position happens to be.Verification: full solution suite green under
-warnaserroron net9.0 and net10.0 — 3915 passed / 2 skipped (hardware) on each, plus 217 inDaqifi.Mcp.Tests. Zero warnings. Bench validation does not apply and was not skipped silently: this PR changes no production code and performs no device interaction.Not merging — for review.