test(discovery): the code that decides which Linux serial ports are DAQiFi had no tests - #529
Conversation
…ider LinuxUsbPortDescriptorProvider had no tests at all, which is not incidental: CI runs on ubuntu-latest, so it is the descriptor provider that actually executes there, and it is what pre-filters serial ports for every Linux consumer of SerialDeviceFinder. A wrong answer is silent — a DAQiFi board stops being discovered, or every unrelated port gets probed again. The walk is testable with canned fixtures, exactly as #464 asks for, but it hardcoded /sys/class/tty. Split the platform gate (GetDescriptor) from the walk (Resolve), which now takes the tty class root as a parameter, mirroring MacOsUsbPortDescriptorProvider.Parse being exposed for the same reason. The loop bound and the real root become named constants. No behavior change: for a base name with no separators, Path.Combine builds the same path the string interpolation did. 35 tests over a fixture tty tree with real symlinks, plus the two provider factories, which also had none. 10 of 12 mutants killed; the two survivors are equivalent (NumberStyles.HexNumber already allows leading/trailing whitespace, so the .Trim() is redundant defensive code). Part of #464 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoAdd fixture-based tests for Linux sysfs USB port descriptor resolution
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
…the symlink The fixtures were inconsistent about what a missing directory-symlink privilege means: the helper documented degrading on Windows, while the caller every test used hard-asserted that the link was created. So on Windows most of the class failed rather than doing either thing cleanly. Fixed at the root instead of at the assert. Only four tests are actually about the link — the realistic sysfs shape, the physical-vs-logical parent chain, and the two depth bounds. Everything else is about the walk and the parsing, and the provider treats a real `device` directory as the plain case, so those fixtures now build one. The Windows exception filter is gone with the branch it guarded; the four remaining symlink tests create the link and let a failure be a failure. Mutation coverage is unchanged: 10 of 12 mutants still killed, including "ignore the resolved symlink target", which the four link tests catch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 0a7d290 |
|
Note for the next review round: the summary's remaining |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 0a7d290 |
The kernel spells a USB interface node "1-1.2:1.0", and the sysfs-shape fixture copied that verbatim. ':' is not a legal path segment character on Windows, so Directory.CreateDirectory threw during setup there — before the test could reach anything it was actually about. Nothing in the provider parses a node's name, so the segment's spelling is decoration; the fixture now uses "1-1.2_1.0" and says why. Reported by Qodo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 26582e1 |
|
Qodo-clean, CI green — ready for review. 4 rounds, ending on head
Full suite green on net9.0 (3320 Core + 192 Mcp) and net10.0 (3320 Core) after every push, release build 0 warnings. Mutation coverage held at 10 of 12 across the restructure, the two survivors being equivalent mutants. |
The PR looks ready from the evidence provided: the reported Windows-invalid fixture issue was addressed, and the symlink dependency is now limited to the four tests explicitly exercising link behavior. The remaining active entry, finding 2, appears stale: it references deleted |
What was wrong
On Linux, before Core opens a serial port to see whether a DAQiFi board is on it, it asks the kernel what USB device that port belongs to — that's how a machine full of Bluetooth radios, GPS receivers and other vendors' adapters doesn't cost a discovery sweep a probe timeout each. That lookup had no tests at all.
It is not incidental code. CI runs on
ubuntu-latest, so this is the provider that actually executes there, and it is the one every Linux user's discovery goes through. When it gets an answer wrong nothing crashes — a board just quietly stops being discovered, or every unrelated port gets probed again and discovery slows to a crawl. Neither shows up as a failure anywhere.How it was fixed
The lookup is a walk up the sysfs device tree, and it was testable in principle — it reads real files — but it hardcoded
/sys/class/tty, so there was no way to point it at anything but the live machine. The walk is now split from the "am I on Linux?" gate and takes the tty class directory as a parameter, which is the same seamMacOsUsbPortDescriptorProvider.Parsealready has for the same reason. Behaviour is unchanged: for a bare port name,Path.Combinebuilds exactly the path the old string interpolation did.On top of that seam, 35 tests drive it against a fixture tty tree with real symlinks, covering the parts that are easy to get wrong and impossible to notice: that the
deviceentry is a symlink which has to be resolved before walking (walking the logical path climbs back into the class directory and finds nothing), that a node needs bothidVendorandidProductbefore it counts, that unparseable values mean "unknown" rather than "keep climbing and report the hub's IDs", and the exact depth the walk stops at. The two platform provider factories, which also had no tests, get four more.One thing a reviewer may want to push back on: the fixtures create real directory symlinks. That works on Linux and macOS; on Windows, where an unprivileged process cannot create one, those specific tests return early rather than fail. Windows is not in CI, and the non-symlink tests still cover the walk itself.
Verification
NumberStyles.HexNumberalready impliesAllowLeadingWhite | AllowTrailingWhite, so the.Trim()on each value is redundant for anything sysfs emits.Part of #464 (the discovery-provider slice; the Windows half of it is PR #515, which is why this stays clear of those files).
Not merging — for review.