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

问题描述 投票:16回答:4

我们正在使用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%。 ;)

c++ code-coverage gcov lcov
4个回答
12
投票

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

此信息的价值有限。分支覆盖数据的主要用例是复杂的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--no-branch-coverage

2
投票

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

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

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

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

0
投票

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

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

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


0
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.