1

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.

1 Answers1

1

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.
amon
  • 57,091
  • 2
  • 89
  • 149
  • Is it common practice to build the compilation units separately like this? This is actually what cmake does as well, right? When I would be using cmake, should assuring that there's no name-clash between test and source files be enough? (for example by appending a `test` to all the test-files. – LeonTheProfessional Mar 23 '22 at 19:10
  • I have followed your steps, and end up with the same directory-structure as you (the warning is also not appearing anymore), however `src/QuickMaths.cpp`, `src/QuickMaths.hpp`, and `test/QuickMaths.cpp` show 0 lines executed (0% coverage) – LeonTheProfessional Mar 23 '22 at 19:24
  • Okay, turns out this is a problem with my gcc-version (11.1) - this seems to be incompatible with my system's gcov-version - after switching to gcc-9 via `update-alternatives` this shows the exact result you describe. – LeonTheProfessional Mar 23 '22 at 19:54
  • 1
    @LeonTheProfessional The gcov version must always match the compiler version. Gcov is part of GCC and is not installed separately. If the default `gcov` does not match the compiler you used, you must tell gcovr. You can do this either via a command line flag `gcovr --gcov-executable gcov-11 -r ../` or via an environment variable `GCOV=gcov-11 gcovr -r ../` – amon Mar 23 '22 at 19:59