Almost every team that ships serious software eventually puts a coverage number on a dashboard. The trouble is that the number arrives looking like magic: you run your tests, a tool emits "84%," and a green badge appears. Understanding how code coverage works turns that badge back into something you can reason about · what was measured, on which build, and whether the gaps that remain actually matter.
This article follows the pipeline from start to finish: what coverage is, how a code coverage tool instruments your build, how it collects data while your tests run on a host or a real target, the metrics it computes from statement up to MC/DC, and how those numbers become a report you can act on.
Code coverage works by adding lightweight instrumentation probes to your program at compile time, recording which probes fire while your tests execute, and grading the result against metrics ranging from statement coverage to MC/DC.
What code coverage is and why teams measure it
Code coverage is a measurement of how much of your source code is actually exercised when your tests run. It answers a blunt question that a passing test suite cannot: which lines, branches and logical conditions did my tests never touch? A green test run tells you the cases you wrote behave as expected. Coverage tells you about the cases you forgot to write.
Teams measure it for a few reasons that compound on each other. It exposes untested code paths before they reach production. It is an objective input to code review, so "did you test the error path?" becomes a number rather than an opinion. And in regulated industries, structural coverage is part of the verification evidence that standards such as DO-178C, ISO 26262 and IEC 62304 expect to see, with the stricter metrics reserved for the most critical software. Coverage is a gap finder, not a quality guarantee · but a gap finder is exactly what a thorough test strategy needs.
A passing test suite tells you what you remembered to check. Coverage tells you what you forgot.
How instrumentation works: probes inserted at compile time
The heart of code coverage instrumentation is simple to state. A code coverage tool inserts small tracking probes into your program so that, whenever a particular piece of code executes, a corresponding counter is incremented. When the run finishes, the set of counters that fired tells you exactly what ran.
The interesting part is where those probes are inserted. RKTracer performs source-level or preprocessed-level instrumentation during compilation. As the compiler processes each translation unit, the tool works on the preprocessed source · after macros and includes are expanded · and adds probes around statements, branches and individual conditions. This timing matters: because instrumentation happens on the preprocessed code the compiler actually sees, the coverage data maps cleanly back to your real source lines and to the logic that genuinely ships.
- Your original source is never modified. The probes are added to the compilation stream, not written back into your
.c,.cppor other files. Your repository stays byte-for-byte identical, and there is nothing to revert. - A runtime library is linked in automatically. The probes need somewhere to store their counters and a way to emit them. The tool links a small runtime support library into the executable for you · including when you cross-compile for a different architecture · so you do not hand-edit build scripts or link flags.
- It rides your existing compiler. Instrumentation uses the same compiler and the same options as your normal build, so the instrumented binary reflects the optimization and configuration of the code you ship, not a convenient stand-in.
If you want the full architecture, including how the tool slots into the compile and link steps, the how RKTracer works page walks through it step by step.
Instrumenting the preprocessed source lets the tool measure logical structure · individual conditions inside a decision, the basis for MC/DC · that is often invisible once the compiler has lowered everything to machine code. That is why source-level instrumentation underpins the stronger coverage metrics.
Running your tests and collecting data
Once you have an instrumented build, collecting coverage is just running your tests · all of them. Instrumentation is agnostic about how the code is driven, so every kind of test contributes to the same picture:
- Unit tests that exercise individual functions and edge cases in isolation.
- Integration tests that wire modules together and walk the seams between them.
- Functional and system tests that drive the application the way a user or upstream system would.
- UI and end-to-end tests, including browser automation with tools such as Selenium.
Just as importantly, it does not matter where the instrumented program runs. The same build can be exercised on your development host, inside a simulator or emulator, or on the real target hardware, and every run feeds the same counters. That flexibility means you can gather coverage from a fast host run during development and then confirm it on the device that actually ships, using one consistent toolchain across C, C++, CUDA, Rust, C#, Java, JavaScript, TypeScript, Go and Python.
Capturing coverage on targets without a file system
On a host, emitting coverage data is trivial: the runtime writes a file when the program exits. Embedded targets are where naive coverage tools fall apart, because a bare-metal microcontroller or a constrained RTOS often has no file system to write to, and sometimes no clean program exit either.
The way coverage works in that world is to avoid the file system entirely. Instead of writing a file, the runtime streams the counter data out over a transport the board already has. In practice that means reusing the existing debug channel · a serial link, an SWO trace pin, an RTT channel over the debug probe, or a JTAG connection · so no extra hardware is needed. Where even a live link is awkward, the runtime can accumulate coverage in a reserved RAM buffer that the host reads back through the debugger after the test run. Either way the data leaves the device and is reconstructed into the same coverage database you would get on a host.
We go deep on this in measuring coverage on targets without a file system, but the headline is that no file system is not the same as no coverage.
The metrics, from statement to MC/DC
Once the data is collected, the tool grades it against several coverage metrics. They form a ladder: each one is stricter than the last and tells you something the weaker metrics cannot. A full breakdown lives on the features and metrics page, but here is the working set.
| Metric | What it proves |
|---|---|
| Function | Every function was entered at least once |
| Statement | Every executable line ran at least once |
| Branch / decision | Every decision took both its true and false outcome |
| Condition | Every individual condition in a decision was both true and false |
| MC/DC | Each condition independently changed the decision's outcome (C and C++) |
| Multi-condition | Every combination of conditions in a decision was exercised |
| Delta | Coverage of only the lines changed between two runs |
Function and statement coverage are the entry point: did the code run at all. Branch or decision coverage steps up to control flow, requiring that each if, while or ternary go both ways. Condition coverage looks inside a compound decision and asks that each operand take both values. MC/DC · Modified Condition/Decision Coverage, available for C and C++ · is the strict one: it requires you to show that each condition, on its own, can flip the decision's result, which is why safety-critical software leans on it. Multi-condition coverage is the exhaustive cousin that checks every combination of operands. And delta coverage scopes the question to just the lines you changed, which is what makes coverage gates on pull requests practical.
The pipeline in four moves
- Instrument at compile time · probes added to the preprocessed source, originals untouched.
- Run every kind of test, on host, simulator, emulator or the real target.
- Collect the counters, streaming off constrained devices when there is no file system.
- Grade against statement, branch, condition, MC/DC, multi-condition and delta · then report.
Reports: color-coded HTML and mergeable XML
The final stage turns raw counters into something a person can read. RKTracer produces color-coded HTML reports with drill-down at three levels: a project summary that shows the headline percentages, a per-file view, and a per-function view where each source line is tinted green for covered and red for missed, with partially covered decisions flagged so you can see exactly which branch or condition is still open.
Alongside the HTML, the tool emits machine-readable XML for your CI pipeline, dashboards and quality gates. A property that matters more than it sounds is that coverage data is mergeable: results from many separate test runs · unit tests here, an integration suite there, a nightly target run · combine into a single consolidated figure. That is what lets a large test estate, spread across machines and stages, roll up into one trustworthy number. If you are wiring this into a build, the CMake application guide and the broader documentation cover the report and merge steps for common toolchains.
How RKTracer does it with zero workflow change
Everything above can sound like a lot of plumbing. The point of a good code coverage tool is that you do not see the plumbing. With RKTracer the entire setup is a prefix: you put the rktracer keyword in front of your normal build command and run it as usual.
# Prefix your normal build, no source edits, no wrappers $ rktracer make app compiler auto-detected: arm-none-eabi-gcc 12.2 ✓ instrumented 128 files · source unmodified ✓ runtime library linked automatically $ ctest # run your existing tests on host or target $ rkresults --report html ✓ Statement 96.4% ✓ Decision 92.1% ✓ MC/DC 88.7% (C and C++)
RKTracer auto-detects the compiler, instruments during compilation, and leaves your source and build files untouched.
It auto-detects the compiler, instruments during compilation, links the runtime for you, and asks for no edits to your source or build scripts. The architecture behind that · how the prefix hooks the compile and link steps without disturbing your workflow · is laid out in detail on the how RKTracer works page. RKTracer's job ends at measurement: it tells you, precisely, what your tests did and did not reach.
From coverage to closing the gaps
Measuring a gap is only useful if you can close it. RKTracer is a code coverage tool · it does not write tests itself · so closing gaps is handled by two companion tools that consume its data.
- RKMCP serves the precise uncovered lines, decisions and MC/DC conditions to your AI coding agent over the Model Context Protocol. The agent reads exactly which logic is unexercised and writes the tests that hit it, with the coverage report as its feedback loop.
- RKTracerGen is a deterministic, offline unit-test generator. Rather than calling out to a model, it analyzes the code and emits unit tests directly · useful when you need repeatable generation without a network dependency.
The division of labor is deliberate: RKTracer answers "what is uncovered," and RKMCP or RKTracerGen answer "here are the tests that cover it." That keeps the measurement honest · the coverage number is never produced by the same tool that wrote the tests.
The bottom line
How code coverage works is, at heart, a four-step pipeline: instrument the build at compile time without touching your source, run every kind of test wherever the code lives, collect the counter data even from devices with no file system, and grade it against metrics from statement all the way to MC/DC. Understanding that pipeline is what lets you trust the number on the dashboard · and act on the red lines underneath it.
If you would like to see this on your own toolchain, RKTracer measures every one of these metrics with a single prefix and no workflow change. Book a demo to watch it instrument your real build, or download the free trial and put a coverage report on your own code this afternoon.