Loading...
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) 2000-2025 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #pragma once /* BEGIN IGNORE CODESTYLE */ /* Dynamic mock allows an individual test executable to control what a mock does. * T_MOCK_DYNAMIC_DECLARE() * Declare a dynamic mock. This declaration should come in a header file under the mocks/ folder. * The header file should be included in both the respective .c file and in the test .c file that * wants to set the behaviour of the mock. * It declares the signature of the mocked function so that if the signature changes the compiler * can assure that the mock and its setters are in sync. * T_MOCK_DYNAMIC() * Define the dynamic mock. This should come in a .c file under the mocks/ folder. * This defines the mock function itself using the T_MOCK() macro. * * The test has 4 possible way to control the mock. It can temporarily set the return value, * it can set a temporary block callback, it can set a permanent return value or a permanent function. * @argument args_def is how the function arguments are defined in a function definition. * This can be copy-pasted directly from the original function definition. * @argument args_invoke is how the same arguments are passed to a function call * @argument (optional) default_action should be a scope of code that will be executed if no mock control * is set up. it can reference the arguments in args_def and also call the original * function. If this argument is not supplied, the default action is to call the original XNU * function with the same arguments. * * Example: * // we want to mock a function from XNU that has the signature: * size_t foobar(int a, char b); * * // in a header in the mocks library (tests/unit/mocks) add: * T_MOCK_DYNAMIC_DECLARE(size_t, foobar, (int a, char b)); * * // in a .c file in the mock library (tests/unit/mocks) add: * T_MOCK_DYNAMIC(size_t, foobar, (int a, char b), (a, b), { return 0 }); * * // Now to control the mock, in a T_DECL test you can do: * T_DECL(test, "test") { * T_MOCK_SET_RETVAL(foobar, size_t, 42); * // ... call into XNU which will call foobar() * * T_MOCK_SET_CALLBACK(foobar, size_t, (int a, char b), { * T_ASSERT_EQ(a, b, "args equal"); * return a + b; * }); * // ... call into XNU which will call foobar() * } * * // The third option is to define a permanent return value for the mock that will * // be in effect for all tests in the executable. * // This essentially overrides the default-value that's defined in the T_MOCK_DYNAMIC() * T_MOCK_SET_PERM_RETVAL(foobar, size_t, 43); * * // The fourth option is for the test to define a permanent function in the global scope * // that will be called every time the mock is called. * T_MOCK_SET_PERM_FUNC(size_t, foobar, (int a, char b)) { * return b - a; * } * * It's possible for multiple mock controls of different types to be active at the same time. The priority * in which the dynamic mock tries to find them is * 1. ret-val * 2. block call back * 3. permanent ret-val / permanent function * The effect of the ret-val and callback setters is limited to the scope the they are in. This * is achieved using a cleanup function in the setter. * It is possible for multiple setters of the same type to be invoked during the flow of the same scope. * In that case, the last setter that was invoked is in effect. * * It is not possible to have multiple static function setters and/or permanent ret-val setter for the * same mock in the same test executable. This would cause a compile/link error due to duplicate symbol. */ #define _T_MOCK_RETVAL_CALLBACK(name) _mock_retval_callback_ ## name #define _T_MOCK_CALLBACK(name) _mock_callback_ ## name #define _T_MOCK_PERM_RETVAL_FUNC(name) _mock_p_retval_func_ ## name #define _T_MOCK_PERM_FUNC(name) _mock_func_ ## name #define T_MOCK_DYNAMIC_DECLARE(ret, name, args_def) \ extern ret (^_T_MOCK_RETVAL_CALLBACK(name))(void); \ extern ret (^_T_MOCK_CALLBACK(name)) args_def; \ extern ret (*_T_MOCK_PERM_RETVAL_FUNC(name))(void); \ extern ret (*_T_MOCK_PERM_FUNC(name)) args_def; \ extern ret name args_def #define _T_MOCK_DYNAMIC_WITH_IMPL(ret, name, args_def, args_invoke, default_action) \ ret (^_T_MOCK_RETVAL_CALLBACK(name)) (void) = NULL; \ ret (^_T_MOCK_CALLBACK(name)) args_def = NULL; \ ret (*_T_MOCK_PERM_RETVAL_FUNC(name)) (void) = NULL; \ ret (*_T_MOCK_PERM_FUNC(name)) args_def = NULL; \ T_MOCK(ret, name, args_def) { \ if (_T_MOCK_RETVAL_CALLBACK(name) != NULL) { \ return _T_MOCK_RETVAL_CALLBACK(name)(); \ } \ if (_T_MOCK_CALLBACK(name) != NULL) { \ return _T_MOCK_CALLBACK(name) args_invoke; \ } \ if (_T_MOCK_PERM_RETVAL_FUNC(name) != NULL) { \ return _T_MOCK_PERM_RETVAL_FUNC(name)(); \ } \ if (_T_MOCK_PERM_FUNC(name) != NULL) { \ return _T_MOCK_PERM_FUNC(name) args_invoke; \ } \ default_action; \ } #define _T_MOCK_DYNAMIC_DEFAULT_IMPL(ret, name, args_def, args_invoke) \ _T_MOCK_DYNAMIC_WITH_IMPL(ret, name, args_def, args_invoke, { return name args_invoke; }) /* T_MOCK_DYNAMIC() selects which of the above versions to call depending on the number of arguments it gets * - T_MOCK_DYNAMIC(a, b, c, d) with 4 arguments expands to * _T_MOCK_GET_INSTANCE(a, b, c, d, _T_MOCK_DYNAMIC_WITH_IMPL, _T_MOCK_DYNAMIC_DEFAULT_IMPL)(a, b, c, d) * then NAME is _T_MOCK_DYNAMIC_DEFAULT_IMPL so this expands to * _T_MOCK_DYNAMIC_DEFAULT_IMPL(a, b, c, d) * - T_MOCK_DYNAMIC(a, b, c, d, e) with 5 arguments expands to * _T_MOCK_GET_INSTANCE(a, b, c, d, e, _T_MOCK_DYNAMIC_WITH_IMPL, _T_MOCK_DYNAMIC_DEFAULT_IMPL)(a, b, c, d, e) * then NAME is _T_MOCK_DYNAMIC_WITH_IMPL so this expands to * _T_MOCK_DYNAMIC_WITH_IMPL(a, b, c, e, e) */ #define _T_MOCK_GET_INSTANCE(_1, _2, _3, _4, _5, NAME, ...) NAME #define T_MOCK_DYNAMIC(...) _T_MOCK_GET_INSTANCE(__VA_ARGS__, _T_MOCK_DYNAMIC_WITH_IMPL, _T_MOCK_DYNAMIC_DEFAULT_IMPL)(__VA_ARGS__) #define _UT_CONCAT2(a, b) a ## b #define _UT_CONCAT(a, b) _UT_CONCAT2(a, b) static inline void _mock_set_cleaner(void ***ptr) { **ptr = NULL; } /* How it works? * - For each mock that is defined using T_MOCK_DYNAMIC() the macro above defines a few * global variables with the function name suffixed, and also defines the mock function to check * these global variables. * - The test executable can then set any of them using the T_MOCK_SET_X() macros below * - T_MOCK_SET_RETVAL() and T_MOCK_SET_CALLBACK() should be used from inside T_DECL and have a * cleaner that undoes their effect at the end of the scope they are defined in. * The cleaner has a __COUNTER__ concatenated so that it's possible to have more than one such * T_MOCK_SET_X() invocation in the same scope * - T_MOCK_SET_PERM_RETVAL() and T_MOCK_SET_PERM_FUNC() should be used in the global scope * and has a constructor function that sets the global variable when the executable loads */ #define _T_MOCK_CLEANER(name) _UT_CONCAT(_cleaner_ ## name, __COUNTER__) #define _T_MOCK_RETVAL_CAPTURE(name, N) _UT_CONCAT(_mock_retval_capture_ ## name, N) /* to set a return value, we set a global that holds a callback block that returns the value. * The callback variable is a pointer and NULL indicates it's not set * The value expression the user gives is first captured in a local variable since some * expressions can't be captured by a block (array reference for instance) */ #define _T_MOCK_SET_RETVAL_IMPL(name, ret, val, N) \ ret _T_MOCK_RETVAL_CAPTURE(name, N) = val; \ _T_MOCK_RETVAL_CALLBACK(name) = ^ret(void) { return _T_MOCK_RETVAL_CAPTURE(name, N); }; \ __attribute__((cleanup(_mock_set_cleaner))) void **_T_MOCK_CLEANER(name) = \ (void**)&_T_MOCK_RETVAL_CALLBACK(name) #define T_MOCK_SET_RETVAL(name, ret, val) _T_MOCK_SET_RETVAL_IMPL(name, ret, val, __COUNTER__) /* to set a mock callback block from the user we set a dedicated callback for that, so it doesn't * interfere with SET_RETVAL */ #define T_MOCK_SET_CALLBACK(name, ret, args_def, body) \ _T_MOCK_CALLBACK(name) = ^ret args_def body; \ __attribute__((cleanup(_mock_set_cleaner))) void **_T_MOCK_CLEANER(name) = \ (void**)&_T_MOCK_CALLBACK(name) #define _T_MOCK_CTOR_SETTER(name) _ctor_setter_ ## name #define _T_MOCK_PERM_HOOK(name) PERM_HOOK_ ## name /* To set a permanent return value, we define a function that returns it, and set it to the * extern global in a constructor. * This setter needs to be in the global scope of the tester */ #define T_MOCK_SET_PERM_RETVAL(name, ret, val) \ ret _T_MOCK_PERM_HOOK(name)(void) { return (val); } \ __attribute__((constructor)) void _T_MOCK_CTOR_SETTER(name)() { \ _T_MOCK_PERM_RETVAL_FUNC(name) = _T_MOCK_PERM_HOOK(name); \ } /* To set a permanent function that will be called from the mock we declare it, set it to the extern * in a constructor and define it. * This needs to be in the global scope and the body of the function needs to follows it immediately */ #define T_MOCK_SET_PERM_FUNC(ret, name, args_def) \ ret _T_MOCK_PERM_HOOK(name) args_def; \ __attribute__((constructor)) void _T_MOCK_CTOR_SETTER(name)() { \ _T_MOCK_PERM_FUNC(name) = _T_MOCK_PERM_HOOK(name); \ } \ ret _T_MOCK_PERM_HOOK(name) args_def /* T_MOCK_CALL_QUEUE() * Allow tests to define a call expectation queue for a mock * * This macro wraps a definition of a struct and defines easy helpers to * manage a global queue of elements of that struct. * A test can use this along with a mock callback to verify and control what the mock * does in every call it gets. * @argument type_name the name of the struct to define * @argument struct_body the elements of the struct * * Example: * // for mocking the function foobar() we'll define a struct that will allow the mock * // to verify its arguments and control its return value. The elements of the struct can * // be anything. * T_MOCK_CALL_QUEUE(fb_call, { * int expected_a_eq; * bool expected_b_small; * size_t ret_val; * }) * * T_MOCK_SET_PERM_FUNC(size_t, foobar, (int a, char b)) { * fb_call call = dequeue_fb_call(); * T_ASSERT_EQ(a, call.expected_a_eq, "a arg"); * if (call.expected_b_small) * T_ASSERT_LE(b, 127, "b arg too big"); * return call.ret_val; * } * * // in the test we set up the expected calls before calling the code that ends up in the mock * T_DECL(test, "test") { * enqueue_fb_call( (fb_call){ .expected_a = 1, .expected_b = 2, .ret_val = 3 }); * enqueue_fb_call( (fb_call){ .expected_a = 10, .expected_b = 20, .ret_val = 30 }); * // ... call into XNU which will call foobar() * assert_empty_fb_call(); // check all calls were consumed * } */ #define _T_MOCK_CALL_LST(type_name) _lst_ ## type_name #define T_MOCK_CALL_QUEUE(type_name, struct_body) \ typedef struct s_ ## type_name struct_body type_name; \ struct _node_ ## type_name { \ STAILQ_ENTRY(_node_ ## type_name) next; \ type_name d; \ }; \ static STAILQ_HEAD(, _node_ ## type_name) _T_MOCK_CALL_LST(type_name) = \ STAILQ_HEAD_INITIALIZER(_T_MOCK_CALL_LST(type_name)); \ static void enqueue_ ## type_name (type_name value) { \ struct _node_ ## type_name *node = calloc(1, sizeof(struct _node_ ## type_name)); \ node->d = value; \ STAILQ_INSERT_TAIL(&_T_MOCK_CALL_LST(type_name), node, next); \ } \ static type_name dequeue_ ## type_name (void) { \ struct _node_ ## type_name *node = STAILQ_FIRST(&_T_MOCK_CALL_LST(type_name)); \ T_QUIET; T_ASSERT_NOTNULL(node, "consumed too many " #type_name); \ type_name d = node->d; \ STAILQ_REMOVE_HEAD(&_T_MOCK_CALL_LST(type_name), next); \ free(node); \ return d; \ } \ static void assert_empty_ ## type_name (void) { \ T_QUIET; T_ASSERT_TRUE( STAILQ_EMPTY(&_T_MOCK_CALL_LST(type_name)), \ "calls not fully consumed " #type_name); \ } \ static void clear_ ## type_name (void) { \ STAILQ_INIT(&_T_MOCK_CALL_LST(type_name)); \ } /* END IGNORE CODESTYLE */ |