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

Getting useful GCov results for header-only libraries

本文关键字:有用 GCov 结果 获取      更新时间:2023-10-16

对于我的仅标头C++库(大量模板等(,我使用 GCov 来检查测试覆盖率。但是,它报告所有标头的 100% 覆盖率,因为编译器首先不会生成未使用的函数。手动发现未覆盖的功能很容易,但违背了持续集成的目的......

如何自动解决此问题?我是否应该只使用"命中线/LOC"作为我的覆盖率指标,然后再也不会达到100%?

除了通常的标志到 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
我还使用 GCov 来检查测试覆盖

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

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

使用

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

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

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

模板样本.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;
};

模板样本测试.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 生成的代码覆盖率输出:

模板样本.hpp 覆盖率

警告:"函数"统计信息报告为 100%,对于未实例化的模板函数,这并不真实。

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

第一种是添加标志-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(

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

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

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