Code Coverage

From WineHQ Wiki
Revision as of 19:24, 29 February 2016 by KyleAuble (talk | contribs) (Move over Code Coverage page (merge Profile Guided Optimization too))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Code-coverage analysis is a debugging technique that tracks how often code follows different execution paths. This is particularly useful for digging up sneaky corner cases and identifying performance problems.

Using Gcov

One reliable tool for this technique is gcov, the GNU test coverage profiler, which can be used alongside the GCC compiler (sorry, Clang fans). Using gcov requires compiling and linking with the --coverage flag, which significantly increases compile and run times, so we recommend first Building Wine normally. Then you can selectively rebuild whichever folder contains the code you're interested in:

cd mydir/    # Directory that contains myfile.c and myfile.o
make clean && make CFLAGS=--coverage LDFLAGS=--coverage

After rebuilding with the hooks for gcov, run whatever program you want to test the code (including Wine's Conformance Tests). Once you're done running your test, call gcov in the same directory you rebuilt from to generate the report:

gcov myfile.c
less myfile.c.gcov

The .gcov file should have three columns: the number of times each line was run, its line number in the source file, and the actual line of code. The execution count will contain one of three possible symbols:

  • "-" for lines with purely syntactical code ("{" for example)
  • "#####" if the line has code that was never executed
  • A positive number counting the number of times that line was executed

Dialog-warning.svg If a line is optimized out by the compiler, it will appear as if it was never run. For this reason, the gcov documentation recommends disabling optimizations for any code you wish to analyze with gcov.

Dialog-warning.svg The wine-preloader currently requires building with -nostartfiles and -nodefaultlibs flags; consequently, it cannot be rebuilt for analyzing with gcov.

An Example

We'll use the file dlls/advapi32/registry.c to give a detailed example of how gcov can used to validate code in Wine. At the time of writing, the RegSetValueW function included the following lines:

if (name && name[0])  /* need to create the subkey */
    if ((ret = RegCreateKeyW( hkey, name, &subkey )) != ERROR_SUCCESS) return ret;

Although we have at least a few tests written for this function, they can only check for cases that we're already aware of. But by using gcov, we can tease out other cases and write appropriate tests to further validate the code.

First, we can see what cases our tests already cover by running gcov as described above:

cd dlls/advapi32
make clean && make CFLAGS=--coverage LDFLAGS=--coverage
cd tests
make test
cd ..
gcov registry.c
less registry.c.gcov

The part of the gcov results that we're interested in are these lines:

    4: 1273:    if (name && name[0])  /* need to create the subkey */
    -: 1274:    {
#####: 1275:        if ((ret = RegCreateKeyW( hkey, name, &subkey )) != ERROR_SUCCESS) return ret;
    -: 1276:    }

Line 1275 is never executed (!), most likely because the "name" parameter was never passed to the function. In order to completely validate this line, we need to trigger it. So we'll first sketch a quick unit-test to add to the others:

ret = RegSetValueW(hkey_main, name1W, REG_SZ, string1W, sizeof(string1W));
ok(ret == ERROR_SUCCESS, "RegSetValueW failed: %d, GLE=%d\n", ret, GetLastError());
test_hkey_main_Value_A(name1A, string1A, sizeof(string1A));
test_hkey_main_Value_W(name1W, string1W, sizeof(string1W));

Now we need to check that this test is actually activating our code as expected. We'll re-run gcov like before, only we must first clean out the .gcda and .gcov files so that gcov returns fresh results:

rm *.gcda *.gcov
cd tests
make test
cd ..
gcov registry.c
less registry.c.gcov

Behold! Line 1275 is being called exactly once, which we can safely assume is due to our new test:

    5: 1273:    if (name && name[0])  /* need to create the subkey */
    -: 1274:    {
    1: 1275:        if ((ret = RegCreateKeyW( hkey, name, &subkey )) != ERROR_SUCCESS) return ret;
    -: 1276:    }

Nice Reports with Lcov

If you're looking for something a little snazzier than gcov's plaintext output, lcov is a souped-up extension of gcov that pretty-prints html reports. Even if not packaged for your distro, you should be able to install from source (with make install) simply enough.

To adapt our above example for lcov, running the conformance tests to check the code, the commands go something like this:

cd dlls/advapi32
make clean
make CFLAGS="-g -O2 -fprofile-arcs -ftest-coverage" LDFLAGS="-lgcov -fprofile-arcs"
lcov --directory . --zerocounters
make -C tests/ testclean
make -C tests/ test -k
lcov --directory . --capture --output-file
xdg-open index.html

Profile-guided optimization

A closely related technique to code coverage is profile-guided optimization. In this technique, the compiler selectively optimizes code based on the likelihood of different execution paths. The goal here is not so much about improving the quality of the source-code but rather to allow the compiler to optimize a build more aggressively (though the improved performance may bring previously unnoticed bugs to the surface).

A few compilers provide this capability. For example, to optimize the same dll as in our previous snippets with GCC:

cd dlls/advapi32
make clean && make EXTRACFLAGS=-fprofile-generate LDFLAGS=-fprofile-generate
make -C tests/ testclean    # Or if you're optimizing for a particular program...
make -C tests/ test -k      # ... run that one in wine instead
make clean && make EXTRACFLAGS=-fprofile-use LDFLAGS=-fprofile-use

See also