CodeCoverage.cmake
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 # 2019-05-06, Anatolii Kurotych 45 # - Remove unnecessary --coverage flag 46 # 47 # 2019-12-13, FeRD (Frank Dana) 48 # - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor 49 # of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. 50 # - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY 51 # - All setup functions: accept BASE_DIRECTORY, EXCLUDE list 52 # - Set lcov basedir with -b argument 53 # - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be 54 # overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) 55 # - Delete output dir, .info file on 'make clean' 56 # - Remove Python detection, since version mismatches will break gcovr 57 # - Minor cleanup (lowercase function names, update examples...) 58 # 59 # 2019-12-19, FeRD (Frank Dana) 60 # - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets 61 # 62 # 2020-01-19, Bob Apthorpe 63 # - Added gfortran support 64 # 65 # 2020-02-17, FeRD (Frank Dana) 66 # - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters 67 # in EXCLUDEs, and remove manual escaping from gcovr targets 68 # 69 # 2021-01-19, Robin Mueller 70 # - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run 71 # - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional 72 # flags to the gcovr command 73 # 74 # 2020-05-04, Mihchael Davis 75 # - Add -fprofile-abs-path to make gcno files contain absolute paths 76 # - Fix BASE_DIRECTORY not working when defined 77 # - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines 78 # 79 # 2021-05-10, Martin Stump 80 # - Check if the generator is multi-config before warning about non-Debug builds 81 # 82 # 2022-02-22, Marko Wehle 83 # - Change gcovr output from -o <filename> for --xml <filename> and --html <filename> output respectively. 84 # This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". 85 # 86 # 2022-09-28, Sebastian Mueller 87 # - fix append_coverage_compiler_flags_to_target to correctly add flags 88 # - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) 89 # 90 # USAGE: 91 # 92 # 1. Copy this file into your cmake modules path. 93 # 94 # 2. Add the following line to your CMakeLists.txt (best inside an if-condition 95 # using a CMake option() to enable it just optionally): 96 # include(CodeCoverage) 97 # 98 # 3. Append necessary compiler flags for all supported source files: 99 # append_coverage_compiler_flags() 100 # Or for specific target: 101 # append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) 102 # 103 # 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og 104 # 105 # 4. If you need to exclude additional directories from the report, specify them 106 # using full paths in the COVERAGE_EXCLUDES variable before calling 107 # setup_target_for_coverage_*(). 108 # Example: 109 # set(COVERAGE_EXCLUDES 110 # '${PROJECT_SOURCE_DIR}/src/dir1/*' 111 # '/path/to/my/src/dir2/*') 112 # Or, use the EXCLUDE argument to setup_target_for_coverage_*(). 113 # Example: 114 # setup_target_for_coverage_lcov( 115 # NAME coverage 116 # EXECUTABLE testrunner 117 # EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") 118 # 119 # 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set 120 # relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) 121 # Example: 122 # set(COVERAGE_EXCLUDES "dir1/*") 123 # setup_target_for_coverage_gcovr_html( 124 # NAME coverage 125 # EXECUTABLE testrunner 126 # BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" 127 # EXCLUDE "dir2/*") 128 # 129 # 5. Use the functions described below to create a custom make target which 130 # runs your test executable and produces a code coverage report. 131 # 132 # 6. Build a Debug build: 133 # cmake -DCMAKE_BUILD_TYPE=Debug .. 134 # make 135 # make my_coverage_target 136 # 137 138 include(CMakeParseArguments) 139 140 option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) 141 142 # Check prereqs 143 find_program( GCOV_PATH gcov ) 144 find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) 145 find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) 146 find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) 147 find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) 148 find_program( CPPFILT_PATH NAMES c++filt ) 149 150 if(NOT GCOV_PATH) 151 message(FATAL_ERROR "gcov not found! Aborting...") 152 endif() # NOT GCOV_PATH 153 154 # Check supported compiler (Clang, GNU and Flang) 155 get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 156 foreach(LANG ${LANGUAGES}) 157 if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") 158 if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) 159 message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") 160 endif() 161 elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" 162 AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") 163 message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") 164 endif() 165 endforeach() 166 167 set(COVERAGE_COMPILER_FLAGS "-g --coverage" 168 CACHE INTERNAL "") 169 170 if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 171 include(CheckCXXCompilerFlag) 172 check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) 173 if(HAVE_cxx_fprofile_abs_path) 174 set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") 175 endif() 176 endif() 177 if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") 178 include(CheckCCompilerFlag) 179 check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) 180 if(HAVE_c_fprofile_abs_path) 181 set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") 182 endif() 183 endif() 184 185 set(CMAKE_Fortran_FLAGS_COVERAGE 186 ${COVERAGE_COMPILER_FLAGS} 187 CACHE STRING "Flags used by the Fortran compiler during coverage builds." 188 FORCE ) 189 set(CMAKE_CXX_FLAGS_COVERAGE 190 ${COVERAGE_COMPILER_FLAGS} 191 CACHE STRING "Flags used by the C++ compiler during coverage builds." 192 FORCE ) 193 set(CMAKE_C_FLAGS_COVERAGE 194 ${COVERAGE_COMPILER_FLAGS} 195 CACHE STRING "Flags used by the C compiler during coverage builds." 196 FORCE ) 197 set(CMAKE_EXE_LINKER_FLAGS_COVERAGE 198 "" 199 CACHE STRING "Flags used for linking binaries during coverage builds." 200 FORCE ) 201 set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 202 "" 203 CACHE STRING "Flags used by the shared libraries linker during coverage builds." 204 FORCE ) 205 mark_as_advanced( 206 CMAKE_Fortran_FLAGS_COVERAGE 207 CMAKE_CXX_FLAGS_COVERAGE 208 CMAKE_C_FLAGS_COVERAGE 209 CMAKE_EXE_LINKER_FLAGS_COVERAGE 210 CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 211 212 get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 213 if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) 214 message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") 215 endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) 216 217 if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") 218 link_libraries(gcov) 219 endif() 220 221 # Defines a target for running and collection code coverage information 222 # Builds dependencies, runs the given executable and outputs reports. 223 # NOTE! The executable should always have a ZERO as exit code otherwise 224 # the coverage generation will not complete. 225 # 226 # setup_target_for_coverage_lcov( 227 # NAME testrunner_coverage # New target name 228 # EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 229 # DEPENDENCIES testrunner # Dependencies to build first 230 # BASE_DIRECTORY "../" # Base directory for report 231 # # (defaults to PROJECT_SOURCE_DIR) 232 # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative 233 # # to BASE_DIRECTORY, with CMake 3.4+) 234 # NO_DEMANGLE # Don't demangle C++ symbols 235 # # even if c++filt is found 236 # ) 237 function(setup_target_for_coverage_lcov) 238 239 set(options NO_DEMANGLE SONARQUBE) 240 set(oneValueArgs BASE_DIRECTORY NAME) 241 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) 242 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 243 244 if(NOT LCOV_PATH) 245 message(FATAL_ERROR "lcov not found! Aborting...") 246 endif() # NOT LCOV_PATH 247 248 if(NOT GENHTML_PATH) 249 message(FATAL_ERROR "genhtml not found! Aborting...") 250 endif() # NOT GENHTML_PATH 251 252 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 253 if(DEFINED Coverage_BASE_DIRECTORY) 254 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 255 else() 256 set(BASEDIR ${PROJECT_SOURCE_DIR}) 257 endif() 258 259 # Collect excludes (CMake 3.4+: Also compute absolute paths) 260 set(LCOV_EXCLUDES "") 261 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) 262 if(CMAKE_VERSION VERSION_GREATER 3.4) 263 get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) 264 endif() 265 list(APPEND LCOV_EXCLUDES "${EXCLUDE}") 266 endforeach() 267 list(REMOVE_DUPLICATES LCOV_EXCLUDES) 268 269 # Conditional arguments 270 if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) 271 set(GENHTML_EXTRA_ARGS "--demangle-cpp") 272 endif() 273 274 # Setting up commands which will be run to generate coverage data. 275 # Cleanup lcov 276 set(LCOV_CLEAN_CMD 277 ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . 278 -b ${BASEDIR} --zerocounters 279 ) 280 # Create baseline to make sure untouched files show up in the report 281 set(LCOV_BASELINE_CMD 282 ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b 283 ${BASEDIR} -o ${Coverage_NAME}.base 284 ) 285 # Run tests 286 set(LCOV_EXEC_TESTS_CMD 287 ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} 288 ) 289 # Capturing lcov counters and generating report 290 set(LCOV_CAPTURE_CMD 291 ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b 292 ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture 293 ) 294 # add baseline counters 295 set(LCOV_BASELINE_COUNT_CMD 296 ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base 297 -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total 298 ) 299 # filter collected data to final coverage report 300 set(LCOV_FILTER_CMD 301 ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove 302 ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info 303 ) 304 # Generate HTML output 305 set(LCOV_GEN_HTML_CMD 306 ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o 307 ${Coverage_NAME} ${Coverage_NAME}.info 308 ) 309 if(${Coverage_SONARQUBE}) 310 # Generate SonarQube output 311 set(GCOVR_XML_CMD 312 ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} 313 ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} 314 ) 315 set(GCOVR_XML_CMD_COMMAND 316 COMMAND ${GCOVR_XML_CMD} 317 ) 318 set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) 319 set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") 320 endif() 321 322 323 if(CODE_COVERAGE_VERBOSE) 324 message(STATUS "Executed command report") 325 message(STATUS "Command to clean up lcov: ") 326 string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") 327 message(STATUS "${LCOV_CLEAN_CMD_SPACED}") 328 329 message(STATUS "Command to create baseline: ") 330 string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") 331 message(STATUS "${LCOV_BASELINE_CMD_SPACED}") 332 333 message(STATUS "Command to run the tests: ") 334 string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") 335 message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") 336 337 message(STATUS "Command to capture counters and generate report: ") 338 string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") 339 message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") 340 341 message(STATUS "Command to add baseline counters: ") 342 string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") 343 message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") 344 345 message(STATUS "Command to filter collected data: ") 346 string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") 347 message(STATUS "${LCOV_FILTER_CMD_SPACED}") 348 349 message(STATUS "Command to generate lcov HTML output: ") 350 string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") 351 message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") 352 353 if(${Coverage_SONARQUBE}) 354 message(STATUS "Command to generate SonarQube XML output: ") 355 string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") 356 message(STATUS "${GCOVR_XML_CMD_SPACED}") 357 endif() 358 endif() 359 360 # Setup target 361 add_custom_target(${Coverage_NAME} 362 COMMAND ${LCOV_CLEAN_CMD} 363 COMMAND ${LCOV_BASELINE_CMD} 364 COMMAND ${LCOV_EXEC_TESTS_CMD} 365 COMMAND ${LCOV_CAPTURE_CMD} 366 COMMAND ${LCOV_BASELINE_COUNT_CMD} 367 COMMAND ${LCOV_FILTER_CMD} 368 COMMAND ${LCOV_GEN_HTML_CMD} 369 ${GCOVR_XML_CMD_COMMAND} 370 371 # Set output files as GENERATED (will be removed on 'make clean') 372 BYPRODUCTS 373 ${Coverage_NAME}.base 374 ${Coverage_NAME}.capture 375 ${Coverage_NAME}.total 376 ${Coverage_NAME}.info 377 ${GCOVR_XML_CMD_BYPRODUCTS} 378 ${Coverage_NAME}/index.html 379 WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 380 DEPENDS ${Coverage_DEPENDENCIES} 381 VERBATIM # Protect arguments to commands 382 COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 383 ) 384 385 # Show where to find the lcov info report 386 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 387 COMMAND ; 388 COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." 389 ${GCOVR_XML_CMD_COMMENT} 390 ) 391 392 # Show info where to find the report 393 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 394 COMMAND ; 395 COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." 396 ) 397 398 endfunction() # setup_target_for_coverage_lcov 399 400 # Defines a target for running and collection code coverage information 401 # Builds dependencies, runs the given executable and outputs reports. 402 # NOTE! The executable should always have a ZERO as exit code otherwise 403 # the coverage generation will not complete. 404 # 405 # setup_target_for_coverage_gcovr_xml( 406 # NAME ctest_coverage # New target name 407 # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 408 # DEPENDENCIES executable_target # Dependencies to build first 409 # BASE_DIRECTORY "../" # Base directory for report 410 # # (defaults to PROJECT_SOURCE_DIR) 411 # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative 412 # # to BASE_DIRECTORY, with CMake 3.4+) 413 # ) 414 # The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the 415 # GCVOR command. 416 function(setup_target_for_coverage_gcovr_xml) 417 418 set(options NONE) 419 set(oneValueArgs BASE_DIRECTORY NAME) 420 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) 421 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 422 423 if(NOT GCOVR_PATH) 424 message(FATAL_ERROR "gcovr not found! Aborting...") 425 endif() # NOT GCOVR_PATH 426 427 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 428 if(DEFINED Coverage_BASE_DIRECTORY) 429 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 430 else() 431 set(BASEDIR ${PROJECT_SOURCE_DIR}) 432 endif() 433 434 # Collect excludes (CMake 3.4+: Also compute absolute paths) 435 set(GCOVR_EXCLUDES "") 436 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) 437 if(CMAKE_VERSION VERSION_GREATER 3.4) 438 get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) 439 endif() 440 list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") 441 endforeach() 442 list(REMOVE_DUPLICATES GCOVR_EXCLUDES) 443 444 # Combine excludes to several -e arguments 445 set(GCOVR_EXCLUDE_ARGS "") 446 foreach(EXCLUDE ${GCOVR_EXCLUDES}) 447 list(APPEND GCOVR_EXCLUDE_ARGS "-e") 448 list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") 449 endforeach() 450 451 # Set up commands which will be run to generate coverage data 452 # Run tests 453 set(GCOVR_XML_EXEC_TESTS_CMD 454 ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} 455 ) 456 # Running gcovr 457 set(GCOVR_XML_CMD 458 ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} 459 ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} 460 ) 461 462 if(CODE_COVERAGE_VERBOSE) 463 message(STATUS "Executed command report") 464 465 message(STATUS "Command to run tests: ") 466 string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") 467 message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") 468 469 message(STATUS "Command to generate gcovr XML coverage data: ") 470 string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") 471 message(STATUS "${GCOVR_XML_CMD_SPACED}") 472 endif() 473 474 add_custom_target(${Coverage_NAME} 475 COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} 476 COMMAND ${GCOVR_XML_CMD} 477 478 BYPRODUCTS ${Coverage_NAME}.xml 479 WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 480 DEPENDS ${Coverage_DEPENDENCIES} 481 VERBATIM # Protect arguments to commands 482 COMMENT "Running gcovr to produce Cobertura code coverage report." 483 ) 484 485 # Show info where to find the report 486 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 487 COMMAND ; 488 COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." 489 ) 490 endfunction() # setup_target_for_coverage_gcovr_xml 491 492 # Defines a target for running and collection code coverage information 493 # Builds dependencies, runs the given executable and outputs reports. 494 # NOTE! The executable should always have a ZERO as exit code otherwise 495 # the coverage generation will not complete. 496 # 497 # setup_target_for_coverage_gcovr_html( 498 # NAME ctest_coverage # New target name 499 # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 500 # DEPENDENCIES executable_target # Dependencies to build first 501 # BASE_DIRECTORY "../" # Base directory for report 502 # # (defaults to PROJECT_SOURCE_DIR) 503 # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative 504 # # to BASE_DIRECTORY, with CMake 3.4+) 505 # ) 506 # The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the 507 # GCVOR command. 508 function(setup_target_for_coverage_gcovr_html) 509 510 set(options NONE) 511 set(oneValueArgs BASE_DIRECTORY NAME) 512 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) 513 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 514 515 if(NOT GCOVR_PATH) 516 message(FATAL_ERROR "gcovr not found! Aborting...") 517 endif() # NOT GCOVR_PATH 518 519 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 520 if(DEFINED Coverage_BASE_DIRECTORY) 521 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 522 else() 523 set(BASEDIR ${PROJECT_SOURCE_DIR}) 524 endif() 525 526 # Collect excludes (CMake 3.4+: Also compute absolute paths) 527 set(GCOVR_EXCLUDES "") 528 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) 529 if(CMAKE_VERSION VERSION_GREATER 3.4) 530 get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) 531 endif() 532 list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") 533 endforeach() 534 list(REMOVE_DUPLICATES GCOVR_EXCLUDES) 535 536 # Combine excludes to several -e arguments 537 set(GCOVR_EXCLUDE_ARGS "") 538 foreach(EXCLUDE ${GCOVR_EXCLUDES}) 539 list(APPEND GCOVR_EXCLUDE_ARGS "-e") 540 list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") 541 endforeach() 542 543 # Set up commands which will be run to generate coverage data 544 # Run tests 545 set(GCOVR_HTML_EXEC_TESTS_CMD 546 ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} 547 ) 548 # Create folder 549 set(GCOVR_HTML_FOLDER_CMD 550 ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} 551 ) 552 # Running gcovr 553 set(GCOVR_HTML_CMD 554 ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} 555 ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} 556 ) 557 558 if(CODE_COVERAGE_VERBOSE) 559 message(STATUS "Executed command report") 560 561 message(STATUS "Command to run tests: ") 562 string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") 563 message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") 564 565 message(STATUS "Command to create a folder: ") 566 string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") 567 message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") 568 569 message(STATUS "Command to generate gcovr HTML coverage data: ") 570 string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") 571 message(STATUS "${GCOVR_HTML_CMD_SPACED}") 572 endif() 573 574 add_custom_target(${Coverage_NAME} 575 COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} 576 COMMAND ${GCOVR_HTML_FOLDER_CMD} 577 COMMAND ${GCOVR_HTML_CMD} 578 579 BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory 580 WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 581 DEPENDS ${Coverage_DEPENDENCIES} 582 VERBATIM # Protect arguments to commands 583 COMMENT "Running gcovr to produce HTML code coverage report." 584 ) 585 586 # Show info where to find the report 587 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 588 COMMAND ; 589 COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." 590 ) 591 592 endfunction() # setup_target_for_coverage_gcovr_html 593 594 # Defines a target for running and collection code coverage information 595 # Builds dependencies, runs the given executable and outputs reports. 596 # NOTE! The executable should always have a ZERO as exit code otherwise 597 # the coverage generation will not complete. 598 # 599 # setup_target_for_coverage_fastcov( 600 # NAME testrunner_coverage # New target name 601 # EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 602 # DEPENDENCIES testrunner # Dependencies to build first 603 # BASE_DIRECTORY "../" # Base directory for report 604 # # (defaults to PROJECT_SOURCE_DIR) 605 # EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. 606 # NO_DEMANGLE # Don't demangle C++ symbols 607 # # even if c++filt is found 608 # SKIP_HTML # Don't create html report 609 # POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths 610 # ) 611 function(setup_target_for_coverage_fastcov) 612 613 set(options NO_DEMANGLE SKIP_HTML) 614 set(oneValueArgs BASE_DIRECTORY NAME) 615 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) 616 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 617 618 if(NOT FASTCOV_PATH) 619 message(FATAL_ERROR "fastcov not found! Aborting...") 620 endif() 621 622 if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) 623 message(FATAL_ERROR "genhtml not found! Aborting...") 624 endif() 625 626 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 627 if(Coverage_BASE_DIRECTORY) 628 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 629 else() 630 set(BASEDIR ${PROJECT_SOURCE_DIR}) 631 endif() 632 633 # Collect excludes (Patterns, not paths, for fastcov) 634 set(FASTCOV_EXCLUDES "") 635 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) 636 list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") 637 endforeach() 638 list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) 639 640 # Conditional arguments 641 if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) 642 set(GENHTML_EXTRA_ARGS "--demangle-cpp") 643 endif() 644 645 # Set up commands which will be run to generate coverage data 646 set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) 647 648 set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} 649 --search-directory ${BASEDIR} 650 --process-gcno 651 --output ${Coverage_NAME}.json 652 --exclude ${FASTCOV_EXCLUDES} 653 ) 654 655 set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} 656 -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info 657 ) 658 659 if(Coverage_SKIP_HTML) 660 set(FASTCOV_HTML_CMD ";") 661 else() 662 set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} 663 -o ${Coverage_NAME} ${Coverage_NAME}.info 664 ) 665 endif() 666 667 set(FASTCOV_POST_CMD ";") 668 if(Coverage_POST_CMD) 669 set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) 670 endif() 671 672 if(CODE_COVERAGE_VERBOSE) 673 message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") 674 675 message(" Running tests:") 676 string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") 677 message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") 678 679 message(" Capturing fastcov counters and generating report:") 680 string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") 681 message(" ${FASTCOV_CAPTURE_CMD_SPACED}") 682 683 message(" Converting fastcov .json to lcov .info:") 684 string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") 685 message(" ${FASTCOV_CONVERT_CMD_SPACED}") 686 687 if(NOT Coverage_SKIP_HTML) 688 message(" Generating HTML report: ") 689 string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") 690 message(" ${FASTCOV_HTML_CMD_SPACED}") 691 endif() 692 if(Coverage_POST_CMD) 693 message(" Running post command: ") 694 string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") 695 message(" ${FASTCOV_POST_CMD_SPACED}") 696 endif() 697 endif() 698 699 # Setup target 700 add_custom_target(${Coverage_NAME} 701 702 # Cleanup fastcov 703 COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} 704 --search-directory ${BASEDIR} 705 --zerocounters 706 707 COMMAND ${FASTCOV_EXEC_TESTS_CMD} 708 COMMAND ${FASTCOV_CAPTURE_CMD} 709 COMMAND ${FASTCOV_CONVERT_CMD} 710 COMMAND ${FASTCOV_HTML_CMD} 711 COMMAND ${FASTCOV_POST_CMD} 712 713 # Set output files as GENERATED (will be removed on 'make clean') 714 BYPRODUCTS 715 ${Coverage_NAME}.info 716 ${Coverage_NAME}.json 717 ${Coverage_NAME}/index.html # report directory 718 719 WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 720 DEPENDS ${Coverage_DEPENDENCIES} 721 VERBATIM # Protect arguments to commands 722 COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." 723 ) 724 725 set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") 726 if(NOT Coverage_SKIP_HTML) 727 string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") 728 endif() 729 # Show where to find the fastcov info report 730 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 731 COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} 732 ) 733 734 endfunction() # setup_target_for_coverage_fastcov 735 736 function(append_coverage_compiler_flags) 737 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 738 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 739 set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 740 message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") 741 endfunction() # append_coverage_compiler_flags 742 743 # Setup coverage for specific library 744 function(append_coverage_compiler_flags_to_target name) 745 separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") 746 target_compile_options(${name} PRIVATE ${_flag_list}) 747 if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") 748 target_link_libraries(${name} PRIVATE gcov) 749 endif() 750 endfunction()