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