Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lcov woes: weird duplicate constructor marked as not covered & function not marked as covered, even though its lines have been executed

On my quest to learn more about automated testing by getting a small C++ test project up & running with 100% coverage, I've run into the following issue - even though all my actual lines of code and all the execution branches are covered by tests, lcov still reports two lines as untested (they only contain function definitions), as well as a "duplicate" constructor method that is supposedly untested even though it matches my "real" constructor (the only one ever defined & used) perfectly.

(Skip to EDIT for the minimal reproduction case)

If I generate the same coverage statistics (from the same exact source, .gcno & .gcda files) using the gcovr python script and pass the results to the Jenkins Cobertura plugin, it gives me 100% on all counts - lines, conditionals & methods.

Here's what I mean:

The Jenkins Cobertura Coverage page: http://gints.dyndns.info/heap_std_gcovr_jenkins_cobertura.html (everything at a 100%).

The same .gcda files processed using lcov: http://gints.dyndns.info/heap_std_lcov.html (two function definition lines marked as not executed even though lines within those functions are fully covered, as well as functions Hit = functions Total - 1).

The function statistics for that source file from lcov: http:// gints.dyndns.info/heap_std_lcov_func (shows two identical constructor definitions, both referring to the same line of code in the file, one of them marked hit 5 times, the other 0 times).

If I look at the intermediate lcov .info file: http://gints.dyndns.info/lcov_coverage_filtered.info.txt I see that there are two constructor definitions there too, both are supposed to be on the same line: FN:8,_ZN4BBOS8Heap_stdC1Ev & FN:8,_ZN4BBOS8Heap_stdC2Ev.

Oh, and don't mind the messiness around the .uic include / destructor, that's just a dirty way of dealing with What is the branch in the destructor reported by gcov? I happened to be trying out when I took those file snapshots.

Anyone have a suggestion on how to resolve this? Is there some "behind-the-scenes" magic the C++ compiler is doing here? (An extra copy of the constructor for special purposes that I should make sure to call from my tests, perhaps?) What about the regular function definition - how can the definition line be untested even though the body has been fully tested? Is this simply an issue with lcov? Any suggestions welcome - I'd like to understand why this is happening and if there's really some functionality that my tests are leaving uncovered and Cobertura is not complaining about ... or if not, how do I make lcov understand that?

EDIT: adding minimal repro scenario below

lcov_repro_one_bad.cpp:

#include <stdexcept>
class Parent {
public:
    Parent() throw() { }
    virtual void * Do_stuff(const unsigned m) throw(std::runtime_error) =0;
};

class Child : public Parent {
public:
    Child() throw();
    virtual void * Do_stuff(const unsigned m)
        throw(std::runtime_error);
};

Child::Child()
    throw()
    : Parent()
{
}

void * Child::Do_stuff(const unsigned m)
    throw(std::runtime_error)
{
    const int a = m;
    if ( a > 10 ) {
        throw std::runtime_error("oops!");
    }
    return NULL;
}

int main()
{
    Child c;
    c.Do_stuff(5);
    try {
        c.Do_stuff(11);
    }
    catch ( const std::runtime_error & ) { }
    return 0;
}

makefile:

GPP_FLAGS:=-fprofile-arcs -ftest-coverage -pedantic -pedantic-errors -W -Wall -Wextra -Werror -g -O0

all:
    g++ ${GPP_FLAGS} lcov_repro_one_bad.cpp -o lcov_repro_one_bad
    ./lcov_repro_one_bad
    lcov --capture --directory ${PWD} --output-file lcov_coverage_all.info --base-directory ${PWD}
    lcov --output-file lcov_coverage_filtered.info --extract lcov_coverage_all.info ${PWD}/*.*
    genhtml --output-directory lcov_coverage_html lcov_coverage_filtered.info --demangle-cpp --sort --legend --highlight

And here's the coverage I get from that: http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_bad.cpp.gcov.html

As you can see, the supposedly not-hit lines are the definitions of what exceptions the functions may throw, and the extra not-hit constructor for Child is still there in the functions list (click on functions at the top).

I've tried removing the throw declarations from the function definitions, and that takes care of the un-executed lines at the function declarations: http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_v1.cpp.gcov.html (the extra constructor is still there, as you can see).

I've tried moving the function definitions into the class body, instead of defining them later, and that gets rid of the extra constructor: http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_v2.cpp.gcov.html (there's still some weirdness around the Do_stuff function definition, though, as you can see).

And then, of course, if I do both of the above, all is well: http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_ok.cpp.gcov.html

But I'm still stumped as to what the root cause of this is ... and I still want to have my methods (including the constructor) defined in a separate .cpp file, not in the class body, and I do want my functions to have well defined exceptions they can throw!

Here's the source, in case you feel like playing around with this: http://gints.dyndns.info/lcov_repro_src.zip

Any ideas?

Thanks!

like image 791
DeducibleSteak Avatar asked Oct 07 '22 09:10

DeducibleSteak


1 Answers

OK, after some hunting around & reading up on C++ exception declarations, I think I understand what's going on:

  • As far as the un-hit throw declarations are concerned, it seems everything is actually correct here: function throw declarations are supposed to add extra code to the output object file that checks for illegal (as far as the throw declaration is concerned) exceptions thrown. Since I was not testing the case of this happening, it makes sense that that code was never executed, and those statements were marked un-hit. Although the situation is far from ideal here anyway, at least one can see where this is coming from.

  • As far as the duplicate constructors are concerned, this seems to be a known thing with gcc with a longstanding discussion (and various attempts at patches to resolve the resulting object code duplication): http://gcc.gnu.org/bugzilla/show_bug.cgi?id=3187 - basically, there are two versions of the constructor created - one for use with this class, and one for use with child classes, and you need to exercise both, if you want 100% coverage.

like image 125
DeducibleSteak Avatar answered Oct 10 '22 02:10

DeducibleSteak