blob: b25030873a85a538c2e68db0f5f465b391aef552 [file] [log] [blame]
Rui Zhangaedf5a92020-09-03 09:57:33 -07001# 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# 2020-09-02, Rui Zhang
70# - Copied from https://github.com/bilke/cmake-modules/blob/master/CodeCoverage.cmake
71# - No changes
72#
73# USAGE:
74#
75# 1. Copy this file into your cmake modules path.
76#
77# 2. Add the following line to your CMakeLists.txt (best inside an if-condition
78# using a CMake option() to enable it just optionally):
79# include(CodeCoverage)
80#
81# 3. Append necessary compiler flags:
82# append_coverage_compiler_flags()
83#
84# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og
85#
86# 4. If you need to exclude additional directories from the report, specify them
87# using full paths in the COVERAGE_EXCLUDES variable before calling
88# setup_target_for_coverage_*().
89# Example:
90# set(COVERAGE_EXCLUDES
91# '${PROJECT_SOURCE_DIR}/src/dir1/*'
92# '/path/to/my/src/dir2/*')
93# Or, use the EXCLUDE argument to setup_target_for_coverage_*().
94# Example:
95# setup_target_for_coverage_lcov(
96# NAME coverage
97# EXECUTABLE testrunner
98# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*")
99#
100# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set
101# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR)
102# Example:
103# set(COVERAGE_EXCLUDES "dir1/*")
104# setup_target_for_coverage_gcovr_html(
105# NAME coverage
106# EXECUTABLE testrunner
107# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src"
108# EXCLUDE "dir2/*")
109#
110# 5. Use the functions described below to create a custom make target which
111# runs your test executable and produces a code coverage report.
112#
113# 6. Build a Debug build:
114# cmake -DCMAKE_BUILD_TYPE=Debug ..
115# make
116# make my_coverage_target
117#
118include(CMakeParseArguments)
119# Check prereqs
120find_program( GCOV_PATH gcov )
121find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
122find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
123find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
124find_program( CPPFILT_PATH NAMES c++filt )
125if(NOT GCOV_PATH)
126 message(FATAL_ERROR "gcov not found! Aborting...")
127endif() # NOT GCOV_PATH
128if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
129 if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
130 message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
131 endif()
132elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
133 if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang")
134 # Do nothing; exit conditional without error if true
135 elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU")
136 # Do nothing; exit conditional without error if true
137 else()
138 message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
139 endif()
140endif()
141set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage"
142 CACHE INTERNAL "")
143set(CMAKE_Fortran_FLAGS_COVERAGE
144 ${COVERAGE_COMPILER_FLAGS}
145 CACHE STRING "Flags used by the Fortran compiler during coverage builds."
146 FORCE )
147set(CMAKE_CXX_FLAGS_COVERAGE
148 ${COVERAGE_COMPILER_FLAGS}
149 CACHE STRING "Flags used by the C++ compiler during coverage builds."
150 FORCE )
151set(CMAKE_C_FLAGS_COVERAGE
152 ${COVERAGE_COMPILER_FLAGS}
153 CACHE STRING "Flags used by the C compiler during coverage builds."
154 FORCE )
155set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
156 ""
157 CACHE STRING "Flags used for linking binaries during coverage builds."
158 FORCE )
159set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
160 ""
161 CACHE STRING "Flags used by the shared libraries linker during coverage builds."
162 FORCE )
163mark_as_advanced(
164 CMAKE_Fortran_FLAGS_COVERAGE
165 CMAKE_CXX_FLAGS_COVERAGE
166 CMAKE_C_FLAGS_COVERAGE
167 CMAKE_EXE_LINKER_FLAGS_COVERAGE
168 CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
169if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
170 message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
171endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
172if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
173 link_libraries(gcov)
174endif()
175# Defines a target for running and collection code coverage information
176# Builds dependencies, runs the given executable and outputs reports.
177# NOTE! The executable should always have a ZERO as exit code otherwise
178# the coverage generation will not complete.
179#
180# setup_target_for_coverage_lcov(
181# NAME testrunner_coverage # New target name
182# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
183# DEPENDENCIES testrunner # Dependencies to build first
184# BASE_DIRECTORY "../" # Base directory for report
185# # (defaults to PROJECT_SOURCE_DIR)
186# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
187# # to BASE_DIRECTORY, with CMake 3.4+)
188# NO_DEMANGLE # Don't demangle C++ symbols
189# # even if c++filt is found
190# )
191function(setup_target_for_coverage_lcov)
192 set(options NO_DEMANGLE)
193 set(oneValueArgs BASE_DIRECTORY NAME)
194 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS)
195 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
196 if(NOT LCOV_PATH)
197 message(FATAL_ERROR "lcov not found! Aborting...")
198 endif() # NOT LCOV_PATH
199 if(NOT GENHTML_PATH)
200 message(FATAL_ERROR "genhtml not found! Aborting...")
201 endif() # NOT GENHTML_PATH
202 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
203 if(${Coverage_BASE_DIRECTORY})
204 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
205 else()
206 set(BASEDIR ${PROJECT_SOURCE_DIR})
207 endif()
208 # Collect excludes (CMake 3.4+: Also compute absolute paths)
209 set(LCOV_EXCLUDES "")
210 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES})
211 if(CMAKE_VERSION VERSION_GREATER 3.4)
212 get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
213 endif()
214 list(APPEND LCOV_EXCLUDES "${EXCLUDE}")
215 endforeach()
216 list(REMOVE_DUPLICATES LCOV_EXCLUDES)
217 # Conditional arguments
218 if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
219 set(GENHTML_EXTRA_ARGS "--demangle-cpp")
220 endif()
221 # Setup target
222 add_custom_target(${Coverage_NAME}
223 # Cleanup lcov
224 COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . -b ${BASEDIR} --zerocounters
225 # Create baseline to make sure untouched files show up in the report
226 COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b ${BASEDIR} -o ${Coverage_NAME}.base
227 # Run tests
228 COMMAND ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
229 # Capturing lcov counters and generating report
230 COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture
231 # add baseline counters
232 COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total
233 # filter collected data to final coverage report
234 COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info
235 # Generate HTML output
236 COMMAND ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o ${Coverage_NAME} ${Coverage_NAME}.info
237 # Set output files as GENERATED (will be removed on 'make clean')
238 BYPRODUCTS
239 ${Coverage_NAME}.base
240 ${Coverage_NAME}.capture
241 ${Coverage_NAME}.total
242 ${Coverage_NAME}.info
243 ${Coverage_NAME} # report directory
244 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
245 DEPENDS ${Coverage_DEPENDENCIES}
246 VERBATIM # Protect arguments to commands
247 COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
248 )
249 # Show where to find the lcov info report
250 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
251 COMMAND ;
252 COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info."
253 )
254 # Show info where to find the report
255 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
256 COMMAND ;
257 COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
258 )
259endfunction() # setup_target_for_coverage_lcov
260# Defines a target for running and collection code coverage information
261# Builds dependencies, runs the given executable and outputs reports.
262# NOTE! The executable should always have a ZERO as exit code otherwise
263# the coverage generation will not complete.
264#
265# setup_target_for_coverage_gcovr_xml(
266# NAME ctest_coverage # New target name
267# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
268# DEPENDENCIES executable_target # Dependencies to build first
269# BASE_DIRECTORY "../" # Base directory for report
270# # (defaults to PROJECT_SOURCE_DIR)
271# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
272# # to BASE_DIRECTORY, with CMake 3.4+)
273# )
274function(setup_target_for_coverage_gcovr_xml)
275 set(options NONE)
276 set(oneValueArgs BASE_DIRECTORY NAME)
277 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
278 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
279 if(NOT GCOVR_PATH)
280 message(FATAL_ERROR "gcovr not found! Aborting...")
281 endif() # NOT GCOVR_PATH
282 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
283 if(${Coverage_BASE_DIRECTORY})
284 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
285 else()
286 set(BASEDIR ${PROJECT_SOURCE_DIR})
287 endif()
288 # Collect excludes (CMake 3.4+: Also compute absolute paths)
289 set(GCOVR_EXCLUDES "")
290 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
291 if(CMAKE_VERSION VERSION_GREATER 3.4)
292 get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
293 endif()
294 list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
295 endforeach()
296 list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
297 # Combine excludes to several -e arguments
298 set(GCOVR_EXCLUDE_ARGS "")
299 foreach(EXCLUDE ${GCOVR_EXCLUDES})
300 list(APPEND GCOVR_EXCLUDE_ARGS "-e")
301 list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
302 endforeach()
303 add_custom_target(${Coverage_NAME}
304 # Run tests
305 ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
306 # Running gcovr
307 COMMAND ${GCOVR_PATH} --xml
308 -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS}
309 --object-directory=${PROJECT_BINARY_DIR}
310 -o ${Coverage_NAME}.xml
311 BYPRODUCTS ${Coverage_NAME}.xml
312 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
313 DEPENDS ${Coverage_DEPENDENCIES}
314 VERBATIM # Protect arguments to commands
315 COMMENT "Running gcovr to produce Cobertura code coverage report."
316 )
317 # Show info where to find the report
318 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
319 COMMAND ;
320 COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
321 )
322endfunction() # setup_target_for_coverage_gcovr_xml
323# Defines a target for running and collection code coverage information
324# Builds dependencies, runs the given executable and outputs reports.
325# NOTE! The executable should always have a ZERO as exit code otherwise
326# the coverage generation will not complete.
327#
328# setup_target_for_coverage_gcovr_html(
329# NAME ctest_coverage # New target name
330# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
331# DEPENDENCIES executable_target # Dependencies to build first
332# BASE_DIRECTORY "../" # Base directory for report
333# # (defaults to PROJECT_SOURCE_DIR)
334# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
335# # to BASE_DIRECTORY, with CMake 3.4+)
336# )
337function(setup_target_for_coverage_gcovr_html)
338 set(options NONE)
339 set(oneValueArgs BASE_DIRECTORY NAME)
340 set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
341 cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
342 if(NOT GCOVR_PATH)
343 message(FATAL_ERROR "gcovr not found! Aborting...")
344 endif() # NOT GCOVR_PATH
345 # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
346 if(${Coverage_BASE_DIRECTORY})
347 get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
348 else()
349 set(BASEDIR ${PROJECT_SOURCE_DIR})
350 endif()
351 # Collect excludes (CMake 3.4+: Also compute absolute paths)
352 set(GCOVR_EXCLUDES "")
353 foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
354 if(CMAKE_VERSION VERSION_GREATER 3.4)
355 get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
356 endif()
357 list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
358 endforeach()
359 list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
360 # Combine excludes to several -e arguments
361 set(GCOVR_EXCLUDE_ARGS "")
362 foreach(EXCLUDE ${GCOVR_EXCLUDES})
363 list(APPEND GCOVR_EXCLUDE_ARGS "-e")
364 list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
365 endforeach()
366 add_custom_target(${Coverage_NAME}
367 # Run tests
368 ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
369 # Create folder
370 COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
371 # Running gcovr
372 COMMAND ${GCOVR_PATH} --html --html-details
373 -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS}
374 --object-directory=${PROJECT_BINARY_DIR}
375 -o ${Coverage_NAME}/index.html
376 BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME} # report directory
377 WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
378 DEPENDS ${Coverage_DEPENDENCIES}
379 VERBATIM # Protect arguments to commands
380 COMMENT "Running gcovr to produce HTML code coverage report."
381 )
382 # Show info where to find the report
383 add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
384 COMMAND ;
385 COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
386 )
387endfunction() # setup_target_for_coverage_gcovr_html
388function(append_coverage_compiler_flags)
389 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
390 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
391 set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
392 message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
393endfunction() # append_coverage_compiler_flags
394