Skip to content

Testing

Baudrun ships two layers of automated tests plus a manual playbook for the parts that need a human at the keyboard.

LayerWhat it coversWhere
Unit testsPure-data invariants: JSON round-trips, parser edge cases, protocol checksums / CRCs, hex input validation, highlight runtime.cargo test in the repo root.
Wire-level transfer testsReal XMODEM / YMODEM / Send-Hex code paths against a baud-paced virtual pty pair. Byte-diff against deterministic fixtures. Unix-only.scripts/transfer-tests/
Manual playbookThings that need eyes on the UI: import dialogs, paste safety prompts, scrollback retention, transfer cancel UX.scripts/virtual-serial/TESTING.md in the source tree.

Standard Cargo. Runs on macOS, Windows, and Linux in CI on every push.

Terminal window
cargo test

The tests live alongside the code they exercise (#[cfg(test)] mod tests { ... } at the bottom of each module). No external fixtures, no I/O, no temp files. Fast — the whole suite finishes in seconds.


Drive Baudrun’s actual send paths against a virtual serial bridge and byte-diff the output. The harness exists because the production app is the wrong shape for protocol regression testing — gpui UI, profile picker, port enumeration — but the protocol code itself (under src/data/transfer.rs) is fully decoupled.

The harness lifts transfer.rs verbatim via #[path] so it exercises the same XMODEM / YMODEM state machines the app ships. Refactors there are picked up automatically at the harness’s next build — there’s no parallel copy to keep in sync.

Terminal window
# Build the bridge and the harness.
(cd scripts/virtual-serial && cargo build --release)
(cd scripts/transfer-tests && cargo build --release)
# Generate the deterministic fixture set under test/transfers/.
./scripts/transfer-tests/regen-fixtures.sh

You’ll also need lrzsz for the XMODEM / YMODEM receiver subprocess:

Terminal window
brew install lrzsz # macOS — installs lrb / lrx
sudo apt install lrzsz # Debian / Ubuntu — installs rb / rx

The harness searches both name conventions.

Terminal window
./scripts/transfer-tests/target/release/transfer-tests # all 11 cases
./scripts/transfer-tests/target/release/transfer-tests --quick # skip 1 MiB T9
./scripts/transfer-tests/target/release/transfer-tests --tests hex # subset

Last verified 2026-05-15 on macOS arm64:

TestWhatBaudWallResult
T1hex ASCII (Hello)9 6000.01 sok
T3hex binary / non-printable9 6000.01 sok
T5hex 1 KiB random9 6001.35 sok
T11YMODEM 512 B over slow link9 6005.92 sok
T6YMODEM 4 KiB115 2003.69 sok
T9YMODEM 1 MiB115 200126.00 sok
T7cXMODEM classic (128 B / checksum)115 2001.56 sok
T7CXMODEM-CRC (128 B / CRC-16)115 2001.56 sok
T7kXMODEM-1K (1024 B / CRC-16)115 2001.55 sok
T8XMODEM single-block (SUB padding)115 2001.08 sok
T10YMODEM cancel mid-transfer115 2001.18 sok

11 / 11 passed, 143.9 s wall. T9 dominates the run time. The other ten cases finish in well under 18 seconds combined.

The IDs (T1 … T11) match the section numbering in scripts/virtual-serial/TESTING.md, which spells out the manual version of each test. T2 (hex input format equivalence) and T4 (hex input validation) are UI-layer concerns and aren’t automated — parse_hex_string is unit-tested separately if that coverage matters to you.

IDCategoryNotes
T1hex round-trip”Hello” — printable ASCII baseline.
T3hex round-trip00 01 02 ff fe 7f — control bytes + 0xff + DEL.
T5hex round-trip1 KiB random — exercises the rate-limited write path at the 9600-baud floor.
T6YMODEM4 KiB at 115200 — the “happy path” file transfer.
T9YMODEM1 MiB at 115200 — many-block stress, retry-loop sensitivity.
T11YMODEM512 B at 9600 — slow-link timing margins, lrzsz’s own per-block timeouts.
T7cXMODEM classic4 KiB with 128-byte blocks + 8-bit checksum. Receiver-initiated with NAK.
T7CXMODEM-CRC4 KiB with 128-byte blocks + CRC-16. Receiver-initiated with 'C'.
T7kXMODEM-1K4 KiB with 1024-byte blocks + CRC-16. Header byte (STX vs SOH) signals block size.
T8XMODEM single-block6 B ("hello\n") — exercises the SUB-padding path for the last block.
T10cancel mid-transfer1 MiB YMODEM with cancel tripped 150 ms in. Asserts TransferError::Cancelled, verifies the bridge survives.

Unit tests catch invariant violations (the checksum of a known block must be 0x5A); the wire layer catches integration regressions (the protocol state machine actually drives the state-machine partner on the other side of the wire to completion). Both layers find things the other misses. The manual playbook then covers UI-level concerns no harness can sanely automate — does the cancel button actually look pressed during a 1 MiB transfer, does the import-pack dialog give a useful error on malformed JSON.

For the full design — why no threads in the read path, why #[path] over a library extraction, why deterministic seed — see the harness README in the source tree.