LCOV/GCOV 分支覆盖,C++ 生成分支遍布各处

Posted

技术标签:

【中文标题】LCOV/GCOV 分支覆盖,C++ 生成分支遍布各处【英文标题】:LCOV/GCOV branch coverage with C++ producing branches all over the place 【发布时间】:2017-06-19 14:48:21 【问题描述】:

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

将分支覆盖与 C++ 结合使用会使报告在整个地方都出现分支。我们怀疑(正如搜索问题所表明的那样)大多数异常处理代码会创建这些“隐藏分支”。而 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%。 ;)

【问题讨论】:

正如 maxschlepzig 所解释的,您实际上想要“部分分支覆盖”,因为您明确想要排除一些异常部分(这通常很有用)。要实现这一点,您可能需要过滤结果,请参阅 maxschlepzig 的答案。 【参考方案1】:

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

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

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

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

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

if (p == nullptr) 
  return false;

return true;

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

lcov 生成的genhtml 的输入是相对简单的文本格式(参见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 )

【讨论】:

我在使用使用 GCC 7.3.1 的 GNAT Community 2018 编译 Ada 以进行覆盖时遇到了同样的问题,当最初使用 --coverage 编译并且没有优化时分支覆盖被炸毁。添加 -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls 选项减少了记录的 Ada 分支覆盖率。【参考方案2】:

GCC 会添加一堆异常处理的东西。尤其是当你进行函数调用时。

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

我应该补充一点,您可能只希望这些标志用于测试。 所以是这样的:

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

【讨论】:

感谢您的发现。但是当我们使用异常时,“-fno-exceptions”是没有选择的。如果您使用异常并禁用它们,GCC 甚至拒绝首先编译代码。看起来,这种行为没有真正的解决方案。 好的。但这就是造成问题的原因。您可以在不同的单元中分解您的异常,并且只链接到正在测试的文件。 lcov/gcov 库在程序集级别检查分支。您可以查看程序集输出以查看它缺少的分支(即由异常处理引起的分支)。查看输出程序集: objdump -S --disassemble [your_exe] > asm_output【参考方案3】:

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

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

【讨论】:

很久以前,我们一直在前进。并放弃了整个项目(好吧,客户放弃了它:D)但是,很高兴看到,仍有一些事情发生!【参考方案4】:

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

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

但是,我不确定您的项目中是否有任何要求,即您的代码只能使用 O0,而不是 O1O2O3 甚至 Os

【讨论】:

优化器可能会省略 EH 代码,它可以看到不会引发异常,但是您是否能够在较大的项目中获得令人满意的结果,或者在使用会引发堆耗尽的 STL 容器时?【参考方案5】:

我刚刚遇到了同样的问题,由于异常,我想摆脱这些未发现的分支。我找到了适合我的解决方案:

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

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

【讨论】:

【参考方案6】:

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

我最近获准将我的 lcov 更新上传到上游。你可以在https://github.com/henry2cox/lcov找到他们

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

一个外围附加更改是添加“过滤”,如上所述 - 主要是因为没有它,分支覆盖似乎无法用于 C++ 代码。 您可以在 lcovutil::ReadCurrentSource::containsConditional 方法中找到(公认的 hacky 且容易绕过的)正则表达式 - 近行 .../bin/lcovutil.pm:1122

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

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

【讨论】:

你好亨利;欢迎来到堆栈溢出。您之前的答案(不是评论)已被删除,因为它没有任何有用的信息。这个答案至少包含一个指向 GitHub 存储库的链接,所以这更有用。查看Help Center 了解更多信息和关于在此处回答的建议。

以上是关于LCOV/GCOV 分支覆盖,C++ 生成分支遍布各处的主要内容,如果未能解决你的问题,请参考以下文章

git分支覆盖合并,以及创新分支并且推送

代码覆盖率/分支覆盖率推荐值

git 分支覆盖master分支

Git远程仓库及分支管理

git 一个分支完全覆盖另一个分支

如何将一个远程分支覆盖而不是合并到另一个分支?