blob: e668ab4bdd85c51f9e11d54287afdc1ea6c15d9c [file] [log] [blame]
// 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);
}