]> git.lizzy.rs Git - rust.git/blob - src/doc/unstable-book/src/compiler-flags/source-based-code-coverage.md
Rollup merge of #79871 - Pratyush:patch-1, r=joshtriplett
[rust.git] / src / doc / unstable-book / src / compiler-flags / source-based-code-coverage.md
1 # `source-based-code-coverage`
2
3 The tracking issue for this feature is: [#79121].
4
5 ------------------------
6
7 ## Introduction
8
9 The Rust compiler includes two code coverage implementations:
10
11 * A GCC-compatible, gcov-based coverage implementation, enabled with [`-Zprofile`], which operates on DebugInfo.
12 * A source-based code coverage implementation, enabled with `-Zinstrument-coverage`, which uses LLVM's native coverage instrumentation to generate very precise coverage data.
13
14 This document describes how to enable and use the LLVM instrumentation-based coverage, via the `-Zinstrument-coverage` compiler flag.
15
16 ## How it works
17
18 When `-Zinstrument-coverage` is enabled, the Rust compiler enhances rust-based libraries and binaries by:
19
20 * Automatically injecting calls to an LLVM intrinsic ([`llvm.instrprof.increment`]), at functions and branches in compiled code, to increment counters when conditional sections of code are executed.
21 * Embedding additional information in the data section of each library and binary (using the [LLVM Code Coverage Mapping Format] _Version 4_, supported _only_ in LLVM 11 and up), to define the code regions (start and end positions in the source code) being counted.
22
23 When running a coverage-instrumented program, the counter values are written to a `profraw` file at program termination. LLVM bundles tools that read the counter results, combine those results with the coverage map (embedded in the program binary), and generate coverage reports in multiple formats.
24
25 ## Enable coverage profiling in the Rust compiler
26
27 Rust's source-based code coverage requires the Rust "profiler runtime". Without it, compiling with `-Zinstrument-coverage` generates an error that the profiler runtime is missing.
28
29 The Rust `nightly` distribution channel should include the profiler runtime, by default.
30
31 *IMPORTANT:* If you are building the Rust compiler from the source distribution, the profiler runtime is *not* enabled in the default `config.toml.example`. Edit your `config.toml` file and ensure the `profiler` feature is set it to `true`:
32
33 ```toml
34 # Build the profiler runtime (required when compiling with options that depend
35 # on this runtime, such as `-C profile-generate` or `-Z instrument-coverage`).
36 profiler = true
37 ```
38
39 If changed, rebuild the Rust compiler (see [rustc-dev-guide-how-to-build-and-run]).
40
41 ### Building the demangler
42
43 LLVM coverage reporting tools generate results that can include function names and other symbol references, and the raw coverage results report symbols using the compiler's "mangled" version of the symbol names, which can be difficult to interpret. To work around this issue, LLVM coverage tools also support a user-specified symbol name demangler.
44
45 One option for a Rust demangler is [`rustfilt`], which can be installed with:
46
47 ```shell
48 cargo install rustfilt
49 ```
50
51 Another option, if you are building from the Rust compiler source distribution, is to use the `rust-demangler` tool included in the Rust source distribution, which can be built with:
52
53 ```shell
54 $ ./x.py build rust-demangler
55 ```
56
57 ## Compiling with coverage enabled
58
59 Set the `-Zinstrument-coverage` compiler flag in order to enable LLVM source-based code coverage profiling.
60
61 With `cargo`, you can instrument your program binary *and* dependencies at the same time.
62
63 For example (if your project's Cargo.toml builds a binary by default):
64
65 ```shell
66 $ cd your-project
67 $ cargo clean
68 $ RUSTFLAGS="-Zinstrument-coverage" cargo build
69 ```
70
71 If `cargo` is not configured to use your `profiler`-enabled version of `rustc`, set the path explicitly via the `RUSTC` environment variable. Here is another example, using a `stage1` build of `rustc` to compile an `example` binary (from the [`json5format`] crate):
72
73 ```shell
74 $ RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc \
75     RUSTFLAGS="-Zinstrument-coverage" \
76     cargo build --example formatjson5
77 ```
78
79 Note that some compiler options, combined with `-Zinstrument-coverage`, can produce LLVM IR and/or linked binaries that are incompatible with LLVM coverage maps. For example, coverage requires references to actual functions in LLVM IR. If any covered function is optimized out, the coverage tools may not be able to process the coverage results. If you need to pass additional options, with coverage enabled, test them early, to confirm you will get the coverage results you expect.
80
81 ## Running the instrumented binary to generate raw coverage profiling data
82
83 In the previous example, `cargo` generated the coverage-instrumented binary `formatjson5`:
84
85 ```shell
86 $ echo "{some: 'thing'}" | target/debug/examples/formatjson5 -
87 ```
88 ```json5
89 {
90     some: 'thing',
91 }
92 ```
93
94 After running this program, a new file, `default.profraw`, should be in the current working directory. It's often preferable to set a specific file name or path. You can change the output file using the environment variable `LLVM_PROFILE_FILE`:
95
96
97 ```shell
98 $ echo "{some: 'thing'}" \
99     | LLVM_PROFILE_FILE="formatjson5.profraw" target/debug/examples/formatjson5 -
100 ...
101 $ ls formatjson5.profraw
102 formatjson5.profraw
103 ```
104
105 If `LLVM_PROFILE_FILE` contains a path to a non-existent directory, the missing directory structure will be created. Additionally, the following special pattern strings are rewritten:
106
107 * `%p` - The process ID.
108 * `%h` - The hostname of the machine running the program.
109 * `%t` - The value of the TMPDIR environment variable.
110 * `%Nm` - the instrumented binary’s signature: The runtime creates a pool of N raw profiles, used for on-line profile merging. The runtime takes care of selecting a raw profile from the pool, locking it, and updating it before the program exits. `N` must be between `1` and `9`, and defaults to `1` if omitted (with simply `%m`).
111 * `%c` - Does not add anything to the filename, but enables a mode (on some platforms, including Darwin) in which profile counter updates are continuously synced to a file. This means that if the instrumented program crashes, or is killed by a signal, perfect coverage information can still be recovered.
112
113 ## Installing LLVM coverage tools
114
115 LLVM's supplies two tools—`llvm-profdata` and `llvm-cov`—that process coverage data and generate reports. There are several ways to find and/or install these tools, but note that the coverage mapping data generated by the Rust compiler requires LLVM version 11 or higher. (`llvm-cov --version` typically shows the tool's LLVM version number.):
116
117 * The LLVM tools may be installed (or installable) directly to your OS (such as via `apt-get`, for Linux).
118 * If you are building the Rust compiler from source, you can optionally use the bundled LLVM tools, built from source. Those tool binaries can typically be found in your build platform directory at something like: `rust/build/x86_64-unknown-linux-gnu/llvm/bin/llvm-*`.
119 * You can install compatible versions of these tools via `rustup`.
120
121 The `rustup` option is guaranteed to install a compatible version of the LLVM tools, but they can be hard to find. We recommend [`cargo-bintools`], which installs Rust-specific wrappers around these and other LLVM tools, so you can invoke them via `cargo` commands!
122
123 ```shell
124 $ rustup component add llvm-tools-preview
125 $ cargo install cargo-binutils
126 $ cargo profdata -- --help  # note the additional "--" preceeding the tool-specific arguments
127 ```
128
129 ## Creating coverage reports
130
131 Raw profiles have to be indexed before they can be used to generate coverage reports. This is done using [`llvm-profdata merge`] (or `cargo cov -- merge`), which can combine multiple raw profiles and index them at the same time:
132
133 ```shell
134 $ llvm-profdata merge -sparse formatjson5.profraw -o formatjson5.profdata
135 ```
136
137 Finally, the `.profdata` file is used, in combination with the coverage map (from the program binary) to generate coverage reports using [`llvm-cov report`] (or `cargo cov -- report`), for a coverage summaries; and [`llvm-cov show`] (or `cargo cov -- show`), to see detailed coverage of lines and regions (character ranges) overlaid on the original source code.
138
139 These commands have several display and filtering options. For example:
140
141 ```shell
142 $ llvm-cov show -Xdemangler=rustfilt target/debug/examples/formatjson5 \
143     -instr-profile=formatjson5.profdata \
144     -show-line-counts-or-regions \
145     -show-instantiations \
146     -name=add_quoted_string
147 ```
148
149 <img alt="Screenshot of sample `llvm-cov show` result, for function add_quoted_string" src="img/llvm-cov-show-01.png" class="center"/>
150 <br/>
151 <br/>
152
153 Some of the more notable options in this example include:
154
155 * `--Xdemangler=rustfilt` - the command name or path used to demangle Rust symbols (`rustfilt` in the example, but this could also be a path to the `rust-demangler` tool)
156 * `target/debug/examples/formatjson5` - the instrumented binary (from which to extract the coverage map)
157 * `--instr-profile=<path-to-file>.profdata` - the location of the `.profdata` file created by `llvm-profdata merge` (from the `.profraw` file generated by the instrumented binary)
158 * `--name=<exact-function-name>` - to show coverage for a specific function (or, consider using another filter option, such as `--name-regex=<pattern>`)
159
160 ## Interpreting reports
161
162 There are four statistics tracked in a coverage summary:
163
164 * Function coverage is the percentage of functions that have been executed at least once. A function is considered to be executed if any of its instantiations are executed.
165 * Instantiation coverage is the percentage of function instantiations that have been executed at least once. Generic functions and functions generated from macros are two kinds of functions that may have multiple instantiations.
166 * Line coverage is the percentage of code lines that have been executed at least once. Only executable lines within function bodies are considered to be code lines.
167 * Region coverage is the percentage of code regions that have been executed at least once. A code region may span multiple lines: for example, in a large function body with no control flow. In other cases, a single line can contain multiple code regions: `return x || (y && z)` has countable code regions for `x` (which may resolve the expression, if `x` is `true`), `|| (y && z)` (executed only if `x` was `false`), and `return` (executed in either situation).
168
169 Of these four statistics, function coverage is usually the least granular while region coverage is the most granular. The project-wide totals for each statistic are listed in the summary.
170
171 ## Test coverage
172
173 A typical use case for coverage analysis is test coverage. Rust's source-based coverage tools can both measure your tests' code coverage as percentage, and pinpoint functions and branches not tested.
174
175 The following example (using the [`json5format`] crate, for demonstration purposes) show how to generate and analyze coverage results for all tests in a crate.
176
177 Since `cargo test` both builds and runs the tests, we set both the additional `RUSTFLAGS`, to add the `-Zinstrument-coverage` flag, and `LLVM_PROFILE_FILE`, to set a custom filename for the raw profiling data generated during the test runs. Since there may be more than one test binary, apply `%m` in the filename pattern. This generates unique names for each test binary. (Otherwise, each executed test binary would overwrite the coverage results from the previous binary.)
178
179 ```shell
180 $ RUSTFLAGS="-Zinstrument-coverage" \
181     LLVM_PROFILE_FILE="json5format-%m.profraw" \
182     cargo test --tests
183 ```
184
185 Make note of the test binary file paths, displayed after the word "`Running`" in the test output:
186
187 ```text
188    ...
189    Compiling json5format v0.1.3 ($HOME/json5format)
190     Finished test [unoptimized + debuginfo] target(s) in 14.60s
191
192      Running target/debug/deps/json5format-fececd4653271682
193 running 25 tests
194 ...
195 test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
196
197      Running target/debug/deps/lib-30768f9c53506dc5
198 running 31 tests
199 ...
200 test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
201 ```
202
203 You should have one ore more `.profraw` files now, one for each test binary. Run the `profdata` tool to merge them:
204
205 ```shell
206 $ cargo profdata -- merge \
207     -sparse json5format-*.profraw -o json5format.profdata
208 ```
209
210 Then run the `cov` tool, with the `profdata` file and all test binaries:
211
212 ```shell
213 $ cargo cov -- report \
214     --use-color --ignore-filename-regex='/.cargo/registry' \
215     --instr-profile=json5format.profdata \
216     --object target/debug/deps/lib-30768f9c53506dc5 \
217     --object target/debug/deps/json5format-fececd4653271682
218 $ cargo cov -- show \
219     --use-color --ignore-filename-regex='/.cargo/registry' \
220     --instr-profile=json5format.profdata \
221     --object target/debug/deps/lib-30768f9c53506dc5 \
222     --object target/debug/deps/json5format-fececd4653271682 \
223     --show-instantiations --show-line-counts-or-regions \
224     --Xdemangler=rustfilt | less -R
225 ```
226
227 _Note the command line option `--ignore-filename-regex=/.cargo/registry`, which excludes the sources for dependencies from the coverage results._
228
229 ### Tips for listing the binaries automatically
230
231 For `bash` users, one suggested way to automatically complete the `cov` command with the list of binaries is with a command like:
232
233 ```bash
234 $ cargo cov -- report \
235     $( \
236       for file in \
237         $( \
238           RUSTFLAGS="-Zinstrument-coverage" \
239             cargo test --tests --no-run --message-format=json \
240               | jq -r "select(.profile.test == true) | .filenames[]" \
241               | grep -v dSYM - \
242         ); \
243       do \
244         printf "%s %s " -object $file; \
245       done \
246     ) \
247   --instr-profile=json5format.profdata --summary-only # and/or other options
248 ```
249
250 Adding `--no-run --message-format=json` to the _same_ `cargo test` command used to run
251 the tests (including the same environment variables and flags) generates output in a JSON
252 format that `jq` can easily query.
253
254 The `printf` command takes this list and generates the `--object <binary>` arguments
255 for each listed test binary.
256
257 ### Including doc tests
258
259 The previous examples run `cargo test` with `--tests`, which excludes doc tests.[^79417]
260
261 To include doc tests in the coverage results, drop the `--tests` flag, and apply the
262 `-Zinstrument-coverage` flag, and some doc-test-specific options in the
263 `RUSTDOCFLAGS` environment variable. (The `cargo profdata` command does not change.)
264
265 ```bash
266 $ RUSTFLAGS="-Zinstrument-coverage" \
267   RUSTDOCFLAGS="-Zinstrument-coverage -Zunstable-options --persist-doctests target/debug/doctestbins" \
268   LLVM_PROFILE_FILE="json5format-%m.profraw" \
269     cargo test
270 $ cargo profdata -- merge \
271     -sparse json5format-*.profraw -o json5format.profdata
272 ```
273
274 The `-Zunstable-options --persist-doctests` flag is required, to save the test binaries
275 (with their coverage maps) for `llvm-cov`.
276
277 ```bash
278 $ cargo cov -- report \
279     $( \
280       for file in \
281         $( \
282           RUSTFLAGS="-Zinstrument-coverage" \
283           RUSTDOCFLAGS="-Zinstrument-coverage -Zunstable-options --persist-doctests target/debug/doctestbins" \
284             cargo test --no-run --message-format=json \
285               | jq -r "select(.profile.test == true) | .filenames[]" \
286               | grep -v dSYM - \
287         ) \
288         target/debug/doctestbins/*/rust_out; \
289       do \
290         [[ -x $file ]] && printf "%s %s " -object $file; \
291       done \
292     ) \
293   --instr-profile=json5format.profdata --summary-only # and/or other options
294 ```
295
296 Note, the differences in this `cargo cov` command, compared with the version without
297 doc tests, include:
298
299 * The `cargo test ... --no-run` command is updated with the same environment variables
300   and flags used to _build_ the tests, _including_ the doc tests. (`LLVM_PROFILE_FILE`
301   is only used when _running_ the tests.)
302 * The file glob pattern `target/debug/doctestbins/*/rust_out` adds the `rust_out`
303   binaries generated for doc tests (note, however, that some `rust_out` files may not
304   be executable binaries).
305 * `[[ -x $file ]] &&` filters the files passed on to the `printf`, to include only
306   executable binaries.
307
308 [^79417]: There is ongoing work to resolve a known issue
309 [(#79417)](https://github.com/rust-lang/rust/issues/79417) that doc test coverage
310 generates incorrect source line numbers in `llvm-cov show` results.
311
312 ## Other references
313
314 Rust's implementation and workflow for source-based code coverage is based on the same library and tools used to implement [source-based code coverage in Clang]. (This document is partially based on the Clang guide.)
315
316 [#79121]: https://github.com/rust-lang/rust/issues/79121
317 [`-Zprofile`]: profile.md
318 [`llvm.instrprof.increment`]: https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic
319 [LLVM Code Coverage Mapping Format]: https://llvm.org/docs/CoverageMappingFormat.html
320 [rustc-dev-guide-how-to-build-and-run]: https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html
321 [`rustfilt`]: https://crates.io/crates/rustfilt
322 [`json5format`]: https://crates.io/crates/json5format
323 [`cargo-bintools`]: https://crates.io/crates/cargo-bintools
324 [`llvm-profdata merge`]: https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge
325 [`llvm-cov report`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-report
326 [`llvm-cov show`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-show
327 [source-based code coverage in Clang]: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html