testing/mock.c (695 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stdarg.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "mock.h" #include "platform_api.h" #include "platform_io_api.h" #include "testing.h" /** * Default allocation routine for saving pointer argument data. * * @param expected The expectation context for the argument to save. * @param call The calling context for the argument to save. */ static void mock_alloc_and_copy (const struct mock_arg *expected, struct mock_arg *call) { call->ptr_value = platform_malloc (expected->ptr_value_len); if (call->ptr_value != NULL) { call->ptr_value_len = expected->ptr_value_len; memcpy (call->ptr_value, (void*) ((uintptr_t) call->value), call->ptr_value_len); } } /** * Default allocation routine for making internal copies of expectation argument data. * * @param arg_data The data to copy into the expectation argument. * @param arg_length The length of the data to copy. * @param arg_save The argument buffer to copy the data to. * * @return 0 if the data was successfully copied or an error code. */ static int mock_alloc_and_copy_arg_data (const void *arg_data, size_t arg_length, void **arg_save) { *arg_save = platform_malloc (arg_length); if (*arg_save == NULL) { return MOCK_NO_MEMORY; } memcpy (*arg_save, arg_data, arg_length); return 0; } /** * Default copy routine for performing a shallow copy of data into an output parameter. * * @param expected The expectation context for the argument to copy. * @param call The calling context to copy into. * @param out_len Buffer space available in the function argument. */ static void mock_copy_output_data (const struct mock_arg *expected, struct mock_arg *call, size_t out_len) { memcpy ((void*) ((uintptr_t) call->value), expected->out_data, out_len); } /** * Free a single expected call instance. * * @param call The instance to free. */ static void mock_free_call (struct mock_call *call) { int i; if (call->argv != NULL) { for (i = 0; i < call->argc; i++) { if (call->argv[i].ptr_value != NULL) { call->argv[i].free (call->argv[i].ptr_value); } if (call->argv[i].flags & MOCK_ARG_FLAG_ALLOCATED) { call->argv[i].free ((void*) ((uintptr_t) call->argv[i].value)); } if (call->argv[i].flags & MOCK_ARG_FLAG_OUT_ALLOCATED) { call->argv[i].out_free ((void*) call->argv[i].out_data); } } platform_free (call->argv); } platform_free (call); } /** * Add an expectation to the mock. * * @param mock The mock instance to add the expectation to. * @param func_call The function call that is expected. * @param instance The instance the call will be executed against. * @param return_val The value to return for the expectation. * @param ... A list of expectations for all the arguments to the expected call. * * @return 0 if the expectation was successfully added or an error code. */ int mock_expect (struct mock *mock, void *func_call, void *instance, int64_t return_val, ...) { struct mock_call *expectation; struct mock_expect_arg arg; va_list args; int i; int status; if ((mock == NULL) || (func_call == NULL)) { return MOCK_INVALID_ARGUMENT; } expectation = platform_malloc (sizeof (struct mock_call)); if (expectation == NULL) { return MOCK_NO_MEMORY; } memset (expectation, 0, sizeof (struct mock_call)); expectation->func = func_call; expectation->instance = instance; expectation->return_val = return_val; expectation->argc = mock->func_arg_count (func_call); if (expectation->argc) { expectation->argv = platform_calloc (expectation->argc, sizeof (struct mock_arg)); if (expectation->argv == NULL) { platform_free (expectation); return MOCK_NO_MEMORY; } } va_start (args, return_val); for (i = 0; i < expectation->argc; i++) { arg = va_arg (args, struct mock_expect_arg); if (arg.alloc == NULL) { expectation->argv[i].alloc = mock_alloc_and_copy; } else { expectation->argv[i].alloc = arg.alloc; } if (arg.free == NULL) { expectation->argv[i].free = platform_free; } else { expectation->argv[i].free = arg.free; } if (arg.flags & MOCK_ARG_FLAG_ALLOCATED) { if (arg.copy != NULL) { status = arg.copy ((void*) ((uintptr_t) arg.value), arg.ptr_value_len, (void**) &expectation->argv[i].value); } else { status = mock_alloc_and_copy_arg_data ((void*) ((uintptr_t) arg.value), arg.ptr_value_len, (void**) &expectation->argv[i].value); } if (status != 0) { mock_free_call (expectation); return status; } } else { expectation->argv[i].value = arg.value; } expectation->argv[i].ptr_value_len = arg.ptr_value_len; expectation->argv[i].validate = arg.validate; expectation->argv[i].flags = arg.flags; if (arg.flags & MOCK_ARG_FLAG_SAVED_VALUE) { expectation->argv[i].save_arg = arg.save_arg; } else { expectation->argv[i].save_arg = -1; } } va_end (args); if (mock->expected == NULL) { mock->expected = expectation; mock->next_call = expectation; } else { mock->exp_tail->next = expectation; if (mock->next_call == NULL) { mock->next_call = expectation; } } mock->exp_tail = expectation; mock->exp_count++; return 0; } /** * Base handler for adding output paramater data to an expectation. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. * @param out_data The data to use to fill the paramater when called. * @param out_length The length of the output data provided. * @param length_arg The argument index for dynamically sized buffers. * @param is_temp Flag indicating if the output data should be copied or held by reference. * @param is_ptr Flag indicating if the output data should be stored at a location referenced by the * parameter value. * @param copy The function to use to copy the data into the parameter. * @param out_dup The function to use for creating a copy of the output data. * @param free The fuction to use to free the copied output data. * * @return 0 if the mock was updated successfully or an error code. */ static int mock_expect_output_common (struct mock *mock, int arg, const void *out_data, size_t out_length, int length_arg, bool is_temp, bool is_ptr, mock_arg_copy copy, mock_arg_alloc_expect out_dup, mock_arg_free free) { int status; if ((mock == NULL) || (out_data == NULL) || (arg < 0)) { return MOCK_INVALID_ARGUMENT; } if (mock->exp_tail == NULL) { return MOCK_NO_EXPECTATION; } if ((arg >= mock->exp_tail->argc) || (length_arg >= mock->exp_tail->argc)) { return MOCK_BAD_ARG_INDEX; } if (is_ptr) { mock->exp_tail->argv[arg].flags |= MOCK_ARG_FLAG_OUT_PTR_PTR; } if (is_temp) { status = out_dup (out_data, out_length, (void**) &mock->exp_tail->argv[arg].out_data); if (status != 0) { return status; } mock->exp_tail->argv[arg].out_free = free; mock->exp_tail->argv[arg].flags |= MOCK_ARG_FLAG_OUT_ALLOCATED; } else { mock->exp_tail->argv[arg].out_data = out_data; } mock->exp_tail->argv[arg].out_len = out_length; mock->exp_tail->argv[arg].size_arg = length_arg; if (copy == NULL) { mock->exp_tail->argv[arg].copy = mock_copy_output_data; } else { mock->exp_tail->argv[arg].copy = copy; } return 0; } /** * Indicate that a parameter to a mock expectation should be treated as an output parameter. This * will only update the last expectation that was added to the mock. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. This is a zero-based index based * on the variable argument list in the expectation. * @param out_data The data to use to fill the parameter when called. This is stored by reference. * @param out_length The length of the output data provided. * @param length_arg If the output size is dynamic, this is the argument index that specifies the * size of the output buffer provided to the function. Set this to -1 if the length is fixed. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_output (struct mock *mock, int arg, const void *out_data, size_t out_length, int length_arg) { return mock_expect_output_common (mock, arg, out_data, out_length, length_arg, false, false, NULL, NULL, NULL); } /** * Indicate that a parameter to a mock expectation should be treated as an output parameter. This * will only update the last expectation that was added to the mock. * * This call allows temporary variables to be used for output data. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. This is a zero-based index based * on the variable argument list in the expectation. * @param out_data The data to use to fill the parameter when called. This data is copied. * @param out_length The length of the output data provided. * @param length_arg If the output size is dynamic, this is the argument index that specifies the * size of the output buffer provided to the function. Set this to -1 if the length is fixed. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_output_tmp (struct mock *mock, int arg, const void *out_data, size_t out_length, int length_arg) { return mock_expect_output_common (mock, arg, out_data, out_length, length_arg, true, false, NULL, mock_alloc_and_copy_arg_data, platform_free); } /** * Indicate that a parameter to a mock expectation should be treated as an output parameter. The * parameter is a pointer to another pointer that will hold the output data. This will only update * the last expectation that was added to the mock. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. This is a zero-based index based * on the variable argument list in the expectation. * @param out_data The data to use to fill the parameter when called. This is stored by reference. * @param out_length The length of the output data provided. * @param length_arg If the output size is dynamic, this is the argument index that specifies the * size of the output buffer provided to the function. Set this to -1 if the length is fixed. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_output_ptr (struct mock *mock, int arg, const void *out_data, size_t out_length, int length_arg) { return mock_expect_output_common (mock, arg, out_data, out_length, length_arg, false, true, NULL, NULL, NULL); } /** * Indicate that a parameter to a mock expectation should be treated as an output parameter. The * parameter is a pointer to another pointer that will hold the output data. This will only update * the last expectation that was added to the mock. * * This call allows temporary variables to be used for output data. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. This is a zero-based index based * on the variable argument list in the expectation. * @param out_data The data to use to fill the parameter when called. This is stored by reference. * @param out_length The length of the output data provided. * @param length_arg If the output size is dynamic, this is the argument index that specifies the * size of the output buffer provided to the function. Set this to -1 if the length is fixed. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_output_ptr_tmp (struct mock *mock, int arg, const void *out_data, size_t out_length, int length_arg) { return mock_expect_output_common (mock, arg, out_data, out_length, length_arg, true, true, NULL, mock_alloc_and_copy_arg_data, platform_free); } /** * Indicate that a parameter to a mock expectation should be treated as an output parameter. The * expectation can perform a deep copy of the output data in the case of nested structures. This * will only update the last expectation that was added to the mock. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. This is a zero-based index based * on the variable argument list in the expectation. * @param out_data The data to use to fill the parameter when called. This is stored by reference. * @param out_length The length of the output data provided. * @param copy The function to use to copy the data into the output parameter. Set this to null to * use the default copy mechanism, which is a shallow copy. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_output_deep_copy (struct mock *mock, int arg, const void *out_data, size_t out_length, mock_arg_copy copy) { return mock_expect_output_common (mock, arg, out_data, out_length, -1, false, false, copy, NULL, NULL); } /** * Indicate that a parameter to a mock expectation should be treated as an output parameter. The * expectation can perform a deep copy of the output data in the case of nested structures. This * will only update the last expectation that was added to the mock. * * This call allows temporary variables to be used for output data. * * @param mock The mock instance to update. * @param arg The argument index to set as an output parameter. This is a zero-based index based * on the variable argument list in the expectation. * @param out_data The data to use to fill the parameter when called. This data is copied. * @param out_length The length of the output data provided. * @param copy The function to use to copy the data into the output parameter. Set this to null to * use the default copy mechanism, which is a shallow copy. * @param out_dup The function to use to create a copy of the output data. Set this to null to use * the default copy mechanism, which is a shallow copy. * @param free The function to use to free the local copy of output data. Set this to null to use * the default free function. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_output_deep_copy_tmp (struct mock *mock, int arg, const void *out_data, size_t out_length, mock_arg_copy copy, mock_arg_alloc_expect out_dup, mock_arg_free free) { return mock_expect_output_common (mock, arg, out_data, out_length, -1, true, false, copy, out_dup, free); } /** * Find the entry for a saved argument with the specified ID. * * @param mock The mock instance to search. * @param id The ID of the saved argument to find. * * @return The entry for the saved argument or null if no entry with the ID exists. */ static struct mock_save_arg* mock_find_save_arg (struct mock *mock, int id) { struct mock_save_arg *pos = mock->save; while (pos && (pos->id != id)) { pos = pos->next; } return pos; } /** * Indicate that the value of a parameter to a mock expectation should be saved upon call. This * saved value can be used as the expected value is future expectations. This will only update the * last expectation that was added to the mock. * * @param mock The mock instance to update. * @param arg The argument index that should be saved. This is a zero-based index based on the * variable argument list in the expectation. * @param id The ID to associate with this saved value. This ID must be unique and will be used in * future expectations to reference this saved value. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_save_arg (struct mock *mock, int arg, int id) { struct mock_save_arg *saved; if ((mock == NULL) || (arg < 0) || (id < 0)) { return MOCK_INVALID_ARGUMENT; } if (mock->exp_tail == NULL) { return MOCK_NO_EXPECTATION; } if (arg >= mock->exp_tail->argc) { return MOCK_BAD_ARG_INDEX; } saved = mock_find_save_arg (mock, id); if (saved != NULL) { return MOCK_SAVE_ARG_EXISTS; } saved = platform_malloc (sizeof (struct mock_save_arg)); if (saved == NULL) { return MOCK_NO_MEMORY; } memset (saved, 0, sizeof (struct mock_save_arg)); saved->next = mock->save; saved->id = id; mock->save = saved; mock->next_id = id + 1; mock->exp_tail->argv[arg].save_arg = id; return 0; } /** * Determine the next available ID for use when created saved arguments. * * @param mock The mock instance to query. * * @return The next available ID for a saved value. */ int mock_expect_next_save_id (struct mock *mock) { if (mock == NULL) { return 0; } return mock->next_id; } /** * Share a saved argument from one mock instance with another. A saved argument that is shared will * be saved in both mock instances when the expectation that saves the argument is called. It will * exist independently in the context of both mock instances for validation. * * @param from The mock instance that contains the expectation that will save the argument value. * @param src_id The ID for the argument to share. * @param to The mock instance that will be shared the saved argument. * @param dest_id The ID to assign the argument in the shared mock instance. * * @return 0 if the argument was shared successfully or an error code. */ int mock_expect_share_save_arg (struct mock *from, int src_id, struct mock *to, int dest_id) { struct mock_save_arg *saved; struct mock_save_arg *shared; if ((from == NULL) || (to == NULL) || (src_id < 0) || (dest_id < 0)) { return MOCK_INVALID_ARGUMENT; } saved = mock_find_save_arg (from, src_id); if (saved == NULL) { return MOCK_NO_SAVE_ARG; } shared = platform_malloc (sizeof (struct mock_save_arg)); if (shared == NULL) { return MOCK_NO_MEMORY; } memset (shared, 0, sizeof (struct mock_save_arg)); shared->next = to->save; shared->id = dest_id; to->save = shared; saved->shared = shared; return 0; } /** * Set a custom action to execute while processing an expected call on the mock. The action will be * executed prior to any argument processing by the mock. * * This will only update the last expectation that was added to the mock and an expectation can only * have one action assigned. If this is called multiple times, the last action will be the one that * is executed. * * @param mock The mock instance to update. * @param action The action to execute in response to the expected call. * @param context A user context to save with the expectation. This will be available to the action * through expected parameters for the call that are provided to the action callback. * * @return 0 if the mock was updated successfully or an error code. */ int mock_expect_external_action (struct mock *mock, mock_call_action action, void *context) { if ((mock == NULL) || (action == NULL)) { return MOCK_INVALID_ARGUMENT; } if (mock->exp_tail == NULL) { return MOCK_NO_EXPECTATION; } mock->exp_tail->action = action; mock->exp_tail->context = context; return 0; } /** * Print the information for an expected function call. * * @param mock The mock expecting the function call. * @param call The call information to print. */ static void mock_print_func (struct mock *mock, struct mock_call *call) { int i; platform_printf ("%s (%p", mock->func_name_map ((void*) call->func), call->instance); for (i = 0; i < call->argc; i++) { platform_printf (", 0x%lx", call->argv[i].value); } platform_printf (")" NEWLINE); } /** * Validate a received function argument against an expected one. * * @param mock The mock instance being verified. * @param cur_exp The index of the current expectation being checked. * @param arg_name The name of the argument being validated. * @param expected The expected argument. * @param actual The actual argument passed to the function. * * @return 0 if the argument matched the expected or 1 if not. */ static int mock_validate_arg (struct mock *mock, int cur_exp, const char *arg_name, struct mock_arg *expected, struct mock_arg *actual) { int fail = 0; if (!(expected->flags & MOCK_ARG_FLAG_ANY_VALUE)) { if ((expected->flags & MOCK_ARG_FLAG_PTR_PTR) && !(actual->flags & MOCK_ARG_FLAG_PTR_PTR)) { platform_printf ("(%s, %d) Unexpected NULL argument: name=%s" NEWLINE, mock->name, cur_exp, arg_name); /* No point to check anything else. The argument was null. */ return 1; } if (expected->flags & MOCK_ARG_FLAG_NOT_NULL) { if ((expected->flags & MOCK_ARG_FLAG_PTR_PTR) && !(expected->flags & MOCK_ARG_FLAG_PTR_PTR_NOT_NULL)) { if (expected->value != actual->value) { platform_printf ( "(%s, %d) Unexpected pointer argument: name=%s, expected=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, arg_name, expected->value, actual->value); fail = 1; } } else { if (actual->value == 0) { if (expected->flags & MOCK_ARG_FLAG_PTR_PTR_NOT_NULL) { platform_printf ( "(%s, %d) Unexpected pointer to NULL pointer: name=%s" NEWLINE, mock->name, cur_exp, arg_name); } else { platform_printf ("(%s, %d) Unexpected NULL argument: name=%s" NEWLINE, mock->name, cur_exp, arg_name); } fail = 1; } else if (expected->ptr_value_len) { if (actual->ptr_value == NULL) { platform_printf ( "(%s, %d) No pointer contents to validate: name=%s" NEWLINE, mock->name, cur_exp, arg_name); fail = 1; } else { char prefix[100]; snprintf (prefix, sizeof (prefix), "(%s, %d, arg=%s) ", mock->name, cur_exp, arg_name); if (expected->validate) { fail |= expected->validate (prefix, (void*) ((uintptr_t) expected->value), actual->ptr_value); } else { fail |= testing_validate_array_prefix ( (void*) ((uintptr_t) expected->value), actual->ptr_value, expected->ptr_value_len, prefix); } } } } } else if (expected->flags & MOCK_ARG_FLAG_SAVED_VALUE) { struct mock_save_arg *saved = mock_find_save_arg (mock, expected->save_arg); if (saved) { if (saved->saved) { if (saved->value != actual->value) { platform_printf ( "(%s, %d) Unexpected saved argument: id=%d, name=%s, expected=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, expected->save_arg, arg_name, saved->value, actual->value); fail = 1; } } else { platform_printf ("(%s, %d) Argument ID %d value not saved." NEWLINE, mock->name, cur_exp, expected->save_arg); fail = 1; } } else { platform_printf ("(%s, %d) Unknown saved argument ID: id=%d" NEWLINE, mock->name, cur_exp, expected->save_arg); fail = 1; } } else if (expected->flags & MOCK_ARG_FLAG_GREATER_EQUAL) { if (actual->value < expected->value) { platform_printf ( "(%s, %d) Unexpected argument: name=%s, expected at least=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, arg_name, expected->value, actual->value); fail = 1; } } else if (expected->flags & MOCK_ARG_FLAG_GREATER) { if (actual->value <= expected->value) { platform_printf ( "(%s, %d) Unexpected argument: name=%s, expected more than=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, arg_name, expected->value, actual->value); fail = 1; } } else if (expected->flags & MOCK_ARG_FLAG_LESS_EQUAL) { if (actual->value > expected->value) { platform_printf ( "(%s, %d) Unexpected argument: name=%s, expected no more than=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, arg_name, expected->value, actual->value); fail = 1; } } else if (expected->flags & MOCK_ARG_FLAG_LESS) { if (actual->value >= expected->value) { platform_printf ( "(%s, %d) Unexpected argument: name=%s, expected less than=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, arg_name, expected->value, actual->value); fail = 1; } } else if (expected->value != actual->value) { platform_printf ( "(%s, %d) Unexpected argument: name=%s, expected=0x%lx, actual=0x%lx" NEWLINE, mock->name, cur_exp, arg_name, expected->value, actual->value); fail = 1; } } return fail; } /** * Verify that all expected calls were executed. * * @param mock The mock to verify. * * @return 0 if the expectations were all met or 1 if not. */ int mock_validate (struct mock *mock) { int fail = 0; struct mock_call *exp_pos; struct mock_call *call_pos; struct mock_call *search; int current = 0; int i; if (mock) { if (mock->exp_count != mock->call_count) { platform_printf ( "(%s) Unexpected number of function calls: expected=%d, actual=%d" NEWLINE, mock->name, mock->exp_count, mock->call_count); fail = 1; } exp_pos = mock->expected; call_pos = mock->called; while ((exp_pos != NULL) || (call_pos != NULL)) { if (call_pos == NULL) { platform_printf ("(%s, %d) Not called: ", mock->name, current); mock_print_func (mock, exp_pos); fail = 1; current++; exp_pos = exp_pos->next; } else if (exp_pos == NULL) { platform_printf ("(%s, after %d) Unexpected call: ", mock->name, current); mock_print_func (mock, call_pos); fail = 1; call_pos = call_pos->next; } else { if (exp_pos->func != call_pos->func) { search = exp_pos->next; while ((search != NULL) && (call_pos->func != search->func)) { search = search->next; } if (search != NULL) { while (exp_pos != search) { platform_printf ("(%s, %d) Not called: ", mock->name, current); mock_print_func (mock, exp_pos); fail = 1; current++; exp_pos = exp_pos->next; } } else { platform_printf ("(%s, before %d) Unexpected call: ", mock->name, current); mock_print_func (mock, call_pos); fail = 1; call_pos = call_pos->next; } } if ((exp_pos != NULL) && (call_pos != NULL) && (exp_pos->func == call_pos->func)) { if (exp_pos->instance != call_pos->instance) { platform_printf ( "(%s, %d) Unexpected object instance: expected=%p, actual=%p" NEWLINE, mock->name, current, exp_pos->instance, call_pos->instance); fail = 1; } if (exp_pos->argc != call_pos->argc) { platform_printf ( "(%s, %d) Unexpected number of arguments: expected=%d, actual=%d" NEWLINE, mock->name, current, exp_pos->argc, call_pos->argc); fail = 1; } else { for (i = 0; i < exp_pos->argc; i++) { fail |= mock_validate_arg (mock, current, mock->arg_name_map ((void*) exp_pos->func, i), &exp_pos->argv[i], &call_pos->argv[i]); } } current++; exp_pos = exp_pos->next; call_pos = call_pos->next; } } } } else { fail = 1; } return fail; } /** * Helper function used by MOCK_FUNCTION_TABLE macros to generically implement "func_arg_count" * function of mock interface * * @param table Pointer to mock function table defined using MOCK_FUNCTION_TABLE macros * @param table_size Number of entries inside mock function table * @param entry_size Size of mock function table entry. Different tables might use different * entries size due to the maximum number of function arguments * @param func Mock interface function pointer * * @return Number of function arguments */ int mock_function_arg_count (const void *table, size_t table_size, size_t entry_size, void *func) { const uint8_t *base = (const uint8_t*) table; const struct mock_function_table_entry *entry; size_t i; for (i = 0; i < table_size; i++) { entry = (const struct mock_function_table_entry*) (base + (i * entry_size)); if (entry->base.func_ptr == func) { return entry->base.arg_count; } } return 0; } /** * Helper function used by MOCK_FUNCTION_TABLE macros to generically implement "func_arg_name_map" * function of mock interface * * @param table Pointer to mock function table defined using MOCK_FUNCTION_TABLE macros * @param table_size Number of entries inside mock function table * @param entry_size Size of mock function table entry. Different tables might use different * entries size due to the maximum number of function arguments * @param func Mock interface function pointer * @param arc Argument index for requested argument name * * @return Name of the requested argument or "unknown" if function of argument is not found */ const char* mock_function_arg_name_map (const void *table, size_t table_size, size_t entry_size, void *func, int arg) { const uint8_t *base = (const uint8_t*) table; const struct mock_function_table_entry *entry; size_t i; for (i = 0; i < table_size; i++) { entry = (const struct mock_function_table_entry*) (base + (i * entry_size)); if ((entry->base.func_ptr == func) && (arg < (int) entry->base.arg_count)) { return entry->arg_names[arg]; } } return "unknown"; } /** * Helper function used by MOCK_FUNCTION_TABLE macros to generically implement "func_name_map" * function of mock interface * * @param table Pointer to mock function table defined using MOCK_FUNCTION_TABLE macros * @param table_size Number of entries inside mock function table * @param entry_size Size of mock function table entry. Different tables might use different * entries size due to the maximum number of function arguments * @param func Mock interface function pointer * * @return Function name or "unknown" if function is not found */ const char* mock_function_name_map (const void *table, size_t table_size, size_t entry_size, void *func) { const uint8_t *base = (const uint8_t*) table; const struct mock_function_table_entry *entry; size_t i; for (i = 0; i < table_size; i++) { entry = (const struct mock_function_table_entry*) (base + (i * entry_size)); if (entry->base.func_ptr == func) { return entry->base.func_name; } } return "unknown"; } /** * Initialize the mock instance. * * @param mock The mock to initialize. * * @return 0 if the mock was successfully initialized or an error code. */ int mock_init (struct mock *mock) { if (mock == NULL) { return MOCK_INVALID_ARGUMENT; } memset (mock, 0, sizeof (struct mock)); mock->name = "mock"; return 0; } /** * Release all memory used by a list of call entries. * * @param head The head of the list of calls. */ static void mock_release_call_list (struct mock_call *head) { struct mock_call *next; while (head != NULL) { next = head->next; mock_free_call (head); head = next; } } /** * Release all memory used by a list of saved arguments. * * @param head The head of the list saved arguments. */ static void mock_release_saved_args (struct mock_save_arg *head) { struct mock_save_arg *next; while (head != NULL) { next = head->next; platform_free (head); head = next; } } /** * Release the resources used by a mock instance. * * @param mock The mock instance to release. */ void mock_release (struct mock *mock) { if (mock != NULL) { mock_release_call_list (mock->expected); mock_release_call_list (mock->called); mock_release_saved_args (mock->save); } } /** * Set the name for the mock instance. * * @param mock The mock to update. * @param name The name to assign to the mock. */ void mock_set_name (struct mock *mock, const char *name) { if (mock != NULL) { mock->name = (name != NULL) ? name : "mock"; } } /** * Allocate a instance for an executed function call. * * @param func The function that was called. * @param instance The instance the function was called against. * @param args The number of arguments passed to the function. * @param ... A list of int64_t arguments from the function. * * @return The new call instance or null. */ struct mock_call* mock_allocate_call (const void *func, const void *instance, size_t args, ...) { struct mock_call *call; va_list arg_list; size_t i; call = platform_malloc (sizeof (struct mock_call)); if (call == NULL) { return NULL; } memset (call, 0, sizeof (struct mock_call)); if (args) { call->argv = platform_calloc (args, sizeof (struct mock_arg)); if (call->argv == NULL) { platform_free (call); return NULL; } } call->func = func; call->instance = instance; call->argc = args; va_start (arg_list, args); for (i = 0; i < args; i++) { call->argv[i].value = va_arg (arg_list, int64_t); } va_end (arg_list); return call; } /** * Push an executed function call on to the call list. * * @param mock The mock that executed the call. * @param call the call that was executed. */ static void mock_push_call (struct mock *mock, struct mock_call *call) { if (mock->called == NULL) { mock->called = call; } else { mock->call_tail->next = call; } mock->call_tail = call; mock->call_count++; } /** * Return from an executed function call. * * @param mock The mock that executed the call. * @param call The call that was executed. * * @return The result of the function execution. */ int64_t mock_return_from_call (struct mock *mock, struct mock_call *call) { struct mock_call *expected; int64_t status = 0; int i; size_t out_len; int64_t value_ptr; bool update_value; if (call == NULL) { return MOCK_NO_MEMORY; } mock_push_call (mock, call); expected = mock->next_call; while ((expected != NULL) && (expected->func != call->func)) { expected = expected->next; } if (expected != NULL) { /* Call any external action registered with the expectation. */ if (expected->action) { status = expected->action (expected, call); } for (i = 0; i < expected->argc; i++) { update_value = false; /* Dereference pointer parameters. */ if ((call->argv[i].value != 0) && (expected->argv[i].flags & MOCK_ARG_FLAG_PTR_PTR)) { value_ptr = (int64_t) ((uintptr_t) (*((int**) ((uintptr_t) call->argv[i].value)))); if ((expected->argv[i].out_data == NULL) || (expected->argv[i].flags & MOCK_ARG_FLAG_OUT_PTR_PTR)) { call->argv[i].value = value_ptr; call->argv[i].flags |= MOCK_ARG_FLAG_PTR_PTR; } else { update_value = true; } } /* Save the contents of a pointer parameter. */ if ((expected->argv[i].ptr_value_len != 0) && (call->argv[i].value != 0)) { expected->argv[i].alloc (&expected->argv[i], &call->argv[i]); call->argv[i].free = expected->argv[i].free; } /* Fill an output parameter with data. */ if ((expected->argv[i].out_data != NULL) && (call->argv[i].value != 0)) { if (expected->argv[i].size_arg < 0) { out_len = expected->argv[i].out_len; } else if (expected->argv[i].out_len > (size_t) call->argv[expected->argv[i].size_arg].value) { out_len = call->argv[expected->argv[i].size_arg].value; } else { out_len = expected->argv[i].out_len; } expected->argv[i].copy (&expected->argv[i], &call->argv[i], out_len); } /* Save argument values. */ if (expected->argv[i].save_arg >= 0) { struct mock_save_arg *save = mock_find_save_arg (mock, expected->argv[i].save_arg); if (save && !save->saved) { save->value = call->argv[i].value; save->saved = true; if (save->shared) { save->shared->value = save->value; save->shared->saved = true; } } } /* We are done with the argument that was passed. Store the dereferenced pointer. */ if (update_value) { call->argv[i].value = value_ptr; call->argv[i].flags |= MOCK_ARG_FLAG_PTR_PTR; } } if (status == 0) { status = expected->return_val; } mock->next_call = expected->next; } return status; }