gcov 在 Clang 和 GCC 上产生不同的结果

Ива*_*зов 13 c++ code-coverage cmake gcov lcov

我试图了解如何使用 CMake、googletest 和 gcov 进行测试覆盖率来正确构建 C++ 项目。我想构建一个适用于任何平台/编译器的通用 CMakeLists.txt。

是我的第一次尝试。但是,如果我尝试构建项目,然后运行 ​​lcov(以生成报告),我会发现如果使用 CLang(正确结果)或 GCC(错误结果),则会得到不同的结果。请注意,我在 MacO 上,并且通过 brew ( brew install gcc) 安装了 gcc。

此外,我在 main 中使用了以下标志CMakeLists.txt

if(CODE_COVERAGE)
    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage" )
endif()
Run Code Online (Sandbox Code Playgroud)

CMakeLists.txt注意:如果您在我的文件或使用中发现错误/奇怪的内容lcov,我愿意接受任何类型的反馈!

我的图书馆

#include "library.h"

#include <iostream>

void foo(){
    std::cout << "Foo!" << std::endl;
}

void bar(int n){
    if (n > 0){
        std::cout << "n is grater than 0!" << std::endl;
    }
    else if (n < 0){
        std::cout << "n is less than 0!" << std::endl;
    }
    else{
        std::cout << "n is exactly 0!" << std::endl;
    }
}

void baz(){  // LCOV_EXCL_START
    std::cout << "Baz!" << std::endl;
}
// LCOV_EXCL_STOP

Run Code Online (Sandbox Code Playgroud)

我的测试


#ifndef GCOV_TUTORIAL_TEST_LIBRARY_H
#define GCOV_TUTORIAL_TEST_LIBRARY_H

#include "../src/library.h"

#include <gtest/gtest.h>


namespace gcov_tutorial::tests {
    TEST(TestFooSuite,TestFoo){
        foo();
    }
    TEST(TestBarSuite,TestBarGreaterThanZero){
        bar(100);
    }
    TEST(TestBarSuite,TestBarEqualToZero){
        //bar(0);
    }
    TEST(TestBarSuite,TestBarLessThanZero){
        bar(-100);
    }
}

#endif //GCOV_TUTORIAL_TEST_LIBRARY_H

Run Code Online (Sandbox Code Playgroud)

CLang编译

#!/bin/bash

# Rationale: https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail

# BASE_DIR is the project's directory, containing the src/ and tests/ folders.
BASE_DIR=$PWD
COVERAGE_FILE=coverage.info

GCOV_PATH=/usr/bin/gcov
CLANG_PATH=/usr/bin/clang
CLANGPP_PATH=/usr/bin/clang++

rm -rf build
mkdir build && cd build

# Configure
cmake -DCMAKE_C_COMPILER=$CLANG_PATH -DCMAKE_CXX_COMPILER=$CLANGPP_PATH -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Release ..

# Build (for Make on Unix equivalent to `make -j $(nproc)`)
cmake --build . --config Release

# Clean-up for any previous run.
rm -f $COVERAGE_FILE
lcov --zerocounters --directory .
# Run tests
./tests/RunTests
# Create coverage report by taking into account only the files contained in src/
lcov --capture --directory tests/ -o $COVERAGE_FILE --include "$BASE_DIR/src/*" --gcov-tool $GCOV_PATH
# Create HTML report in the out/ directory
genhtml $COVERAGE_FILE --output-directory out
# Show coverage report to the terminal
lcov --list $COVERAGE_FILE
# Open HTML
open out/index.html
Run Code Online (Sandbox Code Playgroud)

使用 CLang 编译时的覆盖率报告

海湾合作委员会编译

#!/bin/bash

# Rationale: https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail

# BASE_DIR is the project's directory, containing the src/ and tests/ folders.
BASE_DIR=$PWD
COVERAGE_FILE=coverage.info

GCOV_PATH=/usr/local/bin/gcov-11
GCC_PATH=/usr/local/bin/gcc-11
GPP_PATH=/usr/local/bin/g++-11

rm -rf build
mkdir build && cd build

# Configure
cmake -DCMAKE_C_COMPILER=$GCC_PATH -DCMAKE_CXX_COMPILER=$GPP_PATH -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Release ..

# Build (for Make on Unix equivalent to `make -j $(nproc)`)
cmake --build . --config Release

# Clean-up for any previous run.
rm -f $COVERAGE_FILE
lcov --zerocounters --directory .
# Run tests
./tests/RunTests
# Create coverage report by taking into account only the files contained in src/
lcov --capture --directory tests/ -o $COVERAGE_FILE --include "$BASE_DIR/src/*" --gcov-tool $GCOV_PATH
# Create HTML report in the out/ directory
genhtml $COVERAGE_FILE --output-directory out
# Show coverage report to the terminal
lcov --list $COVERAGE_FILE
# Open HTML
open out/index.html
Run Code Online (Sandbox Code Playgroud)

GCC 的覆盖范围报告

Ale*_*ing 16

您实际上在这里问两个问题。

  1. 为什么这两个编译器的覆盖率结果不同?
  2. 如何构建 CMake 项目以实现代码覆盖率?

答案 1:覆盖范围差异

这里简单的答案是,您正在构建Release模式,而不是RelWithDebInfo模式。默认情况下,GCC 不会像 Clang 那样放入那么多调试信息。在我的系统上,添加-DCMAKE_CXX_FLAGS="-g"build-and-run-cov-gcc.sh脚本中会产生与 Clang 相同的结果,就像在RelWithDebInfo.

无论出于何种原因,Clang 在默认情况下或在启用覆盖范围时似乎都会跟踪更多调试信息。海湾合作委员会没有这些相同的护栏。我们学到的教训是:收集覆盖率信息是调试的一种形式;如果您想要准确的结果,则必须为编译器使用调试感知配置。

答案2:构建系统结构

在您的构建中设置通常是一个糟糕的主意CMAKE_CXX_FLAGS。该变量旨在成为构建用户注入自己的标志的钩子。正如我在本网站的另一个答案中详细介绍的那样,存储此类设置的现代方法是在预设中

我将删除if (CODE_COVERAGE)顶级 CMakeLists.txt 的部分,然后创建以下CMakePresets.json文件:

{
  "version": 4,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 23,
    "patch": 0
  },
  "configurePresets": [
    {
      "name": "gcc-coverage",
      "displayName": "Code coverage (GCC)",
      "description": "Enable code coverage on GCC-compatible compilers",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "RelWithDebInfo",
        "CMAKE_CXX_FLAGS": "-fprofile-arcs -ftest-coverage"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "gcc-coverage",
      "configurePreset": "gcc-coverage",
      "configuration": "RelWithDebInfo"
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

那么你的构建脚本就可以大大简化。

#!/bin/bash

# Rationale: https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail

# Set up defaults for CC, CXX, GCOV_PATH
export CC="${CC:-gcc-11}"
export CXX="${CXX:-g++-11}"
: "${GCOV_PATH:=gcov-11}"

# Record the base directory
BASE_DIR=$PWD

# Clean up old build
rm -rf build

# Configure
cmake --preset gcc-coverage

# Build
cmake --build --preset gcc-coverage

# Enter build directory
cd build

# Clean-up counters for any previous run.
lcov --zerocounters --directory .

# Run tests
./tests/RunTests

# Create coverage report by taking into account only the files contained in src/
lcov --capture --directory tests/ -o coverage.info --include "$BASE_DIR/src/*" --gcov-tool $GCOV_PATH

# Create HTML report in the out/ directory
genhtml coverage.info --output-directory out

# Show coverage report to the terminal
lcov --list coverage.info

# Open HTML
open out/index.html
Run Code Online (Sandbox Code Playgroud)

这里的关键是以下几行:

# Configure
cmake --preset gcc-coverage
# Build
cmake --build --preset gcc-coverage
Run Code Online (Sandbox Code Playgroud)

该脚本现在允许您通过环境变量改变编译器和覆盖工具,并且CMakeLists.txt不必对正在使用的编译器做出任何假设。

在我的(Linux)系统上,我可以成功运行以下命令:

$ CC=gcc-12 CXX=g++-12 GCOV=gcov-12 ./build-and-run-cov.sh
Run Code Online (Sandbox Code Playgroud)

结果-海湾合作委员会

$ CC=clang-13 CXX=clang++-13 GCOV=$PWD/llvm-cov-13.sh ./build-and-run-cov.sh
Run Code Online (Sandbox Code Playgroud)

哪里有一个与标志兼容的llvm-cov-13.sh包装器。有关更多详细信息,请参阅此答案。llvm-cov-13--gcov-tool

#!/bin/bash
exec llvm-cov-13 gcov "$@"
Run Code Online (Sandbox Code Playgroud)

结果-clang

正如您所看到的,既然使用了正确的标志,结果就无法区分。

  • 前一周我看到了您关于使用 CMake 预设的答案,所以我想首先表示感谢。这比使用工具链简单多了!您是否有任何原因不使用“configurePresets.environment”来设置“CC”和“CXX”?您甚至可以使用继承来为 gcc 和 clang 创建覆盖配置。也可能值得将 `binaryDir` 设置为 `${sourceDir}/build/${presetName}` 以方便拥有多个预设。 (3认同)
  • 请注意,您可以创建指向“llvm-cov”的“gcov”符号链接,而不是包装脚本。 (2认同)

归档时间:

查看次数:

1798 次

最近记录:

4 年 前