3.4. Which code has been tested?

Deciding what code should be tested next can be a difficult decision. And in any given project, there is always code that isn't tested where bugs could be lurking. This section goes over how to identify these sections using a tool called gcov.

To use gcov on wine, do the following:

  1. In order to activate code coverage in the wine source code, when running make set CFLAGS like so make CFLAGS="-fprofile-arcs -ftest-coverage". Note that this can be done at any directory level. Since compile and run time are significantly increased by these flags, you may want to only use these flags inside a given dll directory.

  2. Run any application or test suite.

  3. Run gcov on the file which you would like to know more about code coverage.

The following is an example situation when using gcov to determine the coverage of a file could be helpful. We'll use the dlls/lzexpand/lzexpand_main.c. file. At one time the code in this file was not fully tested (as it may still be). For example at the time of this writing, the function LZOpenFileA had the following lines in it:

if ((mode&~0x70)!=OF_READ)
        return fd;
if (fd==HFILE_ERROR)
        return HFILE_ERROR;
cfd=LZInit(fd);
if ((INT)cfd <= 0) return fd;
return cfd;
        
Currently there are a few tests written to test this function; however, these tests don't check that everything is correct. For instance, HFILE_ERROR may be the wrong error code to return. Using gcov and directed tests, we can validate the correctness of this line of code. First, we see what has been tested already by running gcov on the file. To do this, do the following:
git clone git://source.winehq.org/git/wine.git wine
mkdir build
cd build
../wine/configure
make depend && make CFLAGS="-fprofile-arcs -ftest-coverage"
cd dlls/lxexpand/tests
make test
cd ..
gcov ../../../wine/dlls/lzexpand/lzexpand_main.c
  0.00% of 3 lines executed in file ../../../wine/include/wine/unicode.h
  Creating unicode.h.gcov.
  0.00% of 4 lines executed in file /usr/include/ctype.h
  Creating ctype.h.gcov.
  0.00% of 6 lines executed in file /usr/include/bits/string2.h
  Creating string2.h.gcov.
  100.00% of 3 lines executed in file ../../../wine/include/winbase.h
  Creating winbase.h.gcov.
  50.83% of 240 lines executed in file ../../../wine/dlls/lzexpand/lzexpand_main.c
  Creating lzexpand_main.c.gcov.
less lzexpand_main.c.gcov
        
Note that there is more output, but only output of gcov is shown. The output file lzexpand_main.c.gcov looks like this.
        9:  545:        if ((mode&~0x70)!=OF_READ)
        6:  546:                return fd;
        3:  547:        if (fd==HFILE_ERROR)
    #####:  548:                return HFILE_ERROR;
        3:  549:        cfd=LZInit(fd);
        3:  550:        if ((INT)cfd <= 0) return fd;
        3:  551:        return cfd;
        
gcov output consists of three components: the number of times a line was run, the line number, and the actual text of the line. Note: If a line is optimized out by the compiler, it will appear as if it was never run. The line of code which returns HFILE_ERROR is never executed (and it is highly unlikely that it is optimized out), so we don't know if it is correct. In order to validate this line, there are two parts of this process. First we must write the test. Please see Chapter 5 to learn more about writing tests. We insert the following lines into a test case:
INT file;

/* Check for nonexistent file. */
file = LZOpenFile("badfilename_", &test, OF_READ);
ok(file == LZERROR_BADINHANDLE, 
   "LZOpenFile succeeded on nonexistent file\n");
LZClose(file);
       
Once we add in this test case, we now want to know if the line in question is run by this test and works as expected. You should be in the same directory as you left off in the previous command example. The only difference is that we have to remove the *.da files in order to start the count over (if we leave the files than the number of times the line is run is just added, e.g. line 545 below would be run 19 times) and we remove the *.gcov files because they are out of date and need to be recreated.

rm *.da *.gcov
cd tests
make
make test
cd ..
gcov ../../../wine/dlls/lzexpand/lzexpand_main.c
  0.00% of 3 lines executed in file ../../../wine/include/wine/unicode.h
  Creating unicode.h.gcov.
  0.00% of 4 lines executed in file /usr/include/ctype.h
  Creating ctype.h.gcov.
  0.00% of 6 lines executed in file /usr/include/bits/string2.h
  Creating string2.h.gcov.
  100.00% of 3 lines executed in file ../../../wine/include/winbase.h
  Creating winbase.h.gcov.
  51.67% of 240 lines executed in file ../../../wine/dlls/lzexpand/lzexpand_main.c
  Creating lzexpand_main.c.gcov.
less lzexpand_main.c.gcov
      

Note that there is more output, but only output of gcov is shown. The output file lzexpand_main.c.gcov looks like this.

       10:  545:        if ((mode&~0x70)!=OF_READ)
        6:  546:                return fd;
        4:  547:        if (fd==HFILE_ERROR)
        1:  548:                return HFILE_ERROR;
        3:  549:        cfd=LZInit(fd);
        3:  550:        if ((INT)cfd <= 0) return fd;
        3:  551:        return cfd;
      

Based on gcov, we now know that HFILE_ERROR is returned once. And since all of our other tests have remain unchanged, we can assume that the one time it is returned is to satisfy the one case we added where we check for it. Thus we have validated a line of code. While this is a cursory example, it demostrates the potential usefulness of this tool.

For a further in depth description of gcov, the official gcc compiler suite page for gcov is http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Gcov.html. There is also an excellent article written by Steve Best for Linux Magazine which describes and illustrates this process very well at http://www.linux-mag.com/2003-07/compile_01.html.