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 > 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-profdata
和 llvm-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,而不是 O1
、O2
、O3
甚至 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++ 生成分支遍布各处的主要内容,如果未能解决你的问题,请参考以下文章