test(sdcard): the SD card block had no tests that could see inside a text exchange - #528
Conversation
SdCardOperations is 1,357 lines and every test of it ran through the DaqifiStreamingDevice facade it was extracted from (#344). That covers what lands on the wire, but the real text-exchange engine is opaque to it: how many exchanges an operation performs, whether the shared-SPI-bus switch is handed over as the exchange's prepare phase or run inline around it, and what happens to the restore when the link drops mid-operation are all invisible at that distance. Adds 57 tests that drive the collaborator directly through a fake IDeviceOperationHost, which reproduces the engine's prepare / setup / finalize phase order and throws on every member outside the SD block's remit. The existing facade tests are untouched. What they pin that the facade could not: - The retry budget, and which failures are deliberately NOT retried — a "No SD Card Detected" storage reply is non-transient and must cost one exchange, not two. - The bus switch and restore are the exchange's prepare/finalize phases (#407), paired per exchange so the gap between retry attempts is never one in which the device sits switched to the card. - A device that drops mid-exchange gets no restore written at it. - The listing asks for the 1s completion window, not the 250ms default. - Terminator splitting scans from the end, so a stale terminator leading the response cannot swallow the listing behind it (#396). - The single-download gate refuses a second reader on a transport an earlier transfer still owns (#399), and an abandoned transfer skips the LAN restore for the same reason (#399/#401). - A file the listing reports as 0 bytes downloads as a legitimate empty file, while an ambiguous duplicate name falls back to "size unknown" and re-issues the GET (#398 gap 2). Eight mutations confirm the tests bite: scanning the terminator from the start (2 failures), retrying the no-card marker (1), restoring LAN after the link dropped (1), restoring onto an abandoned worker's transport (1), re-enabling LAN over WiFi (1), dropping the retry budget to zero (9), running the SPI switch inline instead of as the prepare phase (2), and ignoring the listed file size (1). Part of #464 (slice 4). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoAdd collaborator-level tests for SdCardOperations text-exchange behavior
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
Code Review by Qodo
1.
|
Both park a transfer on a stream that deliberately ignores its cancellation token — the stand-in for native I/O nothing can interrupt. That is the case the production hard deadline exists to bound, so a regression in the deadline meant these tests would hang the run rather than fail it. Each await that could outlive a regression now carries an explicit upper bound. The abandon test's bound is a cancellation token rather than WaitAsync(TimeSpan): the TimeSpan overload reports a TimeoutException, which is the very exception that test asserts, so a hang would have passed as a success. A cancelled token surfaces as TaskCanceledException instead. Verified by mutation: pushing the hard deadline a day into the future now fails the test in 30s with "Expected TimeoutException, actual TaskCanceledException" instead of hanging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit a02075f |
The bound is a hang guard, not a performance assertion, so it was set far above what these paths take. But it is used by four awaits across two tests, so a regression could burn most of a minute before failing — slow feedback on exactly the case the guard exists to report. 10s keeps roughly thirty times the headroom over the longest thing under it (the ~300ms hard deadline the abandon test asserts; everything else is immediate) while cutting worst-case failure feedback to seconds. The two tests together run in ~430ms, measured stable over six consecutive runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit f6c3243 |
|
Qodo-clean, CI green — ready for review. 3 rounds on head
Full suite green on net9.0 (3342 Core + 192 Mcp) and net10.0 (3342), Release build 0 warnings. No bench validation — test-only change, and no board is attached to this session. |
What was wrong
The SD card block is the biggest single piece of device logic in Core — listing, downloading, deleting, logging, the shared-SPI-bus handover — and nothing tested it on its own. Every test of it ran through the device class it was split out of, which means every test watched it from behind the real text-exchange machinery. From there you can see which commands went out, but not the things that actually go wrong on a real card: how many times an operation retried, whether the bus switch was performed as part of the exchange or loosely around it, whether a device that dropped mid-operation still got commands written at it, or whether a download that timed out left its worker holding the transport. Those behaviours were all built deliberately, in response to specific field failures, and none of them had a test that would notice if they were undone.
How it was fixed
57 tests that drive the SD block directly, through a stand-in device that records everything it is asked to do and refuses anything outside the block's remit. The stand-in reproduces the real exchange's phase order, so a test can now assert that the SPI-bus switch happens inside the exchange, that each attempt hands the bus back before the retry delay, and that a device which drops mid-listing gets no restore written at it. On the download path, tests hold one transfer open to prove a second is refused rather than allowed onto the same stream, and park a transfer where no cancellation can reach it to prove that when it's abandoned Core stops writing to that link entirely.
Worth pushing back on if you disagree: this adds a second test file for the same type rather than reorganising the existing one.
SdCardOperationsTests.csis 131 tests of end-to-end behaviour through the device, and it is the evidence that the extraction changed nothing — rewriting it to reach the collaborator would throw that evidence away to save a filename. It is untouched; the new file says in its header why it exists beside it.Verification
Full suite green on net9.0 (3342 Core + 192 Mcp) and net10.0 (3342), Release build with 0 warnings. Eight mutations of the production code confirm the tests bite rather than merely execute: scanning the listing terminator from the start instead of the end (2 failures), retrying the "no SD card" reply as if it were transient (1), restoring the LAN interface after the link already dropped (1), restoring it onto an abandoned worker's transport (1), re-enabling LAN over a WiFi connection (1), dropping the retry budget to zero (9), running the SPI switch inline instead of as the exchange's prepare phase (2), and ignoring the listed file size when judging an empty transfer (1). The new tests were also run 8 times consecutively to check the two timing-sensitive download tests are stable.
No bench validation: this is a test-only change and no board is connected to this session.
Part of
#464— slice 4 of the four the issue lists, after#522(WiFi module updater) and#527(PIC32 updater/bootloader).Not merging — for review.