Repository navigation
test(seam): pin the three channels out of sendPanDimensionsToRadio() - #5970
Conversation
There was a problem hiding this comment.
Issue fit
No linked issue, and none is needed: this is the non-blocking follow-up @jensenpat asked for on #5920, which the author agreed to land separately (#5920 comment, 14:53 today). GOVERNANCE.md wants an RFC for architectural change and an issue for bug fixes; a test-only PR pinning an existing seam is neither. The stated goal — pin Flex wire delivery, ANAN typed dispatch and Icom's no-command path at the GUI/model seam — is what the diff does, and the header's central claim is one I could check and confirm: shapesDisplayRatesLocally() is m_backend != nullptr && m_flexBackend == nullptr (RadioModel.h:1563), only AnanBackend overrides setPanPixelWidth (IRadioBackend.h:451 is a no-op default), so Icom does accept the typed call and eat it in the default no-op. The account given twice in #5920's thread ("Icom returns false from the seam … and is then stopped by your guard") was wrong, and this test's assertion is right.
Scope
| File | What it changes | Claimed by the title/body? | Verdict |
|---|---|---|---|
tests/pan_dimension_routing_test.cpp |
New socket-free test: Part A reads the two call-site predicates per family through the production family switch; Part B reads the call site as text | Yes | In scope |
tests/tests.cmake (+16) |
Registers pan_dimension_routing_test, AETHER_SOURCE_DIR define, comment |
Yes | In scope |
Everything in the diff is explained by the stated intent. No product code touched, no CHANGELOG.md entry, no ci.yml step (correct — AGENTS.md:253 forbids one; the unconditional add_executable/add_test means full-suite.yml and sanitizers.yml pick it up from declaration). No AETHER_SETTINGS_CONSUMERS entry, which matches the sibling backend_family_switch_test with the identical link line. No new public surface, no deletions, no UI or default changed — §6 preference check is trivially clean.
Socket-test disclosure. The PR adds no socket test. Part A does construct the real FlexBackend and SimBackend, which construct a RadioConnection and a PanadapterStream on worker threads (FlexBackend.cpp:45-57), and PanadapterStream::init() constructs a QUdpSocket (PanadapterStream.cpp:95-99). Nothing binds, listens or connects — the socket is an inert object until a bind path runs — and this is the same exposure the already-registered backend_family_switch_test has. Recording it rather than calling it a finding.
Blockers
None.
Nits (non-blocking)
- The
&& hasCommandPlane()conjunct is not falsifiable by any row in Part A, and Part B pins it in place anyway. Details and a suggested wording in the inline comment on line 385. Short version: for anan/hl2/icom the seam short-circuits first so the guard never runs, and for flex/simFlexBackend'sRadioConnectionis created in the constructor and cleared only in the destructor (FlexBackend.cpp:54,:103), sohasCommandPlane()is always true when it is reached. The one state where the guard actually blocks a send ism_backend == nullptr, androutingFor()returns early on exactly that state. - No
profile.isValid()check (line 355).TestSettingsProfile's constructor early-returns having redirected nothing if theQTemporaryDirfails, and Part A reachesAppSettings(PanadapterStream.cpp:113), so the failure mode is "runs against the real settings store" rather than "fails". 48 of the 71 tests using the profile guard it, includingbackend_family_switch_test. - The scanner does not know raw string literals. A future
R"(...)"with an unbalanced brace in that function makesfunctionBody()return empty and the test FAIL rather than pass vacuously, so this is fail-loud and fine as-is — worth one line next to the state enum (line 206) so the next reader doesn't have to derive it. Relatedly,kSeamFirstGuardrequires thesendCommandto be the guarded block's first statement, so adding aqCDebuginside that block fails a test that isn't about behaviour. That brittleness is inherent to the approach the file already discloses; just naming the specific edge.
What I tried to break
Findings below are reasoned from code and from re-implementing the test's own logic — I have no build and ran nothing from this PR.
- "Part B could report health because it never matches anything." Re-implemented
functionBody()(comment/string/char states, brace depth,simplified()) and all four regexes in Python and ran them against the realsrc/gui/MainWindow_Wiring.cppat this head: body scanned to the true closing brace at line 2882 with depth 0, andseamFirst,wireSpelling,xpixFromDeviceWidth,ypixFromDeviceHeightall true withseamCalls == planeChecks == wireSends == 1. Then ran the same analyser overkRealShapeand all nine controls: reference healthy, all nine mutations rejected (eachfromstring applies), and the no-function source reads unparsed. The two traps work — the}in the comment and the"} {"literal neither end the body early nor add a phantom send. Part B is neither vacuous nor a spelling checker. - "The test won't compile / the mock is stale."
IRadioBackendhas exactly 11 pure virtuals (capabilities,connectRadio,disconnectRadio,isConnected, four slice setters,setPanCenter,setKeying,invokeExtension);WidthRecordingBackendoverrides all 11 and nothing else. - "Part A's five-family table is asserted, not measured." Walked
makeBackend()andsetupBackend()(RadioModel.cpp:775-960): flex →m_connection = flex->connection()andm_flexBackend = flex; sim →m_connectionset,m_flexBackend = nullptr; anan/hl2/icom → pure seam backends, neither set, andteardownBackend()nullsm_connectionat:2036before the rebuild. Every row follows. - "Calling a real backend's seam unconnected will crash or write to hardware."
AnanBackend::setPanPixelWidthignorespanId, returns onpixels < 2, and null-checksm_dspbefore the queued invoke — safe with no device. HL2 and Icom hit the interface's no-op. - "The pan-id translation check compares a value to itself or asserts a one-way map."
neutralPanIndexFor()fillsm_backendPanIdByIndexalongside the forward map (RadioModel.cpp:10345-10371), andbackendPanIdFor()reads the inverse, sorecorder->panIds.value(0) == "test-2"is a real round trip, not an echo. - "The PR fixed one of several
xpixels=senders." Two siblings exist —RadioModel.cpp:6021andMainWindow.cpp:11211, bothxpixels=1024 ypixels=700. Both are insidedisplay panafall createreply callbacks, andcreatePanadapter()returns early for any non-Flex backend atRadioModel.cpp:5992, so neither is reachable on a family that lacks a command plane. No unfixed sibling; Part B's "exactly onexpixels=send" is correctly scoped to the function. - Green CI is real but narrow. All five checks pass on
a659be4(build, check-macos, check-windows, Static checks, sanitizer configure). None of them runs this test — the per-PR gate is the frozenci-test-gate.txtallow-list — so "it compiles and links" is what green proves here. Part A's runtime behaviour (five real backend construct/teardown cycles per process, including two with worker threads) is unexercised untilfull-suite.ymlruns post-merge; I could not check it, and the mutation runs behind Part A listed in the header are the author's, not reproduced by me.
Recommendation
Approve with nits. This is the rare source-text test that earns its keep: I reproduced its analyser independently and it both accepts the real call site and rejects every one of nine mutations, the Icom row corrects a factual error that shipped twice in #5920's discussion and that I confirmed was an error, and the scope is two test files with nothing smuggled. The three nits are all small and none needs a re-review — the one worth acting on before merge is the line-385 wording, because as written the test teaches a reader that the command-plane guard protects Icom when the seam is what actually does it.
🤖 aethersdr-agent · cost: $12.4234 · model: claude-opus-5
… Principle VIII. aethersdr#5920's review asked for a socket-free routing test covering Flex wire delivery, ANAN typed dispatch and Icom's no-command path. The same review also had to correct the PR's own account of the Icom path: shapesDisplayRatesLocally() covers every non-Flex backend, so Icom ACCEPTS the typed width and eats it in the default no-op rather than declining the seam and being stopped by hasCommandPlane(). The PR discussion said the latter twice and nothing in the build noticed, which is the gap this closes. Two halves, because the decision cannot be called from a test. It lives in a MainWindow method no test target constructs, and it cannot move down into RadioModel: check_command_plane.py is a per-file SHRINK-ONLY ratchet with src/models/RadioModel.cpp frozen at its baseline, so relocating one sendCommand() there fails it. Part A builds every family through the production family switch and reads the two predicates that call site consults, with no socket, no device and no radio. Part B reads the call site itself as text. The composition is what makes it a proof rather than two half-measures. ANAN has NO command plane, which Part A measures; tidying the condition into `hasCommandPlane() && !requestLocalPanPixelWidth(...)` reads identically, satisfies every stated requirement of aethersdr#4448, short-circuits before the seam call, and silently stops the width reaching the analyzer. Part B pins the order that prevents it. Seven mutation runs against real production source, one behind each behavioural assertion; the nine text-analyser controls run on every invocation. Three checks are labelled controls rather than claims, because they exist only so a vacuous run cannot read as a healthy one. No production file changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a659be4 to
3ff7a7b
Compare
|
All three nits are fixed. 1. The Not via The Icom row is reworded as you suggested as well, so it no longer credits the One correction to the PR body: its mutation table says forcing 2. 3. Raw string literals. Recorded next to the state enum, including why the Checks on this head: 37/37 in the test itself, full local |
rfoust
left a comment
There was a problem hiding this comment.
Issue fit
No linked issue. This is the requested test-only follow-up to #5920. The current head addresses all three previous review nits: it measures the no-backend state, checks settings-profile creation before accessing settings, and documents the text scanner's limitations. It pins the existing Flex fallback, ANAN typed route, Icom no-op acceptance, HL2 and Sim paths, backend pan-id translation, and empty-id refusal.
Scope
| File | Change | Claimed? | Verdict |
|---|---|---|---|
tests/pan_dimension_routing_test.cpp |
Socket-free behavioral coverage and guarded source-shape checks with mutation controls | Yes | In scope |
tests/tests.cmake |
Register and link the test; supply source root | Yes | In scope |
Everything in the diff follows the stated intent. No production, UI, default, protocol, or changelog changes. Corrected the PR body's stale mutation count to include the fourth no-plane assertion, as the author's follow-up already explains.
Blockers
None.
Nits (non-blocking)
No outstanding actionable nits. Source-shape coverage is deliberately brittle and is not a C++ parser; it complements the backend behavior checks rather than proving rendered spectrum quality or live firmware convergence.
Verification
Fresh native RelWithDebInfo build on the clean merge of main 0817cec57a0195fb9d1d24d129e0baec6dc0f134 and PR head 3ff7a7bd1daac5a79b22c4d950048e0804d53218 (tree 16796c5a59689b7145093d412d1234b39f8290cf). Revalidated after both #6030 and #5992 advanced main.
- Pinned Qt 6.12.0, package revision 202609280346. This Mac's Homebrew qtkeychain carries an absolute Qt 6.11 dependency; a process-local
DYLD_FRAMEWORK_PATHpointed at the pinned kit resolves it. A dyld trace confirmed exactly one QtCore, from 6.12.0, and the final tests emitted no duplicate-class warnings. Shared libraries were untouched. - Focused CTest selection:
pan_dimension_routing_test,backend_family_switch_test, andspectrum_preview_logic_test: 3/3 passed, 4.42 seconds with single-Qt linkage. - The new test passes 33 assertions, including all nine built-in mutation controls. Reversed the two predicates in the actual production GUI call site: the test failed at its seam-first assertion (CTest exit 8). Restored the exact source, reran green, and verified a clean diff against the integration tree.
- Strict test registration, engine-boundary, and command-plane checks passed;
git diff --checkclean. No boundary baseline increase. - Required Static checks and Linux/macOS/Windows CI pass on the exact PR head. Their older base is covered by the fresh integration test above; post-merge CI remains independent verification.
Reviewed the issue-fit and lifecycle/failure paths independently. Inspected real backend constructors: they allocate inert socket objects but this test never binds, listens, connects, or addresses hardware. No synthetic firmware peer or live radio action. The test profile guards settings isolation. An automated code-review skill was unavailable; this was a manual adversarial pass.
Recommendation
Approve. The earlier concerns are addressed and the scoped coverage survives fresh integration testing. This approval applies to 3ff7a7bd1daac5a79b22c4d950048e0804d53218.
Summary
The non-blocking follow-up @jensenpat asked for on #5920: "a socket-free routing
test could pin Flex wire delivery, ANAN typed dispatch, and Icom's no-command
path at the shared GUI/model seam." Tests only — no file under
src/changes.It also closes the gap that review exposed twice. #5920's own description said
Icom declines the typed call and is then stopped by the
hasCommandPlane()guard; the approving review corrected it —
shapesDisplayRatesLocally()coversevery non-Flex backend, so Icom accepts the typed width and eats it in the
default no-op. The behaviour was right and the story was wrong, and nothing in
the build could tell. Now one assertion does.
Two halves, because the decision cannot be called from a test. It lives in
MainWindow::sendPanDimensionsToRadio(), and no test target in this repoconstructs a
MainWindow. Nor can the block move down intoRadioModel, whichwould have made it directly testable: the fallback is raw Flex wire text and
tools/check_command_plane.pyis a per-file, shrink-only ratchet withsrc/models/RadioModel.cppfrozen at its baseline count, so relocating onesendCommand()there fails the ratchet. That is whatRadioModel::requestLocalPanWeightedAverage's own comment means by "moving it inhere would add a raw command above the seam". So the call site stays where it is
and the test pins it from both sides.
Part A — behaviour. Every family built through the production family switch
(
rebuildBackendForTest()calls the samerebuildBackendForFamily()thatconnectToRadio()calls, minus the dial). No socket, no device, no radio.xpixels=/ypixels=text, exactly as beforePlus the dispatch itself: the point count reaches the backend unchanged, and
addressed with the backend's pan id rather than the model key — a width
addressed
0xe1000000is a width no backend can resolve to a receiver.Part B — the call site, read as text. A brace matcher that knows string
literals, char literals and both comment forms, so a
{in either cannot endthe body early and the block's own comment is not read as code. It pins that the
condition is those two predicates, negated and
&&-joined in that order, thatthe seam gets
panLocalSpectrumPointsFor(sw)while the wire keepspanXpixelsFor(sw)/panYpixelsFor(sw), and that the fallback keeps the Flexspelling and its
.arg()order.The composition is the point, not a workaround for the two halves. A
reviewer tidying the condition into
hasCommandPlane() && !requestLocalPanPixelWidth(...)reads identically andsatisfies every stated requirement of #4448 — and because ANAN has no command
plane, it short-circuits before the seam call and the ANAN width silently stops
reaching the analyzer. The panadapter goes soft again with nothing red. Part A
supplies "ANAN has no plane", Part B supplies "the seam is first", and neither
alone closes it.
Two things a reviewer may want to rule on differently, both deliberate:
The alternative that would let a test call the decision is moving the block
into
RadioModel, which the command-plane ratchet refuses; extracting apredicate helper that
MainWindowthen calls would make the test passthrough a function whose only caller is the thing being tested. The
limitation is real and stated in the file: it is a brace matcher and a
regular expression, not a compiler.
two lines each, and Sim is the one family where both channels are open —
which is the case that makes the ordering requirement general rather than an
ANAN quirk.
Constitution principle honored
Principle VIII — evidence over assertion. A claim made twice in #5920's
discussion, corrected once by a reviewer reading the source, is now a check that
fails if the claim stops being true.
Test plan
Local build passes (
cmake --build build)Full local
ctestCI
Every behavioural assertion has a mutation run behind it, each edit
applied to real production source and rebuilt on its own:
Part B's nine controls run on every invocation — the guard dropped,
the halves swapped, the negation dropped,
&&weakened to||, the seamhanded the raw device width,
xpix/ypixtransposed, the wire fieldsrenamed, the wire fed the crop-widened count, and a second unguarded send
added. Each must be rejected, and a faithful copy of the real shape must
be accepted, so the analyser cannot report health by never matching.
Three checks are labelled controls, not claims, and deliberately have
no mutation behind them: that every family built, that the fixture's two
pan ids actually differ (that one exists so the pan-id assertion is not
comparing a value to itself), and that the source file opened.
Not exercised on a radio, and it should not be: the test opens no socket
and addresses no device. feat(anan): give the panadapter one point per screen pixel #5920's own ANAN-G2 bench results stand.
Checklist
AppSettingscalls. None added.MeterSmoother. No meter UI touched.behaviour changes — one new test target, no
src/edit.🤖 Generated with Claude Code