LCOV/GCOV 分支覆盖,C 生产分支遍布各地

新手上路,请多包涵

我们正在使用 LCOV/GCOV 来生成我们项目的测试覆盖率。最近我们尝试另外启用分支覆盖。但看起来,这并没有产生我们从高级开发人员视图中预期的结果。

将分支覆盖与 C++ 一起使用会使报告在整个地方都出现分支。我们怀疑(正如搜索问题所表明的那样)大多数异常处理代码会创建这些“隐藏分支”。 GCOV/LCOV 似乎并没有跳过这些。

我创建了一个小测试项目来显示问题: https ://github.com/ghandmann/lcov-branch-coverage-weirdness

目前我们使用 Ubuntu 16.04。和:

  • 海合会 v5.4
  • lcov & genhtml v1.12

我们的生产代码是在启用 c++11 的情况下构建的。最小的示例不是在启用 c++11 的情况下构建的,但是当我们对所有不同的选项(c++ 标准、优化、 -fno-exceptions )进行了一些试验时,我们没有得出一个可以接受的结果。

有人有什么想法吗?小费?我们是否以错误的方式使用任何东西?这是 - 如其他地方所述 - 真的是预期的行为吗?

更新:

正如 gcc-help 邮件列表中 所指出的那样,这些“隐藏分支”是由于异常处理而出现的。因此,添加 -fno-exceptions 切换到 gcc 会为“简单”程序产生 100% 的分支覆盖率。但是当异常被禁用时,gcc 拒绝编译实际使用异常的代码(例如try-catch、throw)。因此,对于真正的生产代码,这不是一个选项。看起来,在这种情况下,您必须简单地将 ~50% 的覆盖率声明为新的 100%。 ;)

原文由 Sven Eppler 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.6k
2 个回答

问题是 GCC 还记录每一行的分支信息,其中可能由于某些抛出的异常而退出范围(例如,在 Fedora 25 上使用 GCC 6.3.1 和 lcov 1.12)。

这些信息的价值是有限的。分支覆盖数据的主要用例是复杂的 if 语句,它们具有如下的多子句逻辑表达式:

 if (foo < 1 && (bar > x || y == 0))

假设您有兴趣验证您的测试套件是否也涵盖 bar > x 案例,或者您是否只有 y == 0 的测试用例。

为此,分支覆盖数据收集和 lcov 的 genhtml 的可视化很有用。对于简单的 if 语句,如

if (p == nullptr) {
  return false;
}
return true;

您不需要分支覆盖率数据,因为您可以通过查看以下几行的覆盖率来查看分支是否被采用。

genhtml 生成的 lcov 的输入是相对简单的文本格式(参见 geninfo(1) )。因此,您可以对其进行后处理,以便删除所有以 BRDA: 开头且不属于 if 语句的行。参见例如 filterbr.py 它实现了这种方法。另请参阅 gen-coverage.py 了解其他 lcov/genhtml 处理步骤和 将生成的跟踪文件上传到 codecov 的示例项目(codecov 不使用 genhtml 但可以导入 lcov 跟踪文件和显示分支覆盖数据)。

(非)替代品

  • 只有当您的 C++ 代码不使用任何异常时,才可以选择禁用异常
  • -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls 之类的东西编译会在一定程度上减少记录的分支覆盖数据的数量,但不会太多
  • Clang 支持 GCOV 样式覆盖收集,但也实现了一种不同的方法,称为 “基于源的代码覆盖” (使用 -fprofile-instr-generate -fcoverage-mapping 编译和使用 llvm-profdatallvm-cov 进行后处理 --- )。但是,该工具链不支持分支覆盖率数据(截至 2017-05-01)。
  • 默认情况下,lcov+genhtml 不会生成分支覆盖率数据 - 有时您并不需要它(见上文),因此,可以选择禁用它(参见 --rc lcov_branch_coverage=0--no-branch-coverage )

原文由 maxschlepzig 发布,翻译遵循 CC BY-SA 3.0 许可协议

新手上路,请多包涵

C++的lcov隐藏未覆盖分支确实让人头疼,十分感谢指出了主要原因,最后我们使用了下述方法解决:

#ifndef UNIT_TEST
#define Try  try
#define Catch(aa)      catch(aa)  
#define Throw(ex)      throw(ex)
#define What(w)        ((w).what())
#else 
#define Try  
#define Catch(aa)  if(0) 
#define Throw(ex)
#define What(w)       ("")
#endif

然后在gtest的编译脚本中启用了 -fno-exceptions -DUNIT_TEST 编译选项解决了令人讨厌的未覆盖分支问题

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题