blob: 76828f435bd313535558034e97f1a05f0e12dfe0 [file] [log] [blame] [view]
fweacae1cd2022-02-17 09:45:38 -08001Project: /_project.yaml
2Book: /_book.yaml
3
4# Code coverage with Bazel
5
6Bazel features a `coverage` sub-command to produce code coverage
7reports on repositories that can be tested with `bazel coverage`. Due
8to the idiosyncrasies of the various language ecosystems, it is not
9always trivial to make this work for a given project.
10
11This page documents the general process for creating and viewing
12coverage reports, and also features some language-specific notes for
13languages whose configuration is well-known. It is best read by first
fwe6c2bd4a2022-02-18 10:47:46 -080014reading [the general section](#creating-a-coverage-report), and then
fweacae1cd2022-02-17 09:45:38 -080015reading about the requirements for a specific language. Note also the
fwe6c2bd4a2022-02-18 10:47:46 -080016[remote execution section](#remote-execution), which requires some
fweacae1cd2022-02-17 09:45:38 -080017additional considerations.
18
19While a lot of customization is possible, this document focuses on
20producing and consuming [`lcov`][lcov] reports, which is currently the
21most well-supported route.
22
fwe6c2bd4a2022-02-18 10:47:46 -080023## Creating a coverage report {:#creating-a-coverage-report}
fweacae1cd2022-02-17 09:45:38 -080024
25### Preparation
26
27The basic workflow for creating coverage reports requires the
28following:
29
30- A basic repository with test targets
31- A toolchain with the language-specific code coverage tools installed
32- A correct "instrumentation" configuration
33
34The former two are language-specific and mostly straightforward,
35however the latter can be more difficult for complex projects.
36
37"Instrumentation" in this case refers to the coverage tools that are
38used for a specific target. Bazel allows turning this on for a
39specific subset of files using the
40[`--instrumentation_filter`](/reference/command-line-reference#flag--instrumentation_filter)
41flag, which specifies a filter for targets that are tested with the
42instrumentation enabled. To enable instrumentation for tests, the
43[`--instrument_test_targets`](/reference/command-line-reference#flag--instrument_test_targets)
44flag is required.
45
46By default, bazel tries to match the target package(s), and prints the
47relevant filter as an `INFO` message.
48
49### Running coverage
50
51To produce a coverage report, use [`bazel coverage
52--combined_report=lcov
53[target]`](/reference/command-line-reference#coverage). This runs the
54tests for the target, generating coverage reports in the lcov format
55for each file.
56
57Once finished, bazel runs an action that collects all the produced
58coverage files, and merges them into one, which is then finally
59created under `$(bazel info
60output_path)/_coverage/_coverage_report.dat`.
61
62Coverage reports are also produced if tests fail, though note that
63this does not extend to the failed tests - only passing tests are
64reported.
65
66### Viewing coverage
67
68The coverage report is only output in the non-human-readable `lcov`
69format. From this, we can use the `genhtml` utility (part of [the lcov
70project][lcov]) to produce a report that can be viewed in a web
71browser:
72
73```console
74genhtml --output genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat"
75```
76
77Note that `genhtml` reads the source code as well, to annotate missing
78coverage in these files. For this to work, it is expected that
79`genhtml` is executed in the root of the bazel project.
80
81To view the result, simply open the `index.html` file produced in the
82`genhtml` directory in any web browser.
83
84For further help and information around the `genhtml` tool, or the
85`lcov` coverage format, see [the lcov project][lcov].
86
fwe6c2bd4a2022-02-18 10:47:46 -080087## Remote execution {:#remote-execution}
fweacae1cd2022-02-17 09:45:38 -080088
89Running with remote test execution currently has a few caveats:
90
91- The report combination action cannot yet run remotely. This is
92 because Bazel does not consider the coverage output files as part of
93 its graph (see [this issue][remote_report_issue]), and can therefore
94 not correctly treat them as inputs to the combination action. To
95 work around this, use `--strategy=CoverageReport=local`.
96 - Note: It may be necessary to specify something like
97 `--strategy=CoverageReport=local,remote` instead, if Bazel is set
98 up to try `local,remote`, due to how Bazel resolves strategies.
99- `--remote_download_minimal` and similar flags can also not be used
100 as a consequence of the former.
101- Bazel will currently fail to create coverage information if tests
102 have been cached previously. To work around this,
103 `--nocache_test_results` can be set specifically for coverage runs,
104 although this of course incurs a heavy cost in terms of test times.
105- `--experimental_split_coverage_postprocessing` and
106 `--experimental_fetch_all_coverage_outputs`
107 - Usually coverage is run as part of the test action, and so by
108 default, we don't get all coverage back as outputs of the remote
109 execution by default. These flags override the default and obtain
110 the coverage data. See [this issue][split_coverage_issue] for more
111 details.
112
113## Language-specific configuration
114
115### Java
116
117Java should work out-of-the-box with the default configuration. The
118[bazel toolchains][bazel_toolchains] contain everything necessary for
119remote execution, as well, including JUnit.
120
121### Python
122
123#### Prerequisites
124
125Running coverage with python has some prerequisites:
126
127- A bazel binary that includes [b01c859][python_coverage_commit],
128 which should be any Bazel >3.0.
129- A [modified version of coverage.py][modified_coveragepy].
130<!-- TODO: Upstream an lcov implementation so that this becomes usable -->
131
132#### Consuming the modified coverage.py
133
134A way to do this is via [rules_python][rules_python], this provides
135the ability to use a `requirements.txt` file, the requirements listed
136in the file are then created as bazel targets using the
137[pip_install][pip_install_rule] repository rule.
138
139The `requirements.txt` should have the following entry:
140
141```text
142git+https://github.com/ulfjack/coveragepy.git@lcov-support
143```
144
145The `rules_python`, `pip_install`, and the `requirements.txt` file should then be used in the WORKSPACE file as:
146
147```python
148load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
149
150http_archive(
151 name = "rules_python",
152 url = "https://github.com/bazelbuild/rules_python/releases/download/0.5.0/rules_python-0.5.0.tar.gz",
153 sha256 = "cd6730ed53a002c56ce4e2f396ba3b3be262fd7cb68339f0377a45e8227fe332",
154)
155
156load("@rules_python//python:pip.bzl", "pip_install")
157
158pip_install(
159 name = "python_deps",
160 requirements = "//:requirements.txt",
161)
162```
163
164Note: The version of `rules_python` is incidental - this was simply
165the latest at the time of writing. Refer to the
166[upstream][rules_python] for up-to-date instructions.
167
168The coverage.py requirement can then be consumed by test targets by
169setting the following in `BUILD` files:
170
171```python
172load("@python_deps//:requirements.bzl", "entry_point")
173
174alias(
175 name = "python_coverage_tools",
176 actual = entry_point("coverage"),
177)
178
179py_test(
180 name = "test",
181 srcs = ["test.py"],
182 env = {
183 "PYTHON_COVERAGE": "$(location :python_coverage_tools)",
184 },
185 deps = [
186 ":main",
187 ":python_coverage_tools",
188 ],
189)
190```
191<!-- TODO: Allow specifying a target for `PYTHON_COVERAGE`, instead of having to use `$(location)` -->
192
193
194[lcov]: https://github.com/linux-test-project/lcov
195[rules_python]: https://github.com/bazelbuild/rules_python
196[bazel_toolchains]: https://github.com/bazelbuild/bazel-toolchains
197[remote_report_issue]: https://github.com/bazelbuild/bazel/issues/4685
198[split_coverage_issue]: https://github.com/bazelbuild/bazel/issues/4685
199[python_coverage_commit]: https://github.com/bazelbuild/bazel/commit/b01c85962d88661ec9f6c6704c47d8ce67ca4d2a
200[modified_coveragepy]: https://github.com/ulfjack/coveragepy/tree/lcov-support
201[pip_install_rule]: https://github.com/bazelbuild/rules_python#installing-pip-dependencies