4

I'm using gcov for the first time and I'm having a problem which is similar to the one reported in this thread. However, I wasn't able to solve my problem following the comments in that thread.

I'm trying to measure the coverage of a concrete test case in KMyMoney-4.6.4, namely "testConstructor" in the test file "mymoneyaccounttest.cpp". This test case is using the following method, which is in the header file "mymoneyaccount.h" and has executable code:

 const QString& institutionId(void) const {
    return m_institution;
  }

So, I build the program and the tests, and then execute: gcov mymoneyaccount.cpp

Among the coverage information displayed, I obtain:

... File 'mymoneyaccount.cpp' Lines executed:8.91% of 393 Creating 'mymoneyaccount.cpp.gcov'

File 'mymoneyaccount.h' Lines executed:25.00% of 4 Creating 'mymoneyaccount.h.gcov' ...

The coverage information in "mymoneyaccount.cpp.gcov" is ok. In contrast, "mymoneyaccount.h.gcov" shows:

    6:   81:class KMM_MYMONEY_EXPORT MyMoneyAccount : public MyMoneyObject, public MyMoneyKeyValueContainer
    ...
    -:  192:  const QString& institutionId(void) const {
    -:  193:    return m_institution;
    -:  194:  }
    ...
    -:  272:  int accountCount(void) const {
#####:  273:    return m_accountList.count();
    -:  274:  };

Maybe I'm wrong, but this result means that gcov is not considering "return m_institution;" as executable code while does it considering "return m_accountList.count()" as executable code. Moreover, it shows "6" in the line of the class declaration, so this file has coverage information but not what I was expecting.

I have to say that maybe the problem has to do with the names of the files and the directories:

1- The object files are created in "CMakefiles/kmm_mymoney.dir" ending with ".cpp.o", for instance, mymoneyaccount.cpp.o. Thus, the "gcno" and "gcda" files are created with the name "mymoneyaccount.cpp.gcno" and "mymoneyaccount.cpp.gcda" (of course, in the directory "CMakefiles/kmm_mymoney.dir").

2- When I execute: gcov -o CMakeFiles/kmm_mymoney.dir mymoneyaccount.cpp gcov gives the following error:

mymoneyaccount.gcno:cannot open notes file

So I have to rename those files: mv mymoneyaccount.cpp.gcno mymoneyaccount.gcno mv mymoneyaccount.cpp.gcda mymoneyaccount.gcda

Finally, I have another question. When I execute gcov in the file containing the test cases, I mean, "gcov mymoneyaccounttest.cpp" instead of "gcov mymoneyaccount.cpp", I also obtain a "mymoneyaccount.h.gcov" file, but the coverage information is even worse:

#####:   81:class KMM_MYMONEY_EXPORT MyMoneyAccount : public MyMoneyObject, public MyMoneyKeyValueContainer

Anyway, the question is: should I execute "gcov" on the implementation file "mymoneyaccount.cpp" or in the test file "mymoneyaccounttest.cpp"?

Sorry for the length. Thanks.

Community
  • 1
  • 1
pedret
  • 201
  • 2
  • 3
  • 7
  • 1
    [Possibly related](http://stackoverflow.com/questions/18661078/code-coverage-of-inline-function). Note that member functions defined in the class definition are implicitly inline. [See also gcov inline docs](https://gcc.gnu.org/onlinedocs/gcc/Gcov-and-Optimization.html). – Tony Delroy Sep 01 '15 at 11:30
  • Thank you very much Tony!! That link had the solution. I just changed the optimization from -O2 to -O0 and now the result is: 1: 192: const QString& institutionId(void) const { 1: 193: return m_institution; -: 194: } I have to note that to obtain "mymoneyaccount.cpp.gcov" I execute "gcov mymoneyaccount.cpp", but to obtain "mymoneyaccount.h.gcov" I have to execute "gcov mymoneyaccounttest.cpp". Do you know why? Both files include "mymoneyaccount.h". – pedret Sep 02 '15 at 12:41

3 Answers3

3

Thank you very much Tony!! That link had the solution. I just changed the optimization from -O2 to -O0 and now the result is:

1: 192: const QString& institutionId(void) const { 
1: 193:     return m_institution; 
-: 194: } 

I have to note that to obtain "mymoneyaccount.cpp.gcov" I execute "gcov mymoneyaccount.cpp", but to obtain "mymoneyaccount.h.gcov" I have to execute "gcov mymoneyaccounttest.cpp". Do you know why? Both files include "mymoneyaccount.h".

pedret
  • 201
  • 2
  • 3
  • 7
  • Changing the optimizer level to see expected results is not always a correct solution! If your code uses normally O2 you have to inspect your code also with O2 also. Otherwise the execution path is not the same for production as with measuring/testing. Maybe with O2 code is inlined which is not used with O0! – Klaus Sep 02 '15 at 12:56
  • Thanks for your answer @Klaus. I have been googling about gcov and optimizations and found this post: [link](http://stackoverflow.com/questions/3432542/how-can-i-get-more-accurate-results-from-gcov) Do you think I should use some flags instead of changing the optimization level then? I require to have the full coverage information... – pedret Sep 02 '15 at 13:11
2

I want to complete the answer that I gave to @Klaus.

Both combinations "-O2 -g" and "-O2" gives the following coverage information for this method (I inserted the message only to be sure that the method was being executed in the tests):

-:  192:  const QString& institutionId(void) const {
1:  193:    std::cout << "Inside institutionId\n";
-:  194:    return m_institution;
-:  195:  }

That means that the lines 192 and 194 are not considered as executable, but only the cout. The flag -g does not affect the result (at least in this case).

If I build the program using "-O0", the result is:

1:  192:  const QString& institutionId(void) const {
1:  193:    std::cout << "Inside institutionId\n";
1:  194:    return m_institution;
-:  195:  }
pedret
  • 201
  • 2
  • 3
  • 7
1

has coverage information but not what I was expecting.

This is maybe an XY problem, because you think that your program executes in a way you believe but it simply runs totally different.

I will show you a very simple program:

class Example
{
    private:
        int value;

    public:
        Example(const Example& in): value(in.value)
        {
            std::cout << "Here we copy" << std::endl;
        }

        Example(int _value): value(_value)
        {
            std::cout << "Runs with default" << std::endl;
        }

        Example( Example&& in): value( in.value)
        {
            std::cout << "Moved away" << std::endl;
        }

        void show()
        {
            std::cout << "value " <<  value << std::endl;
        }
};

Example GetIt()
{
    return Example(2);
}

int main()
{
    Example ex(Example(1));
    ex.show();

    std::cout << "----------------" << std::endl;

    Example ex2( GetIt());
    ex2.show();
}

The output is:

Runs with default
value 1
Runs with default 
value 2

As you can see, there is no copy constructor called as you could expect and also there is no move constructor called. The construction is simply elided! So what do you expect gcov prints out? Simply it prints the call to the direct call to the constructor with int parameter. Nothing else!

You say:

I require to have the full coverage information...

There is simply no way to get more information as the truth!

If your expectation is completely different to what you get from your analyzing tool, check if your assumptions are true. For my expectations the tools gcov and gprof are really good and the instrumentation of code works nearly perfect. But code which will optimized away will not generate any debug/profiling/coverage information. With debug it is not always true, because newer versions of gcc generate some meta information which give a link to the original source code which enables to set breakpoints also if the code is not longer present in the executable. But for elided constructors there is nothing present also while debugging because the program flow is simply changed! No cout, no call to the constructor and no further information for profiling/coverage. The code is simply not executed and simply not in use. As you can see, the cout is not called! This simply tells you that the code is "modified" to operate in an other way as you have coded it!

My example shows only one aspect of optimizing! This is only eliding! There are a lot of other optimizing steps and strategies, also with O0! Remark that my code was compiled with O0 and the copy construction was moved away!

Hints:

  • If your debug/coverage/profiling looks different to your expectations: Look at the assembly to find out what is really executed and which source lines are involved. You can call objdump to get the source and assembler intermixed for this!

  • If you scale down your optimizer, keep in mind your program simply runs different code. Results can not be taken without a "intelligent" interpretation. This needs a bit of experience how to deal with such data.

  • If you are really have a need for "full" information, ask why!

  • If you think you get not enough information on a program execution path, you should be happy! Normally this exactly tells you, that your execution is shortened a bit by optimzing.

  • And if you get no profiling information on a single line/method: Simply look at the block which uses/calls this functionality. Mostly the time consumption is as short that further investigation is useless.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • Thank you very much @Klaus for your detailed answer. In my case, for a method that was actually executed (I even printed a message to be sure of that) gcov did not provide the expected coverage information. When I turned -O2 to -O0, that information was provided. The program has been compiled with the flag "-g" for debug. I can check if that is problem as you pointed out. Maybe "I require to have the full coverage information..." is not the correct: I require to have all the coverage information of the code that was executed. – pedret Sep 04 '15 at 10:40