I'm using GCC 4.9 with GCOV to get code and branch coverage. However, the results for branch coverage are utterly useless for my C++ code. It seems GCC inlines templates despite using all -fno-*-inline
flags I know of.
Here is a small example application that illustrates the problem:
#include <string>
#include <iostream>
using namespace std;
int main() {
string foo;
foo = "abc";
cout << foo << endl;
}
I compile the program with g++ -O0 -fno-inline -fno-inline-small-functions -fno-default-inline --coverage -fprofile-arcs test.cpp -o test
After running test
, gcovr -r . -b
prints:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Branches Taken Cover Missing
------------------------------------------------------------------------------
test.cpp 14 7 50% 7,8,9,10
------------------------------------------------------------------------------
TOTAL 14 7 50%
------------------------------------------------------------------------------
There is not a single branch in our main
function. For example, line 7 contains string foo;
. It seems the constructor of std::basic_string<...>
has some if statement in it, but that's not useful information when looking at the coverage of main
.
The problem is that all these inlined branches sum up and the branch coverage calculated for my actual unit tests are about 40% as a result. I'm interested in the branch coverage of my code, as opposed to how much branches I hit in the C++ standard library.
Is there any way to completely shut down inlining in the compiler or to tell GCOV to not consider inlined branches? I couldn't find any guide on the GCOV homepage or someplace else regarding that topic.
Any help is much appreciated.
Lcov is a graphical front-end for gcov. It collects gcov data for multiple source files and creates HTML pages containing the source code annotated with coverage information. It also adds overview pages for easy navigation within the file structure. Lcov supports statement, function, and branch coverage measurement.
gcov file is produced for each source (or header) file containing code, which was compiled to produce the data files. The mangledname part of the output file name is usually simply the source file name, but can be something more complicated if the ' -l ' or ' -p ' options are given.
The "Branch" column shows the branches that have been executed (✔) or skipped (x). This corresponds to the entries in the GCOV file. The "Exec" column shows how often a certain line has been executed. The "Source" column shows the actual source code.
Well, you should always double-check your expectations. Thanks a lot @Useless for pointing me to the gcov
output itself. You weren't quite right, though: the branches are not attributed to the test.cpp
file. Running gcovr
with -k
and looking at all the intermediate files shows that gcov
correctly produces files such as #usr#include#c++#4.9#bits#basic_string.h.gcov
that show coverage for the C++ standard library side of things.
However, the reason for all the branches in test.cpp
is not inlining. It's exceptions. Each call into the standard library is a branch because of potential exceptions (e.g. std::bad_alloc
). Adding -fno-exceptions
to the compiler flags gives the following output:
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Branches Taken Cover Missing
------------------------------------------------------------------------------
test.cpp 4 2 50% 10
------------------------------------------------------------------------------
TOTAL 4 2 50%
------------------------------------------------------------------------------
Digging deeper into the gcov
output via cat foo.cpp.gcov
prints:
-: 0:Source:test.cpp
-: 0:Graph:/home/neverlord/gcov/test.gcno
-: 0:Data:/home/neverlord/gcov/test.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:#include <string>
-: 2:#include <iostream>
-: 3:
-: 4:using namespace std;
-: 5:
function main called 1 returned 100% blocks executed 100%
1: 6:int main() {
1: 7: string foo;
call 0 returned 1
1: 8: foo = "abc";
call 0 returned 1
1: 9: cout << foo << endl;
call 0 returned 1
call 1 returned 1
call 2 returned 1
function _GLOBAL__sub_I_main called 1 returned 100% blocks executed 100%
function _Z41__static_initialization_and_destruction_0ii called 1 returned 100% blocks executed 100%
4: 10:}
call 0 returned 1
branch 1 taken 1 (fallthrough)
branch 2 taken 0
branch 3 taken 1 (fallthrough)
branch 4 taken 0
Sorry for the noise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With