如何告诉 gcov 忽略不可命中的 C++ 代码行?
Posted
技术标签:
【中文标题】如何告诉 gcov 忽略不可命中的 C++ 代码行?【英文标题】:How do I tell gcov to ignore un-hittable lines of C++ code? 【发布时间】:2011-04-03 01:48:51 【问题描述】:我正在使用 gcov 来衡量我的 C++ 代码中的覆盖率。我想达到 100% 的覆盖率,但受到以下事实的阻碍:有些代码行在理论上是不可命中的(需要实现但从未调用的方法,switch
的默认分支声明等)。这些分支中的每一个都包含一个 assert( false );
语句,但 gcov 仍将它们标记为未命中。
我希望能够告诉 gcov 忽略这些分支。有没有办法给 gcov 提供这些信息——通过注释源代码,或通过任何其他机制?
【问题讨论】:
是什么让你如此确定这些线条是不可击中的?如果是因为您无法命中它们,那么这就是您试图通过代码覆盖率找出的原因。 @deus-ex-machina399:不,不是因为我没能打到他们。这是由于对代码的理解和分析。当然,我可能错了,但我不是用代码覆盖率分析来试图验证我对源代码的理解。我正在使用代码覆盖率分析来验证我的测试套件的质量。 @doron,一个应该不可命中的代码示例是测试基础架构中的故障路径。当然,你可以不用这样的路径,但我有。 您还可以将 lcov 排除标记与 gcov 和 gcovr 一起使用。 【参考方案1】:请使用lcov。它隐藏了 gcov 的复杂性,产生良好的输出,允许每个测试的详细输出,具有简单的文件过滤和 - ta-taa - 已审查行的行标记:
来自 geninfo(1):
以下标记可被 geninfo 识别:
LCOV_EXCL_LINE 将排除包含此标记的行。 LCOV_EXCL_START 标记排除部分的开始。当前行是本节的一部分。 LCOV_EXCL_STOP 标记排除部分的结束。当前行不属于本节。
【讨论】:
有趣,我没听说过lcov。谢谢推荐! 终于开始测试这个......并且在升级到 lcov >= 1.8 的版本后,它很有魅力。谢谢! 我不喜欢“使用其他工具”形式的答案。我实际上想知道如何让 gcov 忽略行,并且没有切换到 lcov 的选项。所以这并不能回答问题。 还有一些额外的选项:ltp.sourceforge.net/coverage/lcov/geninfo.1.php【参考方案2】:一个名为gcovr 的工具可用于汇总 gcov 的输出,并且 (from at least version 3.4) 它支持与 lcov 相同的排除标记。
来自this answer:
以下标记可被 geninfo 识别:
LCOV_EXCL_LINE 将排除包含此标记的行。 LCOV_EXCL_START 标记排除部分的开始。当前行是本节的一部分。 LCOV_EXCL_STOP 标记排除部分的结束。当前行不属于本节。
您也可以将上面的'LCOV'
替换为'GCOV'
或'GCOVR'
。它们都有效。
【讨论】:
【参考方案3】:您能否介绍相关功能的单元测试,这些功能的存在只是为了通过直接攻击理论上不可攻击的代码路径来关闭 gcov?由于它们是单元测试,因此它们可能会忽略这种情况的“不可能”。他们可以调用从未调用过的函数,传递无效的枚举值来捕获默认分支等。
然后要么仅在使用 NDEBUG 编译的代码版本上运行这些测试,要么在测试触发断言的工具中运行它们 - 无论你的测试框架支持什么。
虽然规范说代码必须存在,但我觉得有点奇怪,而不是规范包含对代码的功能要求。特别是,这意味着您的测试没有测试这些需求,这是保持需求功能的一个很好的理由。我个人想修改规范说,“如果使用无效的枚举值调用,该函数将失败assert
。调用者不应在发布模式下调用具有无效枚举值的函数”。或者类似的。
大概它目前所说的内容是“所有 switch 语句都必须有一个默认情况”。但这意味着编码标准通过引入死代码来干扰可观察的行为(至少,在 gcov 下可观察)。编码标准不应该这样做,因此功能规范应尽可能考虑编码标准。
如果做不到这一点,您也许可以将不可命中的代码包装在#if !GCOV_BUILD
中,并为 gcov 的利益进行单独的构建。此构建将无法满足某些要求,但前提是您对代码的分析是否正确,它会给您信心,让您相信测试套件会测试其他所有内容。
编辑:您说您使用的是不可靠的代码生成器,但您还通过注释源代码来寻求解决方案。如果您正在更改源代码,您可以在很多情况下删除死代码吗?并不是说更改生成的源是理想的,但需求必须...
【讨论】:
并不是“规范”说功能必须存在。我们有一个代码生成器,可以为函数生成原型,即使它们没有被使用。 (修复代码生成器将是一个更好的选择,但不幸的是,这不在我的控制之下。)有时会出现这种情况的另一种情况是您正在实现接口(即从具有纯虚函数的类派生)但仅使用该界面的一部分。 让单元测试直接调用函数并不是一个坏主意,尽管必须在 NDEBUG 构建上运行测试会很痛苦(目前我们的单元测试都在调试构建上运行)。这听起来比它的价值更多的工作。我可以摆脱断言,尽管出于文档目的我喜欢它们。我可以用抛出一个特殊的异常来替换它们,除了在单元测试期间,它永远不会被捕获......这不是一个坏主意。 @jchl:“您正在实现一个接口(即从具有纯虚函数的类派生)但只使用该接口的一部分。” - 有点。但是,如果我正在为该类编写全面的测试,我仍然会让该类定义它们的作用,并从测试中调用未使用的函数以确保它们这样做。如果我没有编写全面的测试,我不会在乎我是否有代码覆盖率;-) 回应您的编辑:不,我无法删除死代码。我们的政策是从不手动编辑生成的代码(它在构建过程中重新生成,因此可以适应对代码生成器的更改)。 非常不合理的想法。【参考方案4】:我不相信这是可能的。 Gcov 依赖 gcc 生成额外的代码来产生覆盖输出。 GCov 本身只是解析数据。这意味着 Gcov 无法比 gcc 更好地分析代码(我假设您使用 -Wall 并删除了报告为无法访问的代码)。
请记住,可重定位函数可以从任何地方调用,甚至可能是外部 dll 或可执行文件,因此编译器无法知道哪些可重定位函数不会被调用或这些函数可能有什么输入。
您可能需要使用一些漂亮的静态分析工具来获取您想要的信息。
【讨论】:
以上是关于如何告诉 gcov 忽略不可命中的 C++ 代码行?的主要内容,如果未能解决你的问题,请参考以下文章
使用 gcov 进行交叉分析,但忽略 GCOV_PREFIX 和 GCOV_PREFIX_STRIP