Search code examples
cunit-testinggcovlcovgcovr

.gcda file coverage info not getting updated when two executables run for same source file


There are three files u1.c , u2.c and common.c

content of ut1.c

#include<stdio.h>
void fun1();
int main(){
    fun1();
}

content of ut2.c

#include<stdio.h>
void fun2();
int main(){
    fun2();
}

content of common.c

void fun1(){
        printf("fun1\n");
}

void fun2(){
        printf("fun2\n");
}

Compilation steps:-

gcc -Wall -fprofile-arcs -ftest-coverage ut1.c common.c -o ut1
gcc -Wall -fprofile-arcs -ftest-coverage ut2.c common.c -o ut2

Execution steps:-

./ut1
./ut2

Now when running gcov common.c only fun2 coverage is coming.

    -:    0:Source:common.c
    -:    0:Graph:common.gcno
    -:    0:Data:common.gcda
    -:    0:Runs:1
    -:    0:Programs:1
    -:    1:void fun1(){
    -:    2:        printf("fun1\n");
    -:    3:}
    -:    4:
    1:    5:void fun2(){
    1:    6:        printf("fun2\n");
    1:    7:}

Solution

  • This is because you are compiling common.c two separate times.

    When running your ut1 and ut2 programs, we can see the following warning (tested with GCC 10):

    $ ./ut1
    fun1
    $ ./ut2
    fun2
    libgcov profiling error:/tmp/gcov-test/common.gcda:overwriting an existing profile data with a different timestamp
    

    Each time when you compile with coverage enabled, GCC will assign a checksum to the coverage data. This checksum is primarily used by the gcov tool to ensure that the gcno file and gcda files match. When compiling ut1 and ut2, different checksums will be used. So instead of appending coverage data, ut2 sees the invalid checksum and will overwrite the data.

    The solution is to treat common.c as a separate compilation unit and link it with ut1 and ut2. For example:

    # compile common.c
    gcc -Wall --coverage -c common.c -o common.o
    
    # compile ut1 and ut2, and link with common.o
    gcc -Wall --coverage ut1.c common.o -o ut1
    gcc -Wall --coverage ut2.c common.o -o ut2
    

    Then, the gcov output should be as expected:

            -:    0:Source:common.c
            -:    0:Graph:common.gcno
            -:    0:Data:common.gcda
            -:    0:Runs:2
            -:    1:#include<stdio.h>
            1:    2:void fun1(){
            1:    3:        printf("fun1\n");
            1:    4:}
            -:    5:
            1:    6:void fun2(){
            1:    7:        printf("fun2\n");
            1:    8:}
    

    If you cannot change how your project is compiled, you could collect the coverage data with a tool such as lcov or gcovr, and then merge it. For example, the workflow with gcovr would be as follows:

    1. Compile ut1, execute it, and save the coverage data as a gcovr JSON report:

      gcc -Wall --coverage ut1.c common.c -o ut1
      ./ut1
      gcovr --json ut1.json
      rm *.gcda *.gcno
      
    2. Compile ut2, execute it, and save the coverage data as a gcovr JSON report:

      gcc -Wall --coverage ut2.c common.c -o ut2
      ./ut2
      gcovr --json ut2.json
      
    3. Create a combined report:

      gcovr -a ut1.json -a ut2.json --html-details coverage.html
      

    While gcovr cannot output gcov-style textual reports, it can show the coverage as HTML:

    The gcovr HTML report shows that both fun1 and fun2 are covered

    Full code for this answer is at https://gist.github.com/latk/102b125dff160484f93d8997204fc201