Writing

Feed Software, technology, sysadmin war stories, and more.

Wednesday, June 1, 2011

C++ code coverage with gcov and lcov

Good coverage can be the difference between a good test and a useless test. The problem can be finding out whether your tests actually manage to run all of your code. Fortunately, if you write in C++ on some Unix-ish system with a few GNU things installed, you can find out exactly what's going on.

Ever watch a security guard patrolling a large area? Chances are, they will interact with some kind of device rather often. Some of them will swipe a device they are carrying up against some fixed tag that's attached to a light pole, the side of a building, or something else. Others will swipe their badge at every card reader they encounter, even if going through a door in a direction which does not require a scan.

Doing this kind of activity essentially proves to the people in charge that they were in fact in the area at a certain time. Code coverage works the same way. It tells you if execution ever made it to a certain chunk of your code. If it didn't, chances are you have a dark corner where bugs and other badness can live undetected for a long time.

The good news is that gcov will report on this kind of stuff and will tell you what sort of coverage you have. The bad news is that it can be kind of dense and hard to read the results. Fortunately, there is another way. The Linux Test Project has a tool called LCOV which will give you the same data but in a far nicer form: HTML.

Here's a slightly-modified screenshot of what it looks like when run in a directory where testing has not been a high priority:

Code coverage snapshot

Wow, that's a lot of red. Note that I've removed most of the file name components so as to not identify the project.

Actually running it can be a little fiddly, so I recommend using a small script to make it less annoying.

#!/bin/sh
OUTPUT_DIR=/some/path
if (! test -d $OUTPUT_DIR )
then
  echo "Missing path: $OUTPUT_DIR"
  exit 1
fi
make clean || exit 1
export LDFLAGS="-lgcov"
export CPPFLAGS="-fprofile-arcs -ftest-coverage"
make || exit 1
lcov -c -i -d . -o .coverage.base
lcov -c -d . -o .coverage.run
lcov -d . -a .coverage.base -a .coverage.run -o .coverage.total
genhtml --no-branch-coverage -o $OUTPUT_DIR .coverage.total
rm -f .coverage.base .coverage.run .coverage.total

That is but one way to run it. Adjust to suit your own needs.


August 2, 2011: This post has an update.