The differential harness
The port has one rule for behaviour: ask the C server. The harness turns that rule into tests. It records what the C server says to a scripted conversation, then replays the same conversation against the Rust binary and compares.
scenario (JSON) C oracle in Docker parity/traces/<name>/
parity/scenarios/*.json ──────▶ janus-c:bc0de3b3 on a random port ──▶ raw.json (git-ignored)
steps: requests, polls every request and reply, byte for byte normalised.json (committed)
│
│ cargo run -p xtask -- parity-run
▼
Rust binary on a free loopback port, with the same sample configs
requests replayed ─▶ replies normalised the same way ─▶ diffed with normalised.json ─▶ PASS or FAIL + diffThe pieces
| Piece | Where | Does |
|---|---|---|
| Scenarios | parity/scenarios/*.json | A conversation: requests, polls for events, config edits before start. |
| Oracle image | parity/oracle/Dockerfile.oracle | Builds the C server at the pinned commit with every sample config enabled. Tag janus-c:bc0de3b3. |
| Capture | cargo run -p xtask -- capture --scenario <name> | Runs a scenario against the oracle in a container named janus-cap-<name> (removed afterwards) and writes the trace. |
| Normaliser | xtask/src/capture/normalise.rs, copied in parity/src/numbered.rs | Replaces what differs between runs (ids, transactions, times, addresses, SSRCs) with placeholders such as $SESSION_1. A test keeps the two copies equal. |
| Runner | cargo run -p xtask -- parity-run | Starts the Rust binary per scenario, replays, normalises and diffs. Exits non-zero on any difference. |
| Media probe | cargo run -p xtask -- parity-media | A loopback WebRTC client (ICE, DTLS-SRTP, RTP, RTCP) runs against both servers; the two normalised reports are diffed. --golden compares with the recorded one, no Docker. |
| SIP and Lua probes | parity-sip, parity-lua | The same idea for SIP calls against a UDP peer and for Lua number handling. Need Docker and python3. |
Running it
cargo run -p xtask -- parity-run # every scenario, builds janus first
cargo run -p xtask -- parity-run --scenario echotest # one scenario
cargo run -p xtask -- parity-run --bin target/debug/janus --no-buildA passing scenario prints its name and the number of exchanges. A failing one prints which exchanges differ and a unified diff of the first, like this (a real failure caused by another process holding the NoSIP port range):
FAIL nosip-requests (trace has 440 exchanges, server gave 440, 22 differ)
first difference: exchange 48 of 440, request GET /janus/2409443595219312?maxev=1
--- expected (trace)
+++ actual (janus)
- "m=audio 20000 RTP/AVP 111",
+ "m=audio 20020 RTP/AVP 111",Scenarios bind real ports
NoSIP gets RTP ports 20000 and up, Streaming uses 25810 and up. Run the harness while no other janus is using those ranges, or scenarios that print ports will differ.
What a pass means, and what it does not
A pass means the Rust server gave the same normalised replies as the C server to every request in the scenario. It does not cover:
- Timing-dependent events:
webrtcup,media,slowlink, ICE failures. No peer connects in a scenario. The media probe and Janus Bench cover these. - Hash-table order of
listreplies. The C server's order depends on GLib; the normaliser sorts the one array known to differ (VideoCall'slist), and a port that lists in another order must decide whether to sort elsewhere. - A VideoRoom subscriber's offer, the SDP of Lua and Duktape, and live SIP: nothing there is captured.
The full list is in parity/scenarios/README.md. When the oracle moves past the pin, drift is logged in PARITY_LOG.md and PARITY.md is regenerated with cargo run -p xtask -- parity-matrix.
Rules
- Every wave that touches a component updates
parity/status.json, then runscargo run -p xtask -- progress. The README bars come from it. PARITY.mdis generated. Do not edit it.- Never modify the C checkout. It is read-only evidence.