kunit: mock: added parameter formatters

Added parameter formatters which provide string formatting for
parameters that have no matchers to be matched against.

Change-Id: Iafe3c676bc7cff4ec8a9d063fd17bcc99b4ded86
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
diff --git a/include/test/mock.h b/include/test/mock.h
index 060cf83..004dcf7 100644
--- a/include/test/mock.h
+++ b/include/test/mock.h
@@ -113,6 +113,18 @@
 					  struct mock_param_matcher *matchers[],
 					  int len);
 
+struct mock_param_formatter {
+	struct list_head node;
+	const char *type_name;
+	void (*format)(struct mock_param_formatter *formatter,
+		       struct test_stream *stream,
+		       const void *param);
+};
+
+void mock_register_formatter(struct mock_param_formatter *formatter);
+
+void mock_unregister_formatter(struct mock_param_formatter *formatter);
+
 #define MOCK(name) name##_mock
 
 /**
@@ -792,4 +804,41 @@
 		const char *struct_name,
 		struct mock_struct_matcher_entry *entries);
 
+struct mock_struct_formatter_entry {
+	size_t member_offset;
+	struct mock_param_formatter *formatter;
+};
+
+static inline void init_mock_struct_formatter_entry_internal(
+		struct mock_struct_formatter_entry *entry,
+		size_t offset,
+		struct mock_param_formatter *formatter)
+{
+	entry->member_offset = offset;
+	entry->formatter = formatter;
+}
+
+#define INIT_MOCK_STRUCT_FORMATTER_ENTRY(entry, type, member, formatter)       \
+		init_mock_struct_formatter_entry_internal(entry,	       \
+							  offsetof(type,       \
+								   member),    \
+								   formatter)
+
+static inline void INIT_MOCK_STRUCT_FORMATTER_ENTRY_LAST(
+		struct mock_struct_formatter_entry *entry)
+{
+	entry->formatter = NULL;
+}
+
+struct mock_param_formatter *mock_struct_formatter(
+		struct test *test,
+		const char *struct_name,
+		struct mock_struct_formatter_entry *entries);
+
+struct mock_param_formatter *mock_find_formatter(const char *type_name);
+
+#define FORMATTER_FROM_TYPE(type) mock_find_formatter(#type)
+
+extern struct mock_param_formatter unknown_formatter[];
+
 #endif /* _TEST_MOCK_H */
diff --git a/test/common-mocks.c b/test/common-mocks.c
index 4ea87b9..e84d2dd 100644
--- a/test/common-mocks.c
+++ b/test/common-mocks.c
@@ -375,3 +375,135 @@
 DEFINE_RETURN_ACTION_WITH_TYPENAME(ulonglong, unsigned long long);
 DEFINE_RETURN_ACTION_WITH_TYPENAME(ptr, void *);
 
+struct mock_param_integer_formatter {
+	struct mock_param_formatter formatter;
+	const char *fmt_str;
+};
+
+static void mock_format_integer(struct mock_param_formatter *pformatter,
+				struct test_stream *stream,
+				const void *pparam)
+{
+	struct mock_param_integer_formatter *formatter =
+			container_of(pformatter,
+				     struct mock_param_integer_formatter,
+				     formatter);
+	long long param = CONVERT_TO_ACTUAL_TYPE(long long, pparam);
+
+	stream->add(stream, formatter->fmt_str, param);
+}
+
+#define INTEGER_FORMATTER_INIT(type, fmt) { \
+	.formatter = { \
+		.type_name = #type, \
+		.format = mock_format_integer, \
+	}, \
+	.fmt_str = fmt, \
+}
+
+static struct mock_param_integer_formatter integer_formatters[] = {
+	INTEGER_FORMATTER_INIT(u8, "%PRIu8"),
+	INTEGER_FORMATTER_INIT(u16, "%PRIu16"),
+	INTEGER_FORMATTER_INIT(u32, "%PRIu32"),
+	INTEGER_FORMATTER_INIT(u64, "%PRIu64"),
+	INTEGER_FORMATTER_INIT(char, "%c"),
+	INTEGER_FORMATTER_INIT(unsigned char, "%hhu"),
+	INTEGER_FORMATTER_INIT(signed char, "%hhd"),
+	INTEGER_FORMATTER_INIT(short, "%hd"),
+	INTEGER_FORMATTER_INIT(unsigned short, "%hu"),
+	INTEGER_FORMATTER_INIT(int, "%d"),
+	INTEGER_FORMATTER_INIT(unsigned int, "%u"),
+	INTEGER_FORMATTER_INIT(long, "%ld"),
+	INTEGER_FORMATTER_INIT(unsigned long, "%lu"),
+	INTEGER_FORMATTER_INIT(long long, "%lld"),
+	INTEGER_FORMATTER_INIT(unsigned long long, "%llu"),
+	INTEGER_FORMATTER_INIT(void *, "%px"),
+};
+
+static void mock_format_string(struct mock_param_formatter *formatter,
+			       struct test_stream *stream,
+			       const void *pparam)
+{
+	const char *param = CONVERT_TO_ACTUAL_TYPE(const char *, pparam);
+
+	stream->add(stream, "%s", param);
+}
+
+static struct mock_param_formatter string_formatter = {
+	.type_name = "const char *",
+	.format = mock_format_string,
+};
+
+static void mock_format_unknown(struct mock_param_formatter *formatter,
+				struct test_stream *stream,
+				const void *param)
+{
+	stream->add(stream, "%pS", param);
+}
+
+struct mock_param_formatter unknown_formatter[] = {
+	{
+		.type_name = "<unknown>",
+		.format = mock_format_unknown,
+	},
+	{},
+};
+
+static int mock_register_all_formatters(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(integer_formatters); i++)
+		mock_register_formatter(&integer_formatters[i].formatter);
+
+	mock_register_formatter(&string_formatter);
+
+	return 0;
+}
+test_pure_initcall(mock_register_all_formatters);
+
+struct mock_struct_formatter {
+	struct mock_param_formatter formatter;
+	const char *type_name;
+	struct mock_struct_formatter_entry *entries;
+};
+
+static void mock_format_struct(struct mock_param_formatter *pformatter,
+			       struct test_stream *stream,
+			       const void *pparam)
+{
+	struct mock_struct_formatter *formatter =
+			container_of(pformatter,
+				     struct mock_struct_formatter,
+				     formatter);
+	struct mock_struct_formatter_entry *entry;
+	const char *param = CONVERT_TO_ACTUAL_TYPE(const char *, pparam);
+	const char *member_ptr;
+
+	stream->add(stream, "%s {", formatter->type_name);
+	for (entry = formatter->entries; entry->formatter; entry++) {
+		member_ptr = param + entry->member_offset;
+		entry->formatter->format(entry->formatter, stream, member_ptr);
+		stream->add(stream, ", ");
+	}
+	stream->add(stream, "}");
+}
+
+struct mock_param_formatter *mock_struct_formatter(
+		struct test *test,
+		const char *type_name,
+		struct mock_struct_formatter_entry *entries)
+{
+	struct mock_struct_formatter *formatter;
+
+	formatter = test_kzalloc(test, sizeof(*formatter), GFP_KERNEL);
+	if (!formatter)
+		return NULL;
+
+	formatter->formatter.type_name = type_name;
+	formatter->formatter.format = mock_format_struct;
+	formatter->type_name = type_name;
+	formatter->entries = entries;
+
+	return &formatter->formatter;
+}
diff --git a/test/mock.c b/test/mock.c
index 12afb6c..0710b7f 100644
--- a/test/mock.c
+++ b/test/mock.c
@@ -178,15 +178,53 @@
 	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)
 {
-	/*
-	 * Cannot find formatter, so just print the pointer of the
-	 * symbol.
-	 */
-	stream->add(stream, "<%pS>", 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(
diff --git a/test/test-stream-test.c b/test/test-stream-test.c
index eb7d4cf..659df66 100644
--- a/test/test-stream-test.c
+++ b/test/test-stream-test.c
@@ -77,6 +77,7 @@
 
 static int test_stream_test_init(struct test *test)
 {
+	struct mock_struct_formatter_entry *entries;
 	struct test_stream_test_context *ctx;
 
 	ctx = test_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
@@ -92,6 +93,27 @@
 	if (!ctx->stream)
 		return -ENOMEM;
 
+	entries = test_kzalloc(test, sizeof(*entries) * 3, GFP_KERNEL);
+	if (!entries) {
+		test_warn(test,
+			  "Could not allocate arg formatter for struct va_format");
+		return 0;
+	}
+
+	INIT_MOCK_STRUCT_FORMATTER_ENTRY(&entries[0],
+					 struct va_format,
+					 fmt,
+					 FORMATTER_FROM_TYPE(const char *));
+	INIT_MOCK_STRUCT_FORMATTER_ENTRY(&entries[1],
+					 struct va_format,
+					 va,
+					 unknown_formatter);
+	INIT_MOCK_STRUCT_FORMATTER_ENTRY_LAST(&entries[2]);
+
+	mock_register_formatter(mock_struct_formatter(test,
+						      "struct va_format *",
+						      entries));
+
 	return 0;
 }
 
@@ -119,6 +141,11 @@
 	test_cleanup(mock_get_trgt(mock_test));
 }
 
+static void test_stream_test_exit(struct test *test)
+{
+	mock_unregister_formatter(mock_find_formatter("struct va_format *"));
+}
+
 static struct test_case test_stream_test_cases[] = {
 	TEST_CASE(test_stream_test_add),
 	TEST_CASE(test_stream_test_append),
@@ -130,6 +157,7 @@
 static struct test_module test_stream_test_module = {
 	.name = "test-stream-test",
 	.init = test_stream_test_init,
+	.exit = test_stream_test_exit,
 	.test_cases = test_stream_test_cases,
 };
 module_test(test_stream_test_module);