I'm trying to use gcovr (version 5.0) to collect branch coverage metrics, but I see this error in many places in my code. I am certain the code it running over both branches in many places, but instead of 2/2, I get a 0/2. I notice this is usually in template code, but can't find reference to any known issues with templates in gcovr, and it works for a lot of other template code too. I am not sure how to make a "smallest example which shows the problem" as it's only become visible when working on a larger project.
My question is really: how do I debug my gcovr usage and resolve the issue? I need 100% coverage for my project, and just having 100% without metrics proving it isn't enough. We need proof.
for the snippet above, the xml output seems to indicate the gathering step is going wrong, but I don't know where else to look.
<line number="194" hits="655482" branch="true" condition-coverage="0% (0/2)">
<conditions>
<condition number="0" type="jump" coverage="0%"/>
</conditions>
</line>
We're using the flags --exclude-unreachable-branches --exclude-throw-branches
UPDATE:
It seems this only happens if the template code is the last instantiated template in a file and the template has tests which cover different template specifications. I have an example which shows that merely adding a procedure which refers to a later template suddenly makes the prior template show branches, and if I remove tests such that only one type is tested, then the branches reappear too.
Tested with g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 with the following command line: g++ test.cpp -o test -ftest-coverage -fprofile-arcs -O0
then running gcovr with gcovr --gcov-executable=/usr/bin/gcov --print-summary --exclude-unreachable-branches --exclude-throw-branches --h
and it seems somehow I am running gcovr 4.2 for this example, not 5.0, which might be part of why the small example issue does not reproduce 100% the same as it does in our main build pipeline which is definitely using gcovr 5.0 as we are using the exclude filter which was only introduced in 5.0. I will try to get 5.0 working with this example and see what the difference is there.
example.hpp
#pragma once
#include <cstdint>
#include <cstdio>
class Example
{
public:
template <typename T>
static T* firstProcedure(int32_t n)
{
if (n <= 0)
return nullptr;
if (n < sizeof(T))
{
printf("uh oh\n");
}
else
{
printf("ok\n");
}
return nullptr;
}
template <typename T>
static T* secondProcedure(int32_t n)
{
if (n <= 0)
return nullptr;
if (n < sizeof(T))
{
printf("uh oh\n");
}
else
{
printf("ok\n");
}
return nullptr;
}
template <typename T>
static T* lastProcedure(int32_t n)
{
if (n <= 0)
return nullptr;
if (n < sizeof(T))
{
printf("uh oh\n");
}
else
{
printf("ok\n");
}
return nullptr;
}
};
test.cpp
#include "example.hpp"
#include <cstdio>
void Test1()
{
uint32_t* a = Example::firstProcedure<uint32_t>(1);
uint8_t* b = Example::firstProcedure<uint8_t>(1);
}
void Test2()
{
uint32_t* a = Example::secondProcedure<uint32_t>(1);
#if 1 // disable this and branch coverage can be seen.
uint8_t* b = Example::secondProcedure<uint8_t>(1);
#endif
}
void LastTest()
{
#if 0 // enable this to see branch coverage on secondProcedure.
uint32_t* a = Example::lastProcedure<uint32_t>(1);
#endif
}
int main() noexcept
{
Test1();
Test2();
//LastTest(); // don't need to run this to get the benefit
return 0;
}