gcov produces different results on Clang and GCC - c++

I'm trying to understand how to properly structure a C++ project by using CMake, googletest, and gcov for test coverage. I would like to build a general CMakeLists.txt that would work for any platform/compiler.
This is my first attempt. However, if I try to build the project and then run lcov (to generate the report), I see that I have different results if I use CLang (right result) or GCC (wrong result).
Note that I'm on MacOs and I installed gcc through brew (brew install gcc).
Moreover I used the following flags in my main CMakeLists.txt:
if(CODE_COVERAGE)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage" )
endif()
Note: If you find something wrong/weird in my CMakeLists.txt files or lcov usage, I'm open to any kind of feedback!
My library
#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
My tests
#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
CLang Compilation
#!/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
GCC Compilation
#!/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

You are actually asking two questions, here.
Why do the coverage results differ between these two compilers?
How do I structure a CMake project for code coverage?
Answer 1: Coverage differences
The simple answer here is that you are building in Release mode, rather than RelWithDebInfo mode. GCC does not put as much debugging information in by default as Clang does. On my system, adding -DCMAKE_CXX_FLAGS="-g" to your build-and-run-cov-gcc.sh script yields the same results as Clang, as does building in RelWithDebInfo.
For whatever reason, it appears that Clang tracks more debug information either by default or when coverage is enabled. GCC does not have these same guardrails. The lesson to take away is this: collecting coverage information is a form of debugging; you must use a debugging-aware configuration for your compiler if you want accurate results.
Answer 2: Build system structure
It is generally a terrible idea to set CMAKE_CXX_FLAGS inside your build. That variable is intended to be a hook for your build's users to inject their own flags. As I detail in another answer on this site, the modern approach to storing such settings is in the presets
I would get rid of the if (CODE_COVERAGE) section of your top-level CMakeLists.txt and then create the following CMakePresets.json file:
{
"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"
}
]
}
Then your build script can be simplified considerably.
#!/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
The key here is the following lines:
# Configure
cmake --preset gcc-coverage
# Build
cmake --build --preset gcc-coverage
This script now lets you vary the compiler and coverage tool via environment variables and the CMakeLists.txt doesn't have to make any assumptions about what compiler is being used.
On my (Linux) system, I can run the following commands successfully:
$ CC=gcc-12 CXX=g++-12 GCOV=gcov-12 ./build-and-run-cov.sh
$ CC=clang-13 CXX=clang++-13 GCOV=$PWD/llvm-cov-13.sh ./build-and-run-cov.sh
Where llvm-cov-13.sh is a wrapper for llvm-cov-13 for compatibility with the --gcov-tool flag. See this answer for more detail.
#!/bin/bash
exec llvm-cov-13 gcov "$#"
As you can see, the results are indistinguishable now that the correct flags are used.

Related

I can't compile SimGrid - S4U

How to compile a C ++ simulation in SimGrid? I am using Ubuntu, following the installation steps in the documentation but when I try to test some example of the documentation itself, there are several errors and warnings.
I went to the examples folder and tried to run some of the S4U interface, but without success.
I tried this: g++ example.cpp -o example
And:
erros and warnings
Build example:
tar xvf simgrid-3.27.tar.gz
cd simgrid-3.27/ && mkdir build && cd build/
cmake ..
make
make tests ## build tests and examples
sudo make install
The make options https://simgrid.org/doc/latest/Installing_SimGrid.html .. ( Ref. https://simgrid.org/doc/latest/index.html ) and simgrid-3.27/docs/source/Installing_SimGrid.rst,
line 342:- make tests: Build the tests and examples.
When the examples have been built, the executable´s are in "build/examples/..". E.g. simgrid-3.27/build/examples/cpp/actor-create/s4u-actor-create

Why does gcovr generate empty coverage statistics?

I'm confused about how to use gcov. I've got a cmake project that has two test executables which use googletest. I've added the required flags to my cmake script:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage -g -O0")
And I've recompiled my code (using CLion and g++ 10.1).
Now I switch to the build directory and manually run both test suites. This, according to the docs should generate some files that can be used to generate the coverage report. Now I should be able to run
gcovr .
from the root of the build tree (right?), however the output is this:
(base) ciaran#DESKTOP-K0APGUV:/mnt/d/libOmexMeta/cmake-build-release-wsl-ubuntu1804-gcc101$ gcovr .
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
------------------------------------------------------------------------------
TOTAL 0 0 --%
------------------------------------------------------------------------------
Any idea what I've doing wrong?
#Edit
Also running
gcovr -r . --html --html-details -o example-html-details.html
Works, but generate an empty report
You have to give the source files:
-r , --root
The root directory of your source files. Defaults to ‘.’, the current directory. File names are reported relative to this root.
If you build and run out of sources dir it may fail to find what it needs.

coverage with gcc9 and lcov

A recent OS upgrade made my coverage script fail miserably.
lcov 1.13
gcov (GCC) 9.1.1
The part of my CMake that is used to generate coverage data:
if ($ENV{COVERAGE})
message("Setting up for coverage")
enable_testing()
include(CodeCoverage)
setup_target_for_coverage(${PROJECT_NAME}_coverage tests coverage)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
endif ()
The lcov command I issued after building tests:
lcov --capture --directory build/ --output-file coverage.info
Unfortunately now fails with:
Capturing coverage data from build/
Found gcov version: 9.1.1
Scanning build/ for .gcda files ...
geninfo: WARNING: no .gcda files found in build/ - skipping!
Finished .info-file creation
The error message makes sense because there are no .gcda files - only .gcno files. I am not sure if they serve the same purpose and / or can be used with lcov.
I issued nm some_binary | grep gcov and there are a lot of symbols in the form of:
00000000004b3520 d __gcov_._ZZZN6__pstl10__internal15__pattern_walk2IRKNS_9execution2v115parallel_policyEN9__gnu_cxx17__normal_iteratorIPKiSt6vectorIiSaIiEEEENS8_IPiSD_EEZSt9transformIS6_SE_SG_ZN12_GLOBAL__N_150ParallelTransformTest_NoDataShouldReturnEmpty_Test8TestBodyEvEUlRKT_E_ENSt9enable_ifIXsrNS3_19is_execution_policyINSt5decayISK_E4typeEEE5valueET1_E4typeEOSK_T0_SY_SU_T2_EUlRS9_RiE_St17integral_constantIbLb0EEEESU_SX_SY_SY_SU_SZ_T3_S13_IbLb1EEENKUlvE_clEvENKUlSE_SE_E_clESE_SE_
So I guess the CMake is still correctly trying to give me coverage data.
It worked fine on gcc 7 if I recall correctly.
Is there a new solution / CMake flag to issue / lcov flag to issue? Or is it broken right now and there is no workaround? Or perhaps I was doing something odd the whole time?
I believe GCC 9 outputs coverage data as JSON by default now, as mentioned in the change notes.
The gcov tool has changed its intermediate format to a new JSON format.
It also looks like lcov have an open issue for handling this new format.

Build C++ project with makefile (without Cmake) and run tests using Jenkins

I am trying to build my makefile C++ project using Jenkins.
See project structure below. Project is on a bitbucket repository and job profile is set Freestyle Project.
Project is successfully built on Jenkins server however it looks like it just uploads the project from repository to its workspace and says "Finshed with success" but does not run a makefile.
Console output:
Checking out Revision 6720229e2d82a9e958f69afabe361c65d1647398 (refs/remotes/origin/master)
> git config core.sparsecheckout # timeout=10
> git checkout -f 6720229e2d82a9e958f69afabe361c65d1647398
Commit message: "My commit"
> git rev-list --no-walk 084977a421fc8fb064297f64407e2d137a1b32a1 # timeout=10
Finished: SUCCESS
On my local however both test and main projects are built successfully with make.
Basically there are two questions:
How to build my project (including the test one) with Makefile on Jenkins? (i.e. how to run a make command on Jenkins). I do not want to use a Cmake. Is it possible?
If both projects built successfully, how to run the test project and see test results in console/write to file in Jenkins?
My project structure:
MyProject
|+src/ <-- source files main project
|+include/ <-- header files main project
|+bin/ <-- binaries main project
|+test/ <-- test project
|~test/
| |+bin/ <-- test binaries
| |+gtest/ <-- gtest headers
| |+lib/ <-- gtest binaries
| |-test.cpp <-- test source
|-Makefile
My Makefile:
# Compiler options and variables def
CC=g++
CPPFLAGS= -c -Wall
GFLAGS = -g
INC_DIR = include
INC_DIR_TEST = test
TST += \
*.cpp
VPATH += test/
TESTLIB += \
*.a
SRC += \
*.cpp
BIN = bin
TSTBIN = test/bin
# build
all: program test
OBJ = $(patsubst %.cpp, $(BIN)/%.o, $(SRC))
TSTOBJ = $(patsubst %.cpp, $(TSTBIN)/%.o, $(TST))
program: $(OBJ)
$(CC) $(GFLAGS) $? -o $#
test: $(TSTOBJ) $(TESTLIB) $(BIN)/file1.o $(BIN)/file2.o
$(CC) $(GFLAGS) $(TESTLIB) $(BIN)/file1.o $(BIN)/file2.o $< -o $#
$(BIN)/%.o: src/%.cpp
$(CC) $(CPPFLAGS) -I$(INC_DIR) $< -o $#
$(TSTBIN)/%.o: test/%.cpp
$(CC) $(CPPFLAGS) -I$(INC_DIR_TEST) -I$(INC_DIR) $< -o $#
clean:
rm *.o *.exe bin/*.o test/bin/*.o
Update:
As I understand, this type of project has to be marked as pipeline rather than freestyle project that will allow to choose a build tool and run shell command from Jenkinsfile file that facilitates considerably the CI process.
However I cannot find any examples of building C++ project with make and GNU build tools.
This is a Jenkinsfile example from official documentation
pipeline {
agent { docker 'maven:3.3.3' }
stages {
stage('build') {
steps {
sh 'mvn --version'
}
}
}
}
I am wondering now how this should be modified in order to build a simplest C++ project with makefile. Am I on a right way?
Second question is still actual: how to run my gtests and record results in Jenkins after the make command works?
Update:
I have used a batchfile as it can be ran from java code in Jenkinsfile. Now my testing Jenkinsfile looks like:
pipeline {
agent any
stages {
stage('build') {
steps {
echo 'building..'
bat 'batchfile.cmd'
}
}
}
}
Batchfile code:
PATH = "C:\Program Files (x86)\GnuWin32\bin"
make
There are still some bugs but at least make command is ran and the commands from makefile are called.
So general conclusion:
As it looks like there is no any make plugin for jenkins, the pipeline stages can run a batchfile (or shell script for UNIX), and this batchfile can call make. Of course make has to be installed on a server and the path to environment variable has to be specified.
Maybe there are some better approaches or I am wrong, please correct me.

Code coverage warrnings spam output

In CMake tests configuration I added flags to generate codecoverage
IF( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR
"${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage ")
endif()
Each time I run tests - my output is spammed with those messages:
profiling: /xxxx/xxxxxj/projects/build-xxxxx-CLang-Debug/tests/CMakeFiles/xxxxxxxxxxxxxt_ut.dir/tests/gui/ship_design/ut_ship_stats_header.cpp.gcda: cannot merge previous GCDA file: mismatched number of counters (14)
profiling:
/xxxx/xxxxxx/projects/build-xxxxxxxxxxxxxxx-CLang-Debug/tests/CMakeFiles/xxxxxxxxxxxxxxx_ut.dir/tests/gui/ship_design/ut_ship_stats_header.cpp.gcda:
cannot merge previous GCDA file: corrupt arc tag (0x2b8e100f)
profiling:
/xxxx/xxxxxx/projects/build-xxxxxxxxxxxxxxx-CLang-Debug/tests/CMakeFiles/xxxxxxxxxxxxxxx_ut.dir/tests/ut_generate_hex_path.cpp.gcda:
cannot merge previous GCDA file: corrupt arc tag (0x65646f6e)
I don't have to say that this make reading test run results at least difficult. Above problem vanished when I remove all gcda files generated previously. So there are two possible solutions.
I may miss some valuable configuration of code coverage data (ie. call lcov --zerocounters
FIND_PROGRAM( LCOV_PATH lcov )
COMMAND ${LCOV_PATH} -z --directory ${PROJECT_BINARY_DIR}}
I need to add custom target removing all gcda files before test run is committed.
How should I approach this problem?
I think you might be doing something unusual in your workflow, as the problem you're seeing shouldn't happen all of the time. But, what I've added here should help you figure that out, or work around it completely.
Firstly, you should take advantage of CMake's build-types and create your own "coverage type".
## coverage flags
set(CMAKE_CXX_FLAGS_COVERAGE "-g -O0 -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used by the C++ compiler during coverage builds.")
set(CMAKE_C_FLAGS_COVERAGE "-g -O0 -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used by the C compiler during coverage builds.")
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "-g -O0 -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used for linking binaries during coverage builds.")
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "-g -O0 -fprofile-arcs -ftest-coverage" CACHE STRING "Flags used by the shared libraries linker during coverage builds.")
mark_as_advanced(
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE)
## Update the documentation string of CMAKE_BUILD_TYPE for GUIs
set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel RelWithAssert Coverage." FORCE)
Then create a custom target.
## create our "make coverage" target
add_custom_target(coverage
COMMAND if test ! -d ../output \; then mkdir ../output\; fi
COMMAND find ${CMAKE_BINARY_DIR} -name \*.gcda -delete
COMMAND lcov -b CMakeFiles/ -d . -z
COMMAND lcov -b -d . -c -i -o test_base.info
COMMAND ./env-shell.sh ctest -j2 || true
COMMAND lcov -b CMakeFiles/ -d . -c -o test_run.info
COMMAND lcov -b CMakeFiles/ -d . -a test_base.info -a test_run.info -o test_total.info
COMMAND lcov -o reports.info -r test_total.info '/usr/include/*' '/usr/local/*' '/cvmfs/*' '*/numpy' '/usr/lib/gcc/*' ${p} '${CMAKE_BINARY_DIR}/CMakeFiles/' '${CMAKE_BINARY_DIR}/steamshovel/*'
COMMAND genhtml --ignore-errors source --legend -o ../output/`date +%Y-%m-%d` reports.info
)
Now, taking advantage of out-of-source builds, run cmake in a "coverage" directory parallel to your source.
$ pwd
/home/user/my_project/src
$ mkdir ../coverage
$ cd ../coverage
$ cmake -DCMAKE_BUILD_TYPE=Coverage ../src
[ ... cmake's output here ...]
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/my_project/coverage
$
Now, build your project, and your tests, and run your "coverage" target.
$ make
$ make test-bins
$ make coverage
[ ... make's output here ... ]
$
The make coverage target we defined in our CMakeLists.txt will:
if it doesn't exist, create an "output" directory parallel to our source and build directories
find and delete all *.gcda files in our current build directory
zero and initialize our lcov counters and output file
run our tests via ctest
"compile" the lcov output and generate our HTML coverage report
Note that you may need to adjust things for your particular project.
At this point I highly recommend automating all of this. Add it to your continuous integration, if you have some, or even set up a cron job so that this runs overnight and you have a fresh new coverage report to start your day. (These examples are from a working project that does a nightly coverage report handled by buildbot.)