Skip to content

test(hl2): name the fault, bound the envelope read. Principle VIII. - #6377

Merged
rfoust merged 2 commits into
aethersdr:mainfrom
on8st:test/hl2-tx-passband-onset-nits
Oct 12, 2026
Merged

rfoust merged 2 commits into
aethersdr:mainfrom
on8st:test/hl2-tx-passband-onset-nits

Conversation

@on8st

@on8st on8st commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Refs #6347

Answers the two non-blocking notes left on the approving review of #6347.
tests/hl2_tx_passband_onset_test.cpp only.

Limits

  • Test only. No product code, no tolerance and no bound changes; the
    measured figures are the same before and after.
  • Neither path occurs on a healthy run. Both were reached by scratch
    variants of the test that are not in this PR.
  • AddressSanitizer instrumented the test's own translation unit only, not
    the library it links. The out-of-range read it reports is a BUS on a
    wild address, not a redzone hit.
  • Built and run on one Mac (macOS, Qt 6.12.0), TXA build.

Change

  1. run(): when the modulator reports a fault that is not a stall drop,
    the check failed with ": no fault but a stall drop". The FAIL line now
    states the expectation that broke: "every modulator fault is a stall
    drop", as the comment above run() describes it.
  2. onset(): firstAbove() returns 1e9 when a level is never reached.
    halfAt is then far past the capture, and envDb indexed c.iq out
    of range before checkKeyUp or checkMidOver could fail. envDb now
    returns the -400 dB floor for any index outside [t0, c.iq.size()).
  3. onset(), from this PR's own review: when the 50 % point is never
    reached, leadMs is the 1e9 sentinel too, not 1e9 - 1e9 = 0, and the
    short-capture return sets it as well. So the mid-over lead check fails
    with the others, where it passed for the wrong reason.

Evidence

Four scratch variants, the test file compiled with -fsanitize=address
and linked against the tree's library at 5a8a6eb3:

variant result
before this PR, 50 % level made unreachable AddressSanitizer: BUS on unknown address, in envDb called from onset(); no check ran
this PR, 50 % level made unreachable no sanitizer report; envelope printed as -400.0 dB; 6 checks failed cleanly, exit 1
this PR, one non-stall fault forced FAIL: USB: every modulator fault is a stall drop (and per leg), exit 1
this PR, unmodified all checks passed, no sanitizer report; key-up 50 % at 95.54 ms, rise 14.77 ms; mid-over 50 % at 72.92 ms

Full ctest at e726c404, -j8: 755 of 758 passed, 3 failed, all three
also on the serial rerun. Besides the two below, vkamp_connection_test
failed with only one of its two known checks (#5254).

hl2_tx_passband_onset_test passed in 6.69 s. check_test_registration.py --strict, check_wdsp_test_links.py --strict and
gen_touchpoint_manifest.py --check pass.

73, ON8ST

🤖 Generated with Claude Code

Answers the two non-blocking notes on the approving review of aethersdr#6347.
Test only; no tolerance or bound changes.

run(): the branch taken when the modulator reports a fault that is not
a stall drop failed with ": no fault but a stall drop". The FAIL line
now states the expectation that broke: "every modulator fault is a
stall drop", as the comment above run() describes it.

onset(): firstAbove() returns 1e9 when a level is never reached, which
puts halfAt far past the capture, and envDb then indexed c.iq out of
range before checkKeyUp or checkMidOver could fail. envDb returns the
-400 dB floor for any index outside [t0, c.iq.size()).

Refs aethersdr#6347

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@on8st
on8st requested a review from a team as a code owner October 11, 2026 06:39

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue fit

Yes. No linked issue — this is a Refs #6347 follow-up to review notes, which GOVERNANCE.md does not require an issue for (test-only, clear root cause, no architectural surface). Both stated changes are present and do what the body says, and I could reproduce the out-of-range path by reading: firstAbove() returns the 1e9 sentinel at tests/hl2_tx_passband_onset_test.cpp:273 when a level is never reached, halfAt at :279 becomes t0 + 4.8e10, and the pre-fix at < t0-only guard let that be cast to std::size_t and indexed. The length check at :256 passes first, so nothing caught it.

One claim I could not verify: "answers the two non-blocking notes left on the approving review of #6347." list_issue_comments returns issue comments only, not review bodies, and gh api .../pulls/6347/reviews is credential-blocked in this environment. The #6347 thread I can see carries an earlier round of notes (a)–(e), all of which landed in 9c715b47 on that PR, so the referenced notes are plausibly a later review I can't read. The diff stands on its own either way; the #6347 review thread would settle it.

Scope

File What it changes Claimed? Verdict
tests/hl2_tx_passband_onset_test.cpp:121 check() FAIL message reworded Yes, item 1 In scope
tests/hl2_tx_passband_onset_test.cpp:280-284 envDb upper bound + one comment Yes, item 2 In scope

Everything in the diff is explained by the stated intent. Three insertions, two deletions, one file, no product code, no tolerance or bound constant touched — I checked kMaxKeyUpHalfMs, kMinKeyUpRiseMs, kMaxKeyUpRiseMs, kMaxMidHalfMs, kMaxMidLeadMs, kFlatDb, kEdgeMinDb, kEdgeMaxDb, kStop100Db, kStop150Db and kTones against b3e0302ea; all unchanged. No new public surface, no deleted guard, no CHANGELOG.md entry. No sibling copy of this helper exists to leave behind.

On the harness's 98 CodeGuard findings and the ten-file audit list: nine of those ten files are not in this PR. The scan ran against a checkout whose base lags main by three commits (#6364, #6371, #6372) — which is why SpectrumOverlayMenu, SpectrumWidget and the AGC-T MainWindow hunks appear, and why git diff main --stat on the head reports 34 files. That is base lag, not scope creep. The one file this PR does change carries zero CodeGuard findings. I ran the required security-audit subagent over the full list; it returned no findings, confirming the added bound is complete and classifying all 36 cited lines as false positives — the CMDI-BACKTICK/-PIPE/-AMP hits are lexical matches on Markdown-quoted Flex verbs inside // comments and on C++ reference/&Class::signal tokens, the PII-IP/SSRF-LOCALHOST hits in docs/user/screens.json are RFC 5737 TEST-NET-1 documentation addresses, and screens.json:3877 is you@example.com (RFC 2606) plus a truncated tskey-auth-... format hint in alt text, not a key. Credit: security-audit (CodeGuard). The two real-code hits it did read out — MainWindow.cpp:6296 (QFile statusFile("/proc/self/status"), fixed literal) and the MainWindow.cpp:9920-9922 bundle walk feeding QProcess::startDetached list-args overloads with this process's own argv tail — are both false positives and both untouched here.

Blockers

None.

Nits

  • tests/hl2_tx_passband_onset_test.cpp:276 — a sibling sentinel path still produces a vacuous pass. The PR hardens envDb against firstAbove's 1e9 sentinel, but leadMs propagates the same sentinel into a check that then passes for the wrong reason:

        o.leadMs = o.halfMs - o.firstMs;

    When no level is reached at all, firstMs == halfMs == 1e9, so leadMs == 0.0 and checkMidOver's o.leadMs <= kMaxMidLeadMs passes. The explicit short-capture early return at :257-259 reaches the same state from the other side: it sets o.halfMs = 1e9 and returns with firstMs left at its 0.0 default, so leadMs stays 0.0. In both cases o.halfMs <= kMaxMidHalfMs fails first and the test still exits 1, so this is cosmetic — the FAIL list just reports one fewer broken expectation than is actually broken. Pre-existing from #6347 and in the same sentinel-propagation family this PR is cleaning up, so worth naming; explicitly non-blocking and fine to leave for a later pass.

  • tests/hl2_tx_passband_onset_test.cpp:121 — "clearer", not "corrected". The old ": no fault but a stall drop" was ambiguous rather than inverted: it reads either as "there was no fault, but a stall drop" (wrong) or "no fault except a stall drop" (which is the expectation). The new wording is unambiguous and matches the file's convention, since check() at :35 prints FAIL: %s and every other message in the file is phrased as a positive expectation. Worth a one-word softening in the body's framing; the code change is right.

What I tried to break

  • The sentinel path the fix targets. With halfMs == 1e9, halfAt ≈ t0 + 4.8e10 and every call-site offset (1, 2, 4, 6, 10, 12 ms) lands past size(), so all six float to -400. The || short-circuits before the static_cast<std::size_t>(at), so there is no UB on the conversion itself, not just no bad read. Held.
  • Whether the guard changes any passing figure. It cannot: a finite halfMs comes from the n < c.iq.size() loop at :268-272, and every ms argument is positive, so at < halfAt < size() always and only the pre-existing lower bound is live. Checked numerically too — key-up (t0 = 0, halfAt ≈ 4586, capture 19200) and mid-over (t0 = 24000, halfAt ≈ 27500, capture ~43200) put all six offsets well inside. The body's "measured figures are the same before and after" holds by reading.
  • NaN reaching the cast. It can't: if steady were NaN, level is NaN, every >= in firstAbove is false, and it returns the finite 1e9 sentinel. steady == 0.0 exactly is also safe — level is 0, firstAbove returns 0 at n == t0 for every threshold, so at < t0 floors each read to -400.
  • Sibling unguarded reads of c.iq. bin() at :138 indexes iq[from + n] with no internal bound, but both callers pass from = kSettle with count = ((size - kSettle) / kPeriod) * kPeriod, floor-aligned under size - kSettle and behind the length check at :169. The loops at :262-264 and :268-272 are bounded by c.iq.size() directly. Nothing else computes an index. The audit subagent reached the same conclusion independently.
  • The other sentinel consumer, rise1090Ms at :275. Both-unreachable gives 0.0, failing >= kMinKeyUpRiseMs; upper-only-unreachable gives ~1e9, failing <= kMaxKeyUpRiseMs; the reverse split is impossible because the thresholds are monotone (-20 dB is crossed no later than -0.9151 dB). No vacuous pass there — which is why only leadMs is a nit.
  • The early-return path. Onset default-initializes all four members at :230-233, so checkKeyUp/checkMidOver read no uninitialized doubles after :257-259. No UB; see the nit for the cosmetic half.
  • Socket tests: none. This PR adds and modifies no socket, no fake peer, no bind/listen/connectToHost; I grepped the changed file and it has none. Nothing to record, and nothing removed either.
  • Could not check: anything requiring a build or a run. All five checks are green on 5a8a6eb, but note hl2_tx_passband_onset_test appears in no ctest -R filter in ci.yml — PR CI compiles it and never executes it, as the #6347 thread already acknowledged. So green here proves the file still builds on all three platforms, nothing more; the behavioural evidence is the author's local macOS/TXA run and their four ASan scratch variants, which I take at face value and did not reproduce. Every finding above is reasoned from the code, not observed at runtime.

Recommendation

Approve with nits. This is the rare follow-up that is strictly smaller than its justification: three lines, test-only, and the one substantive change converts an out-of-range read into a documented floor without moving any measured figure or weakening any check — the failure it unmasks (halfMs <= 106) now gets to fail cleanly instead of being pre-empted by UB. The disclosure discipline in the body is good, including the ASan caveat that only the test's own TU was instrumented. Next step is a maintainer read of the leadMs nit: either fold a one-line guard into this PR or let it ride as a known cosmetic gap from #6347, since the test exits 1 regardless. Thanks for chasing the review notes down to the sentinel rather than just reworading the message.


🤖 aethersdr-agent · cost: $11.3078 · model: claude-opus-5

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (103 finding(s))

  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in docs/architecture/aetherd-touchpoint-tags.json /tmp/aetherclaude/pr-6377/docs/architecture/aetherd-touchpoint-tags.json:10
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in docs/architecture/aetherd-touchpoint-tags.json /tmp/aetherclaude/pr-6377/docs/architecture/aetherd-touchpoint-tags.json:1145
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:2579
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:4219
  • [MEDIUM] CS-PII-EMAIL — PII detected: email in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:3877
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:1122
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:2579
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:2625
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:2659
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:2756
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:2796
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6377/docs/user/screens.json:4219
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:9686
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:9798
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:9920
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:9921
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:9922
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:6217
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:6296
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:526
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:556
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:559
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:581
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:3660
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:1280
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:1804
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:1896
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:1973
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:2015
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:2050
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:2345
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:3602
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:3693
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:6159
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:7965
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:8030
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:8104
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:8189
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:8370
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6377/src/gui/MainWindow.cpp:8573

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: $12.0558 · model: claude-opus-5

Review of aethersdr#6377: when no level is reached, firstMs and halfMs are both
the 1e9 sentinel, so leadMs was 0 and the mid-over lead check passed for
the wrong reason; the short-capture return left it at 0 as well. Both
now report the sentinel. The test exited 1 either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@on8st

on8st commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Fixed in e726c404, a new commit on top (no rebase).

  • leadMs: you are right, it passed for the wrong reason. When the 50 % point is never reached it is now the 1e9 sentinel too, and the short-capture return sets it as well. So the mid-over lead check fails together with the others.
  • "Clearer", not "corrected": agreed. The body never called the old text wrong, and it now lists this third change.

The two notes this PR answers are jensenpat's inline comments on the approving review of #6347, which your tooling could not read:

tests/hl2_tx_passband_onset_test.cpp:121: This branch is faults > stallDrops (a fault that is not a stall drop), but the message says "no fault but a stall drop". A future red will point at the wrong cause.

:284: firstAbove returns 1e9 when the level is never reached, and halfAt is then far past c.iq.size(). […] On the miss path this diagnostic reads off the end before checkKeyUp can fail cleanly. Clamp at to [t0, c.iq.size()) and return the floor value outside it.

Full ctest at e726c404: 755 of 758. The three that fail are the known ones on this Mac: #6008, #6224 and vkamp_connection_test (#5254, one of its two known checks). This test passes, in 6.88 s.

73, ON8ST

@rfoust rfoust self-assigned this Oct 12, 2026

@rfoust rfoust left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR addresses the two review notes on #6347 and the subsequent leadMs sentinel note. No blockers found; approve with one non-blocking coverage suggestion.

Scope

File/change Claimed? Verdict
tests/hl2_tx_passband_onset_test.cpp: non-stall fault message Yes In scope
Same file: bound envelope reads Yes In scope
Same file: propagate the failure sentinel into leadMs Yes In scope

Everything in the diff is explained by the stated intent. No product-code, measurement-bound, public-surface, or unrelated changes.

Blockers

None.

Non-blocking nit

See the inline comment: retaining socket-free failure-path regression cases would preserve the scratch evidence in an automatically exercised form.

Verification

Reviewed head e726c40 against original base b3e0302 and GitHub merge ba73e3a. Built current main f753dbf plus the PR's sole changed file in an isolated scratch directory using Qt 6.12 and the prescribed ARM64 toolchain, with RADE enabled. Processor settings and test executable are ARM64; no RNNoise x86 sources were present.

Focused hl2_tx_passband_onset_test passed on both the PR version (8.52 s) and current-main baseline (7.34 s). Healthy onset output matched exactly: key-up 95.54 ms, rise 14.77 ms, mid-over 72.92 ms. All five reported CI checks are green; PR CI compiles this target but does not execute it. Main advanced after local validation only in HL2 discovery and its logging test, with no changes to this test or its DSP path.

What I tried to break

  • ASan probes using the actual extracted onset function and injected captures reproduced the baseline out-of-range BUS when the onset is unreachable. The PR returned -400 dB safely, with halfMs and leadMs both 1e9.
  • Short captures left leadMs at zero on the baseline; the PR returned the failure sentinel. Repeated both scenarios with the nonzero mid-over t0 under ASan.
  • A healthy injected capture had identical before/after output; the real DSP test also preserved healthy measurements.
  • Injecting a non-stall fault into the actual extracted run helper produced FAIL: USB: every modulator fault is a stall drop and did not retry.

The ASan probes exercise extracted functions, not a fully sanitizer-instrumented DSP library. No GUI instance was needed for this test-only change, and no hardware or live TX was used. A separate automated review invocation was unavailable.

Comment thread tests/hl2_tx_passband_onset_test.cpp
@rfoust
rfoust merged commit 4785024 into aethersdr:main Oct 12, 2026
5 checks passed
rfoust pushed a commit that referenced this pull request Oct 12, 2026
…6404)

Refs #6377

Answers rfoust's optional note on the approving review of #6377,
`tests/hl2_tx_passband_onset_test.cpp:286`:

> the unreachable-onset and short-capture paths are demonstrated by
scratch
> variants, but remain absent from committed regression coverage.
Consider
> retaining socket-free injected-capture cases so removing this bound or
the
> leadMs sentinel guard fails automatically.

## Limits

- Test only, one file. No product code, no tolerance and no bound
changes.
- Built and run on one Mac (macOS, Qt 6.12.0), TXA build. Per-PR CI
compiles
  this test but does not run it.
- "A level is never reached" is injected as a capture whose settled
level is
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.
- **Found while writing these cases, and fixed in the second commit
(`683e6302`):** a silent capture is not that path either. Its settled
level
is 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.
- Without the envelope-read bound the test fails by crashing, not by a
FAIL
  line: the read is at a wild address.
- AddressSanitizer instrumented the test's own translation unit only,
not
  the library it links.

## Change

1. `onset()` is split. `analyseOnset()` reads an IQ vector and a `t0`;
`onset()` runs the modulator and hands it the capture. The short-capture
return stays in the analysis and sets a `tooShort` flag; `onset()` turns
   that into the failed check it was.
2. The four onset bounds are predicates, so a case can assert that a
result
fails them without failing the test. `checkKeyUp` and `checkMidOver`
call
   them with the same messages.
3. `checkInjectedCaptures()` runs with no modulator, at the key-up `t0`
(0)
   and at the mid-over `t0`:
   - a step, as the control: the 50 % point reads 10 ms and passes;
- a level never reached: `halfMs` and `leadMs` are the 1e9 sentinel, all
     six envelope reads are the -400 dB floor, all four bounds fail;
- a capture one sample too short: `halfMs` and `leadMs` are 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 `bbded449` and at this
head is
identical line for line (121 lines), exit 0 both times.

| | key-up 50 % | key-up rise 10-90 % | mid-over 50 % |
|---|---|---|---|
| before (`bbded449`) | 95.54 ms | 14.77 ms | 72.92 ms |
| this PR | 95.54 ms | 14.77 ms | 72.92 ms |

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:

| mutation | result |
|---|---|
| none | all checks passed, exit 0; under AddressSanitizer too, no
report |
| upper bound dropped from the envelope read | crash (SIGSEGV) in the
first never-reached case, no check after it runs; under AddressSanitizer
`BUS on unknown address` in the envelope read inside `analyseOnset` |
| `leadMs = halfMs - firstMs` again | 4 checks failed, exit 1: "level
never reached: halfMs and leadMs are the sentinel" and "every onset
bound fails", at both `t0` |
| `o.leadMs = 1e9` dropped from the short-capture return | 4 checks
failed, exit 1: "too short: halfMs and leadMs are the sentinel" and
"every onset bound fails", at both `t0` |

From this PR's review (`46d88876`): `rise1090Ms` is the sentinel on
every
failure 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: the
`nr_stereo_independence_*` set), 2 failed:

- `vkamp_connection_test`: failed at `-j8`, passed on the serial rerun
  (#5254).
- `peripheral_auth_dialog_interaction_test`: "dialog 960x680, viewport
658,
page minimum 784" (#6008); fails on clean main on this Mac, and this
diff
  touches no dialog file.

`hl2_tx_passband_onset_test` passed in 6.82 s.
`check_test_registration.py
--strict`, `check_wdsp_test_links.py --strict` and
`gen_touchpoint_manifest.py --check` pass.

73, ON8ST

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants