tests/, run by cargo test, that
live in the same repository as your firmware.
Structure
Point tests at a box with theLAGER_BOX_HOST environment variable and
construct the client with LagerBox::from_env():
boot.rs,
power.rs, sensors.rs) keeps cargo test <name> filtering useful.
Parallel tests and instrument safety
cargo test runs tests on multiple threads by default. That is safe at the
instrument level. The box serializes access per physical instrument: every
net command runs under a per-device lock in the box’s single-owner hardware
service. Because of that per-device lock, parallel tests can never interleave
I/O on one instrument. Examples are a LabJack shared across GPIO/ADC/SPI
nets, or a Keithley shared by supply and battery roles.
Tests sharing a net still observe each other’s state changes (one test’s
disable() is visible to another test reading the same supply). Either
partition nets across tests, or serialize:
Timeouts
Timeout budgets mirror the Lager CLI. Quick commands use 10 s. Some operations block on the box for a caller-controlled duration: the watt and energy integration windows, andwait_for_level. Those operations widen or
drop the client timeout automatically, so a healthy long measurement is
never aborted mid-flight.
Hermetic tests vs. hardware tests
Mark tests that always need real hardware with#[ignore]. A plain
cargo test then stays green on a laptop with no box, or in a PR check:
cargo test runs against a mock box. Its hardware smoke tests
run with cargo test --test hardware -- --ignored.
CI
A minimal GitHub Actions job, assuming the runner can reach the box (e.g. a self-hosted runner on the lab network or a Tailscale-connected runner):A harness that runs against any box
The crate’s own hardware suite uses a pattern worth copying. Every test is#[ignore]d,
so cargo test stays hermetic. Each test reads its net name from an environment
variable, and skips with a note when that variable is unset. The same suite then runs
against whatever nets a given bench configures.
Skipping on an older box
A box too old for an endpoint answers withError::UnsupportedByBox, which is a
better signal than a panic when a fleet is mid-upgrade.
Teardown that actually runs
A test that panics mid-way skips everything after the panic, which on a bench means leaving a supply enabled or a firewall rule in place. Put the restore on a path that survives a failure.BoxLockGuard releases on drop, including during a panic unwind.
Verifying against real hardware
Some behaviors only show up on a bench, and are worth knowing before you write an assertion around them:- A supply’s measurements can be
Nonewhile its output is disabled, depending on the instrument. Assert onenabledand setpoints when the output is off. state()returningOkdoes not mean the instrument answered — check theerrorfield.- An
i2c.scan()hit does not guarantee a subsequent read will ACK. erase()drops the debugger connection; reconnect before reading memory.

