Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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-gcc ships musl-gcc as a thin wrapper around the host gcc plus musl specs. cc-rs looks for the target-prefixed aarch64-linux-musl-gcc name, 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-gcc and AR_aarch64_unknown_linux_musl=ar if you prefer to avoid touching /usr/local/bin.)

  • Debian / Ubuntu: apt install musl-tools (host arch) or one of the gcc-<arch>-linux-musl-cross packages for cross builds; same target-prefix handling applies if cc-rs doesn’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:

FunctionDescription
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:

PatternPrivilegesDescription
arguments__*.snapnoneArgument parsing output
help__*.snapnoneHelp text for every subcommand
commands__fixture__*.snaprootRead-only CLI output (fixture image)
commands__live__*.snaprootCLI 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

  1. Argument parsing: add cases to cli/tests/arguments.rs following the existing pattern.
  2. Help text: cli/tests/help.rs auto-discovers all subcommands by walking the clap tree — no changes needed.
  3. Read-only output: if the fixture image has suitable content, add snapshot tests to commands/fixture.rs.
  4. Mutating commands: add tests to commands/live.rs using the RAII helpers.

Use the snap!("description", output) macro for snapshot tests — the description appears in the snapshot file header.