1

I'm using gcovr for the first time and have hit a confusing issue with this code:

    for (int i = 0; i < 4; i++)
    {
        bool a = i & 1;
        bool b = i & 2;

        if (a && b)
            printf("a and b\n");
        else if (a && !b)
            printf("a not b\n");
        else
            printf("the other two\n");
    }

(The code works as you'd expect, so I'm not going to paste the output.)

However, gcovr decides I don't have full branch coverage:

✓✓     5 for (int i = 0; i < 4; i++)
         {
       4     bool a = i & 1;
       4     bool b = i & 2;

✓✓✓✓  4     if (a && b)
       1        printf("a and b\n");
✓✓✓✗  3      else if (a && !b)
       1         printf("a not b\n");
              else
       2         printf("the other two\n");
          }

Clearly the one of the four permutations is not handled by the else if, but only because it's been handled by the first if.

I'm grumpy because the net result is less than 100% branch coverage. Is this just "the way" or have I fatally misunderstood something?

Edd Inglis
  • 1,067
  • 10
  • 22
  • If you want 100% branch coverage, you have to have your tests call the function with values that causes it to hit every branch.. – Jesper Juhl Jan 29 '20 at 17:03
  • Did this one answer your question: https://stackoverflow.com/questions/9475970/gcovr-branch-coverage-for-simple-case?rq=1 – Klaus Jan 29 '20 at 17:29
  • @JesperJuhl, but that's just my point: the code _does_ hit every branch. – Edd Inglis Jan 29 '20 at 17:52
  • Thanks for the suggestion, @Klaus but that looks like quite a different scenario – Edd Inglis Jan 29 '20 at 17:52
  • 1
    Note that gcovr takes coverage data directly from GCC's instrumentation, which is on the level of assembly code branches/jumps. But whether the compiler inserts unnecessary branches can also depend on your compiler's optimization level! It's not guaranteed that you can hit all branches that the compiler inserts (especially in C++) – amon Jan 30 '20 at 09:06

2 Answers2

1

You may want to refactor:

if (a)
{
    if (b)
    {
        std::cout << "a and b\n";
    }
    else
    {
        std::cout << "a and not b\n";
    }
}
else
{
    std::cout << "not a\n";
}

In your posted code, the a is evaluated in two if statements.
The above example removes the else if condition.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • I'm not really familiar with `gcovr`. Why would this cause it to recognize all branches are being hit while the other code didn't? – scohe001 Jan 29 '20 at 17:10
  • Just a guess, my example is simpler to evaluate and parse. This is how I get around static analysis issues and code coverage issues. I'll let the compiler optimize the code (also, it appears more readable). – Thomas Matthews Jan 29 '20 at 17:14
  • I really don't want to refactor my code*, but I agree this looks like the 'right' solution, so I'll mark accordingly. [*the example is just a toy version of some much more involved production code] – Edd Inglis Jan 29 '20 at 17:53
  • @scohe001 Gcovr's branch coverage data describes the assembly-level control flow, but whether the compiler inserts a branch also depends on the compiler's optimization level. Gcovr cannot parse source code. With OP's original code and `-O0` optimizations the compiler would translate `if (a && !b) {…}` fairly literally as `if (a) { if (!b) { … } } ` – but the control flow prevents `!b` from ever being false when b is an `int`. – amon Jan 30 '20 at 09:16
0

I found a simple alternative to wholesale refactoring:

    for (int i = 0; i < 4; i++)
    {
        bool a = i & 1;
        bool b = i & 2;

        if (a && b)
            printf("a and b\n");
        else if (a /*&& !b*/)    // <--- comment out second condition (but leave it in place)
            printf("a not b\n");
        else
            printf("the other two\n");
    }

I like this approach because it still encapsulates the logic I want, without redundant (and hence problematic) checks.

Edd Inglis
  • 1,067
  • 10
  • 22