为仅标头库获取有用的 GCov 结果

Posted

技术标签:

【中文标题】为仅标头库获取有用的 GCov 结果【英文标题】:Getting useful GCov results for header-only libraries 【发布时间】:2012-03-28 20:04:48 【问题描述】:

对于我的纯头文件 C++ 库(大量模板等),我使用 GCov 检查测试覆盖率。但是,它报告了所有头文件的 100% 覆盖率,因为编译器一开始就没有生成未使用的函数。手动发现未覆盖的功能很容易,但却违背了持续集成的目的……

如何自动解决这个问题?我是否应该只使用“行命中/LOC”作为我的覆盖率指标,而不再达到 100%?

【问题讨论】:

您可以进行调用所有公共方法和函数的单元测试。您将同时获得覆盖范围并测试它是否有效。 是的,但是如果我错过了一个功能,我想轻松发现,如果我可以浏览我的 Cdash 并看到一个覆盖率 我也想很好地回答这个问题。 1. 我同意很高兴看到未调用的实例化。 2. 我没有看到成员模板(在模板类中)的结果,我绝对确定这些是在我的测试代码中实例化和调用的(这有点奇怪)。 好的,至于我的第 2 点,这完全是我的错。您还需要检测您的测试类,因为这些将实例化代码。我正在使用 Eclipse gcov(集成)插件来检查我的测试覆盖率结果(或者,lcov 没有在那里检查结果),并且模板中未实例化的模板代码很容易被发现,因为根本没有注释。我将把它放在一个全面的答案中...... 【参考方案1】:

我还使用 GCov 来检查测试覆盖率(使用 Google 测试框架编写的测试),另外我使用 Eclipse GCov 集成插件或 LCov 工具来生成易于检查的测试覆盖率结果视图。原始的 GCov 输出太难使用了:-(。

如果您只有标头模板库,您还需要检测(使用 G++ 标志 --coverage)实例化模板类和模板成员函数的测试类,以查看这些的合理 GCov 输出。

使用上述工具,很容易发现根本没有用测试用例实例化的模板代码,因为它没有注释。

我已经设置了一个示例并将 LCov 输出复制到您可以检查的 DropBox 链接。

示例代码(TemplateSampleTest.cpp 使用 g++ --coverage 选项检测):

TemplateSample.hpp

template<typename T>
class TemplateSample


public:
    enum CodePath
    
        Path1 ,
        Path2 ,
        Path3 ,
    ;

    TemplateSample(const T& value)
    : data(value)
    
    

    int doSomething(CodePath path)
    
        switch(path)
        
        case Path1:
            return 1;
        case Path2:
            return 2;
        case Path3:
            return 3;
        default:
            return 0;
        

        return -1;
    

    template<typename U>
    U& returnRefParam(U& refParam)
    
        instantiatedCode();
        return refParam;
    

    template<typename U, typename R>
    R doSomethingElse(const U& param)
    
        return static_cast<R>(data);
    

private:
    void instantiatedCode()
    
        int x = 5;
        x = x * 10;
    

    void neverInstantiatedCode()
    
        int x = 5;
        x = x * 10;
    
    T data;
;

TemplateSampleTest.cpp

#include <string>
#include "gtest/gtest.h"
#include "TemplateSample.hpp"

class TemplateSampleTest : public ::testing::Test

public:

    TemplateSampleTest()
    : templateSample(5)
    
    

protected:
    TemplateSample<int> templateSample;

private:
;

TEST_F(TemplateSampleTest,doSomethingPath1)

    EXPECT_EQ(1,templateSample.doSomething(TemplateSample<int>::Path1));


TEST_F(TemplateSampleTest,doSomethingPath2)

    EXPECT_EQ(2,templateSample.doSomething(TemplateSample<int>::Path2));


TEST_F(TemplateSampleTest,returnRefParam)

    std::string stringValue = "Hello";
    EXPECT_EQ(stringValue,templateSample.returnRefParam(stringValue));


TEST_F(TemplateSampleTest,doSomethingElse)

    std::string stringValue = "Hello";
    long value = templateSample.doSomethingElse<std::string,long>(stringValue);
    EXPECT_EQ(5,value);

在此处查看 lcov 生成的代码覆盖率输出:

TemplateSample.hpp coverage

警告:“函数”统计数据报告为 100%,这对于未实例化的模板函数而言并非如此。

【讨论】:

【参考方案2】:

除了 GCC 控制内联的常用标志;

--coverage -fno-inline -fno-inline-small-functions -fno-default-inline

您可以在单元测试文件的顶部实例化您的模板类;

template class std::map<std::string, std::string>;

这将为该模板类中的每个方法生成代码,从而使覆盖工具完美运行。

另外,请确保初始化 *.gcno 文件(对于 lcov 也是如此)

lcov -c -i -b $ROOT -d . -o Coverage.baseline
<run your tests here>
lcov -c -d . -b $ROOT -o Coverage.out
lcov -a Coverage.baseline -a Coverage.out -o Coverage.combined
genhtml Coverage.combined -o HTML

【讨论】:

这是一个非常有帮助的答案,谢谢!我要补充一点,如果您不使用优化进行编译(如果您想要准确的行计数覆盖率,则不应该这样做,请参阅gcc.gnu.org/onlinedocs/gcc/Gcov-and-Optimization.html),那么内联标志应该不是必需的,因为 gcc 不使用内联编译 -O0 (见gcc.gnu.org/onlinedocs/gcc/Inline.html底部)【参考方案3】:

因为我发现这个问题在为我的仅标头库设置测试覆盖率方面非常有用,所以我学到了一些额外的东西,希望它们可以帮助其他人:

即使有这些答案中提到的所有标志,我仍然无法优化未使用的类方法。经过大量实验,我发现 clang source based coverage(这些标志:-fprofile-instr-generate -fcoverage-mapping)包含所有类方法,通常是获取覆盖率数据的最可靠方法。我还使用标志:-O0 -fno-inline -fno-elide-constructors 来进一步降低代码被优化的风险。

对于大型库,模板实例化这件事仍然是个问题。显式实例化它们一切都很好,但如果有人忘记了,你会得到不准确的代码覆盖率指标。请参阅我对 this question 的回答,了解自动调整代码覆盖率数据以解决此问题的方法。

【讨论】:

我无法从 -fprofile-instr-generate -fcoverage-mapping 获取标头的覆盖信息,因为我的标头只有库。它只为我提供了测试标头的.cpp 测试文件(使用-O0【参考方案4】:

我也偶然发现了这个问题,不幸的是,提到的各种标志都不太幸运,但是,我确实发现了两种在处理仅标头函数时生成更准确的覆盖率信息的方法。

首先是添加标志-fkeep-inline-functions(https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fkeep-inline-functions)。

这确实给了我想要的结果,但在尝试与其他库(甚至是普通的 C++ 标准库)集成时遇到了一些严重的问题。我最终得到了链接错误,因为某些应该被链接器删除的函数没有被删除(例如,没有定义的函数声明)。

第二种方法(我最后选择的方法)是在 GCC 中使用__attribute(used)__ 来注释我所有的标头 API 函数。文档 (https://gcc.gnu.org/onlinedocs/gcc-4.3.0/gcc/Function-Attributes.html) 指出:

二手

附加到函数的这个属性意味着即使看起来没有引用该函数,也必须为该函数发出代码。

我使用#define 来包装它,所以我只有在使用 GCC 并启用覆盖时才打开它:

#ifdef _MSC_VER
#define MY_API
#elif defined __GNUC__ && defined COVERAGE
#define MY_API __attribute__((__used__))
#endif // _MSC_VER ? __GNUC__ && COVERAGE

用法如下:

MY_API void some_inline_function() 

我将尝试写下我是如何在某个时候让一切正常工作的

(注:我在编译的时候也使用了-coverage -g -O0 -fno-inline

【讨论】:

以上是关于为仅标头库获取有用的 GCov 结果的主要内容,如果未能解决你的问题,请参考以下文章

将 PHAsset 获取结果限制为仅 3 个资产

如何获取 iframe 响应标头?

将 Vue for 循环的结果限制为仅当前使用的结果

使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率

使用 gcov/lcov/gcovr 在 Android APK 下获取代码覆盖率

gcov:从共享库生成 .gcda 输出?