Home > Blockchain >  gcov coverage limited to test files in minimal g project
gcov coverage limited to test files in minimal g project

Time:03-24

After failing to get coverage with-cmake I set up a minimalistic project to see if I can get coverage working that way. It's derived from using-gtest-without-cmake

It has a src folder with a header and source file in it. QuickMaths.hpp :

#include <cstdint>

using size_t = std::size_t;

size_t
multiply(size_t a, size_t b);

inline size_t
add(size_t a, size_t b)
{
  return a   b;
}

class QuickMaths
{
public:
  size_t operator*() const;
  friend QuickMaths operator (QuickMaths const&, QuickMaths const&);

  QuickMaths(size_t x);

private:
  size_t x;
};

QuickMaths.cpp:

#include "QuickMaths.hpp"

size_t
multiply(size_t a, size_t b)
{
  return a * b;
}

size_t
QuickMaths::operator*() const
{
  return x;
}

QuickMaths::QuickMaths(size_t x)
  : x(x)
{}

QuickMaths
operator (QuickMaths const& a, QuickMaths const& b)
{
  return a.x   b.x;
}

And a test folder with QuickMaths.cpp :

#include <gtest/gtest.h>

#include <QuickMaths.hpp>

TEST(AddTest, shouldAdd)
{
  EXPECT_EQ(add(1UL, 1UL), 2UL);
}

TEST(MultiplyTest, shouldMultiply)
{
  EXPECT_EQ(multiply(2UL, 4UL), 8UL);
}

TEST(QuickMathTest, haveValue)
{
  auto v = QuickMaths{ 4UL };
  EXPECT_EQ(*v, 4UL);
}

and main.cpp :

#include <gtest/gtest.h>

int
main(int argc, char** argv)
{
  testing::InitGoogleTest(&argc, argv);

  return RUN_ALL_TESTS();
}
  • I create a build folder and cd into it, then compile using g --coverage -O0 ../src/QuickMaths.cpp ../test/*.cpp -I../src/ -pthread -lgtest -lgtest_main -lgcov
  • I run ./a.out => output shows tests being run and passing

Lastly I run gcovr -r ../ . and get the following output:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: ../
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
src/QuickMaths.hpp                             2       0     0%   9,11
test/QuickMaths.cpp                            7       0     0%   5,7,10,12,15,17-18
test/main.cpp                                  3       3   100%   
------------------------------------------------------------------------------
TOTAL                                         12       3    25%
------------------------------------------------------------------------------

So it's visible that the gtest setup situated in main is being picked up, but the test cases themselves as well as the code from the src directory is not picked up as executed.

CodePudding user response:

This error occurs because you are linking multiple files with the same name.

There are two clues to the problem:

  1. When running the test, you will see a warning such as the following:

    libgcov profiling error:REDACTED/a-QuickMaths.gcda:overwriting an existing profile data with a different timestamp
    
  2. The coverage report lists three files:

    • src/QuickMaths.hpp
    • test/QuickMaths.cpp
    • test/main.cpp

    But one file is missing entirely:

    • src/QuickMaths.cpp

What has happened?

  • When your tests shut down, raw coverage data is written by the test process into .gcda files. The name of these files depends on the compilation unit.

  • Looking into your build dir, we see the following data (.gcda) and notes (.gcno) files:

    build
    |-- a-QuickMaths.gcda
    |-- a-QuickMaths.gcno
    |-- a-main.gcda
    |-- a-main.gcno
    `-- a.out
    
  • You have provided a total of three files to your compilation command

    • Your command is g ... ../src/QuickMaths.cpp ../test/*.cpp ....
    • The three files are src/QuickMaths.cpp, test/QuickMaths.cpp, test/main.cpp
  • The autogenerated names for the compilation units seems to only consider the input file's basename, ignoring the directory. Since two files have the same basename, the same name for the compilation unit a-QuickMaths is used.

  • Since there's a name clash for the compilation unit, there is also a conflict for the coverage data file names.

  • The result is corrupted coverage data.

The solution is to compile each compilation unit separately and linking them afterwards. You must give each compilation unit a distinct name, possibly using multiple subdirectories. For example:

set -euo pipefail

# compile foo/bar.cpp -> build/foo/bar.o
for source in src/*.cpp test/*.cpp; do
  mkdir -p build/"$(dirname "$source")"
  cd build
  g   --coverage -O0 -pthread -I../src -c -o "${source%.cpp}".o ../"${source}"
  cd -
done

# link the tests
cd build;
g   --coverage -pthread -o testcase src/*.o test/*.o -lgtest
cd -

# run the test
cd build
./testcase
cd -

tree build  # show directory structure

# run gcovr
cd build
gcovr -r ..
cd -

In this example, the build directory would look like:

build
|-- src
|   |-- QuickMaths.gcda
|   |-- QuickMaths.gcno
|   `-- QuickMaths.o
|-- test
|   |-- QuickMaths.gcda
|   |-- QuickMaths.gcno
|   |-- QuickMaths.o
|   |-- main.gcda
|   |-- main.gcno
|   `-- main.o
`-- testcase

And the coverage report is as expected:

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: ..
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
src/QuickMaths.cpp                             9       7    77%   20,22
src/QuickMaths.hpp                             2       2   100%   
test/QuickMaths.cpp                           10      10   100%   
test/main.cpp                                  3       3   100%   
------------------------------------------------------------------------------
TOTAL                                         24      22    91%
------------------------------------------------------------------------------

Additional notes:

  • You're providing a main() but are also linking -lgtest_main. This is unnecessary.
  • The --coverage flag already includes -lgcov.
  • Related