blob: 27ce31df3387df53611e2be0ed7e34e5d8e3e623 [file] [log] [blame]
Imre Kisa21712e2019-10-08 12:56:59 +02001#
2# Copyright (c) 2019-2021, Arm Limited. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6
Juan Pablo Condec51baad2023-06-21 11:28:21 -05007
Imre Kisa21712e2019-10-08 12:56:59 +02008#[===[.rst:
9UnitTest CMake module
10---------------------
11
12Control flow
13^^^^^^^^^^^^
14
151. Setting :cmake:variable:`CLANG_LIBRARY_PATH`
16
17 1. Using :cmake:variable:`CLANG_LIBRARY_PATH` CMake variable
18
19 2. Using ``CLANG_LIBRARY_PATH`` environment variable
20
21 3. Trying to find by ``find_package`` function which calls :cmake:module:`FindLibClang`.
22
232. Checking if ``c-picker`` command is available
24
25
26Variables
27^^^^^^^^^
28
29The module sets the following variables while it's checking its prerequisites.
30
31.. cmake:variable:: GIT_COMMAND
32
33Path of git executable.
34
35.. cmake:variable:: CLANG_LIBRARY_PATH
36
37c-picker uses libclang to parse the source files. If defined this variable
38specifies the path of the library.
39
40.. cmake:variable:: CPICKER_COMMAND
41
42Path of c-picker executable which is part of the c-picker pip package.
43
44.. cmake:variable:: UNIT_TEST_PROJECT_PATH
45
46Path of the project source directory. **This needs to be specified
47by the developer** to point to a suitable working copy of project to be tested.
48
49.. cmake:variable:: CPPUTEST_URL
50
51URL of the CppUTest git repository. By default it points to the official Github
52repository of CppUTest. It can be used to specify a different CppUTest mirror.
53
54.. cmake:variable:: CPPUTEST_REFSPEC
55
56CppUTest git refspec. The default value selects the latest release.
57
58.. cmake:variable:: CPPUTEST_INSTALL_PATH
59
60Temporary directory used during CppUTest build
61
62.. cmake:variable:: CPPUTEST_PACKAGE_PATH
63
64Path of the CppUTest CMake package directory
65
66.. cmake:variable:: CPICKER_CACHE_PATH
67
68Directory of c-picker generated files. Subdirectories are added according to
69the path of the original source file's path.
70
71.. cmake:variable:: UNIT_TEST_COMMON_SOURCES
72
73Lists of source files that are included in all test builds.
74
75
76Functions
77^^^^^^^^^
78
79#]===]
80
81include_guard(DIRECTORY)
82
83include(FetchContent)
84
85set(CLANG_LIBRARY_PATH_HELP "libclang directory for c-picker")
86
87set(CPPUTEST_URL "https://github.com/cpputest/cpputest.git" CACHE STRING "CppUTest repository URL")
88set(CPPUTEST_REFSPEC "v4.0" CACHE STRING "CppUTest git refspec")
89set(CPPUTEST_INSTALL_PATH ${CMAKE_CURRENT_BINARY_DIR}/CppUTest_install CACHE PATH "CppUTest installation directory")
90set(CPPUTEST_PACKAGE_PATH ${CPPUTEST_INSTALL_PATH}/lib/CppUTest/cmake CACHE PATH "CppUTest CMake package directory")
91
92set(CPICKER_CACHE_PATH ${CMAKE_CURRENT_BINARY_DIR}/cpicker_cache CACHE PATH "Directory of c-picker generated file")
93
94set(UNIT_TEST_COMMON_SOURCES ${CMAKE_CURRENT_LIST_DIR}/../common/main.cpp CACHE STRING "List of common test source files")
95
Juan Pablo Condec51baad2023-06-21 11:28:21 -050096if (POSITION_INDEPENDENT_CODE)
97 string(APPEND CPPUTEST_CXX_FLAGS -fPIC " " -pie)
98 string(APPEND CPPUTEST_C_FLAGS -fPIC " " -pie)
99 SET(TEST_COMPILE_OPTIONS ${TEST_COMPILE_OPTIONS} -fPIC -pie)
100 SET(TEST_LINK_OPTIONS ${TEST_LINK_OPTIONS} -fPIC -pie)
101else()
102 string(APPEND CPPUTEST_CXX_FLAGS -no-pie)
103 string(APPEND CPPUTEST_C_FLAGS -no-pie)
104 string(APPEND TEST_COMPILE_OPTIONS -no-pie)
105 string(APPEND TEST_LINK_OPTIONS -no-pie)
106endif()
107
Imre Kisa21712e2019-10-08 12:56:59 +0200108# Checking git
109find_program(GIT_COMMAND "git")
110if (NOT GIT_COMMAND)
111 message(FATAL_ERROR "Please install git")
112endif()
113
114if (DEFINED CLANG_LIBRARY_PATH)
115 message(STATUS "Using CLANG_LIBRARY_PATH from CMake variable (command line or cache)")
116
117 if (DEFINED ENV{CLANG_LIBRARY_PATH})
118 if (NOT (${CLANG_LIBRARY_PATH} STREQUAL $ENV{CLANG_LIBRARY_PATH}))
119 message(WARNING "Both CLANG_LIBRARY_PATH CMake and environment variables are set but have different values")
120 endif()
121 endif()
122else()
123 if (DEFINED ENV{CLANG_LIBRARY_PATH})
124 message(STATUS "Setting CLANG_LIBRARY_PATH based on environment variable")
125 set(CLANG_LIBRARY_PATH $ENV{CLANG_LIBRARY_PATH} CACHE PATH ${CLANG_LIBRARY_PATH_HELP})
126 else()
127 message(STATUS "Setting CLANG_LIBRARY_PATH based on find_package")
128 find_package(LibClang REQUIRED)
129 set(CLANG_LIBRARY_PATH ${LibClang_LIBRARY_DIRS} CACHE PATH ${CLANG_LIBRARY_PATH_HELP})
130 endif()
131endif()
132
133message(STATUS "CLANG_LIBRARY_PATH has been set to ${CLANG_LIBRARY_PATH}")
134
135# Checking c-picker
136find_program(CPICKER_COMMAND "c-picker")
137if (NOT CPICKER_COMMAND)
138 message(FATAL_ERROR "Please install c-picker using pip")
139endif()
140
141#[===[.rst:
142.. cmake:command:: unit_test_init_cpputest
143
144 .. code-block:: cmake
145
146 unit_test_init_cpputest()
147
148 The ``unit_test_init_cpputest`` CMake function fetches and build CppUTest unit testing framework.
149 It also enables linking the library to the test binaries.
150
151 Global dependencies:
152
153 ``CPPUTEST_URL``
154 Root directory of the c-picker generated files
155
156 ``CPPUTEST_REFSPEC``
157 Common source files for every test build
158
159#]===]
160function(unit_test_init_cpputest)
161 # Fetching CppUTest
162 FetchContent_Declare(
163 cpputest
164 GIT_REPOSITORY ${CPPUTEST_URL}
165 GIT_TAG ${CPPUTEST_REFSPEC}
166 GIT_SHALLOW TRUE
167 PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/common/cpputest-cmake-fix.patch || true
168 )
169
170 # FetchContent_GetProperties exports cpputest_SOURCE_DIR and cpputest_BINARY_DIR variables
171 FetchContent_GetProperties(cpputest)
172 if(NOT cpputest_POPULATED)
173 message(STATUS "Fetching CppUTest")
174 FetchContent_Populate(cpputest)
175 endif()
176
177 # Build and install CppUTest in CMake time. This makes us able to use CppUTest as a CMake package later.
178 # Memory leak detection is turned off to avoid conflict with memcheck.
179 execute_process(COMMAND
180 ${CMAKE_COMMAND}
181 -DMEMORY_LEAK_DETECTION=OFF
182 -DLONGLONG=ON
183 -DC++11=ON
184 -DCMAKE_INSTALL_PREFIX=${CPPUTEST_INSTALL_PATH}
Juan Pablo Condec51baad2023-06-21 11:28:21 -0500185 -DCPPUTEST_CXX_FLAGS=${CPPUTEST_CXX_FLAGS}
186 -DCPPUTEST_C_FLAGS=${CPPUTEST_C_FLAGS}
Imre Kisa21712e2019-10-08 12:56:59 +0200187 -GUnix\ Makefiles
188 ${cpputest_SOURCE_DIR}
189 WORKING_DIRECTORY
190 ${cpputest_BINARY_DIR}
191 )
192 execute_process(COMMAND ${CMAKE_COMMAND} --build ${cpputest_BINARY_DIR} -- install -j)
193
194 # Finding CppUTest package. CMake will check [package name]_DIR variable.
195 set(CppUTest_DIR ${CPPUTEST_PACKAGE_PATH} CACHE PATH "Path of CppUTestConfig.cmake")
196 find_package(CppUTest CONFIG REQUIRED)
197
198 # find_package sets the CppUTest_INCLUDE_DIRS and CppUTest_LIBRARIES variables
199 include_directories(${CppUTest_INCLUDE_DIRS})
200 link_libraries(${CppUTest_LIBRARIES})
201endfunction()
202
203#[===[.rst:
204.. cmake:command:: unit_test_add_suite
205
206 .. code-block:: cmake
207
208 unit_test_add_suite(
209 NAME test_name
210 SOURCES source_files
211 INCLUDE_DIRECTORIES include_directories
212 COMPILE_DEFINITIONS defines
213 DEPENDS dependencies
214 )
215
216 The ``unit_test_add_suite`` CMake function provides a convenient interface for
217 defining unit test suites. Basically its input is the test source files, include
218 paths and macro definitions and it internally does all the necessary steps to
219 have the test binary registered in CTest as a result.
220
221 Control flow:
222
223 1. Adding new executable named ``NAME``
224
225 2. Iterating throught ``SOURCES``
226
227 1. If it's a normal source file add to the executable's source list
228 2. If it's a YAML file add as a c-picker custom command and add the generated
229 file to the executable's source list
230
231 3. Setting include directories
232
233 4. Setting defines
234
235 5. Adding extra dependencies of the test build
236
237 6. Adding executable to the system as a test
238
239 Inputs:
240
241 ``NAME``
242 Unique name of the test suite
243
244 ``SOURCES`` (multi, optional)
245 Source files
246
247 ``INCLUDE_DIRECTORIES`` (multi, optional)
248 Include directories
249
250 ``COMPILE_DEFINITIONS`` (multi, optional)
251 Defines
252
253 ``DEPENDS`` (multi, optional)
254 Extra targets as dependencies of the test build
255
256 Global dependencies:
257
258 ``UNIT_TEST_PROJECT_PATH``
259 Root directory of the project under test.
260
261 ``CPICKER_CACHE_PATH``
262 Root directory of the c-picker generated files
263
264 ``UNIT_TEST_COMMON_SOURCES``
265 Common source files for every test build
266
267 ``CTest``
268 Built-in testing module of CMake
269
270#]===]
271function(unit_test_add_suite)
272 set(_OPTIONS_ARGS)
273 set(_ONE_VALUE_ARGS NAME)
274 set(_MULTI_VALUE_ARGS SOURCES INCLUDE_DIRECTORIES COMPILE_DEFINITIONS DEPENDS)
275 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
276
277 if(NOT DEFINED BUILD_TESTING)
278 message(FATAL_ERROR
279 "unit_test_add_suite(): "
280 "CTest module should be included in the root CMakeLists.txt before calling this function.")
281 endif()
282
283 if (NOT UNIT_TEST_PROJECT_PATH)
284 message(FATAL_ERROR "UNIT_TEST_PROJECT_PATH is not set")
285 endif()
286
287 set(TEST_NAME ${_MY_PARAMS_NAME})
288 set(TEST_INCLUDE_DIRECTORIES ${_MY_PARAMS_INCLUDE_DIRECTORIES})
289 set(TEST_COMPILE_DEFINITIONS ${_MY_PARAMS_COMPILE_DEFINITIONS})
290 set(TEST_DEPENDS ${_MY_PARAMS_DEPENDS})
291
292 add_executable(${TEST_NAME} ${UNIT_TEST_COMMON_SOURCES})
293
294 foreach(TEST_SOURCE ${_MY_PARAMS_SOURCES})
295 get_filename_component(TEST_SOURCE_EXTENSION ${TEST_SOURCE} EXT)
296
297 if (${TEST_SOURCE_EXTENSION} STREQUAL ".yml")
298 # Building output file name: tests/a/b/test.yml -> ${CPICKER_CACHE_PATH}/a/b/test.c
299 get_filename_component(TEST_SOURCE_DIR ${TEST_SOURCE} DIRECTORY)
300 file(RELATIVE_PATH CPICKER_SOURCE_DIR ${UNIT_TEST_PROJECT_PATH} ${TEST_SOURCE_DIR})
301 get_filename_component(TEST_SOURCE_NAME ${TEST_SOURCE} NAME_WE)
302 set(CPICKER_OUTPUT ${CPICKER_CACHE_PATH}/${TEST_NAME}/${CPICKER_SOURCE_DIR}/${TEST_SOURCE_NAME}.c)
303
304 # Creating output directory
305 get_filename_component(OUTPUT_DIRECTORY ${CPICKER_OUTPUT} DIRECTORY)
306 file(MAKE_DIRECTORY ${OUTPUT_DIRECTORY})
307
308 # Fetching referenced source files as the dependencies of the generated file
309 execute_process(
310 COMMAND
311 ${CMAKE_COMMAND} -E env CLANG_LIBRARY_PATH=${CLANG_LIBRARY_PATH}
312 ${CPICKER_COMMAND} --config ${TEST_SOURCE} --root ${UNIT_TEST_PROJECT_PATH} --print-dependencies
313 OUTPUT_VARIABLE CPICKER_DEPENDENCIES
314 )
315
316 # Adding custom command for invoking c-picker
317 add_custom_command(
318 OUTPUT ${CPICKER_OUTPUT}
319 COMMAND
320 ${CMAKE_COMMAND} -E env CLANG_LIBRARY_PATH=${CLANG_LIBRARY_PATH}
321 ${CPICKER_COMMAND} --config ${TEST_SOURCE} --root ${UNIT_TEST_PROJECT_PATH} > ${CPICKER_OUTPUT}
322 DEPENDS ${TEST_SOURCE} ${CPICKER_DEPENDENCIES}
323 COMMENT "Generating c-picker output ${CPICKER_OUTPUT}"
324 )
325 set(TEST_SOURCE ${CPICKER_OUTPUT})
326 endif()
327
328 target_sources(${TEST_NAME} PRIVATE ${TEST_SOURCE})
329 endforeach()
330
331 target_include_directories(${TEST_NAME} PRIVATE ${TEST_INCLUDE_DIRECTORIES})
332 target_compile_definitions(${TEST_NAME} PRIVATE ${TEST_COMPILE_DEFINITIONS})
Juan Pablo Condec51baad2023-06-21 11:28:21 -0500333 target_compile_options(${TEST_NAME} PRIVATE ${TEST_COMPILE_OPTIONS})
334 target_link_options(${TEST_NAME} PRIVATE ${TEST_LINK_OPTIONS})
Imre Kisa21712e2019-10-08 12:56:59 +0200335 if (TEST_DEPENDS)
336 add_dependencies(${TEST_NAME} ${TEST_DEPENDS})
337 endif()
338 add_test(${TEST_NAME} ${TEST_NAME})
339endfunction()