Rust code coverage reports are surprisingly easy to generate with cargo llvm-cov, and it works by instrumenting your compiled code to track which lines are executed during tests.

Let’s see it in action. Imagine this simple Rust project:

my_project/
├── Cargo.toml
└── src/
    └── lib.rs

src/lib.rs:

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

pub fn subtract(left: usize, right: usize) -> usize {
    left - right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds() {
        assert_eq!(add(2, 2), 4);
    }

    // This test is intentionally missing for subtract
    // #[test]
    // fn it_subtracts() {
    //     assert_eq!(subtract(10, 5), 5);
    // }
}

Cargo.toml:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]

To generate a coverage report, you first need to install cargo-llvm-cov:

cargo install cargo-llvm-cov

Then, run it in your project directory:

cargo llvm-cov --no-run

This command tells cargo to compile your project with coverage instrumentation enabled, but it doesn’t run the tests yet. The output will look something like this:

    Finished dev [unoptimized + debuginfo] target(s) in 0.50s
     Running /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo-llvm-cov
info: Running tests to generate coverage data...
    Running unittests src/lib.rs (target/debug/deps/my_project-xxxxxxxxxxxxxxxx)
...

Once the tests have run and collected data, cargo llvm-cov will generate the actual report. By default, it produces an HTML report that you can open in your browser.

cargo llvm-cov --open

This command will automatically build, run tests, and then open the generated HTML report. You’ll see a breakdown of your code, with lines that were executed marked in green and lines that were not executed marked in red. In our example, the subtract function would likely show up as uncovered.

The core problem this solves is understanding how much of your codebase is actually exercised by your test suite. Without coverage, you might have tests that pass, but they could be missing critical paths or edge cases. cargo llvm-cov bridges this gap by giving you a clear, visual representation of test execution.

Internally, cargo llvm-cov leverages LLVM’s code coverage capabilities. When your code is compiled with the -fprofile-instr-generate -fcoverage-mapping flags (which cargo llvm-cov adds automatically), LLVM inserts probes into your code. These probes record every time a branch or a line is executed. After the tests run, a separate tool (part of LLVM’s profiling infrastructure) processes these execution counts and generates a coverage report based on the source code.

The cargo llvm-cov tool then acts as a convenient wrapper around these LLVM tools, making the process seamless within the Rust ecosystem. It handles the compilation flags, test execution, and report generation.

You can customize the output format and the files included in the report. For instance, to generate a lcov format report (often used by CI/CD systems) and exclude certain files, you’d use:

cargo llvm-cov --output-type lcov --exclude 'target/*' --exclude 'build/*' --output-dir coverage_reports

This command generates a lcov.info file in the coverage_reports directory. The --exclude flag is crucial for preventing your build artifacts and external dependencies from cluttering your coverage statistics.

A common misconception is that 100% code coverage is the ultimate goal. While high coverage is desirable, focusing solely on the percentage can lead to writing tests for the sake of coverage rather than for actual value. It’s more important to have comprehensive tests that cover critical logic, edge cases, and error conditions, even if that means not every single line is hit. Sometimes, lines that are unreachable or purely decorative might remain uncovered, and that’s perfectly acceptable.

The --skip-clean flag can be useful if you’re iterating on coverage generation and don’t want cargo llvm-cov to clean your target directory before running tests, saving build time.

The next step after generating coverage is to integrate it into your CI pipeline, often using tools that can parse the lcov format and display coverage metrics.

Want structured learning?

Take the full Cargo course →