C++ Code Coverage
gcov/lcov (GCC/Clang), llvm-cov (Clang source-based), and OpenCppCoverage (MSVC/Windows) — how each works and how to integrate into CI.
Quick pick
The GCC standard. gcov collects raw data; lcov aggregates + generates HTML reports; works with Clang via --coverage.
- Comes with every GCC installation
- Works with Clang (use --coverage flag)
- lcov produces excellent HTML reports
- Integrates with Codecov, Coveralls, SonarQube
- Branch coverage in addition to line coverage
CMake configuration
target_compile_options(myapp PRIVATE --coverage) target_link_options(myapp PRIVATE --coverage)
Collect & generate report
# After running tests: lcov --capture --directory . --output-file coverage.info lcov --remove coverage.info '*/test/*' '/usr/*' --output-file coverage.info genhtml coverage.info --output-directory coverage_html
Clang's native coverage: source-based, more accurate than gcov, tracks regions not just lines.
- Source-based coverage — more accurate than gcov
- Region coverage (not just line/branch)
- LLVM-integrated, optimized for Clang
- Can export to lcov format for CI compatibility
- JSON export for custom tooling
CMake configuration
target_compile_options(myapp PRIVATE -fprofile-instr-generate -fcoverage-mapping) target_link_options(myapp PRIVATE -fprofile-instr-generate)
Collect & generate report
# Merge profile data llvm-profdata merge -sparse default.profraw -o coverage.profdata # Generate HTML report llvm-cov show ./myapp -instr-profile=coverage.profdata \ -format=html -output-dir=coverage_html # Export to lcov for CI llvm-cov export ./myapp -instr-profile=coverage.profdata \ -format=lcov > coverage.info
Windows-native coverage for MSVC binaries. Works without recompiling (debug symbols only).
- Works on Windows with MSVC — no special compile flags needed
- Just needs PDB debug symbols
- HTML and Cobertura XML export
- Visual Studio / Azure DevOps integration
- Supports child process coverage
CMake configuration
# No CMake flags needed — just need debug builds with PDB cmake -DCMAKE_BUILD_TYPE=Debug ..
Collect & generate report
# Run with coverage (Windows CMD) OpenCppCoverage.exe --sources src\** -- myapp_tests.exe # Output: HTML report in CoverageReport\
GitHub Actions — coverage + Codecov upload
- name: Build with coverage
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage -fno-inline"
cmake --build build
- name: Run tests
run: cd build && ctest --output-on-failure
- name: Collect coverage
run: |
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '*/test/*' '/usr/*' \
--output-file coverage.info
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.info
fail_ci_if_error: trueCoverage targets — a pragmatic guide
< 60%
Danger zone
Large untested areas. Critical bugs hiding in plain sight.
60–80%
Acceptable
Basic coverage. Good for mature libraries or integration-heavy code.
80–95%
Good
Most code paths tested. Some edge cases may be excluded intentionally.
100% coverage is not the goal — it is trivially achievable with meaningless tests. Aim for meaningful coverage of all behaviors, including error paths.