How to merge multiple versions of gcda files?

2019-07-17 08:44发布

问题:

I'm using gcov for get coverage information of my application. However, there are 3 instances of my applications are running concurrently creating 3 versions of "gcda" files. Is there a way to merge different versions of same "gcda" files in my coverage information file.

I want to take the coverage information as just one instance.

回答1:

I have just been looking into the same problem, and here is what I found. TL;DR yes it is possible, in best case by just being careful with compilation order, and in the most general (and complicated) way by using the gcov-tool merge command however it is not straight out of the box and it requires some dedicated setup in order to get it working.

Code

In my example I have one library file, lib.cpp, with two functions:

#include <iostream>

void five(int n) {
        if (n > 5) {
                std::cout << n << " is greater than five" << std::endl;
        } else {
                std::cout << n << " is not greater than five" << std::endl;
        }
}

void ten(int n) {
        if (n > 10) {
                std::cout << n << " is greater than ten" << std::endl;
        } else {
                std::cout << n << " is not greater than ten" << std::endl;
        }
}

and then I have two programs, each of them calling one of the functions, five.cpp

#include <iostream>
#include "lib.hpp"

int main(int argc, char *argv[]) {
        if (argc != 2) {
                std::cerr << "usage: " << argv[0] << " <n>" << std::endl;
                return 2;
        }

        int n = std::stoi(argv[1]);
        five(n);
        return 0;
}

and ten.cpp

#include <iostream>
#include "lib.hpp"

int main(int argc, char *argv[]) {
        if (argc != 2) {
                std::cerr << "usage: " << argv[0] << " <n>" << std::endl;
                return 2;
        }

        int n = std::stoi(argv[1]);
        ten(n);
        return 0;
}

Assume the code is located in a /tmp/merge-gcov directory.

Capturing coverage

Enabling coverage capturing can be done like

g++ -O0 --coverage -o five five.cpp lib.cpp

which will create lib.gcno and five.gcno files. When the five program is being run it will create /tmp/merge-gcov/lib.gcda and /tmp/merge-gcov/five.gcda. Notice that those gcda paths are hardcoded into the binary (but can be manipulated, more about that later).

./five 1
./five 12       # Results from multiple runs will accumulate in gcda files
mkdir report
gcovr -r . --html --html-details --output report/report.html
firefox report/report.html 

Multiple conflicts

So far so good. But if we now also compile the ten program the same way

g++ -O0 --coverage -o ten ten.cpp lib.cpp

then it will create a lib.gcno which is then newer than and different from the lib.gcno that five was compiled with. This means that whenever five or ten is run after the other it will detect that the lib.gcda file is not corresponding to its expectations (of gcno origin) and reset the file contents, thereby discarding any accumulated previous content.

This can be avoided by compiling the lib.cpp file separately first, e.g.

g++ -O0 --coverage -c lib.cpp 
g++ -O0 --coverage -o five lib.o five.cpp
g++ -O0 --coverage -o ten lib.o ten.cpp

now both five and ten will share the same lib.gcno and they will both accumulate lib.gcda.

So if you are careful that all the shared code are compiled only once before linking the binaries you should be good to go for accumulating coverage results from multiple binaries.

Different compilation of shared code

But what if we want to compile the library differently? Perhaps we want to compile one version with debug code disabled and one with it enabled to verify that the code works in both cases. Then the previous solution does not work, and instead the strategy is to put files for each binary into its own directory and then merge those directories later.

g++ -O0 --coverage -c lib.cpp -DENABLE_DEBUG
g++ -O0 --coverage -o five lib.o five.cpp
mkdir gcov-five
mv *.gcno gcov-five/.

Notice I said that the gcda paths were hardcoded earlier? You can either just live with that, run each binary and then move the *.gcda files afterwords. Or you can set environmental variables to make the programs use different directories. GCOV_PREFIX_STRIP will chop of directories from the start of the full path, e.g. GCOV_PREFIX_STRIP=1 and /tmp/merge-gcov/lib.gcda becomes merge-gcov/lib.gcda. The GCOV_PREFIX variable will be put in front of the path.

export GCOV_PREFIX=/tmp/merge-gcov/gcov-five
export GCOV_PREFIX_STRIP=2
# Gives /tmp/merge-gcov/gcov-five/lib.gcda
./five 1
./five 12

# Repeat for ten
g++ -O0 --coverage -c lib.cpp -DDISABLE_DEBUG
g++ -O0 --coverage -o ten lib.o ten.cpp
mkdir gcov-ten
mv *.gcno gcov-ten/.
export GCOV_PREFIX=/tmp/merge-gcov/gcov-ten
./ten 1
./ten 12

# Combine 
gcov-tool merge --outdir merged gcov-five gcov-ten


标签: c++ gcov lcov