Testing
The goal for this project is to maintain a high test coverage, to make sure that these tools function correctly.
Running tests
Running the tests for this project is complicated by the fact that many btrfs operations talk directly to the kernel and require elevated privileges.
You can run all non-privileged tests with regular cargo test commands. This
will still build the privileged tests, but they are skipped.
cargo test
In order to run privileged tests, there is a just target that will build
them, and run (only the test binaries, not cargo itself) using sudo.
This is the recommended way to run the full test suite on this project.
just test
You can build a coverage report (requires cargo-llvm-cov) of the full test
suite similarly, using the coverage target.
just coverage
# open target/coverage/llvm-cov/html/index.html
Static checks
Before committing, run just check. This wraps the formatter check
(nightly rustfmt), cargo deny, taplo for Cargo.toml formatting,
cargo doc (with -Dwarnings), cargo clippy --all-features,
per-libc cargo check for the host arch, the optional CLI features,
and cargo msrv verify against every publishable crate’s declared
rust-version.
The host-arch detection means just check works on x86_64 and
aarch64 alike. The musl half (<host>-unknown-linux-musl) needs a
matching C cross-compiler on PATH, since the zstd-sys and
lzo-sys build scripts compile C code:
-
Nix devshell (
nix develop) provides everything; you don’t need any of the steps below. -
Fedora aarch64:
dnf install musl-gccshipsmusl-gccas a thin wrapper around the host gcc plus musl specs.cc-rslooks for the target-prefixedaarch64-linux-musl-gccname, so symlink it once:sudo ln -s /usr/bin/musl-gcc /usr/local/bin/aarch64-linux-musl-gcc(or set
CC_aarch64_unknown_linux_musl=musl-gccandAR_aarch64_unknown_linux_musl=arif you prefer to avoid touching/usr/local/bin.) -
Debian / Ubuntu:
apt install musl-tools(host arch) or one of thegcc-<arch>-linux-musl-crosspackages for cross builds; same target-prefix handling applies ifcc-rsdoesn’t pick it up automatically.
If the cross C compiler isn’t on PATH, just check prints
skipping <triple> check: <prefix>-linux-musl-gcc not on PATH and
keeps going — only CI is expected to fail on a missing musl
toolchain.
Unit tests
Unit tests live as #[cfg(test)] mod tests blocks within the module they test.
They require no privileges and run with cargo test.
Coverage spans all pure logic across the crates: LE readers, struct size assertions, tree search cursor arithmetic, stream parsing (all 22 v1 command types, CRC validation), superblock parsing, B-tree node parsing, size/time formatting, argument parsing helpers, balance filter parsing, and property classification.
When adding a new feature, add unit tests for any logic that doesn’t require a real kernel or filesystem.
Integration tests
Integration tests live in uapi/tests/ and cli/tests/commands/ and are marked:
#![allow(unused)]
fn main() {
#[ignore = "requires elevated privileges"]
}
They are skipped by cargo test and run only via just test.
Fixture tests (commands/fixture.rs)
Read-only snapshot tests against a pre-built filesystem image
(cli/tests/commands/fixture.img.gz). The image has a fixed UUID, label, and
subvolume layout, so output is fully deterministic. These tests cover all
read-only commands: filesystem df/show/usage/label/du, subvolume list/show,
device stats/usage, all inspect-internal commands, and property get/list.
dump-tree and dump-super tests read the image file directly and do not
require mounting, so they run without elevated privileges even within the
privileged test suite.
Live tests (commands/live.rs)
Tests that create and mutate real btrfs filesystems on loopback devices. These cover all mutating commands: subvolume create/delete/snapshot, send/receive, scrub, balance, device add/remove, quota, qgroup, label set, resize, defrag, replace, and more.
Test helpers
cli/tests/common.rs provides RAII helpers that clean up automatically on drop:
BackingFile → LoopbackDevice → Mount
Convenience functions:
| Function | Description |
|---|---|
single_mount() | 512 MiB single-device filesystem in a tempdir |
deterministic_mount() | Same, with a fixed UUID and label |
fixture_mount() | Mounts the pre-built fixture image read-only |
write_test_data(path, n) | Write deterministic byte-pattern files |
verify_test_data(path, n) | Verify previously written test data |
Snapshot testing with insta
CLI output tests use insta for snapshot testing. Snapshots
live in cli/tests/snapshots/ and are checked in to the repository.
Four snapshot categories:
| Pattern | Privileges | Description |
|---|---|---|
arguments__*.snap | none | Argument parsing output |
help__*.snap | none | Help text for every subcommand |
commands__fixture__*.snap | root | Read-only CLI output (fixture image) |
commands__live__*.snap | root | CLI output from live filesystem tests |
Snapshot workflow
# Run tests; fails if any snapshot has changed:
cargo test
# Run tests and collect pending snapshot changes:
cargo insta test
# Interactively review each changed snapshot:
cargo insta review
# Accept all pending changes at once:
cargo insta accept --all
After running privileged tests via just test, the Justfile fixes ownership of
any root-owned snapshot files and sets INSTA_WORKSPACE_ROOT so snapshots land
in the right directory.
Adding tests for a new subcommand
- Argument parsing: add cases to
cli/tests/arguments.rsfollowing the existing pattern. - Help text:
cli/tests/help.rsauto-discovers all subcommands by walking the clap tree — no changes needed. - Read-only output: if the fixture image has suitable content, add snapshot
tests to
commands/fixture.rs. - Mutating commands: add tests to
commands/live.rsusing the RAII helpers.
Use the snap!("description", output) macro for snapshot tests — the description
appears in the snapshot file header.