524 lines
13 KiB
C
Executable file
524 lines
13 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Mocking API for KUnit.
|
|
*
|
|
* Copyright (C) 2018, Google LLC.
|
|
* Author: Brendan Higgins <brendanhiggins@google.com>
|
|
*/
|
|
|
|
#include <test/mock.h>
|
|
|
|
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);
|
|
}
|