Search code examples
c++code-coveragegcovlcovcontrol-flow-graph

Understand control flow graph in lcov branch coverage output


I am trying to improve my unit tests by inspecting my current code coverage percentage. I am using gcov and lcov to produce a HTML report of the coverage results. However, I am having problems understanding some of the output. I know that a + indicates that a branch was taken and a - that it was not taken.

131                 :          8 :         QString toString() const
132                 :            :         {
133 [ +  - ][ +  - ]:          8 :             return ((negative && !isZero()) ? "-" : "") + QString::number(sec) + "." + QString::number(nano).rightJustified(9, '0');
    [ +  - ][ +  + ]
    [ +  - ][ +  - ]
    [ +  - ][ +  - ]
            [ +  - ]
134                 :            :         }

Variable negative is of type bool as is the return value of method isZero. So I would expect four branches here but I don't see how the output maps to this expectation. So how is the output to be interpreted? Tooltips when hovering over the +/- signs tell me that branches 3, 6, 9, 11, 12, 14, 17, 20, 23 and 26 were taken while branches 4, 7, 10, 15, 18, 21, 24 and 27 were never taken.


Solution

  • Please refer the answer [here] as below[1]:

    The short form, is that lcov's interaction with branching, especially with inline calls, can find some of the hidden branches in the code, that are beyond your control.

    For example, templated functions (of which std::cout << "foo" is one) can be inlined efficiently by the compiler. Which means that any branches present in that code can also be seen by the compiler. This of course lets the compiler do a more thorough optimization. This is the branch you are seeing here. It's not in code you wrote, but in code the template instantiated for you.

    In particular, most formatted output functions on basic_ostream are going to ensure that the stream is in a good state prior to doing the formatting or inserting work.

    When I'm doing branch analysis, I tend to disregard branch misses on functions like this. There are several similar places in C++ which branches are detected by gcov/lcov, but are only implementation details. I try to focus on the branches under my control.

    the reason here is the same, QString::number() is a static function and it calls a inline function [QString::setNum][2] which had many branch inside.

    Can check below as an example to illustrate this:

    foo.h

    extern void foo(int);
    
    class Bar {
        public:
            inline bool doCheckInt(int a) { return a > 0 && a < 2; }
    
    
            static bool checkParam(int a) {
                Bar bar;
                return bar.doCheckInt(a);
    
            }
    };
    

    foo.cpp

    #include "foo.h"
    #include <iostream>
    
    void foo(int num)
    {
        if (Bar::checkParam(num)) {
            std::cout << "when num is equal to 1..." << std::endl;
        } else if (num == 2){
            std::cout << "when num is equal to 2..." << std::endl;
        } else {
            std::cout << "when num is equal to "<< num << std::endl;
        }
    }
    

    main.c

    #include <stdio.h>
    #include "foo.h"
     
    
     int main(void)
     {
        printf("Start calling foo() ...\n");
        foo(1);
        foo(2);
        return 0;
     }
    

    after compile and generate code coverage report(relate method refered [here][3]), can get below coverage report for function foo():

    [![foo_coverage][4]][4]

    we can find that foo have total 8 branchs(2 X 2 X 2), and the two foo(1), foo(2) hit 5 of the branchs. [1]: https://stackoverflow.com/a/69164983/4277805 [2]: https://github.com/radekp/qt/blob/master/src/corelib/tools/qstring.h [3]: https://shenxianpeng.github.io/2021/07/gcov-example/ [4]: https://i.sstatic.net/n6huJ.png