btest: fix bidirectional TCP server tx accounting + gate the client cell on real CHR (JG-31) - #68
Conversation
A `direction=both` TCP session reported totalTxBytes=0 / txAvgBps=0 on the server side despite transmitting the client's receive half (hundreds of MB): the server's bulk-TX loop (tcpTxLoop with sendStatus=true) flushed only its rx into each interval and never its own tx, so the accumulated txBytes never reached the session total. The server-`both` role is the sole sendStatus=true caller, so swapping and recording tx alongside rx in that branch fixes the total with no double-count (TX-only senders use sendStatus=false and are accounted by reportOnlyLoop). Loopback-grounded RED->GREEN: a new TCP-both data-engine test (test/unit/btest-session.test.ts) and an orchestrator-level back-to-back + both-direction anchor (test/unit/btest-command.test.ts) that also confirms sequential sessions on one server do not poison each other. UDP `both` and single-direction TCP were already correct. Refs JG-31 (Track A steps 1-2). The direct client->CHR-server gate and the `btest / client` MATRIX advance to CHR-passed (steps 3-5) remain follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t cell
Adds the direct centrs-client → real RouterOS /tool/bandwidth-server gated test
that was previously deferred future work, closing the last open product-grid
cell. The CHR boots with a host→guest tcp:2000 forward (new `extraPorts` option
on the integration harness, surfacing as `chr.ports.btest`); the centrs client
dials 127.0.0.1:<host port> and proves, on CHR 7.23.1:
- unauthenticated TCP receive (download from the RouterOS server),
- an EC-SRP5 client proof verified by RouterOS's own server verifier
(the half the server cell test could not exercise),
- a wrong-password reject (transport/auth-failed).
TCP only — UDP client→server needs the server's datagrams to traverse SLIRP
back to the host, which the forward does not provide, so it stays
loopback/transitive (as does connection-count > 1 fan-out).
docs/MATRIX.md advances `btest / client` coded → CHR-passed; commands/btest
README + examples and the CHANGELOG reconcile the grounding caveat. Both btest
integration suites verified green on real CHR 7.23.1 (server cell unaffected by
the direction=both tx fix in the parent commit).
Refs JG-31 (Track A steps 3-4). Step 5 (promote `development` into the qa push
gate) is deferred — it depends on Track B's qa active-set policy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughFixes missing server-side TX byte accounting in Changesbtest client CHR-passed and TX accounting fix
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Fixes btest server-side accounting for bidirectional TCP sessions and adds CHR-backed integration coverage that directly validates centrs’s btest client against RouterOS /tool/bandwidth-server, advancing the last open btest / client product-grid cell to CHR-passed.
Changes:
- Fix
direction=bothTCP server TX accounting by recording swapped TX bytes during the server’s status-emitting TX loop. - Add unit coverage for TCP
bothaccounting (including back-to-back sessions) and add a new CHR integration test for centrs client → CHR bandwidth-server over host→guest port forwarding. - Update MATRIX/docs/changelog to reflect the
btest / clientcell asCHR-passedand reconcile grounding notes.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/protocols/btest-session.ts |
Records TX bytes for server-side TCP both sessions during status intervals so totals/averages are non-zero. |
test/unit/btest-session.test.ts |
Adds loopback regression test ensuring both sides record TX+RX for TCP both. |
test/unit/btest-command.test.ts |
Adds orchestrator-level regression test for back-to-back TCP both sessions with correct accounting. |
test/integration/chr.ts |
Extends CHR harness options with extraPorts port-forward mappings. |
test/integration/btest-client.test.ts |
New CHR integration suite: centrs btest client → real RouterOS bandwidth-server (unauth + EC-SRP5 + wrong-pass reject). |
docs/MATRIX.md |
Advances btest / client to CHR-passed and updates grounding narrative. |
commands/btest/README.md |
Reconciles status/grounding documentation and references the new client CHR gate. |
commands/btest/examples.md |
Notes that examples 6 and 8 are now also asserted directly against CHR bandwidth-server. |
CHANGELOG.md |
Documents the new CHR gate and the TCP both server TX accounting fix. |
| * Topology: a `user`/SLIRP guest is unreachable from the host, so the CHR boots | ||
| * with an `extraPorts` forward `{ name: "btest", host: 0, guest: 2000 }` — a host | ||
| * TCP port mapped onto the guest's bandwidth server on 2000. The centrs client |
|
@coderabbitai please review now that limit is reset |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/unit/btest-command.test.ts`:
- Around line 180-183: The test currently includes a guard statement that
returns early if server.ok is false, but there is no explicit assertion for
server.ok being true. This allows the test to pass silently even when server
orchestration fails. Add an explicit expect assertion for server.ok to be true
(similar to the existing assertions for first.ok and second.ok on lines 180-181)
before or alongside these assertions, so that the test fails rather than
silently passes when server initialization fails.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0fca93ae-ce13-4730-85f0-e0de8cd57618
📒 Files selected for processing (9)
CHANGELOG.mdcommands/btest/README.mdcommands/btest/examples.mddocs/MATRIX.mdsrc/protocols/btest-session.tstest/integration/btest-client.test.tstest/integration/chr.tstest/unit/btest-command.test.tstest/unit/btest-session.test.ts
| expect(first.ok).toBe(true); | ||
| expect(second.ok).toBe(true); | ||
| if (!first.ok || !second.ok || !server.ok) return; | ||
| expect(first.data.totalTxBytes).toBeGreaterThan(0); |
There was a problem hiding this comment.
Assert server success explicitly to avoid false-positive test passes.
Line 182 currently returns when server.ok is false, so the test can pass even if server orchestration failed. Add an explicit assertion for server.ok before the type guard.
Suggested fix
expect(first.ok).toBe(true);
expect(second.ok).toBe(true);
- if (!first.ok || !second.ok || !server.ok) return;
+ expect(server.ok).toBe(true);
+ if (!first.ok || !second.ok || !server.ok) return;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| expect(first.ok).toBe(true); | |
| expect(second.ok).toBe(true); | |
| if (!first.ok || !second.ok || !server.ok) return; | |
| expect(first.data.totalTxBytes).toBeGreaterThan(0); | |
| expect(first.ok).toBe(true); | |
| expect(second.ok).toBe(true); | |
| expect(server.ok).toBe(true); | |
| if (!first.ok || !second.ok || !server.ok) return; | |
| expect(first.data.totalTxBytes).toBeGreaterThan(0); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/unit/btest-command.test.ts` around lines 180 - 183, The test currently
includes a guard statement that returns early if server.ok is false, but there
is no explicit assertion for server.ok being true. This allows the test to pass
silently even when server orchestration fails. Add an explicit expect assertion
for server.ok to be true (similar to the existing assertions for first.ok and
second.ok on lines 180-181) before or alongside these assertions, so that the
test fails rather than silently passes when server initialization fails.
- docs (README + btest-client.test.ts header): include `proto: "tcp"` in the `extraPorts` forward examples so they match the `ChrPortForward` shape and the actual call (Copilot). - test/unit/btest-command.test.ts: assert `server.ok` explicitly before the type guard so the back-to-back test fails (not silently passes) if server orchestration fails (CodeRabbit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the review feedback in a5d4ed4:
All doc-comment/assertion only — no transport code touched, so the CHR-green evidence stands. |
1110a2b to
fd543e0
Compare
The per-interval accounting loops (reportOnlyLoop, the tcpTxLoop status branch, udpStatusExchange, tcpStatusSenderLoop) record totals on a statusIntervalMs tick and exit without capturing bytes that arrived after the last tick — or before the first tick on a slow host — so a short run under-reports the final fraction of a second of each direction. driveSession now folds the remaining counter bytes into the totals on stop (guarded so a tick-aligned run gains no phantom interval), keeping data.reports[] lossless against the totals. Both btest CHR integration suites re-verified green on CHR 7.23.1; full unit suite + a 10x stress of the btest unit files clean. Note: this does NOT fix the windows-latest unit failure (UDP loopback `bind ENOTSUP 127.0.0.1`, which also fails the MNDP loopback + error-pages tests on main). That tier is `continue-on-error` (informational, never gates) by design — centrs is not yet Windows-clean — and is tracked separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fd543e0 to
00cab18
Compare
Full review-item dispositionEvery flagged item now has a disposition — nothing dropped:
On the Windows CI checkWhile reviewing, the
One correctionI initially added the final-interval flush (00cab18) thinking it fixed that Windows flake; it does not (the cause is |
…on push (JG-18) (#70) The harder half of JG-18: accumulate per-run CHR results across runs and gate on them; promote `development` into the push matrix (best-effort) now that JG-31 (#68) is CHR-passed. - Policy in one place (`scripts/qa-results-db.ts`): `MUST_PASS_CHANNELS`, `channelPolicy`, `evaluateMustPassGate`, JSONL round-trip — gate is maturity-based (released channels gate; pre-release best-effort). - Accumulator (`scripts/qa-history.ts`): gates over this run, appends to a durable append-log on the `qa-history` orphan branch. - `qa.yaml`: `[stable, long-term, development]` on push; best-effort legs `continue-on-error`; new `accumulate-and-gate` job. CHR-validated end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Links
docs/MATRIX.md→ Peer measurement (btest),btest / clientcellChange type
direction=bothTCP server)extraPortshost→guest forward option (test-only)Notes
bun run lint,bun run lint:ci,bun run test(724 pass / 0 fail),bun run build— all green.bun run test:integrationfor both btest suites — green on real CHR 7.23.1 (server cell + new client cell)./tool/bandwidth-serverenabled+authenticateset via exec;authenticate=yesverifies the client against the router user DB (a dedicatedbtestuser/btestpass group=full). RouterOS ≥ 6.43 btest auth is EC-SRP5 (same curve as mac-telnet MTWEI). The direct client gate is TCP only over a host→guesttcp:2000forward; UDP client→server stays loopback/transitive (needs SLIRP reverse-NAT), as doesconnection-count > 1fan-out.Closes the last open product-grid cell:
btest / clientadvancescoded→CHR-passed, and fixes a bidirectional-TCP server accounting bug found while building the repro. This is Track A (JG-31) of the June-gloom closeout plan, steps 1–4.What changed
1. Fix —
direction=bothTCP server tx was never accounted (src/protocols/btest-session.ts)A
bothTCP session reportedtotalTxBytes=0/txAvgBps=0on the server side despite transmitting the client's receive half (hundreds of MB): the server's bulk-TX loop (tcpTxLoopwithsendStatus=true) flushed only its rx into each interval and never its own tx. The server-bothrole is the solesendStatus=truecaller, so swapping + recording tx alongside rx fixes the total with no double-count (TX-only senders usesendStatus=falseand are accounted byreportOnlyLoop).RED→GREEN loopback grounding:
test/unit/btest-session.test.ts— a TCP-bothdata-engine test (servertotalTxBytes > 0).test/unit/btest-command.test.ts— an orchestrator-level back-to-back + both-direction anchor (two sequential sessions on one server both account tx+rx; the second is not poisoned by the first's teardown).2. Gate — direct centrs client → CHR
/tool/bandwidth-server(test/integration/btest-client.test.ts, new)The previously-deferred direct client→server gate. The CHR boots with a host→guest
tcp:2000forward (newextraPortsoption ontest/integration/chr.ts, surfacing aschr.ports.btest); the centrs client dials127.0.0.1:<host port>. Verified on CHR 7.23.1:This validates the half the server-cell test cannot: centrs's EC-SRP5 client proof verified by RouterOS's own server verifier. Both btest integration suites were run together — the server cell is unaffected by the
bothfix.TCP only — UDP client→server needs the server's datagrams to traverse SLIRP back to the host (reverse-NAT), which this forward does not provide, so it stays loopback/transitive (as does
connection-count > 1fan-out).3. Docs reconciled —
docs/MATRIX.md(btest / client→CHR-passed, priority item 12),commands/btest/{README,examples}.md(grounding caveat now honest about the direct client gate),CHANGELOG.md.Review feedback (all dispositioned)
extraPortsexamples now includeproto: "tcp"— fixed in a5d4ed4.btest-command.test.ts): explicitexpect(server.ok).toBe(true)before the type guard — fixed in a5d4ed4.Deferred (tracked, not in this PR)
developmentchannel into the qa push/schedule gate. Depends on Track B's qa active-set / must-pass policy, which isn't built yet (tracked under the closeout plan, Track B).commands/btest/README.md→ Open questions / Out of scope).🤖 Generated with Claude Code