/* * 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 */