I just made use of a handy tool today to do code coverage testing with C programs, and I thought I'd do a quick rundown of how to use gcov, a code coverage tool for gcc.

The helpful documentation that I used is part of the GCC docs. The manpage for gcov is also helpful. I recommend taking a look at both after reading the rest of this post and before starting on your own.

Compiling your code for use with gcov

In order to compile your code for use with gcov, you must use two additional compile flags with gcc. These flags are -fprofile-arcs and -ftest-coverage. For a simple one file program, it is easy enough to just pass these with your gcc call:

gcc -fprofile-arcs -ftest-coverage -o myprog myprog.c

For something with the GNU autoconf/automake setup (such as the pacman code base), the following worked for me:

./configure <normal parameters> CFLAGS='-fprofile-arcs -ftest-coverage'

A quick note- ensure you are using plain gcc and not something like ccache or distcc, as it may interfere later when trying to get test coverage results back.

Running your program

Once you have compiled your program, it is highly recommended to run your program from the same directory you ran the compile. Simply running your program will output a whole bunch of files in your source directories, with names based off your source file names and ending in things like gcda and gcno. These are the data files recorded with each run of your program, and they are cumulative, meaning you can run your whole test suite and then go looking for code that wasn't ran. To reset the stats, you must delete all these files, which can be done real easily with a command like the following:

find -name '*.gcda' -o -name '*.gcno' -delete

Once you have ran your program, you can use the gcov tool to get some human-readable results. To do this, I simply navigated into my src/ directory and ran a command like the following:

gcov *.c

This produced a .gcov file for each of my C source files with code coverage and line number annotations along the side. This will contain stuff similar to the following:

    -:  844:    /* Opening local database */
  302:  845:    db_local = alpm_db_register_local();
  302:  846:    if(db_local == NULL) {
#####:  847:        pm_printf(PM_LOG_ERROR, _("could not register 'local' database (%s)\n"),
    -:  848:                alpm_strerrorlast());
#####:  849:        cleanup(EXIT_FAILURE);
    -:  850:    }
    -:  851:
    -:  852:    /* start the requested operation */
  302:  853:    switch(config->op) {
    -:  854:        case PM_OP_ADD:
   48:  855:            ret = pacman_add(pm_targets);
   48:  856:            break;
    -:  857:        case PM_OP_REMOVE:
   38:  858:            ret = pacman_remove(pm_targets);
   34:  859:            break;

How to interpret results

As you can guess, the first column is the number of times that line was run, and the second column is the line number. gcov is smart enough to realize that some lines are not an instruction, so simply fills the first column with a -. The most interesting lines are those with #####, which means they were never triggered. In the cases that we see this above, it is simply error messages that are never triggered (which is probably a good thing). The times you really want to watch out for is whole sections of your code that are never triggered, or branches of small statements that you know should be tested.

Other notes

gcov can also be used in tandem with gprof to do some code profiling and hotspot location. However, I tend to use the more powerful (in my opinion) valgrind/callgrind combination due to the power of using kcachegrind to navigate the results. See my earlier post for details.

When using gcov with programs that use libtool, you can have some problems trying to generate the coverage files on the library source files due to libtool's .libs directory. There are gcda/gcno files in multiple places and it took some work to get them to generate properly, but I found the following trick to work:

cd /lib/libsourcefolder/.libs
cp ../*.c . #copy all the source files into the .libs dir, you could link too
gcov *.c

Happy profiling!