Reputation: 11
I am using SonarQube Server to display code coverage reports on a C++ project (using gcov to generate reports). For some files in my project, there are lines in SonarQube that display as "not covered by tests" when the gcov report shows otherwise (lines executed 100%).
Example gcov report (This has been condensed from my actual report):
-: 0:Source:/data/home/user/repos/test_projects/include/test.h
-: 1:namespace test {
-: 2:
-: 3:template <class DataT>
10: 4:std::unique_ptr<Test<DataT>>> create(std::istream&& input) {
-: 5:
10: 6: if (!input.good()) {
1*: 7: return false;
-: 8: }
-: 9:
.
. # Intermediate steps
.
-: 54:
9: 55: if (!result) {
1*: 56: return false;
-: 57: }
.
. # Intermediate steps
.
10*: 62:}
------------------
.
.
.
Using the above report, SonarQube Server shows "Fully covered by tests" on line 7, but "Not covered by tests "on line 56 when they both reported the same (1*). That is, they have the same total number of executions and have an unexpected basic block (still not entirely sure what that term means, but that is why the asterisk is there according to gcov).
I have a designated (googletest) unit test for each of the two conditions and they both pass. Furthermore, I am only using a single unit test executable to generate the coverage so there should not be colliding reports for different execution paths. I know SonarQube does not generate the coverage reports, and simply just interprets them. This led me to believe that this is an issue with my gcov configuration, but I am really just lost as to how SonarQube is interpreting the coverage of these 2 lines differently?
Other info: gcov (GCC) 11.2.1 20220127 (Red Hat 11.2.1-9)
Upvotes: 1
Views: 1268
Reputation: 57640
In the gcov text output format, each line is associated with an execution count. When that count is followed by an asterisk (like 1*
), this means that there were blocks of code associated with the line that weren't executed.
For example, a line might contain multiple blocks if there are control-flow operators like if
, &&
, or ? :
, but also when there are C++ lambdas.
Here, line 7 contains multiple blocks because you are inside a template. In the GCC coverage data model, each template instantiation results in separate functions. As a convenience, gcov aggregates the coverage data across template instantiations, but the fact remains that your test cases do not exercise some of the instantiations.
The gcov documentation shows the following example of a template specialization section:
1*: 7: Foo(): b (1000) {}
------------------
Foo<char>::Foo():
#####: 7: Foo(): b (1000) {}
------------------
Foo<int>::Foo():
1: 7: Foo(): b (1000) {}
------------------
Here, the constructor Foo
is inside a class template. The constructor is instantiated as two distinct functions, Foo<chars>::Foo()
and Foo<int>::Foo()
. Of those, Foo<chars>::Foo()
is uncovered. Thus, the aggregated count 1*
marks that the line is only partially covered.
This data can be interpreted in different ways. We can legitimately treat that line as covered, but also as uncovered. Some tools might be able to deal with template specialization sections elegantly, others not.
SonarQube does not document how they treat template specialization sections in gcov results. For example, it could be that they use the count from the last occurrence of each line in the data, in which case coverage data would only be used from the last template specialization. It could also be that they made a decision to treat a line as uncovered if it contains at least one uncovered block.
In some cases, preparing a SonarQube XML report with gcovr --sonarqube
might help. Gcovr has partial template specialization support, and will treat a line as covered if at least one template specialization covered it. In SonarQube documentation, the resulting XML files are documented as “generic test data”.
Upvotes: 0