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 69include(CMakeParseArguments) 70 71# Check prereqs 72find_program(GCOV_PATH gcov) 73find_program( 74 LCOV_PATH 75 NAMES 76 lcov 77 lcov.bat 78 lcov.exe 79 lcov.perl 80) 81find_program(GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat) 82find_program(GCOVR_PATH gcovr PATHS ${PROJECT_SOURCE_DIR}/scripts/test) 83find_program(SIMPLE_PYTHON_EXECUTABLE python) 84 85if(NOT GCOV_PATH) 86 message(FATAL_ERROR "gcov not found! Aborting...") 87endif() # NOT GCOV_PATH 88 89if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") 90 if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) 91 message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") 92 endif() 93elseif(NOT CMAKE_COMPILER_IS_GNUCXX) 94 message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 95endif() 96 97set( 98 COVERAGE_COMPILER_FLAGS 99 "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 100 CACHE INTERNAL "" 101) 102 103set( 104 CMAKE_CXX_FLAGS_COVERAGE 105 ${COVERAGE_COMPILER_FLAGS} 106 CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE 107) 108set( 109 CMAKE_C_FLAGS_COVERAGE 110 ${COVERAGE_COMPILER_FLAGS} 111 CACHE STRING "Flags used by the C compiler during coverage builds." FORCE 112) 113set( 114 CMAKE_EXE_LINKER_FLAGS_COVERAGE 115 "" 116 CACHE STRING "Flags used for linking binaries during coverage builds." FORCE 117) 118set( 119 CMAKE_SHARED_LINKER_FLAGS_COVERAGE 120 "" 121 CACHE 122 STRING "Flags used by the shared libraries linker during coverage builds." 123 FORCE 124) 125mark_as_advanced( 126 CMAKE_CXX_FLAGS_COVERAGE 127 CMAKE_C_FLAGS_COVERAGE 128 CMAKE_EXE_LINKER_FLAGS_COVERAGE 129 CMAKE_SHARED_LINKER_FLAGS_COVERAGE 130) 131 132if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 133 message( 134 WARNING 135 "Code coverage results with an optimised (non-Debug) build may be misleading" 136 ) 137endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" 138 139if(CMAKE_C_COMPILER_ID STREQUAL "GNU") 140 link_libraries(gcov) 141else() 142 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 143endif() 144 145# Defines a target for running and collection code coverage information Builds 146# dependencies, runs the given executable and outputs reports. NOTE! The 147# executable should always have a ZERO as exit code otherwise the coverage 148# generation will not complete. 149# 150# SETUP_TARGET_FOR_COVERAGE( NAME testrunner_coverage # New 151# target name EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in 152# PROJECT_BINARY_DIR DEPENDENCIES testrunner # Dependencies 153# to build first ) 154function(SETUP_TARGET_FOR_COVERAGE) 155 156 set(options NONE) 157 set(oneValueArgs NAME) 158 set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) 159 cmake_parse_arguments( 160 Coverage 161 "${options}" 162 "${oneValueArgs}" 163 "${multiValueArgs}" 164 ${ARGN} 165 ) 166 167 if(NOT LCOV_PATH) 168 message(FATAL_ERROR "lcov not found! Aborting...") 169 endif() # NOT LCOV_PATH 170 171 if(NOT GENHTML_PATH) 172 message(FATAL_ERROR "genhtml not found! Aborting...") 173 endif() # NOT GENHTML_PATH 174 175 # Setup target 176 add_custom_target( 177 ${Coverage_NAME} 178 # Cleanup lcov 179 COMMAND 180 ${LCOV_PATH} 181 --directory 182 . 183 --zerocounters 184 # Create baseline to make sure untouched files show up in the report 185 COMMAND 186 ${LCOV_PATH} 187 -c 188 -i 189 -d 190 . 191 -o 192 ${Coverage_NAME}.base 193 # Run tests 194 COMMAND 195 ${Coverage_EXECUTABLE} 196 # Capturing lcov counters and generating report 197 COMMAND 198 ${LCOV_PATH} 199 --directory 200 . 201 --capture 202 --output-file 203 ${Coverage_NAME}.info 204 # add baseline counters 205 COMMAND 206 ${LCOV_PATH} 207 -a 208 ${Coverage_NAME}.base 209 -a 210 ${Coverage_NAME}.info 211 --output-file 212 ${Coverage_NAME}.total 213 COMMAND 214 ${LCOV_PATH} 215 --remove 216 ${Coverage_NAME}.total 217 ${COVERAGE_EXCLUDES} 218 --output-file 219 ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned 220 COMMAND 221 ${GENHTML_PATH} 222 -o 223 ${Coverage_NAME} 224 ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned 225 COMMAND 226 ${CMAKE_COMMAND} 227 -E 228 remove 229 ${Coverage_NAME}.base 230 ${Coverage_NAME}.info 231 ${Coverage_NAME}.total 232 ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned 233 WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 234 DEPENDS ${Coverage_DEPENDENCIES} 235 COMMENT 236 "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 237 ) 238 239 # Show info where to find the report 240 add_custom_command( 241 TARGET ${Coverage_NAME} POST_BUILD 242 COMMAND ; 243 COMMENT 244 "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." 245 ) 246 247endfunction() # SETUP_TARGET_FOR_COVERAGE 248 249# Defines a target for running and collection code coverage information Builds 250# dependencies, runs the given executable and outputs reports. NOTE! The 251# executable should always have a ZERO as exit code otherwise the coverage 252# generation will not complete. 253# 254# SETUP_TARGET_FOR_COVERAGE_COBERTURA( NAME ctest_coverage # 255# New target name EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in 256# PROJECT_BINARY_DIR DEPENDENCIES executable_target # Dependencies to 257# build first ) 258function(SETUP_TARGET_FOR_COVERAGE_COBERTURA) 259 260 set(options NONE) 261 set(oneValueArgs NAME) 262 set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) 263 cmake_parse_arguments( 264 Coverage 265 "${options}" 266 "${oneValueArgs}" 267 "${multiValueArgs}" 268 ${ARGN} 269 ) 270 271 if(NOT SIMPLE_PYTHON_EXECUTABLE) 272 message(FATAL_ERROR "python not found! Aborting...") 273 endif() # NOT SIMPLE_PYTHON_EXECUTABLE 274 275 if(NOT GCOVR_PATH) 276 message(FATAL_ERROR "gcovr not found! Aborting...") 277 endif() # NOT GCOVR_PATH 278 279 # Combine excludes to several -e arguments 280 set(COBERTURA_EXCLUDES "") 281 foreach(EXCLUDE ${COVERAGE_EXCLUDES}) 282 set(COBERTURA_EXCLUDES "-e ${EXCLUDE} ${COBERTURA_EXCLUDES}") 283 endforeach() 284 285 add_custom_target( 286 ${Coverage_NAME} 287 # Run tests 288 ${Coverage_EXECUTABLE} 289 # Running gcovr 290 COMMAND 291 ${GCOVR_PATH} 292 -x 293 -r 294 ${PROJECT_SOURCE_DIR} 295 ${COBERTURA_EXCLUDES} 296 -o 297 ${Coverage_NAME}.xml 298 WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 299 DEPENDS ${Coverage_DEPENDENCIES} 300 COMMENT "Running gcovr to produce Cobertura code coverage report." 301 ) 302 303 # Show info where to find the report 304 add_custom_command( 305 TARGET ${Coverage_NAME} POST_BUILD 306 COMMAND ; 307 COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." 308 ) 309 310endfunction() # SETUP_TARGET_FOR_COVERAGE_COBERTURA 311 312function(APPEND_COVERAGE_COMPILER_FLAGS) 313 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 314 set( 315 CMAKE_CXX_FLAGS 316 "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" 317 PARENT_SCOPE 318 ) 319 message( 320 STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}" 321 ) 322endfunction() # APPEND_COVERAGE_COMPILER_FLAGS 323