LCOV / GCOV分支覆盖范围,C ++生成分支机构

时间:2017-02-02 13:42:46

标签: c++ code-coverage gcov lcov

我们正在使用LCOV / GCOV来为我们的项目提供测试覆盖率。最近我们尝试另外启用分支覆盖。但看起来,这只是不会产生我们从高级开发人员的观点中得到的结果。

在C ++中使用branch-coverage会在整个地方分支报表。我们怀疑(正如搜索问题所示)主要是异常处理代码创建这些“隐藏分支”。 GCOV / LCOV似乎没有跳过这些。

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

目前我们使用的是Ubuntu 16.04。用:

  • gcc v5.4
  • lcov& genhtml v1.12

我们的生产代码是在启用c ++ 11的情况下构建的。最小的例子不是在启用c ++ 11的情况下构建的,但是当我们尝试使用所有不同的选项(c ++标准,优化,-fno-exceptions)时,我们没有得到可通过的结果。

有人有想法吗?的窍门?我们使用的是错误的方式吗?这是否 - 正如其他地方所述 - 真正预期的行为?

更新

正如gcc-help mailing list所指出的,这些“隐藏的分支”是由于异常处理而发生的。因此,将-fno-exceptions开关添加到gcc会为“简单”程序生成100%的分支覆盖率。但是当禁用异常时,gcc拒绝编译实际使用异常的代码(例如try-catch,throw)。因此,对于实际生产代码,这不是一种选择。看起来,在这种情况下,你必须简单地宣布约50%的覆盖率为新的100%。 ;)

6 个答案:

答案 0 :(得分:12)

问题是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处理步骤和example project where the resulting trace file is uploaded to codecov(codecov不使用genhtml,但可以导入lcov跟踪文件并显示分支覆盖数据。)

(非)的替代品

  • 禁用异常只是当您的C ++代码不使用任何
  • 时的选项
  • 使用类似-O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls的内容进行编译会略微减少记录的分支覆盖数据的数量,但不会太多
  • Clang支持GCOV样式覆盖率集合,但也实现了一种称为'source-based code coverage'的不同方法(使用-fprofile-instr-generate -fcoverage-mapping进行编译,使用llvm-profdatallvm-cov进行后处理)。但是,该工具链不支持分支覆盖数据(截至2017-05-01)。
  • 默认情况下,lcov + genhtml不生成分支覆盖数据 - 有时你并不真正需要它(见上文),因此,它是一个禁用它的选项(参见--rc lcov_branch_coverage=0和{ {1}})

答案 1 :(得分:2)

GCC将添加一堆异常处理内容。特别是当您进行功能调用时。

您可以通过添加-fno-exceptions -fno-inline来解决此问题 你的构建。

我应该补充一点,你可能只想要这些标志进行测试。 所以像这样:

g++ -O0 --coverage -fno-exceptions -fno-inline main.cpp -o test-coverage 

答案 2 :(得分:1)

正在解决这些限制的PR。 https://github.com/linux-test-project/lcov/pull/86

This paper解释了实现背后的理论。

Result

答案 3 :(得分:0)

您可以尝试g++ -O3 --coverage main.cpp -o testcov。我已经在你的文件上使用g ++ - 5.4尝试了这个并且它运行正常,这意味着使用标准的printf和字符串调用会丢弃异常。

实际上,O0以外的任何优化标志都会导致gcov忽略为CPP文件中的普通标准库调用生成的异常。我不确定是否也会优化正常的异常(我不这么认为,但还没有尝试过)。

但是,我不确定您的项目中是否有任何要求,只有O0应该与您的代码一起使用而不是O1O2O3甚至{{1 }}。

答案 4 :(得分:0)

我刚遇到同样的问题,我想摆脱这些未被覆盖的分支,因为例外。我为我找到了合适的解决方案:

我只是避免使用&#34;抛出异常&#34;在我的代码中,我想直接覆盖。我设计了一个类,它提供了一些抛出异常的方法。由于异常类并不复杂,我并不关心覆盖范围,所以我只是用LCOV_EXCL_START和LCOV_EXCL_STOP排除所有内容。或者,我也可以仅为该异常类关闭分支覆盖。

我承认,这不是一个简单的解决方案,但出于我的目的,它是完美的 由于其他原因(我需要灵活的异常类,所以我可以提供不同的实现:一旦抛出异常,另一次做其他事情)。

答案 5 :(得分:0)

我做了一些工作,向geninfo / lcov / genhtml添加过滤,以删除C / C ++代码中与似乎不包含任何条件的源代码行相关的分支-使用一些相对简单的正则表达式。过滤器似乎可以在我们的代码库中/使用已修改的lcov工具的产品上运行。过滤器并不完美,容易被血腥的用户击败。

我最近获得了lcov更新上游的批准。您可以在https://github.com/henry2cox/lcov

上找到它们

此版本增加了对差异覆盖以及日期和所有者分级的支持。

如上所述,外围的一项附加更改是添加了“过滤”功能-主要是因为如果没有分支覆盖,C ++代码似乎无法使用。
您可以在方法lcovutil :: ReadCurrentSource :: containsConditional-在line ... / bin / lcovutil.pm:1122

附近找到(很容易破解,并且很容易绕开)正则表达式

虽然不完美:此hack似乎适用于我们的代码。您的里程可能会有所不同。

这是用perl / 5.12.5和gcc / 8.3.0和9.2.0进行测试的。它也可以与其他版本一起使用(如果您发现可移植性问题,请告诉我,以便我进行修复)。