// SPDX-License-Identifier: GPL-2.0 /* * Mocking API for KUnit. * * Copyright (C) 2018, Google LLC. * Author: Brendan Higgins */ #include static int mock_void_ptr_init(struct MOCK(void) *mock_void_ptr) { mock_void_ptr->trgt = mock_void_ptr; return 0; } DEFINE_STRUCT_CLASS_MOCK_INIT(void, mock_void_ptr_init); static bool mock_match_params(struct mock_matcher *matcher, struct test_stream *stream, const void **params, int len) { struct mock_param_matcher *param_matcher; bool ret = true, tmp; int i; BUG_ON(matcher->num != len); for (i = 0; i < matcher->num; i++) { param_matcher = matcher->matchers[i]; stream->add(stream, "\t"); tmp = param_matcher->match(param_matcher, stream, params[i]); ret = ret && tmp; stream->add(stream, "\n"); } return ret; } static const void *mock_do_expect(struct mock *mock, const char *method_name, const void *method_ptr, const char * const *type_names, const void **params, int len); static bool mock_is_expectation_satisfied(struct mock_expectation *expectation) { return (expectation->min_calls_expected <= expectation->times_called && expectation->times_called <= expectation->max_calls_expected); } static void mock_write_expectation_unsatisfied_message( struct mock_expectation *expectation, struct test_stream *stream) { stream->add(stream, "%s:%d - Expectation was not called the specified number of times:\n\t", expectation->file_name, expectation->line_no); stream->add(stream, "Expectation: %s,\n\tmin calls: %d, max calls: %d, actual calls: %d", expectation->expectation_text, expectation->min_calls_expected, expectation->max_calls_expected, expectation->times_called); } void mock_validate_expectations(struct mock *mock) { struct mock_expectation *expectation, *expectation_safe; struct mock_method *method; struct test_stream *stream; stream = test_new_stream(mock->test); list_for_each_entry(method, &mock->methods, node) { list_for_each_entry_safe(expectation, expectation_safe, &method->expectations, node) { if (!mock_is_expectation_satisfied(expectation)) { mock_write_expectation_unsatisfied_message( expectation, stream); mock->test->fail(mock->test, stream); } list_del(&expectation->node); } } } static void mock_validate_wrapper(struct test_post_condition *condition) { struct mock *mock = container_of(condition, struct mock, parent); mock_validate_expectations(mock); } void mock_init_ctrl(struct KUNIT_T *test, struct mock *mock) { mock->test = test; INIT_LIST_HEAD(&mock->methods); mock->do_expect = mock_do_expect; mock->type = DEFAULT_MOCK_TYPE; mock->parent.validate = mock_validate_wrapper; list_add_tail(&mock->parent.node, &test->post_conditions); } struct global_mock { struct mock ctrl; bool is_initialized; }; static struct global_mock global_mock = { .is_initialized = false, }; static int mock_init_global_mock(struct test_initcall *initcall, struct KUNIT_T *test) { BUG_ON(global_mock.is_initialized); mock_init_ctrl(test, &global_mock.ctrl); global_mock.is_initialized = true; return 0; } static void mock_exit_global_mock(struct test_initcall *initcall) { BUG_ON(!global_mock.is_initialized); global_mock.ctrl.test = NULL; global_mock.is_initialized = false; } static struct test_initcall global_mock_initcall = { .init = mock_init_global_mock, .exit = mock_exit_global_mock, }; test_register_initcall(global_mock_initcall); struct mock *mock_get_global_mock(void) { BUG_ON(!global_mock.is_initialized); return &global_mock.ctrl; } static struct mock_method *mock_lookup_method(struct mock *mock, const void *method_ptr) { struct mock_method *ret; list_for_each_entry(ret, &mock->methods, node) { if (ret->method_ptr == method_ptr) return ret; } return NULL; } static struct mock_method *mock_add_method(struct mock *mock, const char *method_name, const void *method_ptr) { struct mock_method *method; method = test_kzalloc(mock->test, sizeof(*method), GFP_KERNEL); if (!method) return NULL; INIT_LIST_HEAD(&method->expectations); method->method_name = method_name; method->method_ptr = method_ptr; list_add_tail(&method->node, &mock->methods); return method; } static int mock_add_expectation(struct mock *mock, const char *method_name, const void *method_ptr, struct mock_expectation *expectation) { struct mock_method *method; method = mock_lookup_method(mock, method_ptr); if (!method) { method = mock_add_method(mock, method_name, method_ptr); if (!method) return -ENOMEM; } list_add_tail(&expectation->node, &method->expectations); expectation->method = method; return 0; } struct mock_expectation *mock_add_matcher(struct mock *mock, const char *method_name, const void *method_ptr, struct mock_param_matcher *matchers[], int len) { struct mock_expectation *expectation; struct mock_matcher *matcher; int ret; expectation = test_kzalloc(mock->test, sizeof(*expectation), GFP_KERNEL); if (!expectation) return NULL; matcher = test_kmalloc(mock->test, sizeof(*matcher), GFP_KERNEL); if (!matcher) return NULL; memcpy(&matcher->matchers, matchers, sizeof(*matchers) * len); matcher->num = len; expectation->matcher = matcher; expectation->max_calls_expected = 1; expectation->min_calls_expected = 1; INIT_LIST_HEAD(&expectation->prerequisites); ret = mock_add_expectation(mock, method_name, method_ptr, expectation); if (ret < 0) return NULL; return expectation; } int mock_set_default_action(struct mock *mock, const char *method_name, const void *method_ptr, struct mock_action *action) { struct mock_method *method; method = mock_lookup_method(mock, method_ptr); if (!method) { method = mock_add_method(mock, method_name, method_ptr); if (!method) return -ENOMEM; } method->default_action = action; return 0; } struct mock_param_formatter_repo { struct list_head formatters; }; static struct mock_param_formatter_repo mock_param_formatter_repo = { .formatters = LIST_HEAD_INIT(mock_param_formatter_repo.formatters), }; void mock_register_formatter(struct mock_param_formatter *formatter) { list_add_tail(&formatter->node, &mock_param_formatter_repo.formatters); } void mock_unregister_formatter(struct mock_param_formatter *formatter) { list_del(&formatter->node); } struct mock_param_formatter *mock_find_formatter(const char *type_name) { struct mock_param_formatter *formatter; list_for_each_entry(formatter, &mock_param_formatter_repo.formatters, node) { if (!strcmp(type_name, formatter->type_name)) return formatter; } return NULL; } static void mock_format_param(struct test_stream *stream, const char *type_name, const void *param) { struct mock_param_formatter *formatter; formatter = mock_find_formatter(type_name); if (formatter) formatter->format(formatter, stream, param); else /* * Cannot find formatter, so just print the pointer of the * symbol. */ stream->add(stream, "<%pS>", param); } static void mock_add_method_declaration_to_stream( struct test_stream *stream, const char *function_name, const char * const *type_names, const void **params, int len) { int i; stream->add(stream, "%s(", function_name); for (i = 0; i < len; i++) { mock_format_param(stream, type_names[i], params[i]); if (i < len - 1) stream->add(stream, ", "); } stream->add(stream, ")\n"); } static struct test_stream *mock_initialize_failure_message( struct KUNIT_T *test, const char *function_name, const char * const *type_names, const void **params, int len) { struct test_stream *stream; stream = test_new_stream(test); if (!stream) return NULL; stream->add(stream, "EXPECTATION FAILED: no expectation for call: "); mock_add_method_declaration_to_stream(stream, function_name, type_names, params, len); return stream; } static bool mock_is_expectation_retired(struct mock_expectation *expectation) { return expectation->retire_on_saturation && expectation->times_called == expectation->max_calls_expected; } static void mock_add_method_expectation_error(struct KUNIT_T *test, struct test_stream *stream, char *message, struct mock *mock, struct mock_method *method, const char * const *type_names, const void **params, int len) { stream->clear(stream); stream->set_level(stream, KERN_WARNING); stream->add(stream, message); mock_add_method_declaration_to_stream(stream, method->method_name, type_names, params, len); } static bool mock_are_prereqs_satisfied(struct mock_expectation *expectation, struct test_stream *stream) { struct mock_expectation_prereq_entry *entry, *entry_safe; list_for_each_entry_safe(entry, entry_safe, &expectation->prerequisites, node) { if (!mock_is_expectation_satisfied(entry->expectation)) { stream->add(stream, "Expectation %s matched but prerequisite expectation was not satisfied:\n", expectation->expectation_name); mock_write_expectation_unsatisfied_message( entry->expectation, stream); return false; } /* Don't need to check satisfied prereq again. */ list_del(&entry->node); } return true; } /* Assumes that the var args are null terminated. */ int mock_in_sequence(struct KUNIT_T *test, struct mock_expectation *first, ...) { struct mock_expectation *prereq = first; struct mock_expectation *curr = NULL; struct mock_expectation_prereq_entry *entry; va_list args; va_start(args, first); RetireOnSaturation(first); while ((curr = va_arg(args, struct mock_expectation*))) { RetireOnSaturation(curr); entry = test_kzalloc(test, sizeof(*entry), GFP_KERNEL); if (!entry) { va_end(args); return -ENOMEM; } entry->expectation = prereq; list_add_tail(&entry->node, &curr->prerequisites); prereq = curr; } va_end(args); return 0; } static inline bool does_mock_expectation_match_call( struct mock_expectation *expectation, struct test_stream *stream, const void **params, int len) { return mock_match_params(expectation->matcher, stream, params, len) && mock_are_prereqs_satisfied(expectation, stream); } static struct mock_expectation *mock_apply_expectations( struct mock *mock, struct mock_method *method, const char * const *type_names, const void **params, int len) { struct KUNIT_T *test = mock->test; struct mock_expectation *ret; struct test_stream *attempted_matching_stream; bool expectations_all_saturated = true; struct test_stream *stream = test_new_stream(test); if (list_empty(&method->expectations)) { mock_add_method_expectation_error(test, stream, "Method was called with no expectations declared: ", mock, method, type_names, params, len); if (is_strict_mock(mock)) test->fail(test, stream); else if (is_naggy_mock(mock)) stream->commit(stream); else stream->clear(stream); return NULL; } attempted_matching_stream = mock_initialize_failure_message( test, method->method_name, type_names, params, len); list_for_each_entry(ret, &method->expectations, node) { if (mock_is_expectation_retired(ret)) continue; expectations_all_saturated = false; attempted_matching_stream->add(attempted_matching_stream, "Tried expectation: %s at %s:%d, but\n", ret->expectation_text, ret->file_name, ret->line_no); if (does_mock_expectation_match_call(ret, attempted_matching_stream, params, len)) { /* * Matcher was found; we won't print, so clean up the * log. */ attempted_matching_stream->clear( attempted_matching_stream); return ret; } } if (expectations_all_saturated && !is_nice_mock(mock)) { mock_add_method_expectation_error(test, stream, "Method was called with fully saturated expectations: ", mock, method, type_names, params, len); } else { mock_add_method_expectation_error(test, stream, "Method called that did not match any expectations: ", mock, method, type_names, params, len); stream->append(stream, attempted_matching_stream); } test->fail(test, stream); attempted_matching_stream->clear(attempted_matching_stream); return NULL; } static const void *mock_do_expect(struct mock *mock, const char *method_name, const void *method_ptr, const char * const *param_types, const void **params, int len) { struct mock_expectation *expectation; struct mock_method *method; struct mock_action *action; method = mock_lookup_method(mock, method_ptr); if (!method) return NULL; expectation = mock_apply_expectations(mock, method, param_types, params, len); if (!expectation) { action = method->default_action; } else { expectation->times_called++; if (expectation->action) action = expectation->action; else action = method->default_action; } if (!action) return NULL; return action->do_action(action, params, len); }