Check Code Coverage with Clang and LCOV

Code coverage is a metric to show the code being untested. It can be considered as a hint to add more test cases. When we are writing C/C++, the most notable code coverage testing tool is gcov, which is a GCC built-in coverage testing tool. Besides, we can collect the results and generate beautiful HTML output with lcov, which is an extension to gcov. lcov was originally developed for Linux Test Project and then further extended for user space programs.

Although LLVM/Clang can generate some gcov-like files to track the code coverage, it requires some extra work to generate HTMLs with lcov. That's the reason why I am writing this post.

Build and Install Clang

To support code coverage instrumentation, we have to build LLVM and clang with compiler-rt [1]. Here are the instructions to build a nightly build:

# Checkout llvm
$ git clone http://llvm.org/git/llvm.git

# Checkout compiler-rt
$ cd llvm/projects
$ git clone http://llvm.org/git/compiler-rt.git

# Checkout clang
$ cd ../tools
$ git clone http://llvm.org/git/clang.git

# Build and install llvm and clang
$ cd ../..
$ mkdir llvm-build
$ cd llvm-build
$ ../llvm/configure --prefix="$(cd ..; pwd)/llvm-install" \
                    --enable-optimized
$ make -j8
$ make install

Create a Wrapper Script for LCOV

LLVM has a fast development speed, thus the llvm-cov command line options have been changed, and lcov can no longer recognize it. Thus, we have to create a script llvm-gcov.sh to workaround the problem:

#!/bin/bash
exec llvm-cov gcov "$@"
$ chmod +x llvm-gcov.sh

Build Your Application

To track the code coverage, clang has to instrument your application. You have to compile your application with two extra options:

-fprofile-arcs -ftest-coverage

For example, given the test.c:

#include <stdio.h>
int main(int argc, char **argv) {
  if (argc > 1) {
    printf("GOT: %s\n", argv[1]);
  }
  return 0;
}

We should compile the file with:

$ clang -fprofile-arcs -ftest-coverage test.c

You will notice that there is an extra file test.gcno being generated. It is some data structure for run-time supporting library.

Notice that if you are compiling the source code in two steps, i.e. compiling the source code with -c option, then you have to add --coverage to the LDFLAGS so that the program can linked with the run-time library properly [2]. For example,

# Compile the object file test.c
$ clang -fprofile-arcs -ftest-coverage -c test.c

# Link the program
$ clang --coverage test.o -o a.out

Run Your Application

Now, we can run the application (or the test cases.) If everything goes well, then several *.gcda will be generated.

# Run the program or the test cases.
$ ./a.out

# List the *.gcda files
$ ls *.gcda
test.gcda

# Read the *.gcda with llvm-cov
$ llvm-cov gcov -f -b test.gcda
Function 'main'
Lines executed:50.00% of 4
Branches executed:100.00% of 2
Taken at least once:50.00% of 2
No calls

File 'test.c'
Lines executed:50.00% of 4
Branches executed:100.00% of 2
Taken at least once:50.00% of 2
No calls
test.c:creating 'test.c.gcov'

Collect the Results

Finally, we can collect the results and generate HTML files. However, please notice that LLVM is generating GCOV files with old version (approximately equal to gcc 4.2), thus it is likely to be incompatible with the gcov command. As a result, we have to specify --gcov-tool while running lcov:

# Collect the code coverage results
$ lcov --directory . \
       --base-directory . \
       --gcov-tool llvm-gcov.sh \
       --capture -o cov.info

# Generate HTML files.
$ genhtml cov.info -o output

p.s. Notice that we should specify our wrapper script llvm-gcov.sh created earlier instead of llvm-cov.

Now, we can see the code coverage report at output/index.html.

Conclusion

In this post, I have introduced how to generate code coverage report with lcov while building the application with clang. Hope you enjoy this article. Feel free to let me know if you have any questions.

Troubleshooting

Can't Find libclang_rt.profile-x86_64.a

If you see following link error message, it means that you haven't built clang with compiler-rt:

/usr/bin/ld: cannot find libclang_rt.profile-x86_64.a: No such file or directory
clang-3.7: error: linker command failed with exit code 1