xref: /xnu-12377.61.12/tests/unit/mocks/mock_dynamic.h (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8)
1 /*
2  * Copyright (c) 2000-2025 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #pragma once
30 
31 
32 /* BEGIN IGNORE CODESTYLE */
33 
34 /* Dynamic mock allows an individual test executable to control what a mock does.
35  * T_MOCK_DYNAMIC_DECLARE()
36  *   Declare a dynamic mock. This declaration should come in a header file under the mocks/ folder.
37  *   The header file should be included in both the respective .c file and in the test .c file that
38  *   wants to set the behaviour of the mock.
39  *   It declares the signature of the mocked function so that if the signature changes the compiler
40  *   can assure that the mock and its setters are in sync.
41  * T_MOCK_DYNAMIC()
42  *   Define the dynamic mock. This should come in a .c file under the mocks/ folder.
43  *   This defines the mock function itself using the T_MOCK() macro.
44  *
45  * The test has 4 possible way to control the mock. It can temporarily set the return value,
46  * it can set a temporary block callback, it can set a permanent return value or a permanent function.
47  * @argument args_def is how the function arguments are defined in a function definition.
48  *           This can be copy-pasted directly from the original function definition.
49  * @argument args_invoke is how the same arguments are passed to a function call
50  * @argument (optional) default_action should be a scope of code that will be executed if no mock control
51  *           is set up. it can reference the arguments in args_def and also call the original
52  *           function. If this argument is not supplied, the default action is to call the original XNU
53  *           function with the same arguments.
54  *
55  * Example:
56  * // we want to mock a function from XNU that has the signature:
57  * size_t foobar(int a, char b);
58  *
59  * // in a header in the mocks library (tests/unit/mocks) add:
60  * T_MOCK_DYNAMIC_DECLARE(size_t, foobar, (int a, char b));
61  *
62  * // in a .c file in the mock library (tests/unit/mocks) add:
63  * T_MOCK_DYNAMIC(size_t, foobar, (int a, char b), (a, b), { return 0 });
64  *
65  * // Now to control the mock, in a T_DECL test you can do:
66  * T_DECL(test, "test") {
67  *     T_MOCK_SET_RETVAL(foobar, size_t, 42);
68  *     // ... call into XNU which will call foobar()
69  *
70  *     T_MOCK_SET_CALLBACK(foobar, size_t, (int a, char b), {
71  *         T_ASSERT_EQ(a, b, "args equal");
72  *         return a + b;
73  *     });
74  *     // ... call into XNU which will call foobar()
75  * }
76  *
77  * // The third option is to define a permanent return value for the mock that will
78  * // be in effect for all tests in the executable.
79  * // This essentially overrides the default-value that's defined in the T_MOCK_DYNAMIC()
80  * T_MOCK_SET_PERM_RETVAL(foobar, size_t, 43);
81  *
82  * // The fourth option is for the test to define a permanent function in the global scope
83  * // that will be called every time the mock is called.
84  * T_MOCK_SET_PERM_FUNC(size_t, foobar, (int a, char b)) {
85  *     return b - a;
86  * }
87  *
88  * It's possible for multiple mock controls of different types to be active at the same time. The priority
89  * in which the dynamic mock tries to find them is
90  *   1. ret-val
91  *   2. block call back
92  *   3. permanent ret-val / permanent function
93  * The effect of the ret-val and callback setters is limited to the scope the they are in. This
94  * is achieved using a cleanup function in the setter.
95  * It is possible for multiple setters of the same type to be invoked during the flow of the same scope.
96  * In that case, the last setter that was invoked is in effect.
97  *
98  * It is not possible to have multiple static function setters and/or permanent ret-val setter for the
99  * same mock in the same test executable. This would cause a compile/link error due to duplicate symbol.
100  */
101 
102 #define _T_MOCK_RETVAL_CALLBACK(name)       _mock_retval_callback_ ## name
103 #define _T_MOCK_CALLBACK(name)              _mock_callback_ ## name
104 #define _T_MOCK_PERM_RETVAL_FUNC(name)      _mock_p_retval_func_ ## name
105 #define _T_MOCK_PERM_FUNC(name)             _mock_func_ ## name
106 
107 #define T_MOCK_DYNAMIC_DECLARE(ret, name, args_def)       \
108     extern ret (^_T_MOCK_RETVAL_CALLBACK(name))(void);    \
109     extern ret (^_T_MOCK_CALLBACK(name)) args_def;        \
110     extern ret (*_T_MOCK_PERM_RETVAL_FUNC(name))(void);   \
111     extern ret (*_T_MOCK_PERM_FUNC(name)) args_def;       \
112     extern ret name args_def
113 
114 #define _T_MOCK_DYNAMIC_WITH_IMPL(ret, name, args_def, args_invoke, default_action)  \
115     ret (^_T_MOCK_RETVAL_CALLBACK(name)) (void) = NULL;                   \
116     ret (^_T_MOCK_CALLBACK(name)) args_def = NULL;                        \
117     ret (*_T_MOCK_PERM_RETVAL_FUNC(name)) (void) = NULL;                  \
118     ret (*_T_MOCK_PERM_FUNC(name)) args_def = NULL;                       \
119     T_MOCK(ret, name, args_def) {                                         \
120         if (_T_MOCK_RETVAL_CALLBACK(name) != NULL) {                      \
121             return _T_MOCK_RETVAL_CALLBACK(name)();                       \
122         }                                                                 \
123         if (_T_MOCK_CALLBACK(name) != NULL) {                             \
124             return _T_MOCK_CALLBACK(name) args_invoke;                    \
125         }                                                                 \
126         if (_T_MOCK_PERM_RETVAL_FUNC(name) != NULL) {                     \
127             return _T_MOCK_PERM_RETVAL_FUNC(name)();                      \
128         }                                                                 \
129         if (_T_MOCK_PERM_FUNC(name) != NULL) {                            \
130             return _T_MOCK_PERM_FUNC(name) args_invoke;                   \
131         }                                                                 \
132         default_action;                                                   \
133     }
134 
135 #define _T_MOCK_DYNAMIC_DEFAULT_IMPL(ret, name, args_def, args_invoke) \
136     _T_MOCK_DYNAMIC_WITH_IMPL(ret, name, args_def, args_invoke, { return name args_invoke; })
137 
138 /* T_MOCK_DYNAMIC() selects which of the above versions to call depending on the number of arguments it gets
139  * - T_MOCK_DYNAMIC(a, b, c, d) with 4 arguments expands to
140  *   _T_MOCK_GET_INSTANCE(a, b, c, d, _T_MOCK_DYNAMIC_WITH_IMPL, _T_MOCK_DYNAMIC_DEFAULT_IMPL)(a, b, c, d)
141  *   then NAME is _T_MOCK_DYNAMIC_DEFAULT_IMPL so this expands to
142  *   _T_MOCK_DYNAMIC_DEFAULT_IMPL(a, b, c, d)
143  * - T_MOCK_DYNAMIC(a, b, c, d, e) with 5 arguments expands to
144  *   _T_MOCK_GET_INSTANCE(a, b, c, d, e, _T_MOCK_DYNAMIC_WITH_IMPL, _T_MOCK_DYNAMIC_DEFAULT_IMPL)(a, b, c, d, e)
145  *   then NAME is _T_MOCK_DYNAMIC_WITH_IMPL so this expands to
146  *   _T_MOCK_DYNAMIC_WITH_IMPL(a, b, c, e, e)
147  */
148 #define _T_MOCK_GET_INSTANCE(_1, _2, _3, _4, _5, NAME, ...) NAME
149 #define T_MOCK_DYNAMIC(...) _T_MOCK_GET_INSTANCE(__VA_ARGS__, _T_MOCK_DYNAMIC_WITH_IMPL, _T_MOCK_DYNAMIC_DEFAULT_IMPL)(__VA_ARGS__)
150 
151 
152 
153 #define _UT_CONCAT2(a, b) a ## b
154 #define _UT_CONCAT(a, b) _UT_CONCAT2(a, b)
155 
156 static inline void
_mock_set_cleaner(void *** ptr)157 _mock_set_cleaner(void ***ptr) {
158 	**ptr = NULL;
159 }
160 
161 /* How it works?
162  * - For each mock that is defined using T_MOCK_DYNAMIC() the macro above defines a few
163  * global variables with the function name suffixed, and also defines the mock function to check
164  * these global variables.
165  * - The test executable can then set any of them using the T_MOCK_SET_X() macros below
166  * - T_MOCK_SET_RETVAL() and T_MOCK_SET_CALLBACK() should be used from inside T_DECL and have a
167  * cleaner that undoes their effect at the end of the scope they are defined in.
168  * The cleaner has a __COUNTER__ concatenated so that it's possible to have more than one such
169  * T_MOCK_SET_X() invocation in the same scope
170  * - T_MOCK_SET_PERM_RETVAL() and T_MOCK_SET_PERM_FUNC() should be used in the global scope
171  * and has a constructor function that sets the global variable when the executable loads
172  */
173 
174 #define _T_MOCK_CLEANER(name) _UT_CONCAT(_cleaner_ ## name, __COUNTER__)
175 #define _T_MOCK_RETVAL_CAPTURE(name, N) _UT_CONCAT(_mock_retval_capture_ ## name, N)
176 
177 /* to set a return value, we set a global that holds a callback block that returns the value.
178  * The callback variable is a pointer and NULL indicates it's not set
179  * The value expression the user gives is first captured in a local variable since some
180  * expressions can't be captured by a block (array reference for instance) */
181 #define _T_MOCK_SET_RETVAL_IMPL(name, ret, val, N)                                              \
182         ret _T_MOCK_RETVAL_CAPTURE(name, N) = val;                                              \
183         _T_MOCK_RETVAL_CALLBACK(name) = ^ret(void) { return _T_MOCK_RETVAL_CAPTURE(name, N); }; \
184         __attribute__((cleanup(_mock_set_cleaner))) void **_T_MOCK_CLEANER(name) =              \
185             (void**)&_T_MOCK_RETVAL_CALLBACK(name)
186 #define T_MOCK_SET_RETVAL(name, ret, val) _T_MOCK_SET_RETVAL_IMPL(name, ret, val, __COUNTER__)
187 
188 /* to set a mock callback block from the user we set a dedicated callback for that, so it doesn't
189  * interfere with SET_RETVAL */
190 #define T_MOCK_SET_CALLBACK(name, ret, args_def, body)                              \
191         _T_MOCK_CALLBACK(name) = ^ret args_def body;                                \
192         __attribute__((cleanup(_mock_set_cleaner))) void **_T_MOCK_CLEANER(name) =  \
193             (void**)&_T_MOCK_CALLBACK(name)
194 
195 #define _T_MOCK_CTOR_SETTER(name) _ctor_setter_ ## name
196 #define _T_MOCK_PERM_HOOK(name)   PERM_HOOK_ ## name
197 
198 /* To set a permanent return value, we define a function that returns it, and set it to the
199  * extern global in a constructor.
200  * This setter needs to be in the global scope of the tester */
201 #define T_MOCK_SET_PERM_RETVAL(name, ret, val)                          \
202         ret _T_MOCK_PERM_HOOK(name)(void) { return (val); }             \
203         __attribute__((constructor)) void _T_MOCK_CTOR_SETTER(name)() { \
204             _T_MOCK_PERM_RETVAL_FUNC(name) = _T_MOCK_PERM_HOOK(name);   \
205         }
206 
207 /* To set a permanent function that will be called from the mock we declare it, set it to the extern
208  * in a constructor and define it.
209  * This needs to be in the global scope and the body of the function needs to follows it immediately */
210 #define T_MOCK_SET_PERM_FUNC(ret, name, args_def)                        \
211         ret _T_MOCK_PERM_HOOK(name) args_def;                            \
212         __attribute__((constructor)) void _T_MOCK_CTOR_SETTER(name)() {  \
213             _T_MOCK_PERM_FUNC(name) = _T_MOCK_PERM_HOOK(name);           \
214         }                                                                \
215         ret _T_MOCK_PERM_HOOK(name) args_def
216 
217 
218 /* T_MOCK_CALL_QUEUE()
219  *   Allow tests to define a call expectation queue for a mock
220  *
221  * This macro wraps a definition of a struct and defines easy helpers to
222  * manage a global queue of elements of that struct.
223  * A test can use this along with a mock callback to verify and control what the mock
224  * does in every call it gets.
225  * @argument type_name the name of the struct to define
226  * @argument struct_body the elements of the struct
227  *
228  * Example:
229  * // for mocking the function foobar() we'll define a struct that will allow the mock
230  * // to verify its arguments and control its return value. The elements of the struct can
231  * // be anything.
232  * T_MOCK_CALL_QUEUE(fb_call, {
233  *     int expected_a_eq;
234  *     bool expected_b_small;
235  *     size_t ret_val;
236  * })
237  *
238  * T_MOCK_SET_PERM_FUNC(size_t, foobar, (int a, char b)) {
239  *     fb_call call = dequeue_fb_call();
240  *     T_ASSERT_EQ(a, call.expected_a_eq, "a arg");
241  *     if (call.expected_b_small)
242  *         T_ASSERT_LE(b, 127, "b arg too big");
243  *     return call.ret_val;
244  * }
245  *
246  * // in the test we set up the expected calls before calling the code that ends up in the mock
247  * T_DECL(test, "test") {
248  *     enqueue_fb_call( (fb_call){ .expected_a = 1, .expected_b = 2, .ret_val = 3 });
249  * 	   enqueue_fb_call( (fb_call){ .expected_a = 10, .expected_b = 20, .ret_val = 30 });
250  *     // ... call into XNU which will call foobar()
251  *     assert_empty_fb_call(); // check all calls were consumed
252  * }
253  */
254 
255 #define _T_MOCK_CALL_LST(type_name)  _lst_ ## type_name
256 
257 #define T_MOCK_CALL_QUEUE(type_name, struct_body)                                         \
258     typedef struct s_ ## type_name struct_body type_name;                                 \
259     struct _node_ ## type_name {                                                          \
260         STAILQ_ENTRY(_node_ ## type_name) next;                                           \
261         type_name d;                                                                      \
262     };                                                                                    \
263     static STAILQ_HEAD(, _node_ ## type_name) _T_MOCK_CALL_LST(type_name) =               \
264         STAILQ_HEAD_INITIALIZER(_T_MOCK_CALL_LST(type_name));                             \
265     static void enqueue_ ## type_name (type_name value) {                                 \
266         struct _node_ ## type_name *node = calloc(1, sizeof(struct _node_ ## type_name)); \
267         node->d = value;                                                                  \
268         STAILQ_INSERT_TAIL(&_T_MOCK_CALL_LST(type_name), node, next);                     \
269     }                                                                                     \
270     static type_name dequeue_ ## type_name (void) {                                       \
271         struct _node_ ## type_name *node = STAILQ_FIRST(&_T_MOCK_CALL_LST(type_name));    \
272         T_QUIET; T_ASSERT_NOTNULL(node, "consumed too many " #type_name);                 \
273         type_name d = node->d;                                                            \
274         STAILQ_REMOVE_HEAD(&_T_MOCK_CALL_LST(type_name), next);                           \
275         free(node);                                                                       \
276         return d;                                                                         \
277     }                                                                                     \
278     static void assert_empty_ ## type_name (void) {                                       \
279         T_QUIET; T_ASSERT_TRUE( STAILQ_EMPTY(&_T_MOCK_CALL_LST(type_name)),               \
280                   "calls not fully consumed " #type_name);                                \
281     }                                                                                     \
282     static void clear_ ## type_name (void) {                                              \
283         STAILQ_INIT(&_T_MOCK_CALL_LST(type_name));                                        \
284     }
285 
286 /* END IGNORE CODESTYLE */
287