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