Skip to content

Commit b1b0cad

Browse files
Manu343726foonathan
authored andcommitted
Code coverage with lcov
Includes: - A cmake script to setup lcov targets - A predefined cppast_coverage target (Disabled by default, enable by setting CPPAST_TEST_GCOV var to TRUE/ON/1) - Coverage report submission to coveralls.io
1 parent 91a6f40 commit b1b0cad

File tree

4 files changed

+271
-3
lines changed

4 files changed

+271
-3
lines changed

.travis.yml

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ matrix:
1515
addons:
1616
apt:
1717
sources: ['ubuntu-toolchain-r-test']
18-
packages: ['g++-5']
19-
env: TOOLSET=g++-5 LLVM_DOWNLOAD_OS_NAME=x86_64-linux-gnu-ubuntu-14.04 LLVM_VERSION=4.0.0
18+
packages: ['g++-5', 'lcov']
19+
env: TOOLSET=g++-5 LLVM_DOWNLOAD_OS_NAME=x86_64-linux-gnu-ubuntu-14.04 LLVM_VERSION=4.0.0 CPPAST_TEST_GCOV=ON
2020

2121
- os: linux
2222
compiler: clang
@@ -41,6 +41,8 @@ matrix:
4141
install:
4242
- cd ../
4343

44+
- if [[ "$CPPAST_TEST_GCOV" == "ON" ]]; then pip install --user cpp-coveralls; fi
45+
4446
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wget --no-check-certificate https://www.cmake.org/files/v3.3/cmake-3.3.1-Linux-x86_64.tar.gz; fi
4547
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then tar -xzf cmake-3.3.1-Linux-x86_64.tar.gz; fi
4648
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CMAKE=$TRAVIS_BUILD_DIR/../cmake-3.3.1-Linux-x86_64/bin/cmake; fi
@@ -57,6 +59,9 @@ install:
5759

5860
script:
5961
- mkdir build/ && cd build/
60-
- $CMAKE -DCMAKE_CXX_FLAGS="-Werror -pedantic -Wall -Wextra -Wconversion -Wsign-conversion -Wno-parentheses -Wno-assume" -DLLVM_DOWNLOAD_OS_NAME=$LLVM_DOWNLOAD_OS_NAME -DLLVM_PREFERRED_VERSION=$LLVM_VERSION ../
62+
- $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Werror -pedantic -Wall -Wextra -Wconversion -Wsign-conversion -Wno-parentheses -Wno-assume" -DLLVM_DOWNLOAD_OS_NAME=$LLVM_DOWNLOAD_OS_NAME -DLLVM_PREFERRED_VERSION=$LLVM_VERSION ../ -DCPPAST_TEST_GCOV=$CPPAST_TEST_GCOV
6163
- $CMAKE --build .
6264
- if [[ "$LLVM_VERSION" == "4.0.0" ]]; then ./test/cppast_test \*; else ./test/cppast_test; fi
65+
66+
after_script:
67+
- if [[ "$CPPAST_TEST_GCOV" == "ON" ]]; then $CMAKE --build . --target cppast_coverage && cd ../ && coveralls --no-gcov --lcov-file build/cppast_coverage.info.cleaned; fi

CMakeLists.txt

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ endif()
3232

3333
include(external/external.cmake)
3434

35+
if(build_test AND CPPAST_TEST_GCOV AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
36+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
37+
include(CodeCoverage)
38+
APPEND_COVERAGE_COMPILER_FLAGS()
39+
endif()
40+
3541
add_subdirectory(src)
3642

3743
if(${build_test})

cmake/CodeCoverage.cmake

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Copyright (c) 2012 - 2017, Lars Bilke
2+
# All rights reserved.
3+
#
4+
# Redistribution and use in source and binary forms, with or without modification,
5+
# are permitted provided that the following conditions are met:
6+
#
7+
# 1. Redistributions of source code must retain the above copyright notice, this
8+
# list of conditions and the following disclaimer.
9+
#
10+
# 2. Redistributions in binary form must reproduce the above copyright notice,
11+
# this list of conditions and the following disclaimer in the documentation
12+
# and/or other materials provided with the distribution.
13+
#
14+
# 3. Neither the name of the copyright holder nor the names of its contributors
15+
# may be used to endorse or promote products derived from this software without
16+
# specific prior written permission.
17+
#
18+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
#
29+
# CHANGES:
30+
#
31+
# 2012-01-31, Lars Bilke
32+
# - Enable Code Coverage
33+
#
34+
# 2013-09-17, Joakim Söderberg
35+
# - Added support for Clang.
36+
# - Some additional usage instructions.
37+
#
38+
# 2016-02-03, Lars Bilke
39+
# - Refactored functions to use named parameters
40+
#
41+
# 2017-06-02, Lars Bilke
42+
# - Merged with modified version from github.com/ufz/ogs
43+
#
44+
#
45+
# USAGE:
46+
#
47+
# 1. Copy this file into your cmake modules path.
48+
#
49+
# 2. Add the following line to your CMakeLists.txt:
50+
# include(CodeCoverage)
51+
#
52+
# 3. Append necessary compiler flags:
53+
# APPEND_COVERAGE_COMPILER_FLAGS()
54+
#
55+
# 4. If you need to exclude additional directories from the report, specify them
56+
# using the COVERAGE_EXCLUDES variable before calling SETUP_TARGET_FOR_COVERAGE.
57+
# Example:
58+
# set(COVERAGE_EXCLUDES 'dir1/*' 'dir2/*')
59+
#
60+
# 5. Use the functions described below to create a custom make target which
61+
# runs your test executable and produces a code coverage report.
62+
#
63+
# 6. Build a Debug build:
64+
# cmake -DCMAKE_BUILD_TYPE=Debug ..
65+
# make
66+
# make my_coverage_target
67+
#
68+
69+
include(CMakeParseArguments)
70+
71+
# Check prereqs
72+
find_program( GCOV_PATH gcov )
73+
find_program( LCOV_PATH lcov )
74+
find_program( GENHTML_PATH genhtml )
75+
find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
76+
find_program( SIMPLE_PYTHON_EXECUTABLE python )
77+
78+
if(NOT GCOV_PATH)
79+
message(FATAL_ERROR "gcov not found! Aborting...")
80+
endif() # NOT GCOV_PATH
81+
82+
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
83+
if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
84+
message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
85+
endif()
86+
elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
87+
message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
88+
endif()
89+
90+
set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage"
91+
CACHE INTERNAL "")
92+
93+
set(CMAKE_CXX_FLAGS_COVERAGE
94+
${COVERAGE_COMPILER_FLAGS}
95+
CACHE STRING "Flags used by the C++ compiler during coverage builds."
96+
FORCE )
97+
set(CMAKE_C_FLAGS_COVERAGE
98+
${COVERAGE_COMPILER_FLAGS}
99+
CACHE STRING "Flags used by the C compiler during coverage builds."
100+
FORCE )
101+
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
102+
""
103+
CACHE STRING "Flags used for linking binaries during coverage builds."
104+
FORCE )
105+
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
106+
""
107+
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
108+
FORCE )
109+
mark_as_advanced(
110+
CMAKE_CXX_FLAGS_COVERAGE
111+
CMAKE_C_FLAGS_COVERAGE
112+
CMAKE_EXE_LINKER_FLAGS_COVERAGE
113+
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
114+
115+
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
116+
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
117+
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
118+
119+
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
120+
link_libraries(gcov)
121+
else()
122+
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
123+
endif()
124+
125+
# Defines a target for running and collection code coverage information
126+
# Builds dependencies, runs the given executable and outputs reports.
127+
# NOTE! The executable should always have a ZERO as exit code otherwise
128+
# the coverage generation will not complete.
129+
#
130+
# SETUP_TARGET_FOR_COVERAGE(
131+
# NAME testrunner_coverage # New target name
132+
# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
133+
# DEPENDENCIES testrunner # Dependencies to build first
134+
# COLLECT_EXISTING # Do not run test, collect existing gcov data
135+
# )
136+
function(SETUP_TARGET_FOR_COVERAGE)
137+
138+
set(options NONE COLLECT_EXISTING)
139+
set(oneValueArgs NAME DIRECTORY)
140+
set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES SOURCES)
141+
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
142+
143+
if(NOT LCOV_PATH)
144+
message(FATAL_ERROR "lcov not found! Aborting...")
145+
endif() # NOT LCOV_PATH
146+
147+
if(NOT GENHTML_PATH)
148+
message(FATAL_ERROR "genhtml not found! Aborting...")
149+
endif() # NOT GENHTML_PATH
150+
151+
if(NOT Coverage_DIRECTORY)
152+
set(Coverage_DIRECTORY .)
153+
endif()
154+
155+
if(NOT Coverage_COLLECT_EXISTING)
156+
set(run_command COMMAND ${Coverage_EXECUTABLE})
157+
set(cleanup_command COMMAND ${LCOV_PATH} --directory ${Coverage_DIRECTORY} --zerocounters)
158+
endif()
159+
160+
foreach(source ${Coverage_SOURCES})
161+
list(APPEND sources "\"${Coverage_DIRECTORY}/${source}\"")
162+
endforeach()
163+
164+
# Setup target
165+
add_custom_target(${Coverage_NAME}
166+
167+
# Cleanup lcov
168+
${cleanup_command}
169+
170+
# Run tests
171+
${run_command}
172+
173+
# Capturing lcov counters and generating report
174+
COMMAND ${LCOV_PATH} --directory ${Coverage_DIRECTORY} --capture --output-file ${Coverage_NAME}.info --no-external
175+
COMMAND ${LCOV_PATH} --output-file ${Coverage_NAME}.info.cleaned --extract ${Coverage_NAME}.info ${sources}
176+
COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${Coverage_NAME}.info.cleaned --demangle-cpp
177+
178+
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
179+
DEPENDS ${Coverage_DEPENDENCIES}
180+
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
181+
)
182+
183+
# Show info where to find the report
184+
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
185+
COMMAND ;
186+
COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
187+
)
188+
189+
endfunction() # SETUP_TARGET_FOR_COVERAGE
190+
191+
# Defines a target for running and collection code coverage information
192+
# Builds dependencies, runs the given executable and outputs reports.
193+
# NOTE! The executable should always have a ZERO as exit code otherwise
194+
# the coverage generation will not complete.
195+
#
196+
# SETUP_TARGET_FOR_COVERAGE_COBERTURA(
197+
# NAME ctest_coverage # New target name
198+
# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
199+
# DEPENDENCIES executable_target # Dependencies to build first
200+
# )
201+
function(SETUP_TARGET_FOR_COVERAGE_COBERTURA)
202+
203+
set(options NONE)
204+
set(oneValueArgs NAME)
205+
set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
206+
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
207+
208+
if(NOT SIMPLE_PYTHON_EXECUTABLE)
209+
message(FATAL_ERROR "python not found! Aborting...")
210+
endif() # NOT SIMPLE_PYTHON_EXECUTABLE
211+
212+
if(NOT GCOVR_PATH)
213+
message(FATAL_ERROR "gcovr not found! Aborting...")
214+
endif() # NOT GCOVR_PATH
215+
216+
# Combine excludes to several -e arguments
217+
set(COBERTURA_EXCLUDES "")
218+
foreach(EXCLUDE ${COVERAGE_EXCLUDES})
219+
set(COBERTURA_EXCLUDES "-e ${EXCLUDE} ${COBERTURA_EXCLUDES}")
220+
endforeach()
221+
222+
add_custom_target(${Coverage_NAME}
223+
224+
# Run tests
225+
${Coverage_EXECUTABLE}
226+
227+
# Running gcovr
228+
COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} ${COBERTURA_EXCLUDES}
229+
-o ${Coverage_NAME}.xml
230+
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
231+
DEPENDS ${Coverage_DEPENDENCIES}
232+
COMMENT "Running gcovr to produce Cobertura code coverage report."
233+
)
234+
235+
# Show info where to find the report
236+
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
237+
COMMAND ;
238+
COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
239+
)
240+
241+
endfunction() # SETUP_TARGET_FOR_COVERAGE_COBERTURA
242+
243+
function(APPEND_COVERAGE_COMPILER_FLAGS)
244+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
245+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
246+
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
247+
endfunction() # APPEND_COVERAGE_COMPILER_FLAGS

test/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,13 @@ set_target_properties(cppast_test PROPERTIES CXX_STANDARD 11)
3838

3939
enable_testing()
4040
add_test(NAME test COMMAND cppast_test)
41+
42+
if(CPPAST_TEST_GCOV AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
43+
setup_target_for_coverage(
44+
DIRECTORY ${CMAKE_SOURCE_DIR}
45+
NAME cppast_coverage
46+
SOURCES src/* include/*
47+
COLLECT_EXISTING
48+
)
49+
target_link_libraries(cppast_test PRIVATE gcov)
50+
endif()

0 commit comments

Comments
 (0)