LCOV 问题:奇怪的重复构造函数标记为未覆盖和函数未标记为已覆盖,即使它的行已被执行

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

本文关键字:记为 覆盖 执行 问题 函数 构造函数 LCOV      更新时间:2023-10-16

我想通过启动一个小型C++测试项目来了解更多关于自动化测试的信息;在100%覆盖的情况下运行时,我遇到了以下问题——即使我所有实际的代码行和所有执行分支都被测试覆盖了,lcov仍然报告有两行未经测试(它们只包含函数定义),以及一个"重复"构造函数方法,尽管它与我的"真实"构造函数(唯一定义和使用过的构造函数)完美匹配,但它应该是未经测试的。

(对于最小再现情况,跳至编辑)

如果我使用gcovr-python脚本生成相同的覆盖率统计数据(来自相同的确切来源,.gcno&.gcda文件),并将结果传递给Jenkins-Cobertura插件,它会在所有计数上给我100%的结果-行、条件和;方法。

我的意思是:

Jenkins Cobertura报道页面:http://gints.dyndns.info/heap_std_gcovr_jenkins_cobertura.html(一切都是100%)。

使用lcov处理的.gcda文件相同:http://gints.dyndns.info/heap_std_lcov.html(标记为未执行的两个函数定义行,即使这些函数中的行被完全覆盖,以及函数Hit=函数总计-1)。

lcov中该源文件的函数统计信息:http://gints.dyndns.info/heap_std_lcov_func(显示了两个相同的构造函数定义,都引用了文件中的同一行代码,其中一个标记为hit 5次,另一个为0次)。

如果我查看中间lcov.info文件:http://gints.dyndns.info/lcov_coverage_filtered.info.txt我看到那里也有两个构造函数定义,它们应该在同一行:FN:8,_ZN4BBOS8Heap_stdC1Ev&FN:8,_ZN4BBOS8堆_stdC2Ev。

哦,别介意.uic include/destructor周围的混乱,这只是一种肮脏的处理方式。gcov报告的析构函数中的分支是什么?当我拍摄这些文件快照时,我正好在试用。

有人对如何解决这个问题有什么建议吗?C++编译器在这里有什么"幕后"魔术吗?(可能是一个用于特殊目的的构造函数的额外副本,我应该确保从测试中调用它?)那么常规函数定义呢?即使主体已经完全测试过,定义行怎么能不测试呢?这只是lcov的问题吗?欢迎任何建议-我想了解为什么会发生这种情况,如果我的测试真的有一些功能没有被发现,Cobertura没有抱怨。。。或者,如果没有,我如何让lcov理解这一点?

编辑:在下方添加最小再现场景

lcov_prop_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

以下是我从中得到的报道:http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_bad.cpp.gcov.html

正如你所看到的,所谓的未命中行是函数可能抛出的异常的定义,Child的额外未命中构造函数仍然在函数列表中(单击顶部的函数)。

我已经尝试从函数定义中删除throw声明,这会处理函数声明中未执行的行:http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_v1.cpp.gcov.html(正如您所看到的,额外的构造函数仍然存在)。

我已经尝试将函数定义移动到类主体中,而不是稍后定义它们,这样就去掉了额外的构造函数:http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_v2.cpp.gcov.html(不过,正如您所看到的,Do_stuff函数定义仍然有一些奇怪之处)。

当然,如果我同时做到以上两项,一切都会好起来:http://gints.dyndns.info/lcov_repro_bin/lcov_coverage_html/gints/lcov_repro/lcov_repro_one_ok.cpp.gcov.html

但我仍然困惑于这件事的根本原因是什么。。。我仍然希望我的方法(包括构造函数)定义在一个单独的.cpp文件中,而不是在类主体中,我确实希望我的函数有定义良好的异常,它们可以抛出!

以下是来源,以防你想玩这个:http://gints.dyndns.info/lcov_repro_src.zip

有什么想法吗?

谢谢!

好吧,经过一番狩猎&阅读C++异常声明,我想我明白发生了什么:

  • 就未命中的抛出声明而言,这里似乎一切都是正确的:函数抛出声明应该向输出对象文件添加额外的代码,以检查抛出的非法(就抛出声明而言)异常。由于我没有测试这种情况,所以该代码从未执行过,并且这些语句被标记为未命中,这是有道理的。尽管无论如何,这里的情况远非理想,但至少有人能看出这是从哪里来的。

  • 就重复构造函数而言,这似乎是gcc的一件已知事情,经过长期的讨论(以及各种尝试修补程序来解决由此产生的对象代码重复):http://gcc.gnu.org/bugzilla/show_bug.cgi?id=3187-基本上,创建了两个版本的构造函数-一个用于此类,另一个用于子类,如果您想要100%的覆盖率,则需要同时使用这两个版本。