1

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.

image showing a clearly taken branch, but showing 0% or 0/2 branch coverage

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;
}
Richard Fabian
  • 697
  • 1
  • 5
  • 18
  • That's a tricky one. Does the issue persist when you update to the most recent version, currently gcovr-5.2? Templated code is tricky to handle. While there were no known issues about this is 5.0, the gcov-parser was rewritten since them so behaviour might have changed subtly. But I suspect this code is actually in a header file that's included in multiple places, right? Does the issue persist if you move just this conditional into a separate non-inline, non-static function outside of the header? – amon Sep 05 '22 at 15:23
  • Hi, sorry about the time taken to get back to testing this. Yes, it still persists with 5.2. Yes, the code is in a header file included in more than one place. Unfortunately, I can't extract the method out of the header as it relies on the template type for a callback. Migrating to using a std::function (which I can't use as it allocates memory for the closure of the lambdas) does indeed fix the issue, so it is to do with it being template code. – Richard Fabian Sep 08 '22 at 13:23
  • I've had to resort to evil to solve this - we're now using a strategy object to avoid the template, just so we can get the coverage. I dislike this solution, but at least we have a solution. – Richard Fabian Sep 12 '22 at 12:30
  • With your edit, this increasingly looks like compiler shenanigans, though I can't rule out a bug in gcovr. I'd love to see the raw .gcov files for these template sections. It would also be quite relevant which compiler version you're using (e.g. Clang-14 or GCC-12). – amon Sep 14 '22 at 09:46
  • The sample, even though I was testing it with gcovr 4.2, produces the same problems when run with gcovr 5.2, so I think the example is still useful. – Richard Fabian Sep 14 '22 at 14:49

0 Answers0