Repository navigation
test(hl2): onset failure paths on injected captures. Principle VIII. - #6404
Conversation
Answers rfoust's optional note on the approving review of aethersdr#6377 (tests/hl2_tx_passband_onset_test.cpp:286): keep socket-free injected-capture cases in the committed test, so that removing the envelope-read bound or the leadMs sentinel guard fails automatically. onset() is split: analyseOnset() reads an IQ vector and a t0, onset() runs the modulator and hands it the capture. The four onset bounds are predicates, so a case can assert that a result fails them without failing the test. The existing legs measure what they measured. checkInjectedCaptures() runs at the key-up t0 (0) and the mid-over t0: a step as the control, a capture whose settled level is NaN (the only way a level is never reached, since the scan includes the settled tail), and a capture one sample too short to read. Test only. No tolerance and no bound changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A capture whose settled tail is all zero reached every level at its first sample, so the mid-over leg read a 0 ms onset and passed. The analysis returns the sentinel for it, and an injected silent capture pins that every onset bound fails, at both t0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Issue fit
No Fixes/Closes — this is Refs #6377, answering an optional note left on that PR's approving review asking for the unreachable-onset and short-capture paths to be retained as committed regression coverage instead of scratch variants. The diff does exactly that, and adds a third case (silent capture) found while writing them. A test-only follow-up to a merged PR needs no new issue or RFC under GOVERNANCE.md — bug fixes and test additions with a clear root cause explicitly do not. I could not verify the quoted note's attribution: read_issue/list_issue_comments return the bot and author threads on #6377 but not review comments, so the quote itself is unverifiable from here. Its substance matches the leadMs sentinel and envelope-read bound discussed in that thread's visible comments, so it is plausible; worth a glance from whoever wrote it.
Yes, it solves the ask. Each of the three failure paths is now pinned at both t0 values, and the mutation table in the body names which check dies for which mutation — which is the thing that makes "retaining these cases" worth anything.
Scope
| File / group | What it changes | Claimed? | Verdict |
|---|---|---|---|
tests/hl2_tx_passband_onset_test.cpp |
onset() split into analyseOnset(what, iq, t0) + the modulator-running wrapper; the four onset bounds extracted as predicates; checkInjectedCaptures() added; a new steady == 0.0 early return in the analysis; <array> and <limits> includes |
Yes, including the behaviour change | In scope |
Everything in the diff is explained by the stated intent. One thing worth calling out explicitly because it is not pure added coverage: commit 683e6302 changes what the existing test asserts on a real capture. tests/hl2_tx_passband_onset_test.cpp:264-272 adds
if (steady == 0.0) {
o.halfMs = 1e9;
o.leadMs = 1e9;Before this, an all-zero settled tail made every level "reached" at the first sample, read a 0 ms onset, and passed both mid-over bounds — a silent modulator would have slipped through the onset legs. The direction of change is strictly stronger (a silent capture now fails rather than passes), it fires only on an exactly-zero tail, and the body discloses it under Limits. Not a finding; named so a maintainer sees it rather than reading the title's "injected captures" as additive-only.
No CHANGELOG.md entry (correct). No new public surface, no settings, no capability record, no socket or fake-peer anywhere in the file — the subject is Hl2TxDsp driven in-process through the event loop, which is pre-existing. No removed guard: the check(false, "the onset capture is long enough to read") moved from inside the analysis to the caller at :322-324, same message, same failure — it had to move so the injected too-short case can read the sentinel without failing the test.
CodeGuard
Both CRITICALs refuted. tests/hl2_tx_passband_onset_test.cpp:52 and :70 are:
// Run the event loop -- not sleep -- for `ms`, as the live I/O thread does.
// One fresh over: `audio` at 24 kHz, fed in 10 ms deliveries at real time,Pre-existing // comments using Markdown identifier quoting, both outside every hunk in this diff (the hunks are the includes at 14-24 and the body from 231 on) — the usual whole-file-scan artefact. CS-VLN-CMDI-BACKTICK matches Ruby command substitution; C++ has no backtick operator. security-audit (CodeGuard) confirmed: zero matches for system|popen|exec|QProcess|fork|spawn|getenv in the file, and the only outward calls are processEvents, fprintf, sleep_for and the Hl2TxDsp API. It also returned no findings on the memory-safety questions I put to it, independently checking the std::fill range, never.back(), readable - 1, every iq[n] index and the envDb float-to-size_t cast.
Blockers
None.
Nits (non-blocking)
-
tests/hl2_tx_passband_onset_test.cpp:358-360—rise1090Msis the one bound not sentinelled on any failure path, so the!keyUpRiseOk(…)leg of "every onset bound fails" is leaning on a default rather than on the sentinel. On the NaN capture it is1e9 - 1e9 == 0.0; on silent and too-short it is the member default0.0. The assertion holds because0.0 < kMinKeyUpRiseMs, i.e. because the anti-click ramp's lower bound happens to be above zero. The comment// one sample too short to read. Both come back as 1e9 and fail every bound.is accurate about
halfMs/leadMs, but a reader will take "fail every bound" to mean all four fail for the same reason. Either sentinel it alongside the others on both early returns (o.rise1090Ms = 1e9;) or narrow the sentence to name the two fields. Low stakes: if that lower bound ever moved to zero the injected case would fail loudly, not pass silently. -
tests/hl2_tx_passband_onset_test.cpp:414—checkInjectedCaptures()sits after thereturn 77modulator gate at:408-411, so on any build arm wheremodulatorName() != "wdsp-txa"the analysis paths are never exercised, even though none of them runs the modulator. Free coverage to move it above the gate, with the gate then preserving a failure rather than discarding it:checkInjectedCaptures(); // no modulator: valid in every build arm std::fprintf(stderr, "modulator: %s\n", Hl2TxDsp::modulatorName()); if (std::string(Hl2TxDsp::modulatorName()) != "wdsp-txa") { std::fprintf(stderr, "SKIP: these figures are the TXA modulator's\n"); return g_failures == 0 ? 77 : 1; }
Counter-argument for leaving it: the step control asserts
midHalfOk/midLeadOk, which are TXA figures, so there is a weak case for keeping the whole block TXA-scoped. Your call. -
tests/hl2_tx_passband_onset_test.cpp:375— the step case's message says "the 50 % point reads 10 ms and passes", but it asserts onlymidHalfOk/midLeadOk; a step's 10-90 % rise is 0 ms, so it necessarily failskeyUpRiseOk. "passes the mid-over bounds" would read truer on a future red. -
tests/hl2_tx_passband_onset_test.cpp:254-258— thetooShortreturn leavesenvDbat its{}default (six zeros, i.e. 0 dB = full level), where the other two sentinel paths use the-400.0floor. Nothing reads it (the diagnosticfprintfis downstream of the return) and the brief case does not assert it, so this is consistency only.
What I tried to break
Findings below are reasoned from the head checkout at /tmp/aetherclaude/pr-6404, not reproduced at runtime — I have no build and no test run.
- The NaN trick, hard.
std::abs(std::complex<float>{NaN, 0})ishypot(NaN, 0), which is NaN (C99 Annex F: only an infinite argument overrides a NaN), sosteadyis NaN; the NaN sits atreadable - 1, which is≥ settledFrom, so it actually reaches the mean;steady == 0.0is false for NaN so the new early return does not swallow it;x >= NaNis false for every sample so all fourfirstAbovecalls return1e9;halfAt ≈ t0 + 4.8e10puts everyenvDbread pastiq.size()and onto the-400.0floor. The one way this breaks is-ffast-math//fp:fast, which would let a compiler assume no NaNs and optimise the comparisons — I greppedCMakeLists.txtandcmake/forffast-math|fp:fast|ffinite-math|funsafe-math|Ofastand found none, so the case is sound on all three platforms. leadMs == 0.0by luck? No — bit-exact. The step is zero up tot0+480, sofirstAbove(-60.0)andfirstAbove(-6.0206)both land onn == t0+480and produce the identical expressionstatic_cast<double>(480) * kMsPerOut; their difference is exactly zero, not within-epsilon. SimilarlyhalfMsis480 * (1000/48000), within the1e-6tolerance of 10.0 under any rounding.- Every index.
t0 + 480 ≤ readable(which ist0 + 12032) so thestd::fillrange is in bounds at botht0;neverhas ≥ 12032 elements soback()is valid;readable - 1 ≥ 12031cannot wrap;briefis exactly one sample under the:253guard, which is the point;envDb(12.0)on the step att0 == 0goes toat == -96and hits theat < t0branch rather than casting a negative double. Independently confirmed by security-audit. - Would these pass against the unguarded code? I walked each mutation the body claims. Removing the
steady == 0.0return makes the silent capture readhalfMs == 0.0, which failsz.halfMs >= 1e9— the guard is genuinely under test, not asserted to itself. Removing the length guard makesbriefcomputesteady == 0.1andhalfMs == 0.0, failingb.tooShort. Re-derivingleadMs = halfMs - firstMsmakes the NaN case reportleadMs == 0.0, failing both its sentinel and its bounds check. The cases discriminate. - Could the new guard produce a false red on a healthy run? Only on an exactly-zero settled tail, which no working modulator produces; and
analyseOnsetis reached fromonset()alone, so the passband legs (checkShape) are untouched. The body's before/after figures are identical (95.54 / 14.77 / 72.92 ms), which is consistent, though I cannot reproduce them. - Policy and canon. Socket-free (no
QTcpServer/QUdpSocket/bind/listen/connectToHost/Fake*anywhere in the file); no fake radio peer; registered target, present attests/tests.cmake:6063-6067withSKIP_RETURN_CODE 77and no explicitTIMEOUT, so it takes the retrofit default; DSP behaviour is the socket-free-CTest layer under AGENTS.md's test-layer boundary, which is where this sits. - CI state.
get_check_runsonc4dfc61returns zero check runs — none reported at review time, so there is no green to distrust either way. I also grepped.github/andtools/forhl2_tx_passband_onsetand got no hits, which independently confirms the body's claim that per-PR CI compiles this test but never runs it: the new coverage only executes in the full suite.
Recommendation
Approve with nits. Single test file, one row in the scope table, every claim in the body that I could check held up, and the mutation evidence is specific enough to be checkable. The rise1090Ms nit is the only one with any substance and it is cosmetic today. The one thing worth a maintainer's eye rather than mine is the second commit's behaviour change — a silent capture now fails the onset legs where it previously passed mid-over — which is disclosed and strictly stronger, but is a change to an existing assertion rather than new coverage. Next step: land it, or take the three nits in one follow-up commit first; either is fine. Thanks for turning the scratch variants into committed coverage, and for the before/after output comparison — that is the thing that makes a test-infrastructure refactor reviewable.
🤖 aethersdr-agent · cost: $7.9866 · model: claude-opus-5
|
Cisco CodeGuard — static analysis of this PR (2 finding(s))
Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them. 🤖 aethersdr-agent · cost: $8.1439 · model: claude-opus-5 |
…nciple VIII. Review of aethersdr#6404: - rise1090Ms is 1e9 on every failure path, so "every onset bound fails" holds on the sentinel and not on a default of 0 being under the lower bound; the checks assert it. - The injected cases run before the modulator gate, and the gate keeps a failure instead of discarding it. - The step case says it passes the mid-over bounds. - The too-short return fills the envelope with the floor, as the others. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. All four taken, in
The healthy figures are unchanged: key-up 50 % at 95.54 ms, rise 14.77 ms; mid-over 50 % at 72.92 ms. For the maintainer, as you say: the second commit of this PR makes an existing assertion stricter. A silent capture now fails the onset legs, where it passed the mid-over bounds before. Full ctest at 73, ON8ST |
rfoust
left a comment
There was a problem hiding this comment.
Issue fit
Approve. This follows the optional review note on #6377 by committing socket-free injected captures for unreachable and short onsets at both t0 values. The additional silent-capture case catches an all-zero tail that otherwise passed the mid-over bounds.
Scope
| File | Change | Verdict |
|---|---|---|
tests/hl2_tx_passband_onset_test.cpp |
Split capture analysis from the modulator run; add injected failure-path checks and a silent-tail guard | Within the stated scope |
No product code, tolerance, bound, settings, or public-surface change. The stricter silent-capture assertion is disclosed in the PR body.
Blockers and nits
None.
Verification
I reviewed head 46d8887627aac7518a61064ef3ebb26173b89c76 applied cleanly to current main d7a0802cd2e0423fef4c4e8a47908238e6413e56 in an isolated scratch worktree. The ARM64 hl2_tx_passband_onset_test target built and its focused CTest passed. Disabling the silent-tail guard in the scratch copy made the test fail four checks, two at each t0; restoring the source returned the test to passing. I checked the added test for socket ownership and synthetic peers: none. The PR's five reported checks passed on this head, and there are no unresolved review threads. I did not run the full suite or drive the GUI; this change is confined to a test file.
I tried the short-capture boundary, unreachable-level sentinel and envelope floor, zero settled tail, both onset positions, and the non-TXA gate path. The added checks cover these paths without changing the existing passband thresholds. The two automated backtick alerts refer to C++ comments at lines 52 and 70, not shell execution.
Refs #6377
Answers rfoust's optional note on the approving review of #6377,
tests/hl2_tx_passband_onset_test.cpp:286:Limits
this test but does not run it.
NaN. With finite samples it cannot be reached: the scan runs to the end of
the capture, so it includes the settled tail, whose largest sample is at
least its mean.
(
683e6302): a silent capture is not that path either. Its settled levelis 0, so every level was "reached" at the first sample and it read a 0 ms
onset; an all-zero mid-over capture passed both mid-over bounds. The
analysis now returns the sentinel when the settled level is zero, and an
injected silent capture pins that every onset bound fails, at both
t0.The passband legs already failed on a silent modulator; this closes the
onset legs.
line: the read is at a wild address.
the library it links.
Change
onset()is split.analyseOnset()reads an IQ vector and at0;onset()runs the modulator and hands it the capture. The short-capturereturn stays in the analysis and sets a
tooShortflag;onset()turnsthat into the failed check it was.
fails them without failing the test.
checkKeyUpandcheckMidOvercallthem with the same messages.
checkInjectedCaptures()runs with no modulator, at the key-upt0(0)and at the mid-over
t0:halfMsandleadMsare the 1e9 sentinel, allsix envelope reads are the -400 dB floor, all four bounds fail;
halfMsandleadMsare the sentinel,all four bounds fail.
Evidence
The healthy run prints the same before and after: with the 16 lines of the
injected cases left out, the test's output at
bbded449and at this head isidentical line for line (121 lines), exit 0 both times.
bbded449)Mutations, each a scratch build of the test file only, linked against the
tree's library at this head; nothing of them is in this PR:
BUS on unknown addressin the envelope read insideanalyseOnsetleadMs = halfMs - firstMsagaint0o.leadMs = 1e9dropped from the short-capture returnt0From this PR's review (
46d88876):rise1090Msis the sentinel on everyfailure path too, and the checks assert it; the injected cases run before
the modulator gate, so every build runs them, and the gate keeps a failure
instead of skipping over it.
Full ctest at
46d88876,-j8: 770 of 772 passed (4 skipped: thenr_stereo_independence_*set), 2 failed:vkamp_connection_test: failed at-j8, passed on the serial rerun(Replace retained socket fixtures with injected and opt-in coverage #5254).
peripheral_auth_dialog_interaction_test: "dialog 960x680, viewport 658,page minimum 784" (feat(peripherals): authenticate direct TGXL, PGXL, and Antenna Genius connections #6008); fails on clean main on this Mac, and this diff
touches no dialog file.
hl2_tx_passband_onset_testpassed in 6.82 s.check_test_registration.py --strict,check_wdsp_test_links.py --strictandgen_touchpoint_manifest.py --checkpass.73, ON8ST
🤖 Generated with Claude Code