CMake and coverage with gcov
TL;DR: To generate coverage data in CMake: enable CTest, enable debug, build and link all relevant targets with --coverage
, then run ctest --test-dir build -T Coverage
. Use gcov
or lcov
to generate a CLI or HTML coverage report respectively.
Generate coverage data with CMake §
At a high-level, to generate coverage with CMake:
- Enable CTest1 with
include(CTest)
. - (Optional) Enable debug builds.
- Specify
--coverage
options on the test executable targets, for compile and link stages. - Run
ctest --test-dir build -T Coverage
.
The following headings run through these steps in detail.
Enable CTest §
Make sure include(CTest)
is specified. This typically comes in the top-level CMakeLists.txt
:
Enable debug builds §
It is not necessary to enable debug builds in order for coverage to work, but it does help get more meaningful reports.
I do this by setting CMAKE_BUILD_TYPE
via the command line:
Another option is to set it within the CMakeLists.txt
by calling set(CMAKE_BUILD_TYPE "Debug")
. However, it is generally preferable to set debug builds via the command line to make it possible to switch between debug/release builds without having to modify the project files.
Specify coverage option §
For each test target, add the --coverage
option to the compile and link options. Using the:
See the gcc docs for more details. In short:
- When compiling,
--coverage
is a synonym for-fprofile-arcs
(which generates*.gcda
files) and-ftest-coverage
(which generates*.gcno
files).2 - When linking,
--coverage
is a synonym for-lgcov
(which links thegcov
runtime library).
Make sure to add the --coverage
option to both the test code and the code under test, e.g. your library and/or executable. Otherwise you may find you have “100% coverage” because only your test code is generating coverage data. This will be more obvious once you have lcov set up for HTML reports.
Generate coverage data §
Run ctest
with the option -T Coverage
to generate coverage data. This can be done in conjunction with -T Test
to also run the tests:
Process coverage data §
The coverage data is written to the aforementioned *.gcno
and *.gcda
files. These may be buried deep within the CMake build directory.
Find the coverage data §
To find the coverage data, use find | grep gcno$
to search for all files ending in gcno
:
Likewise for gcda
:
This confirms that the coverage data was generated.
Generate CLI report with gcov §
The coverage data can be used by gcov to generate a report in the CLI.
We have to do a little file wrangling to feed the data into gcov, since it expects to be fed a list of source file names, such as foo.c
, and expects that the corresponding object file foo.o
is in the same directory. However, CMake keeps the full filename and appends .o
, generating files such as foo.c.o
. Fortunately, we can just feed gcov the .gcda
files, and then gcov will perform the same steps as if it were handling a .c
file: it will strip the extension and add .gcno
and .gcna
to get the relevant coverage data. So gcov will find all the right files if we just feed it the gcna
files:3
Generate HTML report with lcov §
By installing lcov (e.g. apt-get install lcov
), we can generate an HTML coverage report. This shows the source code with the untested lines highlighted in red.
This is easier to use than gcov. Just pass in --directory build
and write to coverage.info
, then pass that into genhtml:4
This results in coverage/index.html
and a bunch of supporting files in that directory.
Bonus: report coverage to GitLab §
Simply add the following to your .gitlab-ci.yml
file:
This will match a coverage percentage generated by gcov such as the following:
CTest is “an executable that comes with CMake; it handles running the tests for the project”. ↩︎
Largely based off this comment, which seems to match up with my observations. ↩︎
Thanks to this section of this blog post for providing a demonstration. I decided to pass in
build
instead of.
to--directory
, since that is the build directory where our coverage data will be. ↩︎