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