Portunus

Testing & Quality

How Portunus is tested, how line coverage is measured, and the target it aims for.

Portunus runs in your traffic path, so it has to be correct as well as fast. This page explains how the project is tested and the coverage target it aims for. Every number below comes with the command to reproduce it.

How Portunus is tested

There are five layers, and each one catches a different kind of bug:

LayerWhat runsCatches
Unitinline #[cfg(test)] modules in every crate (cargo test)Logic branches: parsers, ID and newtype rules, rate-limit math, log redaction, error mapping, state machines
Integrationeach crate's own tests/ (the server alone has more than 50 contract files)Behaviour across modules: operator HTTP, CSRF and auth contracts, SQLite migrations, proto round-trips, config parsing
End-to-endthe portunus-e2e crate, with more than two dozen process-level scenariosReal binaries over real sockets: enrollment, live rule push, multi-target failover, RBAC denials, restart recovery, upgrade idempotency
Performancecriterion benches (data_plane, udp_data_plane, operator_api, and more)Throughput and latency regressions, with a CI regression gate
Functionala manual checklist on real hosts before a tagged releaseWhole-product acceptance: install, deploy, and every feature, end to end

The first four layers run in CI on every change. The functional checklist is a release-time pass on real Debian hosts. The v2.0.0 run, for example, went through 41 feature items across install, forwarding, RBAC, quotas, storage, and the Web UI.

Coverage

Line coverage is measured with cargo-llvm-cov, which uses LLVM source-based instrumentation. One command reproduces it:

make coverage          # per-file line coverage, excludes the e2e crate
make coverage HTML=1   # also writes a browsable per-line report under target/llvm-cov/html

The target: logic files that can be unit-tested aim for at least 95% line coverage. Coverage is measured with each crate's own tests/ integration tests included, and the process-level portunus-e2e crate excluded. The e2e crate is left out because it proves behaviour by running real binaries, not by covering lines.

What is left out, and why

Entry and wiring code is not unit-tested. The integration and e2e layers cover it instead, by running it as real processes:

  • main.rs, serve.rs, and the CLI dispatchers
  • the gRPC service and the auth interceptor
  • the axum HTTP handlers and client/control.rs
  • the socket-runtime and TUI event loops

This code only does anything once real processes connect to each other, so an end-to-end test covers it better than a unit test with stubbed dependencies would.

A few logic files also sit just below 95%, and we leave them there. Closing the gap would need things the codebase rules out: the workspace forbids new unsafe, and CI rejects tests that depend on timing. Those files are signal handling (it would have to raise real signals), the enrollment RPC body (it needs a live TLS gRPC server), the resolver's NXDOMAIN and timeout paths (real network), one concurrency-race branch in the UDP registry, the store's defensive branches for a corrupt database, and the one-hour audit-retention sleep loop.

Where it stands (v3.0.0)

CrateLine coverageWhat it holds
portunus-proto100.0%generated gRPC types
portunus-auth99.6%authenticator, token store, RBAC
portunus-core97.9%IDs, config, rate-limit math, log redaction
portunus-standalone91.5%TOML forwarder and stats TUI
portunus-forwarder89.4%TCP and UDP data plane, resolver, SNI
portunus-server87.1%control plane, store, operator API
portunus-client72.9%edge binary, mostly control-stream wiring
Workspace87.5%excludes portunus-e2e

These are whole-crate totals, so they include the wiring code that the 95% target leaves out. That is why the logic-only crates (proto, core, auth) sit near the top, while crates that also hold CLI, control-stream, server, or socket-runtime code (client, server) come out lower, even though their own logic files meet the target. portunus-client is the clearest case: most of its lines are the gRPC control loop and process wiring, which the e2e tests cover.

No coverage gate (yet)

There is no CI coverage gate today. make coverage is a local check, and the 95% target is kept up in review rather than by a threshold in a workflow. Performance works the same way: the bench regression gate catches slowdowns automatically, but the target itself is set by people.

Run the suite yourself

# Whole suite: unit + integration + e2e (skip the embedded Web UI build)
PORTUNUS_SKIP_WEBUI=1 cargo test --workspace

# Line coverage (excludes the e2e crate).
# One-time setup:
cargo install cargo-llvm-cov
rustup component add llvm-tools-preview
make coverage

On this page