aboutsummaryrefslogtreecommitdiff
path: root/cmake/Coverage.cmake
blob: b7a643931797e46d1fa780471619c957cee5e76f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#
# Copyright (c) 2020-2021, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#

#[===[.rst:
Coverage CMake module
---------------------

Control flow
^^^^^^^^^^^^

Using the code coverage feature of the system starts with including
``Coverage`` module. This will implicitly check if all the requirements for
generating coverage are fulfilled. This includes checking the following
conditions.

- Compiler is GCC
- lcov executables exist
- ``c-picker-coverage-mapper`` is available

As the next step it sets the compiler flags to make GCC to generate binaries
with coverage information.


Variables
^^^^^^^^^

The module sets the following variables while it's checking its prerequisites.

.. cmake:variable:: LCOV_COMMAND

Path of lcov executable

.. cmake:variable:: GENHTML_COMMAND

Path of genhtml executable which is part of the lcov package.

.. cmake:variable:: CPICKER_COVERAGE_MAPPER_COMMAND

Path of ``c-picker-coverage-mapper`` executable which is provided by c-picker
pip package.


Functions
^^^^^^^^^

The module also contains functions for setting up the coverage feature.

#]===]

include_guard(DIRECTORY)

# Checking GCC
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
	message(FATAL_ERROR "Coverage measurement is only supported when using GCC")
endif()

# Checking lcov
find_program(LCOV_COMMAND "lcov")
if (NOT LCOV_COMMAND)
	message(FATAL_ERROR "Please install lcov")
endif()

# Checking c-picker-coverage-mapper
find_program(CPICKER_COVERAGE_MAPPER_COMMAND "c-picker-coverage-mapper")
if (NOT CPICKER_COVERAGE_MAPPER_COMMAND)
	message(FATAL_ERROR "Please install c-picker-coverage-mapper using pip (part of c-picker)")
endif()

# Checking genhtml
find_program(GENHTML_COMMAND "genhtml")
if (NOT GENHTML_COMMAND)
	message(FATAL_ERROR "Please install genhtml with genhtml (part of lcov)")
endif()

# Including this file enables code coverage measurement by adding the necessary compiler and
# linker flags.
set(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage -fno-exceptions")
set(CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage")

# Adding custom targets
add_custom_target(coverage)
add_custom_target(coverage_report)

# Adds a file to the dependency list of the target by inserting an accessory
# custom target. The name of the custom target is properly escaped.
function(add_coverage_dependency)
	set(_OPTIONS_ARGS)
	set(_ONE_VALUE_ARGS TARGET DEPENDS)
	set(_MULTI_VALUE_ARGS)
	cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})

	set(TARGET ${_MY_PARAMS_TARGET})
	set(DEPENDS ${_MY_PARAMS_DEPENDS})

	string(REGEX REPLACE "\\/" "_" CUSTOM_TARGET_SUFFIX ${DEPENDS})

	add_custom_target(${TARGET}_target_${CUSTOM_TARGET_SUFFIX} DEPENDS ${DEPENDS})
	add_dependencies(${TARGET} ${TARGET}_target_${CUSTOM_TARGET_SUFFIX})
endfunction()

#[===[.rst:
.. cmake:command:: coverage_generate

  .. code-block:: cmake

    coverage_generate(
    	NAME test_name
    	SOURCE_DIR source_directory
    	BINARY_DIR binary_directory
    	OUTPUT_FILE output_file
    )

  The function outputs an lcov info file for further processing. It also handles
  the remapping of the coverage of the c-picker generated files.

  Control flow:

  1. Running the ``lcov`` command for collecting the coverage data from the
     available ``.gcda`` and ``.gcno`` files in the ``BINARY_DIR``.

  2. The output of previous step is processed by ``c-picker-coverage-mapper``.
     This will remap the coverage of files in ``CPICKER_CACHE_PATH`` to the
     original source files.

  3. Adds the output file to the ``coverage`` target's dependency list.

  Inputs:

  ``NAME``
    Test name included in lcov info file

  ``SOURCE_DIR``
    Directory of source files

  ``BINARY_DIR``
    Directory of the ``.gcda`` and ``.gcno`` files

  ``OUTPUT_FILE``
    Output lcov coverage info file

  Global dependencies:

  ``CPICKER_CACHE_PATH``
    Root directory of the c-picker generated files


#]===]
function(coverage_generate)
	set(_OPTIONS_ARGS)
	set(_ONE_VALUE_ARGS NAME SOURCE_DIR BINARY_DIR OUTPUT_FILE)
	set(_MULTI_VALUE_ARGS)
	cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})

	set(TEST_NAME ${_MY_PARAMS_NAME})
	set(SOURCE_DIR ${_MY_PARAMS_SOURCE_DIR})
	set(BINARY_DIR ${_MY_PARAMS_BINARY_DIR})
	set(TEMP_FILE ${_MY_PARAMS_OUTPUT_FILE}_temp)
	set(OUTPUT_FILE ${_MY_PARAMS_OUTPUT_FILE})

	# Collecting information from .gcda and .gcno files into an lcov .info file
	# Mapping c-picker generated files' coverage info to the original source lines
	add_custom_command(
		OUTPUT ${TEMP_FILE} ${OUTPUT_FILE}
		COMMAND ${LCOV_COMMAND}
			--capture
			--test-name ${TEST_NAME}
			--directory ${BINARY_DIR}
			--base-directory ${SOURCE_DIR}
			--output-file ${TEMP_FILE}
		COMMAND ${CPICKER_COVERAGE_MAPPER_COMMAND}
			--input ${TEMP_FILE}
			--output ${OUTPUT_FILE}
			--mapping-path ${CPICKER_CACHE_PATH}
	)

	add_coverage_dependency(
		TARGET coverage
		DEPENDS ${OUTPUT_FILE}
	)
endfunction()

#[===[.rst:
.. cmake:command:: coverage_filter

  .. code-block:: cmake

    coverage_filter(
    	INPUT_FILE input_file
    	OUTPUT_FILE output_file
    	INCLUDE_DIRECTORY include_directory
    )

  The function filters the coverage data by including only the coverage of the
  files of ``INCLUDE_DIRECTORY`` or its subdirectories. It adds the filtered
  output file to the ``coverage`` target's dependency list.

  Inputs:

  ``INPUT_FILE``
    Input lcov coverage info file

  ``OUTPUT_FILE``
    Output lcov coverage info file

  ``INCLUDE_DIRECTORY``
    Root directory of included files

#]===]
function(coverage_filter)
	set(_OPTIONS_ARGS)
	set(_ONE_VALUE_ARGS INPUT_FILE OUTPUT_FILE INCLUDE_DIRECTORY)
	set(_MULTI_VALUE_ARGS)
	cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})

	set(INPUT_FILE ${_MY_PARAMS_INPUT_FILE})
	set(OUTPUT_FILE ${_MY_PARAMS_OUTPUT_FILE})
	set(INCLUDE_DIRECTORY ${_MY_PARAMS_INCLUDE_DIRECTORY})

	# The pattern must be an absolute path ending with an asterisk
	get_filename_component(INCLUDE_DIRECTORY_ABSPATH "${INCLUDE_DIRECTORY}" ABSOLUTE)
	set(INCLUDE_DIRECTORY_ABSPATH "${INCLUDE_DIRECTORY_ABSPATH}/*")

	add_custom_command(
		OUTPUT ${OUTPUT_FILE}
		COMMAND ${LCOV_COMMAND}
			--extract ${INPUT_FILE} \"${INCLUDE_DIRECTORY_ABSPATH}\"
			--output-file ${OUTPUT_FILE}
		DEPENDS ${INPUT_FILE}
	)

	add_coverage_dependency(
		TARGET coverage
		DEPENDS ${OUTPUT_FILE}
	)
endfunction()

#[===[.rst:
.. cmake:command:: coverage_generate_report

  .. code-block:: cmake

    coverage_generate_report(
    	INPUT_FILE input_file
    	OUTPUT_DIRECTORY output_directory
    )

  The function generates a HTML coverage report from the lcov info file into
  the ``OUTPUT_DIRECTORY``. It adds the output directory to the
  ``coverage_report`` target's dependency list.

  Inputs:

  ``INPUT_FILE``
    Input lcov coverage info file

  ``OUTPUT_DIRECTORY``
    Output directory of the coverage report where the ``index.html`` is the
    root file of the report.

#]===]
function(coverage_generate_report)
	set(_OPTIONS_ARGS)
	set(_ONE_VALUE_ARGS INPUT_FILE OUTPUT_DIRECTORY)
	set(_MULTI_VALUE_ARGS)
	cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})

	set(INPUT_FILE ${_MY_PARAMS_INPUT_FILE})
	set(OUTPUT_DIRECTORY ${_MY_PARAMS_OUTPUT_DIRECTORY})

	add_custom_command(
		OUTPUT ${OUTPUT_DIRECTORY}
		COMMAND genhtml ${INPUT_FILE}
			--show-details
			--output-directory ${OUTPUT_DIRECTORY}
		DEPENDS ${INPUT_FILE}
	)

	add_coverage_dependency(
		TARGET coverage_report
		DEPENDS ${OUTPUT_DIRECTORY}
	)
endfunction()