Automating C++ Code Coverage with LLVM and GitHub Actions

Search for a command to run...

No comments yet. Be the first to comment.
Today we are deconstructing the 4-Star Dragon Ball shader (available on Shadertoy here). This shader is a good example of using 2D math to drive 3D optical effects. To understand it, we must reference

In Part 1, I talked about the high-level "magic" happening on your wrist. How Apple uses neural networks and math to guess your VO2Max even when you’re just walking around the block. But if you're lik
I am a data junkie. I track, measure, and optimize. So, when a severe ankle injury recently sidelined me, the hardest part wasn't the physical pain it was watching my hard earned fitness metrics plumm

In my previous post, I detailed how our move from the Android Emulator to standalone Android-x86 VHDX images gave us impressive cost saving wins for our fuzzing budget. With any infrastructure pivot,

When I was the head of Microsoft's Edge security for the US market, we ran into an infrastructure challenge that forced us to completely rethink how we fuzzed Android. The project we built no longer e
Setting up an automated coverage report is a powerful way to visualize the effectiveness of your test suite. This post breaks down how the warfLang repository generates and publishes its reports, moving from raw source code to a live site hosted on GitHub Pages.
We will look at how this is done using Clang and LLVM-Cov, which provides highly accurate source-based coverage.
To understand the automation, we first need to look at what is happening under the hood. Unlike GCC, which traditionally uses gcov, Clang utilizes "Source-based Code Coverage." This method is generally more precise because it maps coverage directly to the Abstract Syntax Tree (AST).
To enable this, we use two critical compiler flags:
-fprofile-instr-generate: Tells the compiler to insert counters into the code to track how many times each block is executed.-fcoverage-mapping: Adds metadata to the binary that associates these counters with specific source code locations.(Note: If you are migrating to Clang or want to understand how these flags interact with tools like CTest, Kaspar Giger’s guide on Clang Coverage is an excellent primer).
In the coverage-report.yml workflow, the first step is configuring CMake to use Clang and apply these flags.
cmake \(GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=\)BUILD_TYPE \
-DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++
It is crucial to remember that the linker also needs the -fprofile-instr-generate flag to ensure the final executable is capable of writing the coverage data at runtime.
.profraw FilesWhen you run a binary compiled with these flags, it doesn't output a readable report immediately. Instead, it produces a .profraw (Profile Raw) file. This is a highly efficient, compact binary format that contains the raw counter data.
The warfLang workflow uses the LLVM_PROFILE_FILE environment variable to explicitly name the output file so it isn't overwritten:
LLVM_PROFILE_FILE="warfLang.profraw" ./build/test/warfLang_TEST
If you are curious about what exactly is happening inside these binary files, Leo Di Donato wrote a fascinating deep-dive demystifying the .profraw format, explaining how the headers, function hashes, and execution counters are structured.
.profdataBecause a project might involve multiple test runs (e.g., unit tests, CLI tests, integration tests), you often end up with multiple .profraw files. To create a unified report, you must merge them using llvm-profdata.
llvm-profdata merge -sparse warfLang*.profraw -o warfLang.profdata
The -sparse flag is a useful optimization that reduces the size of the output .profdata file by omitting empty or zero-count blocks.
Once we have the merged .profdata, we use llvm-cov show to generate the actual visual report. This step requires the original binary because the mapping between the counters and the source code is still stored within that executable.
llvm-cov show -output-dir=build/out/report -format=html \
-instr-profile=warfLang.profdata \
-object=build/test/warfLang_TEST \
build/src/cli/warfLang src
-object : Specifies the binary files to look in for coverage mapping.-format=html : Generates an interactive website.src : Tells the tool which directory contains the source code to be mapped.The final piece of the puzzle is making the build/out/report directory accessible online. The warfLang workflow achieves this by initializing a new git repository inside that directory, committing the HTML files, and force-pushing it to a dedicated branch (gh-pages-coverage).
- name: Force push to destination branch
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages-coverage
directory: ${{github.workspace}}/build/out/report
By pointing GitHub Pages to the gh-pages-coverage branch, the report becomes live at farzon.org/warfLang/coverage/. This creates a seamless CI/CD loop: every push to the master branch triggers a build, runs tests, generates a new report, and updates the public-facing coverage site automatically.
If you want to explore the underlying tools and mechanics further, these articles provide excellent context:
.profraw binary file.First published 4/17/26 on blog.farzon.org