Skip to content

Development

Ken Tobias edited this page Jul 6, 2026 · 15 revisions

Development

This page covers compiling, testing, and benchmarking the etr codebase.

Prerequisites

To compile and run all tests locally, you need:

  • The Rust toolchain (rustup.rs)
  • just (command runner)
  • tmux (required for local end-to-end tests)
  • OpenSSH client/server with passwordless login to localhost configured (required for E2E tests)
  • mandown (for just mancargo install mandown)

Building

# Debug build
cargo build

# Release build
cargo build --release

Running Tests

We maintain a comprehensive suite of static checks, unit tests, and automated end-to-end tests:

# Run formatting and clippy lints
just check

# Run unit and integration tests (110 tests)
just test

# Run local end-to-end session and reconnect tests (launches etr in tmux)
just e2e-local

# Run local end-to-end tests for -L port forwarding (TCP + UDP, IPv4 + IPv6, reconnect)
just e2e-forward-local

# Run local end-to-end tests for -R port forwarding (TCP + UDP, IPv4 + IPv6, reconnect)
just e2e-reverse-local

# Run local end-to-end test for --env variable forwarding
just e2e-env-local

# Run local end-to-end tests for remote command execution (etr host 'command')
# Part 1: sentinel echo + clean exit; Part 2: fast-exit regression (must not hang)
just e2e-cmd-local

# Regression test: concurrent UDP senders through -L (v0.4.9 per-sender routing)
just e2e-udp-concurrent

# Run throughput/memory stress tests
just stress-local

Installing Shell Completions

just install-completions generates and installs tab-completions for both etr and etrs into the correct XDG directories for all six supported shells:

just install-completions
Shell Installed path Auto-loaded?
bash $XDG_DATA_HOME/bash-completion/completions/ Yes (bash-completion ≥ 2.x)
zsh $XDG_DATA_HOME/zsh/site-functions/ Yes (in zsh's default $fpath)
fish $XDG_CONFIG_HOME/fish/completions/ Yes
elvish $XDG_CONFIG_HOME/elvish/lib/ Needs eval (slurp < …) in rc.elv
nushell $XDG_CONFIG_HOME/nushell/completions/ Needs source in config.nu
powershell $XDG_CONFIG_HOME/powershell/ Needs . … in $PROFILE

The recipe prints sourcing instructions at the end of the run for the shells that require them.

Running Benchmarks

We use criterion to benchmark core performance-sensitive areas of the codebase, including TLS certificate generation, connection handshakes, round-trip latency, and throughput.

# Run the benchmark suite
just bench

CI and Releases

.github/workflows/ci.yml runs on every PR and push to main/feature/**/fix/**/chore/**:

  • Formatting + Clippy lints on ubuntu-latest and windows-latest (the two cover the #[cfg(unix)] and #[cfg(windows)]/#[cfg(not(unix))] code paths respectively).
  • The test suite on ubuntu-latest, macos-latest, and windows-latest.
  • cargo audit for dependency vulnerabilities.

.github/workflows/release.yml runs on v* tags and publishes prebuilt binaries:

Target Runner Binaries
linux-x86_64 ubuntu-latest etr, etrs
linux-aarch64 ubuntu-24.04-arm (native) etr, etrs
macos-aarch64 macos-latest etr, etrs
windows-x86_64 windows-latest etr only
windows-aarch64 windows-latest, cross-compiled to aarch64-pc-windows-msvc etr only

etrs (the server) is Unix-only by design — it daemonizes itself via fork/setsid — so Windows release assets ship the etr client only.

Linux jobs (both ci.yml and release.yml) explicitly apt-get install libutempter0 rather than relying on it being preinstalled — the x86_64 runner image happened to have it, but ubuntu-24.04-arm didn't, which broke the first linux-aarch64 release build (build.rs also hardcoded the x86_64-linux-gnu multiarch path when locating the library; it now scans all /usr/lib/*/ directories instead).

Opening a Pull Request

Before opening a PR — and before each subsequent push to an open PR — run the automated Pre-PR gate:

just pr

This checks you're on a feature branch (not main), that Cargo.toml's version has been bumped past the last tag, that NOTES.md's "Current state" header matches, builds the man pages, verifies Cargo.lock is committed, runs just check and cargo test, then prints a manual checklist (CLI --help, config docs, PROTOCOL.md, README.md, NOTES.md gaps, wiki) that you confirm before it reports the gate passed. See AGENTS.md in the main repo for the full checklist this automates.

After a PR is merged, run:

just merge-pr

This squash-merges via gh, switches to main, pulls, deletes the local feature branch, and resets WIP.md for the next session.

Clone this wiki locally