Product
Solutions
Resources
Company
Download Trial Book a Demo

Tools for Code Coverage: A Practical Guide

The field of coverage tools runs from free command-line classics to six-figure qualification kits. We map what each one actually measures, where it quietly stops, and how to pick the tool that will still hold up when an auditor — not your CI dashboard — is reading the report.

Ask three engineers which of the code coverage tools they trust and you will get three answers, usually shaped by whatever shipped with their toolchain. A C developer reaches for gcov; a Java team leans on JaCoCo; a Python project pulls in Coverage.py without a second thought. These are excellent tools, and for the job most teams do — measuring line and branch coverage of a host build during everyday development — they are hard to beat and cost nothing.

The trouble starts when the question changes. The moment you need MC/DC for a safety standard, or coverage from firmware running on a microcontroller, or numbers an auditor will accept as qualification evidence, the comfortable default tool quietly runs out of road. This guide walks the landscape from the open-source classics up through commercial qualification kits, names exactly what each measures, and gives you a checklist for choosing one that matches the verification you actually have to deliver.

The short version

For host-side line and branch coverage, the free tools are genuinely great. The expensive questions are which metrics (does it do MC/DC?), which targets (can it measure code on the chip that ships?), and whether the numbers are qualifiable. Pick your tool against those, not against the README.

What a code coverage tool actually does

Every code coverage tool, no matter how it is packaged, performs the same three jobs. Understanding them is the fastest way to predict where a given tool will help and where it will fall short.

  • Instrumentation — the tool injects bookkeeping so that execution can be observed. This happens in one of three places: at compile time, when the compiler emits extra counters as it generates code (gcov, llvm-cov); at the source level, by rewriting or wrapping the program text before it is built; or against the binary, by patching the compiled object or bytecode (JaCoCo instruments JVM bytecode, sometimes on the fly). Where instrumentation lives determines what the tool can see and how invasive it is.
  • Data collection — while the instrumented program runs your tests, those counters record which statements, branches and conditions were exercised. The hit data is written somewhere: a .gcda file on disk, a counter in memory, or a stream sent off a device.
  • Report generation — finally the raw counts are mapped back to your source and turned into percentages and annotated listings. lcov and gcovr exist purely for this last step on top of gcov; JaCoCo and Coverage.py bundle reporting in.

The metric a tool can report is constrained by what its instrumentation captured. A tool that only counts line entries can never reconstruct branch coverage after the fact — the information was never recorded. That single fact explains most of the gaps in the sections below.

The open-source landscape

Open-source coverage tooling is mature, widely deployed and, for the majority of software, completely sufficient. It clusters by language ecosystem.

gcov, lcov & gcovr

gcov is the coverage engine built into GCC. You compile with --coverage (which expands to -fprofile-arcs -ftest-coverage), run your program, and gcov reads the resulting .gcno and .gcda files to report per-line execution counts and, with -b, branch outcomes. It is fast, free, and already on every Linux developer's machine.

lcov and gcovr sit on top of gcov to make its output usable: lcov aggregates .gcda data and genhtml renders a browsable HTML report, while gcovr produces text, HTML, Cobertura or JSON summaries that drop neatly into CI. None of these change what gcov measures — they only present it. Their sweet spot is line and branch coverage of a native build, and at that they are excellent.

llvm-cov / source-based coverage

The Clang/LLVM equivalent is source-based coverage, enabled with -fprofile-instr-generate -fcoverage-mapping. After a run, llvm-profdata merges the raw profiles and llvm-cov produces line and region/branch coverage with notably precise mapping back to source expressions — it understands sub-expressions in a way gcov's line model does not. Recent Clang versions can also emit MC/DC-style data with -fcoverage-mcdc, which is a meaningful step, though support is newer, compiler-version-dependent, and still aimed at host builds rather than packaged as qualification evidence.

JaCoCo, Coverage.py, Istanbul/nyc

Outside C/C++ the same pattern repeats, language by language:

  • JaCoCo (JVM) instruments bytecode and reports line, instruction and branch coverage plus a cyclomatic-complexity proxy. It integrates cleanly with Maven, Gradle and SonarQube and is the de facto standard for Java and Kotlin.
  • Coverage.py (Python) traces execution and reports line coverage, with branch coverage available behind a flag. It backs pytest-cov and is ubiquitous in Python CI.
  • Istanbul / nyc (JavaScript/TypeScript) instruments source and reports statement, branch, function and line coverage — the four numbers every JS test runner prints.

The common thread is unmistakable: these tools are built around line and branch coverage of code running on a development host. That is exactly the right target for the verification most software needs.

What most tools measure — and what they miss

Line and branch coverage are nearly universal across coverage tools. The metrics that get scarce are the ones safety standards care about most.

  • Condition, MC/DC and multi-condition coverage are rare. Most mainstream tools stop at branch (decision) coverage. Reporting whether each individual condition inside a compound decision independently affected the outcome — MC/DC — is the exception, not the rule, and where a tool offers it the support is often partial or experimental.
  • The defaults are host-centric. Coverage is collected from a build running on your workstation or CI runner. That tells you about the host binary, not necessarily the one your cross-compiler produces for the target.
  • Rebuilds and source access are assumed. Compile-time instrumentation means recompiling with special flags and having the source and build system in hand — fine for your own code, awkward for third-party or pre-built components.
  • Embedded and target gaps. The collection model usually assumes a file system to write .gcda files to and a process that exits cleanly. Bare-metal firmware, an always-on RTOS task, a GPU kernel or a simulator breaks one or more of those assumptions, and the tool simply has nowhere to put its data.
A coverage tool can only report the metric it was built to capture — and on the platform it was built to run. Everything else is a gap you inherit silently.

Commercial & qualification kits

This is where commercial tools earn their price. In a DO-178C or ISO 26262 program, your verification tools themselves come under scrutiny: if a tool's output is used to satisfy an objective and its failure could let an error through undetected, the tool has to be qualified. That is not something you can retrofit onto a community project.

Certifiable coverage tools therefore ship with a qualification kit — a documented tool operational requirements specification, a verification test suite that demonstrates the tool does what it claims, and the evidence package an assessor needs to grant the tool credit. They typically cover the full structural-coverage ladder including MC/DC, and they are validated against specific compiler and target combinations rather than just a host. The trade-off is cost, a defined (and sometimes narrow) supported-configuration list, and a heavier setup. If you need to determine how much qualification rigor your context actually demands, the framework is laid out in our guide to ISO 26262 tool confidence levels.

The embedded and cross-compiler gap

The single most common way coverage numbers mislead is by being measured on the wrong machine. A host build compiled with your desktop GCC and a target build compiled with arm-none-eabi-gcc are different programs: different optimizations, different inlining, different code paths taken around hardware registers and timing. Coverage gathered on the host can look complete while leaving target-only branches entirely unexercised — we dig into exactly how this goes wrong in why host coverage numbers lie.

The second problem is logistical. The standard collection model writes profile data to a file. Bare-metal devices often have no file system, limited RAM, and no clean exit point at which to flush counters. Getting coverage off such a target means streaming the data out over a debug channel like SWO, RTT or JTAG, or buffering it in scarce memory — none of which the host-oriented tools were designed to do. This is precisely the gap a target-aware coverage tool has to close before its numbers mean anything for embedded software.

How to choose a coverage tool

Match the tool to the verification you owe, not to the language you happen to write in. Run candidates through this checklist:

  1. Metrics — does it report what your standard requires? Statement and branch are table stakes; for DAL A / ASIL D you need genuine MC/DC, and ideally multi-condition.
  2. Targets — can it measure the binary that actually ships: host, embedded (with or without a file system), GPU/CUDA, simulator?
  3. Compiler support — does it work with your cross-compiler and its exact version, or only host GCC/Clang?
  4. Source changes — does instrumentation require editing or wrapping your code, or can it sit transparently in front of your existing build?
  5. CI integration — does it produce machine-readable output and plug into your pipeline and dashboards?
  6. Report formats — HTML for humans, XML/Cobertura/JSON for tools, and an artifact your auditor will accept.
  7. Qualification — if you are in a safety program, is there a qualification kit for your standard and configuration?

Laying the categories side by side makes the trade-offs concrete:

CapabilityTypical open-sourceQualification kitRKTracer
Line coverageYesYesYes
Branch / decisionYesYesYes
MC/DCRare / partialYesYes
Embedded / targetLimitedOftenYes (with/without FS)
GPU / CUDANoRarelyYes
No source changesRecompile w/ flagsVariesPrefix build, no edits
AI test generationNoNoYes

What to walk away with

  • For host line and branch coverage, the free tools are excellent — start there.
  • MC/DC, target/embedded measurement and qualification evidence are where defaults run out.
  • Coverage measured on the host is not coverage on the target — verify on the binary that ships.
  • Choose against your standard, your compiler and your hardware — not the language README.

Where RKTracer fits

None of this is an argument against gcov or JaCoCo. For everyday host development they remain the right call, and RKTracer is not trying to out-gcov gcov on a Linux unit-test run. It is built for the gap the open-source stack leaves open: safety-critical, cross-compiled, multi-metric coverage that has to hold up under audit.

Concretely, RKTracer instruments with no source changes — you prefix your existing build command and it instruments during compilation, auto-detecting your compiler or cross-compiler. It measures the full ladder — statement, decision, condition, MC/DC and multi-condition — and collects coverage from where your code actually runs: the host, embedded targets with or without a file system, GPU/CUDA, and simulators. It generates unit tests with AI to close the gaps it finds, and produces rkresults reports in HTML and XML that publish straight to SonarQube. As an ISO 9001 vendor, RKValidate builds it to the process discipline a regulated program expects.

terminal — coverage on the real target
# Prefix your normal build — no source edits, no wrappers
$ rktracer make controller

  compiler: arm-none-eabi-gcc 12.2 (target config)
   instrumented 96 files — source unmodified

$ ctest # run your existing test suite
$ rkresults --report html --report xml

   Statement 100%
   Decision  100%
   MC/DC     98.4%  (published to SonarQube)

RKTracer reports the full coverage ladder from the cross-compiled target and exports HTML/XML for humans and dashboards.

The bottom line

There is no single best coverage tool — there is the tool that matches the verification you owe. If you are measuring line and branch coverage of a host build, the open-source classics are mature, free and excellent; reach for them without apology. The honesty has to kick in at the edges: when the requirement is MC/DC, when the code runs on a chip rather than a workstation, when the output has to survive an auditor's reading rather than a green CI badge.

Decide which of those edges you live near before you choose. Name the metrics, the targets, the compilers and the evidence you must produce, run your candidates through the checklist above, and let the answer fall out of the requirements. Do that, and the coverage number on your report will mean what everyone downstream assumes it means.

MK
Meera Krishnan
Test Engineering, RKValidate

Meera helps embedded and safety-critical teams move from host-only coverage to qualifiable, target-true structural coverage across C, C++ and beyond.

Keep reading
Code Coverage

Everything You Need to Know About Code Coverage

Statement, branch, decision, condition, MC/DC and multi-condition — what each metric really proves.

Read more
Embedded

Cross-Compiler Coverage: Why Host Numbers Lie

Why coverage gathered on your workstation diverges from coverage on the cross-compiled target that ships.

Read more
DO-178C

MC/DC Explained for DO-178C

Independence pairs, masking vs. unique-cause, and what an avionics assessor expects in your coverage report.

Read more

One tool, every coverage metric, every target

RKTracer measures statement through MC/DC on host, embedded, GPU and simulator targets — no source changes, qualifiable evidence, and AI-generated tests to close the gaps. Book a 30-minute demo or run the free trial today.