* [RFC v2 01/12] Revert "kunit: move string-stream.h to lib/kunit"
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 02/12] kunit: test: add kunit_stream a std::stream like logger Daniel Latypov
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
This reverts commit 109fb06fdc6f6788df7dfbc235f7636a38e28fd4.
string-stream will be used by kunit mocking code to print messages about
mock expectations.
It makes the code signifcantly simpler if string-stream objects can be
part of structs declared in mocking headers.
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/assert.h | 3 +--
{lib => include}/kunit/string-stream.h | 0
lib/kunit/assert.c | 2 --
lib/kunit/string-stream-test.c | 3 +--
lib/kunit/string-stream.c | 3 +--
lib/kunit/test.c | 2 +-
6 files changed, 4 insertions(+), 9 deletions(-)
rename {lib => include}/kunit/string-stream.h (100%)
diff --git a/include/kunit/assert.h b/include/kunit/assert.h
index ad889b539ab3..db6a0fca09b4 100644
--- a/include/kunit/assert.h
+++ b/include/kunit/assert.h
@@ -9,11 +9,10 @@
#ifndef _KUNIT_ASSERT_H
#define _KUNIT_ASSERT_H
+#include <kunit/string-stream.h>
#include <linux/err.h>
-#include <linux/kernel.h>
struct kunit;
-struct string_stream;
/**
* enum kunit_assert_type - Type of expectation/assertion.
diff --git a/lib/kunit/string-stream.h b/include/kunit/string-stream.h
similarity index 100%
rename from lib/kunit/string-stream.h
rename to include/kunit/string-stream.h
diff --git a/lib/kunit/assert.c b/lib/kunit/assert.c
index 33acdaa28a7d..9c12e30792ba 100644
--- a/lib/kunit/assert.c
+++ b/lib/kunit/assert.c
@@ -8,8 +8,6 @@
#include <kunit/assert.h>
#include <kunit/test.h>
-#include "string-stream.h"
-
void kunit_base_assert_format(const struct kunit_assert *assert,
struct string_stream *stream)
{
diff --git a/lib/kunit/string-stream-test.c b/lib/kunit/string-stream-test.c
index 110f3a993250..121f9ab11501 100644
--- a/lib/kunit/string-stream-test.c
+++ b/lib/kunit/string-stream-test.c
@@ -6,11 +6,10 @@
* Author: Brendan Higgins <brendanhiggins@google.com>
*/
+#include <kunit/string-stream.h>
#include <kunit/test.h>
#include <linux/slab.h>
-#include "string-stream.h"
-
static void string_stream_test_empty_on_creation(struct kunit *test)
{
struct string_stream *stream = alloc_string_stream(test, GFP_KERNEL);
diff --git a/lib/kunit/string-stream.c b/lib/kunit/string-stream.c
index 141789ca8949..151a0e7ac349 100644
--- a/lib/kunit/string-stream.c
+++ b/lib/kunit/string-stream.c
@@ -6,12 +6,11 @@
* Author: Brendan Higgins <brendanhiggins@google.com>
*/
+#include <kunit/string-stream.h>
#include <kunit/test.h>
#include <linux/list.h>
#include <linux/slab.h>
-#include "string-stream.h"
-
struct string_stream_fragment_alloc_context {
struct kunit *test;
int len;
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index c36037200310..670d1cc9c105 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -7,12 +7,12 @@
*/
#include <kunit/test.h>
+#include <kunit/string-stream.h>
#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/sched/debug.h>
#include "debugfs.h"
-#include "string-stream.h"
#include "try-catch-impl.h"
static void kunit_set_failure(struct kunit *test)
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 02/12] kunit: test: add kunit_stream a std::stream like logger
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
2020-10-12 22:20 ` [RFC v2 01/12] Revert "kunit: move string-stream.h to lib/kunit" Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 03/12] kunit: test: add concept of post conditions Daniel Latypov
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
A lot of the expectation and assertion infrastructure prints out fairly
complicated test failure messages, so add a C++ style log library for
for logging test results called `struct kunit_stream`.
kunit_stream allows us to construct a message before we know whether we
want to print it out; this can be extremely handy if there is
information you might need for a failure message that is easiest to
collect in the steps leading up to the actual check.
Note that kunit_stream is distinct from string_stream: whereas
string_stream is really just a string builder, kunit_stream is
specifically used for constructing expectation/assertion (introduced
later in this series) messages to a user of KUnit.
Patch was previously reviewed at
https://lore.kernel.org/linux-fsdevel/20190712081744.87097-5-brendanhiggins@google.com/
Primary differences are
* kunit/ => lib/kunit/
* re-expose string_stream_clear(), which was made static since then
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Cc: Stephen Boyd <sboyd@kernel.org>
---
include/kunit/kunit-stream.h | 94 +++++++++++++++++++++++++++++
include/kunit/string-stream.h | 2 +
include/kunit/test.h | 1 +
lib/kunit/Makefile | 1 +
lib/kunit/kunit-stream.c | 110 ++++++++++++++++++++++++++++++++++
lib/kunit/string-stream.c | 2 +-
6 files changed, 209 insertions(+), 1 deletion(-)
create mode 100644 include/kunit/kunit-stream.h
create mode 100644 lib/kunit/kunit-stream.c
diff --git a/include/kunit/kunit-stream.h b/include/kunit/kunit-stream.h
new file mode 100644
index 000000000000..9a768732a04b
--- /dev/null
+++ b/include/kunit/kunit-stream.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * C++ stream style string formatter and printer used in KUnit for outputting
+ * KUnit messages.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#ifndef _KUNIT_KUNIT_STREAM_H
+#define _KUNIT_KUNIT_STREAM_H
+
+#include <linux/types.h>
+#include <kunit/string-stream.h>
+
+struct kunit;
+
+/**
+ * struct kunit_stream - a std::stream style string builder.
+ *
+ * A std::stream style string builder. Allows messages to be built up and
+ * printed all at once. Note that the intention is to only use
+ * &struct kunit_stream to communicate with a user of KUnit, most often to
+ * communicate something about an expectation or an assertion to the user. If
+ * you want a similar interface, but aren't sure if this is the right class for
+ * you to use, you probably want to use the related string_stream class, which
+ * is allowed for generic string construction in a similar manner. This class is
+ * really only for the KUnit library to communicate certain kinds of information
+ * to KUnit users and should not be used by anyone else.
+ *
+ * A note on &struct kunit_stream's usage: a kunit_stream will generally
+ * accompany *one* expectation or assertion. Multiple expectations/assertions
+ * may be validated concurrently at any given time, even within a single test
+ * case, so sharing a kunit_stream between expectations/assertions may result in
+ * unintended consequences.
+ */
+struct kunit_stream {
+ /* private: internal use only. */
+ struct kunit *test;
+ struct string_stream *internal_stream;
+};
+
+/**
+ * alloc_kunit_stream() - constructs a new &struct kunit_stream.
+ * @test: The test context object.
+ * @gfp: The GFP flags to use for internal allocations.
+ *
+ * Constructs a new test managed &struct kunit_stream.
+ */
+struct kunit_stream *alloc_kunit_stream(struct kunit *test,
+ gfp_t gfp);
+
+/**
+ * kunit_stream_add(): adds the formatted input to the internal buffer.
+ * @kstream: the stream being operated on.
+ * @fmt: printf style format string to append to stream.
+ *
+ * Appends the formatted string, @fmt, to the internal buffer.
+ */
+void __printf(2, 3) kunit_stream_add(struct kunit_stream *kstream,
+ const char *fmt, ...);
+
+/**
+ * kunit_stream_append(): appends the contents of @other to @kstream.
+ * @kstream: the stream to which @other is appended.
+ * @other: the stream whose contents are appended to @kstream.
+ *
+ * Appends the contents of @other to @kstream.
+ */
+void kunit_stream_append(struct kunit_stream *kstream,
+ struct kunit_stream *other);
+
+/**
+ * kunit_stream_commit(): prints out the internal buffer to the user.
+ * @kstream: the stream being operated on.
+ *
+ * Outputs the contents of the internal buffer as a kunit_printk formatted
+ * output. KUNIT_STREAM ONLY OUTPUTS ITS BUFFER TO THE USER IF COMMIT IS
+ * CALLED!!! The reason for this is that it allows us to construct a message
+ * before we know whether we want to print it out; this can be extremely handy
+ * if there is information you might need for a failure message that is easiest
+ * to collect in the steps leading up to the actual check.
+ */
+void kunit_stream_commit(struct kunit_stream *kstream);
+
+/**
+ * kunit_stream_clear(): clears the internal buffer.
+ * @kstream: the stream being operated on.
+ *
+ * Clears the contents of the internal buffer.
+ */
+void kunit_stream_clear(struct kunit_stream *kstream);
+
+#endif /* _KUNIT_KUNIT_STREAM_H */
diff --git a/include/kunit/string-stream.h b/include/kunit/string-stream.h
index fe98a00b75a9..44677c386890 100644
--- a/include/kunit/string-stream.h
+++ b/include/kunit/string-stream.h
@@ -46,6 +46,8 @@ int string_stream_append(struct string_stream *stream,
bool string_stream_is_empty(struct string_stream *stream);
+void string_stream_clear(struct string_stream *stream);
+
int string_stream_destroy(struct string_stream *stream);
#endif /* _KUNIT_STRING_STREAM_H */
diff --git a/include/kunit/test.h b/include/kunit/test.h
index 59f3144f009a..687782fa44d9 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -10,6 +10,7 @@
#define _KUNIT_TEST_H
#include <kunit/assert.h>
+#include <kunit/kunit-stream.h>
#include <kunit/try-catch.h>
#include <linux/kernel.h>
#include <linux/module.h>
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index 724b94311ca3..523a7b0f9783 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_KUNIT) += kunit.o
kunit-objs += test.o \
string-stream.o \
assert.o \
+ kunit-stream.o \
try-catch.o
ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
diff --git a/lib/kunit/kunit-stream.c b/lib/kunit/kunit-stream.c
new file mode 100644
index 000000000000..744798693f39
--- /dev/null
+++ b/lib/kunit/kunit-stream.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * C++ stream style string formatter and printer used in KUnit for outputting
+ * KUnit messages.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#include <kunit/test.h>
+#include <kunit/kunit-stream.h>
+#include <kunit/string-stream.h>
+
+void kunit_stream_add(struct kunit_stream *kstream, const char *fmt, ...)
+{
+ va_list args;
+ struct string_stream *stream = kstream->internal_stream;
+
+ va_start(args, fmt);
+
+ if (string_stream_vadd(stream, fmt, args))
+ kunit_err(kstream->test,
+ "Failed to allocate fragment: %s\n",
+ fmt);
+
+ va_end(args);
+}
+
+void kunit_stream_append(struct kunit_stream *kstream,
+ struct kunit_stream *other)
+{
+ int ret;
+
+ ret = string_stream_append(kstream->internal_stream,
+ other->internal_stream);
+
+ if (ret)
+ kunit_err(kstream->test,
+ "Failed to append other stream: %d\n", ret);
+}
+
+void kunit_stream_clear(struct kunit_stream *kstream)
+{
+ string_stream_clear(kstream->internal_stream);
+}
+
+void kunit_stream_commit(struct kunit_stream *kstream)
+{
+ struct string_stream *stream = kstream->internal_stream;
+ struct string_stream_fragment *fragment;
+ struct kunit *test = kstream->test;
+ char *buf;
+
+ buf = string_stream_get_string(stream);
+ if (!buf) {
+ kunit_err(test,
+ "Could not allocate buffer, dumping stream:\n");
+ list_for_each_entry(fragment, &stream->fragments, node) {
+ kunit_err(test, "%s", fragment->fragment);
+ }
+ kunit_err(test, "\n");
+ } else {
+ kunit_err(test, "%s", buf);
+ }
+
+ kunit_stream_clear(kstream);
+}
+
+struct kunit_stream_alloc_context {
+ struct kunit *test;
+ gfp_t gfp;
+};
+
+static int kunit_stream_init(struct kunit_resource *res, void *context)
+{
+ struct kunit_stream_alloc_context *ctx = context;
+ struct kunit_stream *stream;
+
+ stream = kunit_kzalloc(ctx->test, sizeof(*stream), ctx->gfp);
+ if (!stream)
+ return -ENOMEM;
+
+ stream->test = ctx->test;
+ stream->internal_stream = alloc_string_stream(ctx->test, ctx->gfp);
+ if (!stream->internal_stream)
+ return -ENOMEM;
+
+ res->data = stream;
+ return 0;
+}
+
+static void kunit_stream_free(struct kunit_resource *res)
+{
+ /* Do nothing because cleanup is handled by KUnit managed resources */
+}
+
+struct kunit_stream *alloc_kunit_stream(struct kunit *test,
+ gfp_t gfp)
+{
+ struct kunit_stream_alloc_context ctx = {
+ .test = test,
+ .gfp = gfp
+ };
+
+ return kunit_alloc_resource(test,
+ kunit_stream_init,
+ kunit_stream_free,
+ gfp,
+ &ctx);
+}
diff --git a/lib/kunit/string-stream.c b/lib/kunit/string-stream.c
index 151a0e7ac349..a94004134928 100644
--- a/lib/kunit/string-stream.c
+++ b/lib/kunit/string-stream.c
@@ -112,7 +112,7 @@ int string_stream_add(struct string_stream *stream, const char *fmt, ...)
return result;
}
-static void string_stream_clear(struct string_stream *stream)
+void string_stream_clear(struct string_stream *stream)
{
struct string_stream_fragment *frag_container, *frag_container_safe;
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 03/12] kunit: test: add concept of post conditions
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
2020-10-12 22:20 ` [RFC v2 01/12] Revert "kunit: move string-stream.h to lib/kunit" Daniel Latypov
2020-10-12 22:20 ` [RFC v2 02/12] kunit: test: add kunit_stream a std::stream like logger Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 04/12] checkpatch: add support for struct MOCK(foo) syntax Daniel Latypov
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
Add a way to specify that certain conditions must be met at the end of a
test case.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/test.h | 6 ++++++
lib/kunit/test.c | 11 +++++++++++
2 files changed, 17 insertions(+)
diff --git a/include/kunit/test.h b/include/kunit/test.h
index 687782fa44d9..0eb3abb00da4 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -190,6 +190,11 @@ struct kunit_suite {
char *log;
};
+struct kunit_post_condition {
+ struct list_head node;
+ void (*validate)(struct kunit_post_condition *condition);
+};
+
/**
* struct kunit - represents a running instance of a test.
*
@@ -223,6 +228,7 @@ struct kunit {
* protect it with some type of lock.
*/
struct list_head resources; /* Protected by lock. */
+ struct list_head post_conditions;
};
void kunit_init_test(struct kunit *test, const char *name, char *log);
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index 670d1cc9c105..4e8c74c89073 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -228,6 +228,7 @@ void kunit_init_test(struct kunit *test, const char *name, char *log)
{
spin_lock_init(&test->lock);
INIT_LIST_HEAD(&test->resources);
+ INIT_LIST_HEAD(&test->post_conditions);
test->name = name;
test->log = log;
if (test->log)
@@ -269,6 +270,16 @@ static void kunit_case_internal_cleanup(struct kunit *test)
static void kunit_run_case_cleanup(struct kunit *test,
struct kunit_suite *suite)
{
+ struct kunit_post_condition *condition, *condition_safe;
+
+ list_for_each_entry_safe(condition,
+ condition_safe,
+ &test->post_conditions,
+ node) {
+ condition->validate(condition);
+ list_del(&condition->node);
+ }
+
if (suite->exit)
suite->exit(test);
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 04/12] checkpatch: add support for struct MOCK(foo) syntax
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (2 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 03/12] kunit: test: add concept of post conditions Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 05/12] kunit: mock: add parameter list manipulation macros Daniel Latypov
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
KUnit will soon add macros for generating mocks from types, the
generated mock types are named like `struct MOCK(foo)` (where the base
type is struct foo).
Add `struct MOCK(foo)` as a NonptrType so that it is recognized
correctly in declarations.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
scripts/checkpatch.pl | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 504d2e431c60..b40a68e7bc25 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -796,6 +796,10 @@ sub build_types {
(?:
(?:typeof|__typeof__)\s*\([^\)]*\)|
(?:$typeTypedefs\b)|
+ # Matching a \b breaks struct MOCK(foo) syntax,
+ # so we need to have it not lumped in with the
+ # types in @typeList.
+ (?:struct\s+MOCK\($Ident\))|
(?:${all}\b)
)
(?:\s+$Modifier|\s+const)*
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 05/12] kunit: mock: add parameter list manipulation macros
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (3 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 04/12] checkpatch: add support for struct MOCK(foo) syntax Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 06/12] kunit: expose kunit_set_failure() for use by mocking Daniel Latypov
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
Add macros for parsing and manipulating parameter lists needed for
generating mocks.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/params.h | 305 ++++++++++++++++++++++++++++++++++++
lib/kunit/Makefile | 3 +-
lib/kunit/mock-macro-test.c | 150 ++++++++++++++++++
3 files changed, 457 insertions(+), 1 deletion(-)
create mode 100644 include/kunit/params.h
create mode 100644 lib/kunit/mock-macro-test.c
diff --git a/include/kunit/params.h b/include/kunit/params.h
new file mode 100644
index 000000000000..06efcb58c2c0
--- /dev/null
+++ b/include/kunit/params.h
@@ -0,0 +1,305 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Macros for parsing and manipulating parameter lists needed for generating
+ * mocks.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#ifndef _KUNIT_PARAMS_H
+#define _KUNIT_PARAMS_H
+
+#define NUM_VA_ARGS_IMPL(__dummy, \
+ __1, \
+ __2, \
+ __3, \
+ __4, \
+ __5, \
+ __6, \
+ __7, \
+ __8, \
+ __9, \
+ __10, \
+ __11, \
+ __12, \
+ __13, \
+ __14, \
+ __15, \
+ __16, \
+ __nargs, args...) __nargs
+
+#define NUM_VA_ARGS(args...) NUM_VA_ARGS_IMPL(__dummy, ##args, \
+ 16, \
+ 15, \
+ 14, \
+ 13, \
+ 12, \
+ 11, \
+ 10, \
+ 9, \
+ 8, \
+ 7, \
+ 6, \
+ 5, \
+ 4, \
+ 3, \
+ 2, \
+ 1, \
+ 0)
+
+#define CONCAT_INTERNAL(left, right) left##right
+#define CONCAT(left, right) CONCAT_INTERNAL(left, right)
+
+#define EMPTY()
+
+/*
+ * Takes the name of a function style macro such as FOO() and prevents it from
+ * being evaluated on the current pass.
+ *
+ * This is useful when you need to write a "recursive" macro since a macro name
+ * becomes painted after it is pasted. If a different macro is pasted, this
+ * different macro won't be pasted; if we then defer the evaluation of the this
+ * "indirection macro", we can prevent the original definition from getting
+ * painted.
+ *
+ * Example:
+ * #define EXAMPLE EXPAND(FOO()) // FOO() is evaluated on 1st pass.
+ * #define EXAMPLE EXPAND(DEFER(FOO)()) // FOO() is evaluated on the second
+ * // pass.
+ */
+#define DEFER(macro_id) macro_id EMPTY()
+
+/*
+ * Takes the name of a function style macro such as FOO() and prevents it from
+ * being evaluated on the current or following pass.
+ *
+ * This is useful when you need to DEFER inside an operation which causes an
+ * extra pass, like IF.
+ *
+ * Example:
+ * #define EXAMPLE EXPAND(FOO()) // FOO() is evaluated on 1st pass.
+ * #define EXAMPLE EXPAND(DEFER(FOO)()) // FOO() is evaluated on the second
+ * // pass.
+ * #define EXAMPLE EXPAND(OBSTRUCT(FOO)()) // FOO() is evaluated on the third
+ * // pass.
+ */
+#define OBSTRUCT(macro_id) macro_id DEFER(EMPTY)()
+
+#define EXPAND_1(args...) args
+#define EXPAND_2(args...) EXPAND_1(EXPAND_1(args))
+#define EXPAND_4(args...) EXPAND_2(EXPAND_2(args))
+#define EXPAND_8(args...) EXPAND_4(EXPAND_4(args))
+#define EXPAND_16(args...) EXPAND_8(EXPAND_8(args))
+
+/*
+ * Causes multiple evaluation passes of a macro.
+ *
+ * CPP is implemented as a push down automaton. It consumes a stream of tokens
+ * and as it comes across macros, it either evaluates them and pastes the
+ * result, or if the macro is a function macro, it pushes the macro to a stack,
+ * it evaluates the input to the function macro, pops the state from the stack
+ * and continues.
+ *
+ * This macro serves the function of making the cursor return to the beginging
+ * of a macro that requires mulitple passes to evaluate. It is most useful when
+ * used with DEFER(...) and OBSTRUCT(...).
+ */
+#define EXPAND(args...) EXPAND_16(args)
+
+#define INC(id) INC_##id
+#define INC_0 1
+#define INC_1 2
+#define INC_2 3
+#define INC_3 4
+#define INC_4 5
+#define INC_5 6
+#define INC_6 7
+#define INC_7 8
+#define INC_8 9
+#define INC_9 10
+#define INC_10 11
+#define INC_11 12
+#define INC_12 13
+#define INC_13 14
+#define INC_14 15
+#define INC_15 16
+#define INC_16 17
+
+#define DEC(id) DEC_##id
+#define DEC_1 0
+#define DEC_2 1
+#define DEC_3 2
+#define DEC_4 3
+#define DEC_5 4
+#define DEC_6 5
+#define DEC_7 6
+#define DEC_8 7
+#define DEC_9 8
+#define DEC_10 9
+#define DEC_11 10
+#define DEC_12 11
+#define DEC_13 12
+#define DEC_14 13
+#define DEC_15 14
+#define DEC_16 15
+
+#define DROP_FIRST_ARG_INTERNAL(dropped, x, args...) x
+#define DROP_FIRST_ARG(args...) DROP_FIRST_ARG_INTERNAL(args)
+
+#define EQUAL(left, right) EQUAL_##left##_##right
+#define EQUAL_0_0 dropped, 1
+#define EQUAL_1_1 dropped, 1
+#define EQUAL_2_2 dropped, 1
+#define EQUAL_3_3 dropped, 1
+#define EQUAL_4_4 dropped, 1
+#define EQUAL_5_5 dropped, 1
+#define EQUAL_6_6 dropped, 1
+#define EQUAL_7_7 dropped, 1
+#define EQUAL_8_8 dropped, 1
+#define EQUAL_9_9 dropped, 1
+#define EQUAL_10_10 dropped, 1
+#define EQUAL_11_11 dropped, 1
+#define EQUAL_12_12 dropped, 1
+#define EQUAL_13_13 dropped, 1
+#define EQUAL_14_14 dropped, 1
+#define EQUAL_15_15 dropped, 1
+#define EQUAL_16_16 dropped, 1
+
+#define IS_EQUAL(left, right) DROP_FIRST_ARG(EQUAL(left, right), 0)
+
+#define NOT_INTERNAL(condition) NOT_##condition
+#define NOT(condition) NOT_INTERNAL(condition)
+#define NOT_0 1
+#define NOT_1 0
+
+#define IS_NOT_EQUAL(left, right) NOT(IS_EQUAL(left, right))
+
+#define EMPTY_IMPL(tokens) CONCAT(EMPTY_, tokens)
+#define IS_EMPTY(tokens)
+
+#define OR_INTERNAL(left, right) OR_##left##_##right
+#define OR(left, right) OR_INTERNAL(left, right)
+#define OR_0_0 0
+#define OR_0_1 1
+#define OR_1_0 1
+#define OR_1_1 1
+
+#define IF(condition) CONCAT(IF_, condition)
+#define IF_0(body)
+#define IF_1(body) body
+
+#define COMMA() ,
+
+#define APPLY_TOKENS_INTERNAL(tokens, yield_token, seen_token) \
+ IF(yield_token)(IF(seen_token)(COMMA()) tokens)
+#define APPLY_TOKENS(tokens, yield_token, seen_token) \
+ APPLY_TOKENS_INTERNAL(tokens, yield_token, seen_token)
+
+/*
+ * Provides the indirection to keep the PARAM_LIST_RECURSE_INTERNAL from getting
+ * pasted, only useful if used with DEFER(...) or OBSTRUCT(...).
+ */
+#define PARAM_LIST_RECURSE_INDIRECT() PARAM_LIST_RECURSE_INTERNAL
+
+/*
+ * Given a starting index, a number of args, a MACRO to apply, and a list of
+ * types (with at least one element) this will call MACRO with the first type in
+ * the list and index; it will then call itself again on all remaining types, if
+ * any, while incrementing index, and decrementing nargs.
+ *
+ * Assumes nargs is the number of types in the list.
+ */
+#define PARAM_LIST_RECURSE_INTERNAL(index, \
+ nargs, \
+ MACRO, \
+ FILTER, \
+ context, \
+ seen_token, \
+ type, \
+ args...) \
+ APPLY_TOKENS(MACRO(context, type, index), \
+ FILTER(context, type, index), \
+ seen_token) \
+ IF(IS_NOT_EQUAL(nargs, 1)) \
+ (OBSTRUCT(PARAM_LIST_RECURSE_INDIRECT)() \
+ (INC(index), DEC(nargs), \
+ MACRO, FILTER, context, \
+ OR(seen_token, FILTER(context, type, index)), \
+ args))
+
+#define PARAM_LIST_RECURSE(index, nargs, MACRO, FILTER, context, args...) \
+ IF(IS_NOT_EQUAL(nargs, 0)) \
+ (OBSTRUCT(PARAM_LIST_RECURSE_INTERNAL)(index, \
+ nargs, \
+ MACRO, \
+ FILTER, \
+ context, \
+ 0, \
+ args))
+
+#define FILTER_NONE(context, type, index) 1
+
+#define FILTER_INDEX_INTERNAL(index_to_drop, type, index) \
+ IS_NOT_EQUAL(index, index_to_drop)
+#define FILTER_INDEX(index_to_drop, type, index) \
+ FILTER_INDEX_INTERNAL(index_to_drop, type, index)
+
+/*
+ * Applies a MACRO which takes a type and the index of the type and outputs a
+ * sequence of tokens to a list of types.
+ */
+#define FOR_EACH_PARAM(MACRO, FILTER, context, args...) \
+ EXPAND(PARAM_LIST_RECURSE(0,\
+ NUM_VA_ARGS(args),\
+ MACRO,\
+ FILTER,\
+ context,\
+ args))
+
+#define PRODUCE_TYPE_AND_ARG(context, type, index) type arg##index
+#define PARAM_LIST_FROM_TYPES(args...) \
+ FOR_EACH_PARAM(PRODUCE_TYPE_AND_ARG, \
+ FILTER_NONE, \
+ not_used, \
+ args)
+
+#define PRODUCE_TYPE_NAME(context, type, index) #type
+#define TYPE_NAMES_FROM_TYPES(handle_index, args...) \
+ FOR_EACH_PARAM(PRODUCE_TYPE_NAME, \
+ FILTER_INDEX, \
+ handle_index, \
+ args)
+
+#define PRODUCE_PTR_TO_ARG(context, type, index) &arg##index
+#define PTR_TO_ARG_FROM_TYPES(handle_index, args...) \
+ FOR_EACH_PARAM(PRODUCE_PTR_TO_ARG, \
+ FILTER_INDEX, \
+ handle_index, \
+ args)
+
+#define PRODUCE_MATCHER_AND_ARG(ctrl_index, type, index) \
+ IF(IS_EQUAL(index, ctrl_index))(struct mock *arg##ctrl_index) \
+ IF(IS_NOT_EQUAL(index, ctrl_index))( \
+ struct mock_param_matcher *arg##index)
+#define MATCHER_PARAM_LIST_FROM_TYPES(ctrl_index, args...) \
+ FOR_EACH_PARAM(PRODUCE_MATCHER_AND_ARG, \
+ FILTER_NONE, \
+ ctrl_index, \
+ args)
+
+#define PRODUCE_ARG(context, type, index) arg##index
+#define ARG_NAMES_FROM_TYPES(ctrl_index, args...) \
+ FOR_EACH_PARAM(PRODUCE_ARG, \
+ FILTER_INDEX, \
+ ctrl_index, \
+ args)
+
+#define PRODUCE_ARRAY_ACCESSOR(context, type, index) *((type *) params[index])
+#define ARRAY_ACCESSORS_FROM_TYPES(args...) \
+ FOR_EACH_PARAM(PRODUCE_ARRAY_ACCESSOR, \
+ FILTER_NONE, \
+ not_used, \
+ args)
+
+#endif /* _KUNIT_PARAMS_H */
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index 523a7b0f9783..1707660c8b1c 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -10,7 +10,8 @@ ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o
endif
-obj-$(CONFIG_KUNIT_TEST) += kunit-test.o
+obj-$(CONFIG_KUNIT_TEST) += kunit-test.o \
+ mock-macro-test.o
# string-stream-test compiles built-in only.
ifeq ($(CONFIG_KUNIT_TEST),y)
diff --git a/lib/kunit/mock-macro-test.c b/lib/kunit/mock-macro-test.c
new file mode 100644
index 000000000000..6c3dc2193edb
--- /dev/null
+++ b/lib/kunit/mock-macro-test.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test for parameter list parsing macros.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#include <kunit/test.h>
+#include <kunit/params.h>
+
+#define TO_STR_INTERNAL(...) #__VA_ARGS__
+#define TO_STR(...) TO_STR_INTERNAL(__VA_ARGS__)
+
+static void mock_macro_is_equal(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, "dropped, 1", TO_STR(EQUAL(1, 1)));
+ KUNIT_EXPECT_EQ(test, 1, DROP_FIRST_ARG(dropped, 1, 0));
+ KUNIT_EXPECT_EQ(test, 0, DROP_FIRST_ARG(1, 0));
+ KUNIT_EXPECT_EQ(test, 0, DROP_FIRST_ARG(EQUAL(1, 0), 0));
+ KUNIT_EXPECT_EQ(test, 1, IS_EQUAL(1, 1));
+ KUNIT_EXPECT_EQ(test, 0, IS_EQUAL(1, 0));
+}
+
+static void mock_macro_if(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, "body", ""IF(1)("body"));
+ KUNIT_EXPECT_STREQ(test, "", ""IF(0)("body"));
+ KUNIT_EXPECT_STREQ(test, "body", ""IF(IS_EQUAL(1, 1))("body"));
+ KUNIT_EXPECT_STREQ(test, "", ""IF(IS_EQUAL(0, 1))("body"));
+}
+
+static void mock_macro_apply_tokens(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, "type", TO_STR(APPLY_TOKENS(type, 1, 0)));
+ KUNIT_EXPECT_STREQ(test, ", type", TO_STR(APPLY_TOKENS(type, 1, 1)));
+ KUNIT_EXPECT_STREQ(test, "", TO_STR(APPLY_TOKENS(type, 0, 1)));
+}
+
+#define IDENTITY(context, type, index) type
+
+static void mock_macro_param_list_recurse(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, "", TO_STR(PARAM_LIST_RECURSE(0,
+ 0,
+ IDENTITY,
+ FILTER_NONE,
+ not_used)));
+ KUNIT_EXPECT_STREQ(test,
+ "type",
+ TO_STR(EXPAND(PARAM_LIST_RECURSE(0,
+ 1,
+ IDENTITY,
+ FILTER_NONE,
+ not_used,
+ type))));
+ KUNIT_EXPECT_STREQ(test,
+ "type0 , type1 , type2 , type3 , type4 , type5 , "
+ "type6 , type7 , type8 , type9 , type10 , type11 , "
+ "type12 , type13 , type14 , type15",
+ TO_STR(EXPAND(PARAM_LIST_RECURSE(0, 16,
+ IDENTITY,
+ FILTER_NONE,
+ not_used,
+ type0, type1, type2,
+ type3, type4, type5,
+ type6, type7, type8,
+ type9, type10,
+ type11, type12,
+ type13, type14,
+ type15))));
+}
+
+static void mock_macro_for_each_param(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test,
+ "type0 , type1",
+ TO_STR(FOR_EACH_PARAM(IDENTITY,
+ FILTER_NONE,
+ not_used,
+ type0,
+ type1)));
+ KUNIT_EXPECT_STREQ(test,
+ "type1",
+ TO_STR(FOR_EACH_PARAM(IDENTITY,
+ FILTER_INDEX,
+ 0,
+ type0,
+ type1)));
+}
+
+static void mock_macro_param_list_from_types_basic(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, "", TO_STR(PARAM_LIST_FROM_TYPES()));
+ KUNIT_EXPECT_STREQ(test, "int arg0",
+ TO_STR(PARAM_LIST_FROM_TYPES(int)));
+ KUNIT_EXPECT_STREQ(test, "struct kunit_struct * arg0 , int arg1",
+ TO_STR(PARAM_LIST_FROM_TYPES(struct kunit_struct *,
+ int)));
+ KUNIT_EXPECT_STREQ(test,
+ "type0 arg0 , type1 arg1 , type2 arg2 , type3 arg3 , "
+ "type4 arg4 , type5 arg5 , type6 arg6 , type7 arg7 , "
+ "type8 arg8 , type9 arg9 , type10 arg10 , "
+ "type11 arg11 , type12 arg12 , type13 arg13 , "
+ "type14 arg14 , type15 arg15",
+ TO_STR(PARAM_LIST_FROM_TYPES(type0, type1, type2,
+ type3, type4, type5,
+ type6, type7, type8,
+ type9, type10, type11,
+ type12, type13, type14,
+ type15)));
+}
+
+static void mock_macro_arg_names_from_types(struct kunit *test)
+{
+ KUNIT_EXPECT_STREQ(test, "", TO_STR(ARG_NAMES_FROM_TYPES(0)));
+ KUNIT_EXPECT_STREQ(test, "", TO_STR(ARG_NAMES_FROM_TYPES(0, int)));
+ KUNIT_EXPECT_STREQ(test,
+ "arg1",
+ TO_STR(ARG_NAMES_FROM_TYPES(0,
+ struct kunit_struct *,
+ int)));
+ KUNIT_EXPECT_STREQ(test,
+ "arg0 , arg1 , arg3 , arg4 , arg5 , arg6 , arg7 , "
+ "arg8 , arg9 , arg10 , arg11 , arg12 , arg13 , "
+ "arg14 , arg15",
+ TO_STR(ARG_NAMES_FROM_TYPES(2, type0, type1, type2,
+ type3, type4, type5,
+ type6, type7, type8,
+ type9, type10, type11,
+ type12, type13, type14,
+ type15)));
+}
+
+static struct kunit_case mock_macro_test_cases[] = {
+ KUNIT_CASE(mock_macro_is_equal),
+ KUNIT_CASE(mock_macro_if),
+ KUNIT_CASE(mock_macro_apply_tokens),
+ KUNIT_CASE(mock_macro_param_list_recurse),
+ KUNIT_CASE(mock_macro_for_each_param),
+ KUNIT_CASE(mock_macro_param_list_from_types_basic),
+ KUNIT_CASE(mock_macro_arg_names_from_types),
+ {}
+};
+
+static struct kunit_suite mock_macro_test_suite = {
+ .name = "mock-macro-test",
+ .test_cases = mock_macro_test_cases,
+};
+kunit_test_suite(mock_macro_test_suite);
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 06/12] kunit: expose kunit_set_failure() for use by mocking
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (4 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 05/12] kunit: mock: add parameter list manipulation macros Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 07/12] kunit: mock: add internal mock infrastructure Daniel Latypov
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
Being able to fail the test outside of expectations and assertions is a
requirement for new features, e.g. mocking, dynamic analysis, etc.
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/test.h | 2 ++
lib/kunit/test.c | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h
index 0eb3abb00da4..05330593243d 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -233,6 +233,8 @@ struct kunit {
void kunit_init_test(struct kunit *test, const char *name, char *log);
+void kunit_set_failure(struct kunit *test);
+
int kunit_run_tests(struct kunit_suite *suite);
size_t kunit_suite_num_test_cases(struct kunit_suite *suite);
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index 4e8c74c89073..1ccf6dbecd73 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -15,7 +15,7 @@
#include "debugfs.h"
#include "try-catch-impl.h"
-static void kunit_set_failure(struct kunit *test)
+void kunit_set_failure(struct kunit *test)
{
WRITE_ONCE(test->success, false);
}
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 07/12] kunit: mock: add internal mock infrastructure
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (5 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 06/12] kunit: expose kunit_set_failure() for use by mocking Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 08/12] kunit: mock: add basic matchers and actions Daniel Latypov
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
Add the core internal mechanisms that mocks are implemented with; in
particular, this adds the mechanisms by which expectation on mocks are
validated and by which actions may be supplied and then executed when
mocks are called.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/mock.h | 126 +++++++++++++++
lib/kunit/Makefile | 1 +
lib/kunit/mock.c | 364 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 491 insertions(+)
create mode 100644 include/kunit/mock.h
create mode 100644 lib/kunit/mock.c
diff --git a/include/kunit/mock.h b/include/kunit/mock.h
new file mode 100644
index 000000000000..299b423fdd51
--- /dev/null
+++ b/include/kunit/mock.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Mocking API for KUnit.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#ifndef _KUNIT_MOCK_H
+#define _KUNIT_MOCK_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h> /* For PARAMS(...) */
+#include <kunit/test.h>
+#include <kunit/kunit-stream.h>
+#include <kunit/params.h>
+
+/**
+ * struct mock_param_matcher - represents a matcher used in a *call expectation*
+ * @match: the function that performs the matching
+ *
+ * The matching function takes a couple of parameters:
+ *
+ * - ``this``: refers to the parent struct
+ * - ``stream``: a &kunit_stream to which a detailed message should be added as
+ * to why the parameter matches or not
+ * - ``param``: a pointer to the parameter to check for a match
+ *
+ * The matching function should return whether or not the passed parameter
+ * matches.
+ */
+struct mock_param_matcher {
+ bool (*match)(struct mock_param_matcher *this,
+ struct kunit_stream *stream,
+ const void *param);
+};
+
+#define MOCK_MAX_PARAMS 255
+
+struct mock_matcher {
+ struct mock_param_matcher *matchers[MOCK_MAX_PARAMS];
+ int num;
+};
+
+/**
+ * struct mock_action - Represents an action that a mock performs when
+ * expectation is matched
+ * @do_action: the action to perform
+ *
+ * The action function is given some parameters:
+ *
+ * - ``this``: refers to the parent struct
+ * - ``params``: an array of pointers to the params passed into the mocked
+ * method or function. **The class argument is excluded for a mocked class
+ * method.**
+ * - ``len``: size of ``params``
+ *
+ * The action function returns a pointer to the value that the mocked method
+ * or function should be returning.
+ */
+struct mock_action {
+ void *(*do_action)(struct mock_action *this,
+ const void **params,
+ int len);
+};
+
+/**
+ * struct mock_expectation - represents a *call expectation* on a function.
+ * @action: A &struct mock_action to perform when the function is called.
+ * @max_calls_expected: maximum number of times an expectation may be called.
+ * @min_calls_expected: minimum number of times an expectation may be called.
+ * @retire_on_saturation: no longer match once ``max_calls_expected`` is
+ * reached.
+ *
+ * Represents a *call expectation* on a function created with
+ * KUNIT_EXPECT_CALL().
+ */
+struct mock_expectation {
+ struct mock_action *action;
+ int max_calls_expected;
+ int min_calls_expected;
+ bool retire_on_saturation;
+ /* private: internal use only. */
+ const char *expectation_name;
+ struct list_head node;
+ struct mock_matcher *matcher;
+ int times_called;
+};
+
+struct mock_method {
+ struct list_head node;
+ const char *method_name;
+ const void *method_ptr;
+ struct mock_action *default_action;
+ struct list_head expectations;
+};
+
+struct mock {
+ struct kunit_post_condition parent;
+ struct kunit *test;
+ struct list_head methods;
+ /* TODO(brendanhiggins@google.com): add locking to do_expect. */
+ const void *(*do_expect)(struct mock *mock,
+ const char *method_name,
+ const void *method_ptr,
+ const char * const *param_types,
+ const void **params,
+ int len);
+};
+
+void mock_init_ctrl(struct kunit *test, struct mock *mock);
+
+void mock_validate_expectations(struct mock *mock);
+
+int mock_set_default_action(struct mock *mock,
+ const char *method_name,
+ const void *method_ptr,
+ struct mock_action *action);
+
+struct mock_expectation *mock_add_matcher(struct mock *mock,
+ const char *method_name,
+ const void *method_ptr,
+ struct mock_param_matcher *matchers[],
+ int len);
+
+#endif /* _KUNIT_MOCK_H */
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index 1707660c8b1c..a51157970502 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -1,6 +1,7 @@
obj-$(CONFIG_KUNIT) += kunit.o
kunit-objs += test.o \
+ mock.o \
string-stream.o \
assert.o \
kunit-stream.o \
diff --git a/lib/kunit/mock.c b/lib/kunit/mock.c
new file mode 100644
index 000000000000..12fb88899451
--- /dev/null
+++ b/lib/kunit/mock.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mocking API for KUnit.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#include <kunit/mock.h>
+
+static bool mock_match_params(struct mock_matcher *matcher,
+ struct kunit_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];
+ kunit_stream_add(stream, "\t");
+ tmp = param_matcher->match(param_matcher, stream, params[i]);
+ ret = ret && tmp;
+ kunit_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 void fail_and_flush(struct kunit *test, struct kunit_stream *stream)
+{
+ kunit_set_failure(test);
+ kunit_stream_commit(stream);
+}
+
+void mock_validate_expectations(struct mock *mock)
+{
+ struct mock_expectation *expectation, *expectation_safe;
+ struct kunit_stream *stream;
+ struct mock_method *method;
+ int times_called;
+
+ stream = alloc_kunit_stream(mock->test, GFP_KERNEL);
+ list_for_each_entry(method, &mock->methods, node) {
+ list_for_each_entry_safe(expectation, expectation_safe,
+ &method->expectations, node) {
+ times_called = expectation->times_called;
+ if (!(expectation->min_calls_expected <= times_called &&
+ times_called <= expectation->max_calls_expected)
+ ) {
+ kunit_stream_add(stream,
+ "Expectation was not called the specified number of times:\n\t");
+ kunit_stream_add(stream,
+ "Function: %s, min calls: %d, max calls: %d, actual calls: %d\n",
+ method->method_name,
+ expectation->min_calls_expected,
+ expectation->max_calls_expected,
+ times_called);
+ fail_and_flush(mock->test, stream);
+ }
+ list_del(&expectation->node);
+ }
+ }
+}
+
+static void mock_validate_wrapper(struct kunit_post_condition *condition)
+{
+ struct mock *mock = container_of(condition, struct mock, parent);
+
+ mock_validate_expectations(mock);
+}
+
+void mock_init_ctrl(struct kunit *test, struct mock *mock)
+{
+ mock->test = test;
+ INIT_LIST_HEAD(&mock->methods);
+ mock->do_expect = mock_do_expect;
+ mock->parent.validate = mock_validate_wrapper;
+ list_add_tail(&mock->parent.node, &test->post_conditions);
+}
+
+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 = kunit_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);
+
+ 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 = kunit_kzalloc(mock->test,
+ sizeof(*expectation),
+ GFP_KERNEL);
+ if (!expectation)
+ return NULL;
+
+ matcher = kunit_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;
+
+ 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;
+}
+
+static void mock_format_param(struct kunit_stream *stream,
+ const char *type_name,
+ const void *param)
+{
+ /*
+ * Cannot find formatter, so just print the pointer of the
+ * symbol.
+ */
+ kunit_stream_add(stream, "<%pS>", param);
+}
+
+static void mock_add_method_declaration_to_stream(
+ struct kunit_stream *stream,
+ const char *function_name,
+ const char * const *type_names,
+ const void **params,
+ int len)
+{
+ int i;
+
+ kunit_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)
+ kunit_stream_add(stream, ", ");
+ }
+ kunit_stream_add(stream, ")\n");
+}
+
+static struct kunit_stream *mock_initialize_failure_message(
+ struct kunit *test,
+ const char *function_name,
+ const char * const *type_names,
+ const void **params,
+ int len)
+{
+ struct kunit_stream *stream;
+
+ stream = alloc_kunit_stream(test, GFP_KERNEL);
+ if (!stream)
+ return NULL;
+
+ kunit_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 *test,
+ struct kunit_stream *stream,
+ char *message,
+ struct mock *mock,
+ struct mock_method *method,
+ const char * const *type_names,
+ const void **params,
+ int len)
+{
+ kunit_stream_clear(stream);
+ kunit_stream_add(stream, message);
+ mock_add_method_declaration_to_stream(stream,
+ method->method_name, type_names, params, len);
+}
+
+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_stream *attempted_matching_stream;
+ bool expectations_all_saturated = true;
+ struct kunit *test = mock->test;
+ struct kunit_stream *stream = alloc_kunit_stream(test, GFP_KERNEL);
+ struct mock_expectation *ret;
+
+ 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);
+ kunit_stream_commit(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;
+
+ kunit_stream_add(attempted_matching_stream,
+ "Tried expectation: %s, but\n",
+ ret->expectation_name);
+ if (mock_match_params(ret->matcher,
+ attempted_matching_stream, params, len)) {
+ /*
+ * Matcher was found; we won't print, so clean up the
+ * log.
+ */
+ kunit_stream_clear(attempted_matching_stream);
+ return ret;
+ }
+ }
+
+ if (expectations_all_saturated) {
+ 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);
+ kunit_stream_append(stream, attempted_matching_stream);
+ }
+ fail_and_flush(test, stream);
+ kunit_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);
+}
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 08/12] kunit: mock: add basic matchers and actions
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (6 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 07/12] kunit: mock: add internal mock infrastructure Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 09/12] kunit: mock: add macro machinery to pick correct format args Daniel Latypov
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
Add basic matchers and actions needed for any kind of mocking to be
useful; these matchers and actions are how expectations for mocks are
described: what calls the mocks are expected to receive, and what the
mock should do under those circumstances.
Co-developed-by: Daniel Latypov <dlatypov@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
include/kunit/mock.h | 222 ++++++++++++++++++++++++++++++++
lib/kunit/Makefile | 1 +
lib/kunit/common-mocks.c | 271 +++++++++++++++++++++++++++++++++++++++
3 files changed, 494 insertions(+)
create mode 100644 lib/kunit/common-mocks.c
diff --git a/include/kunit/mock.h b/include/kunit/mock.h
index 299b423fdd51..13fdeb8730b5 100644
--- a/include/kunit/mock.h
+++ b/include/kunit/mock.h
@@ -123,4 +123,226 @@ struct mock_expectation *mock_add_matcher(struct mock *mock,
struct mock_param_matcher *matchers[],
int len);
+#define CONVERT_TO_ACTUAL_TYPE(type, ptr) (*((type *) ptr))
+
+/**
+ * DOC: Built In Matchers
+ *
+ * These are the matchers that can be used when matching arguments in
+ * :c:func:`KUNIT_EXPECT_CALL` (more can be defined manually).
+ *
+ * For example, there's a matcher that matches any arguments:
+ *
+ * .. code-block:: c
+ *
+ * struct mock_param_matcher *any(struct kunit *test);
+ *
+ * There are matchers for integers based on the binary condition:
+ *
+ * * eq: equals to
+ * * ne: not equal to
+ * * lt: less than
+ * * le: less than or equal to
+ * * gt: greater than
+ * * ge: greater than or equal to
+ *
+ * .. code-block:: c
+ *
+ * struct mock_param_matcher *kunit_int_eq(struct kunit *test, int expected);
+ * struct mock_param_matcher *kunit_int_ne(struct kunit *test, int expected);
+ * struct mock_param_matcher *kunit_int_lt(struct kunit *test, int expected);
+ * struct mock_param_matcher *kunit_int_le(struct kunit *test, int expected);
+ * struct mock_param_matcher *kunit_int_gt(struct kunit *test, int expected);
+ * struct mock_param_matcher *kunit_int_ge(struct kunit *test, int expected);
+ *
+ * For a detailed list, please see
+ * ``include/linux/mock.h``.
+ */
+
+/* Matches any argument */
+struct mock_param_matcher *kunit_any(struct kunit *test);
+
+/*
+ * Matches different types of integers, the argument is compared to the
+ * `expected` field, based on the comparison defined.
+ */
+struct mock_param_matcher *kunit_u8_eq(struct kunit *test, u8 expected);
+struct mock_param_matcher *kunit_u8_ne(struct kunit *test, u8 expected);
+struct mock_param_matcher *kunit_u8_le(struct kunit *test, u8 expected);
+struct mock_param_matcher *kunit_u8_lt(struct kunit *test, u8 expected);
+struct mock_param_matcher *kunit_u8_ge(struct kunit *test, u8 expected);
+struct mock_param_matcher *kunit_u8_gt(struct kunit *test, u8 expected);
+
+struct mock_param_matcher *kunit_u16_eq(struct kunit *test, u16 expected);
+struct mock_param_matcher *kunit_u16_ne(struct kunit *test, u16 expected);
+struct mock_param_matcher *kunit_u16_le(struct kunit *test, u16 expected);
+struct mock_param_matcher *kunit_u16_lt(struct kunit *test, u16 expected);
+struct mock_param_matcher *kunit_u16_ge(struct kunit *test, u16 expected);
+struct mock_param_matcher *kunit_u16_gt(struct kunit *test, u16 expected);
+
+struct mock_param_matcher *kunit_u32_eq(struct kunit *test, u32 expected);
+struct mock_param_matcher *kunit_u32_ne(struct kunit *test, u32 expected);
+struct mock_param_matcher *kunit_u32_le(struct kunit *test, u32 expected);
+struct mock_param_matcher *kunit_u32_lt(struct kunit *test, u32 expected);
+struct mock_param_matcher *kunit_u32_ge(struct kunit *test, u32 expected);
+struct mock_param_matcher *kunit_u32_gt(struct kunit *test, u32 expected);
+
+struct mock_param_matcher *kunit_u64_eq(struct kunit *test, u64 expected);
+struct mock_param_matcher *kunit_u64_ne(struct kunit *test, u64 expected);
+struct mock_param_matcher *kunit_u64_le(struct kunit *test, u64 expected);
+struct mock_param_matcher *kunit_u64_lt(struct kunit *test, u64 expected);
+struct mock_param_matcher *kunit_u64_ge(struct kunit *test, u64 expected);
+struct mock_param_matcher *kunit_u64_gt(struct kunit *test, u64 expected);
+
+struct mock_param_matcher *kunit_char_eq(struct kunit *test, char expected);
+struct mock_param_matcher *kunit_char_ne(struct kunit *test, char expected);
+struct mock_param_matcher *kunit_char_le(struct kunit *test, char expected);
+struct mock_param_matcher *kunit_char_lt(struct kunit *test, char expected);
+struct mock_param_matcher *kunit_char_ge(struct kunit *test, char expected);
+struct mock_param_matcher *kunit_char_gt(struct kunit *test, char expected);
+
+struct mock_param_matcher *kunit_uchar_eq(struct kunit *test,
+ unsigned char expected);
+struct mock_param_matcher *kunit_uchar_ne(struct kunit *test,
+ unsigned char expected);
+struct mock_param_matcher *kunit_uchar_le(struct kunit *test,
+ unsigned char expected);
+struct mock_param_matcher *kunit_uchar_lt(struct kunit *test,
+ unsigned char expected);
+struct mock_param_matcher *kunit_uchar_ge(struct kunit *test,
+ unsigned char expected);
+struct mock_param_matcher *kunit_uchar_gt(struct kunit *test,
+ unsigned char expected);
+
+struct mock_param_matcher *kunit_schar_eq(struct kunit *test,
+ signed char expected);
+struct mock_param_matcher *kunit_schar_ne(struct kunit *test,
+ signed char expected);
+struct mock_param_matcher *kunit_schar_le(struct kunit *test,
+ signed char expected);
+struct mock_param_matcher *kunit_schar_lt(struct kunit *test,
+ signed char expected);
+struct mock_param_matcher *kunit_schar_ge(struct kunit *test,
+ signed char expected);
+struct mock_param_matcher *kunit_schar_gt(struct kunit *test,
+ signed char expected);
+
+struct mock_param_matcher *kunit_short_eq(struct kunit *test, short expected);
+struct mock_param_matcher *kunit_short_ne(struct kunit *test, short expected);
+struct mock_param_matcher *kunit_short_le(struct kunit *test, short expected);
+struct mock_param_matcher *kunit_short_lt(struct kunit *test, short expected);
+struct mock_param_matcher *kunit_short_ge(struct kunit *test, short expected);
+struct mock_param_matcher *kunit_short_gt(struct kunit *test, short expected);
+
+struct mock_param_matcher *kunit_ushort_eq(struct kunit *test,
+ unsigned short expected);
+struct mock_param_matcher *kunit_ushort_ne(struct kunit *test,
+ unsigned short expected);
+struct mock_param_matcher *kunit_ushort_le(struct kunit *test,
+ unsigned short expected);
+struct mock_param_matcher *kunit_ushort_lt(struct kunit *test,
+ unsigned short expected);
+struct mock_param_matcher *kunit_ushort_ge(struct kunit *test,
+ unsigned short expected);
+struct mock_param_matcher *kunit_ushort_gt(struct kunit *test,
+ unsigned short expected);
+
+struct mock_param_matcher *kunit_int_eq(struct kunit *test, int expected);
+struct mock_param_matcher *kunit_int_ne(struct kunit *test, int expected);
+struct mock_param_matcher *kunit_int_lt(struct kunit *test, int expected);
+struct mock_param_matcher *kunit_int_le(struct kunit *test, int expected);
+struct mock_param_matcher *kunit_int_gt(struct kunit *test, int expected);
+struct mock_param_matcher *kunit_int_ge(struct kunit *test, int expected);
+
+struct mock_param_matcher *kunit_uint_eq(struct kunit *test,
+ unsigned int expected);
+struct mock_param_matcher *kunit_uint_ne(struct kunit *test,
+ unsigned int expected);
+struct mock_param_matcher *kunit_uint_lt(struct kunit *test,
+ unsigned int expected);
+struct mock_param_matcher *kunit_uint_le(struct kunit *test,
+ unsigned int expected);
+struct mock_param_matcher *kunit_uint_gt(struct kunit *test,
+ unsigned int expected);
+struct mock_param_matcher *kunit_uint_ge(struct kunit *test,
+ unsigned int expected);
+
+struct mock_param_matcher *kunit_long_eq(struct kunit *test, long expected);
+struct mock_param_matcher *kunit_long_ne(struct kunit *test, long expected);
+struct mock_param_matcher *kunit_long_le(struct kunit *test, long expected);
+struct mock_param_matcher *kunit_long_lt(struct kunit *test, long expected);
+struct mock_param_matcher *kunit_long_ge(struct kunit *test, long expected);
+struct mock_param_matcher *kunit_long_gt(struct kunit *test, long expected);
+
+struct mock_param_matcher *kunit_ulong_eq(struct kunit *test,
+ unsigned long expected);
+struct mock_param_matcher *kunit_ulong_ne(struct kunit *test,
+ unsigned long expected);
+struct mock_param_matcher *kunit_ulong_le(struct kunit *test,
+ unsigned long expected);
+struct mock_param_matcher *kunit_ulong_lt(struct kunit *test,
+ unsigned long expected);
+struct mock_param_matcher *kunit_ulong_ge(struct kunit *test,
+ unsigned long expected);
+struct mock_param_matcher *kunit_ulong_gt(struct kunit *test,
+ unsigned long expected);
+
+struct mock_param_matcher *kunit_longlong_eq(struct kunit *test,
+ long long expected);
+struct mock_param_matcher *kunit_longlong_ne(struct kunit *test,
+ long long expected);
+struct mock_param_matcher *kunit_longlong_le(struct kunit *test,
+ long long expected);
+struct mock_param_matcher *kunit_longlong_lt(struct kunit *test,
+ long long expected);
+struct mock_param_matcher *kunit_longlong_ge(struct kunit *test,
+ long long expected);
+struct mock_param_matcher *kunit_longlong_gt(struct kunit *test,
+ long long expected);
+
+struct mock_param_matcher *kunit_ulonglong_eq(struct kunit *test,
+ unsigned long long expected);
+struct mock_param_matcher *kunit_ulonglong_ne(struct kunit *test,
+ unsigned long long expected);
+struct mock_param_matcher *kunit_ulonglong_le(struct kunit *test,
+ unsigned long long expected);
+struct mock_param_matcher *kunit_ulonglong_lt(struct kunit *test,
+ unsigned long long expected);
+struct mock_param_matcher *kunit_ulonglong_ge(struct kunit *test,
+ unsigned long long expected);
+struct mock_param_matcher *kunit_ulonglong_gt(struct kunit *test,
+ unsigned long long expected);
+
+/* Matches pointers. */
+struct mock_param_matcher *kunit_ptr_eq(struct kunit *test, void *expected);
+struct mock_param_matcher *kunit_ptr_ne(struct kunit *test, void *expected);
+struct mock_param_matcher *kunit_ptr_lt(struct kunit *test, void *expected);
+struct mock_param_matcher *kunit_ptr_le(struct kunit *test, void *expected);
+struct mock_param_matcher *kunit_ptr_gt(struct kunit *test, void *expected);
+struct mock_param_matcher *kunit_ptr_ge(struct kunit *test, void *expected);
+
+/* Matches memory sections and strings. */
+struct mock_param_matcher *kunit_memeq(struct kunit *test,
+ const void *buf,
+ size_t size);
+struct mock_param_matcher *kunit_streq(struct kunit *test, const char *str);
+
+struct mock_action *kunit_u8_return(struct kunit *test, u8 ret);
+struct mock_action *kunit_u16_return(struct kunit *test, u16 ret);
+struct mock_action *kunit_u32_return(struct kunit *test, u32 ret);
+struct mock_action *kunit_u64_return(struct kunit *test, u64 ret);
+struct mock_action *kunit_char_return(struct kunit *test, char ret);
+struct mock_action *kunit_uchar_return(struct kunit *test, unsigned char ret);
+struct mock_action *kunit_schar_return(struct kunit *test, signed char ret);
+struct mock_action *kunit_short_return(struct kunit *test, short ret);
+struct mock_action *kunit_ushort_return(struct kunit *test, unsigned short ret);
+struct mock_action *kunit_int_return(struct kunit *test, int ret);
+struct mock_action *kunit_uint_return(struct kunit *test, unsigned int ret);
+struct mock_action *kunit_long_return(struct kunit *test, long ret);
+struct mock_action *kunit_ulong_return(struct kunit *test, unsigned long ret);
+struct mock_action *kunit_longlong_return(struct kunit *test, long long ret);
+struct mock_action *kunit_ulonglong_return(struct kunit *test,
+ unsigned long long ret);
+struct mock_action *kunit_ptr_return(struct kunit *test, void *ret);
+
#endif /* _KUNIT_MOCK_H */
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index a51157970502..a7a3c5e0a8bf 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -2,6 +2,7 @@ obj-$(CONFIG_KUNIT) += kunit.o
kunit-objs += test.o \
mock.o \
+ common-mocks.o \
string-stream.o \
assert.o \
kunit-stream.o \
diff --git a/lib/kunit/common-mocks.c b/lib/kunit/common-mocks.c
new file mode 100644
index 000000000000..4d8a3c9d5f0f
--- /dev/null
+++ b/lib/kunit/common-mocks.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Common KUnit mock call arg matchers and formatters.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#include <linux/kernel.h>
+#include <kunit/mock.h>
+
+static bool match_any(struct mock_param_matcher *pmatcher,
+ struct kunit_stream *stream,
+ const void *actual)
+{
+ kunit_stream_add(stream, "don't care");
+ return true;
+}
+
+static struct mock_param_matcher any_matcher = {
+ .match = match_any,
+};
+
+struct mock_param_matcher *kunit_any(struct kunit *test)
+{
+ return &any_matcher;
+}
+
+#define DEFINE_MATCHER_STRUCT(type_name, type) \
+ struct mock_##type_name##_matcher { \
+ struct mock_param_matcher matcher; \
+ type expected; \
+ }
+
+#define DEFINE_TO_MATCHER_STRUCT(type_name) \
+ struct mock_##type_name##_matcher * \
+ to_mock_##type_name##_matcher( \
+ struct mock_param_matcher *matcher) \
+ { \
+ return container_of(matcher, \
+ struct mock_##type_name##_matcher, \
+ matcher); \
+ }
+
+#define DEFINE_MATCH_FUNC(type_name, type, op_name, op) \
+ static bool match_##type_name##_##op_name( \
+ struct mock_param_matcher *pmatcher, \
+ struct kunit_stream *stream, \
+ const void *pactual) \
+ { \
+ struct mock_##type_name##_matcher *matcher = \
+ to_mock_##type_name##_matcher(pmatcher); \
+ type actual = *((type *) pactual); \
+ bool matches = actual op matcher->expected; \
+ \
+ if (matches) \
+ kunit_stream_add(stream, \
+ "%d "#op" %d", \
+ actual, \
+ matcher->expected); \
+ else \
+ kunit_stream_add(stream, \
+ "%d not "#op" %d", \
+ actual, \
+ matcher->expected); \
+ \
+ return matches; \
+ }
+
+#define DEFINE_MATCH_FACTORY(type_name, type, op_name) \
+ struct mock_param_matcher *kunit_##type_name##_##op_name( \
+ struct kunit *test, type expected) \
+ { \
+ struct mock_##type_name##_matcher *matcher; \
+ \
+ matcher = kunit_kmalloc(test, \
+ sizeof(*matcher), \
+ GFP_KERNEL); \
+ if (!matcher) \
+ return NULL; \
+ \
+ matcher->matcher.match = match_##type_name##_##op_name;\
+ matcher->expected = expected; \
+ return &matcher->matcher; \
+ }
+
+#define DEFINE_MATCHER_WITH_TYPENAME(type_name, type) \
+ DEFINE_MATCHER_STRUCT(type_name, type); \
+ DEFINE_TO_MATCHER_STRUCT(type_name) \
+ DEFINE_MATCH_FUNC(type_name, type, eq, ==) \
+ DEFINE_MATCH_FACTORY(type_name, type, eq) \
+ DEFINE_MATCH_FUNC(type_name, type, ne, !=) \
+ DEFINE_MATCH_FACTORY(type_name, type, ne) \
+ DEFINE_MATCH_FUNC(type_name, type, le, <=) \
+ DEFINE_MATCH_FACTORY(type_name, type, le) \
+ DEFINE_MATCH_FUNC(type_name, type, lt, <) \
+ DEFINE_MATCH_FACTORY(type_name, type, lt) \
+ DEFINE_MATCH_FUNC(type_name, type, ge, >=) \
+ DEFINE_MATCH_FACTORY(type_name, type, ge) \
+ DEFINE_MATCH_FUNC(type_name, type, gt, >) \
+ DEFINE_MATCH_FACTORY(type_name, type, gt)
+
+#define DEFINE_MATCHER(type) DEFINE_MATCHER_WITH_TYPENAME(type, type)
+
+DEFINE_MATCHER(u8);
+DEFINE_MATCHER(u16);
+DEFINE_MATCHER(u32);
+DEFINE_MATCHER(u64);
+DEFINE_MATCHER(char);
+DEFINE_MATCHER_WITH_TYPENAME(uchar, unsigned char);
+DEFINE_MATCHER_WITH_TYPENAME(schar, signed char);
+DEFINE_MATCHER(short);
+DEFINE_MATCHER_WITH_TYPENAME(ushort, unsigned short);
+DEFINE_MATCHER(int);
+DEFINE_MATCHER_WITH_TYPENAME(uint, unsigned int);
+DEFINE_MATCHER(long);
+DEFINE_MATCHER_WITH_TYPENAME(ulong, unsigned long);
+DEFINE_MATCHER_WITH_TYPENAME(longlong, long long);
+DEFINE_MATCHER_WITH_TYPENAME(ulonglong, unsigned long long);
+
+DEFINE_MATCHER_WITH_TYPENAME(ptr, void *);
+
+struct mock_memeq_matcher {
+ struct mock_param_matcher matcher;
+ const void *expected;
+ size_t size;
+};
+
+static bool match_memeq(struct mock_param_matcher *pmatcher,
+ struct kunit_stream *stream,
+ const void *pactual)
+{
+ struct mock_memeq_matcher *matcher =
+ container_of(pmatcher,
+ struct mock_memeq_matcher,
+ matcher);
+ const void *actual = CONVERT_TO_ACTUAL_TYPE(const void *, pactual);
+ bool matches = !memcmp(actual, matcher->expected, matcher->size);
+ int i;
+
+ for (i = 0; i < matcher->size; i++)
+ kunit_stream_add(stream, "%02x, ", ((const char *) actual)[i]);
+ if (matches)
+ kunit_stream_add(stream, "== ");
+ else
+ kunit_stream_add(stream, "!= ");
+ for (i = 0; i < matcher->size; i++)
+ kunit_stream_add(stream,
+ "%02x, ",
+ ((const char *) matcher->expected)[i]);
+
+ return matches;
+}
+
+struct mock_param_matcher *kunit_memeq(struct kunit *test,
+ const void *buf,
+ size_t size)
+{
+ struct mock_memeq_matcher *matcher;
+
+ matcher = kunit_kzalloc(test, sizeof(*matcher), GFP_KERNEL);
+ if (!matcher)
+ return NULL;
+
+ matcher->matcher.match = match_memeq;
+ matcher->expected = buf;
+ matcher->size = size;
+
+ return &matcher->matcher;
+}
+
+struct mock_streq_matcher {
+ struct mock_param_matcher matcher;
+ const char *expected;
+};
+
+static bool match_streq(struct mock_param_matcher *pmatcher,
+ struct kunit_stream *stream,
+ const void *pactual)
+{
+ struct mock_streq_matcher *matcher =
+ container_of(pmatcher,
+ struct mock_streq_matcher,
+ matcher);
+ const char *actual = CONVERT_TO_ACTUAL_TYPE(const char *, pactual);
+ bool matches = !strcmp(actual, matcher->expected);
+
+ if (matches)
+ kunit_stream_add(stream, "%s == %s", actual, matcher->expected);
+ else
+ kunit_stream_add(stream, "%s != %s", actual, matcher->expected);
+
+ return matches;
+}
+
+struct mock_param_matcher *kunit_streq(struct kunit *test, const char *str)
+{
+ struct mock_streq_matcher *matcher;
+
+ matcher = kunit_kzalloc(test, sizeof(*matcher), GFP_KERNEL);
+ if (!matcher)
+ return NULL;
+
+ matcher->matcher.match = match_streq;
+ matcher->expected = str;
+
+ return &matcher->matcher;
+}
+
+#define DEFINE_RETURN_ACTION_STRUCT(type_name, type) \
+ struct mock_##type_name##_action { \
+ struct mock_action action; \
+ type ret; \
+ }
+
+#define DEFINE_RETURN_ACTION_FUNC(type_name, type) \
+ void *do_##type_name##_return(struct mock_action *paction, \
+ const void **params, \
+ int len) \
+ { \
+ struct mock_##type_name##_action *action = \
+ container_of(paction, \
+ struct mock_##type_name##_action,\
+ action); \
+ \
+ return (void *) &action->ret; \
+ }
+
+#define DEFINE_RETURN_ACTION_FACTORY(type_name, type) \
+ struct mock_action *kunit_##type_name##_return( \
+ struct kunit *test, \
+ type ret) \
+ { \
+ struct mock_##type_name##_action *action; \
+ \
+ action = kunit_kmalloc(test, \
+ sizeof(*action), \
+ GFP_KERNEL); \
+ if (!action) \
+ return NULL; \
+ \
+ action->action.do_action = do_##type_name##_return; \
+ action->ret = ret; \
+ \
+ return &action->action; \
+ }
+
+#define DEFINE_RETURN_ACTION_WITH_TYPENAME(type_name, type) \
+ DEFINE_RETURN_ACTION_STRUCT(type_name, type); \
+ DEFINE_RETURN_ACTION_FUNC(type_name, type); \
+ DEFINE_RETURN_ACTION_FACTORY(type_name, type)
+
+#define DEFINE_RETURN_ACTION(type) \
+ DEFINE_RETURN_ACTION_WITH_TYPENAME(type, type)
+
+DEFINE_RETURN_ACTION(u8);
+DEFINE_RETURN_ACTION(u16);
+DEFINE_RETURN_ACTION(u32);
+DEFINE_RETURN_ACTION(u64);
+DEFINE_RETURN_ACTION(char);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(uchar, unsigned char);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(schar, signed char);
+DEFINE_RETURN_ACTION(short);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(ushort, unsigned short);
+DEFINE_RETURN_ACTION(int);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(uint, unsigned int);
+DEFINE_RETURN_ACTION(long);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(ulong, unsigned long);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(longlong, long long);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(ulonglong, unsigned long long);
+DEFINE_RETURN_ACTION_WITH_TYPENAME(ptr, void *);
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 09/12] kunit: mock: add macro machinery to pick correct format args
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (7 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 08/12] kunit: mock: add basic matchers and actions Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 10/12] kunit: mock: add class mocking support Daniel Latypov
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan,
Marcelo Schmitt
From: Marcelo Schmitt <marcelo.schmitt1@gmail.com>
Note: It was unclear if there was existing code that could be reused.
This is used by DEFINE_MATCHER to generate matching funcs for primitive
types that don't trigger compiler warnings.
After preprocessing, we now generate matcher func code like
kunit_stream_add(stream, "%p not > "%p", actual, matcher->expected)
as opposed to the hoping %d will work for all types.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt1@gmail.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
lib/kunit/common-mocks.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/lib/kunit/common-mocks.c b/lib/kunit/common-mocks.c
index 4d8a3c9d5f0f..ce8929157ded 100644
--- a/lib/kunit/common-mocks.c
+++ b/lib/kunit/common-mocks.c
@@ -42,6 +42,27 @@ struct mock_param_matcher *kunit_any(struct kunit *test)
matcher); \
}
+#define TYPE_FRMT(type_name) FORMAT_##type_name
+#define FORMAT_u8 "%hu"
+#define FORMAT_u16 "%hu"
+#define FORMAT_u32 "%u"
+#define FORMAT_u64 "%llu"
+#define FORMAT_char "%c"
+#define FORMAT_uchar "%c"
+#define FORMAT_schar "%c"
+#define FORMAT_short "%hd"
+#define FORMAT_ushort "%hu"
+#define FORMAT_int "%d"
+#define FORMAT_uint "%u"
+#define FORMAT_long "%ld"
+#define FORMAT_ulong "%lu"
+#define FORMAT_longlong "%lld"
+#define FORMAT_ulonglong "%llu"
+#define FORMAT_ptr "%p"
+
+#define CMP_FORMAT(type_name, msg, op) \
+ TYPE_FRMT(type_name) msg " " #op " " TYPE_FRMT(type_name)
+
#define DEFINE_MATCH_FUNC(type_name, type, op_name, op) \
static bool match_##type_name##_##op_name( \
struct mock_param_matcher *pmatcher, \
@@ -55,12 +76,12 @@ struct mock_param_matcher *kunit_any(struct kunit *test)
\
if (matches) \
kunit_stream_add(stream, \
- "%d "#op" %d", \
+ CMP_FORMAT(type_name, "", op),\
actual, \
matcher->expected); \
else \
kunit_stream_add(stream, \
- "%d not "#op" %d", \
+ CMP_FORMAT(type_name, " not", op), \
actual, \
matcher->expected); \
\
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 10/12] kunit: mock: add class mocking support
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (8 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 09/12] kunit: mock: add macro machinery to pick correct format args Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 11/12] kunit: mock: add struct param matcher Daniel Latypov
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
Introduce basic class mocking, the ability to automatically generate a
Linux C-style class implementation whose behavior is controlled by test
cases, which can also set expectations on when and how mocks are called.
Co-developed-by: Daniel Latypov <dlatypov@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
include/kunit/mock.h | 433 +++++++++++++++++++++++++++++++++
lib/kunit/Makefile | 5 +-
lib/kunit/kunit-example-test.c | 98 ++++++++
lib/kunit/mock-macro-test.c | 91 +++++++
lib/kunit/mock-test.c | 320 ++++++++++++++++++++++++
5 files changed, 945 insertions(+), 2 deletions(-)
create mode 100644 lib/kunit/mock-test.c
diff --git a/include/kunit/mock.h b/include/kunit/mock.h
index 13fdeb8730b5..9252a0abd295 100644
--- a/include/kunit/mock.h
+++ b/include/kunit/mock.h
@@ -123,6 +123,439 @@ struct mock_expectation *mock_add_matcher(struct mock *mock,
struct mock_param_matcher *matchers[],
int len);
+#define MOCK(name) name##_mock
+
+/**
+ * KUNIT_EXPECT_CALL() - Declares a *call expectation* on a mock function.
+ * @expectation_call: a mocked method or function with parameters replaced with
+ * matchers.
+ *
+ * Example:
+ *
+ * .. code-block:: c
+ *
+ * // Class to mock.
+ * struct example {
+ * int (*foo)(struct example *, int);
+ * };
+ *
+ * // Define the mock.
+ * DECLARE_STRUCT_CLASS_MOCK_PREREQS(example);
+ *
+ * DEFINE_STRUCT_CLASS_MOCK(METHOD(foo), CLASS(example),
+ * RETURNS(int),
+ * PARAMS(struct example *, int));
+ *
+ * static int example_init(struct MOCK(example) *mock_example)
+ * {
+ * struct example *example = mock_get_trgt(mock_example);
+ *
+ * example->foo = foo;
+ * return 0;
+ * }
+ *
+ * DEFINE_STRUCT_CLASS_MOCK_INIT(example, example_init);
+ *
+ * static void foo_example_test_success(struct kunit *test)
+ * {
+ * struct MOCK(example) *mock_example;
+ * struct example *example = mock_get_trgt(mock_example);
+ * struct mock_expectation *handle;
+ *
+ * mock_example = CONSTRUCT_MOCK(example, test);
+ *
+ * handle = KUNIT_EXPECT_CALL(foo(mock_get_ctrl(mock_example),
+ * kunit_int_eq(test, 5)));
+ * handle->action = int_return(test, 2);
+ *
+ * KUNIT_EXPECT_EQ(test, 2, example_bar(example, 5));
+ * }
+ *
+ * Return:
+ * A &struct mock_expectation representing the call expectation.
+ * allowing additional conditions and actions to be specified.
+ */
+#define KUNIT_EXPECT_CALL(expectation_call) mock_master_##expectation_call
+
+#define mock_get_ctrl_internal(mock_object) (&(mock_object)->ctrl)
+#define mock_get_ctrl(mock_object) mock_get_ctrl_internal(mock_object)
+
+#define mock_get_trgt_internal(mock_object) (&(mock_object)->trgt)
+#define mock_get_trgt(mock_object) mock_get_trgt_internal(mock_object)
+
+#define mock_get_test(mock_object) (mock_get_ctrl(mock_object)->test)
+
+#define CLASS(struct_name) struct_name
+#define HANDLE_INDEX(index) index
+#define METHOD(method_name) method_name
+#define RETURNS(return_type) return_type
+/* #define PARAMS(...) __VA_ARGS__ included by linux/tracepoint.h */
+
+#define MOCK_INIT_ID(struct_name) struct_name##mock_init
+#define REAL_ID(func_name) __real__##func_name
+#define INVOKE_ID(func_name) __invoke__##func_name
+
+#define DECLARE_MOCK_CLIENT(name, return_type, param_types...) \
+ return_type name(PARAM_LIST_FROM_TYPES(param_types))
+
+#define DECLARE_MOCK_MASTER(name, ctrl_index, param_types...) \
+ struct mock_expectation *mock_master_##name( \
+ MATCHER_PARAM_LIST_FROM_TYPES(ctrl_index, \
+ param_types))
+
+#define DECLARE_MOCK_COMMON(name, handle_index, return_type, param_types...) \
+ DECLARE_MOCK_CLIENT(name, return_type, param_types); \
+ DECLARE_MOCK_MASTER(name, handle_index, param_types)
+
+#define DECLARE_STRUCT_CLASS_MOCK_STRUCT(struct_name) \
+ struct MOCK(struct_name) { \
+ struct mock ctrl; \
+ struct struct_name trgt; \
+ }
+
+#define DECLARE_STRUCT_CLASS_MOCK_CONVERTER(struct_name) \
+ static inline struct mock *from_##struct_name##_to_mock( \
+ const struct struct_name *trgt) \
+ { \
+ return mock_get_ctrl( \
+ container_of(trgt, \
+ struct MOCK(struct_name), \
+ trgt)); \
+ }
+
+/**
+ * DECLARE_STRUCT_CLASS_MOCK_PREREQS() - Create a mock child class
+ * @struct_name: name of the class/struct to be mocked
+ *
+ * Creates a mock child class of ``struct_name`` named
+ * ``struct MOCK(struct_name)`` along with supporting internally used methods.
+ *
+ * See KUNIT_EXPECT_CALL() for example usages.
+ */
+#define DECLARE_STRUCT_CLASS_MOCK_PREREQS(struct_name) \
+ DECLARE_STRUCT_CLASS_MOCK_STRUCT(struct_name); \
+ DECLARE_STRUCT_CLASS_MOCK_CONVERTER(struct_name)
+
+#define DECLARE_STRUCT_CLASS_MOCK_HANDLE_INDEX_INTERNAL(name, \
+ struct_name, \
+ handle_index, \
+ return_type, \
+ param_types...) \
+ DECLARE_MOCK_COMMON(name, \
+ handle_index, \
+ return_type, \
+ param_types)
+
+#define DECLARE_STRUCT_CLASS_MOCK_HANDLE_INDEX(name, \
+ struct_name, \
+ handle_index, \
+ return_type, \
+ param_types...) \
+ DECLARE_STRUCT_CLASS_MOCK_HANDLE_INDEX_INTERNAL(name, \
+ struct_name, \
+ handle_index, \
+ return_type, \
+ param_types)
+
+/**
+ * DECLARE_STRUCT_CLASS_MOCK()
+ * @name: method name
+ * @struct_name: name of the class/struct
+ * @return_type: return type of the method
+ * @param_types: parameters of the method
+ *
+ * Same as DEFINE_STRUCT_CLASS_MOCK(), but only makes header compatible
+ * declarations.
+ */
+#define DECLARE_STRUCT_CLASS_MOCK(name, \
+ struct_name, \
+ return_type, \
+ param_types...) \
+ DECLARE_STRUCT_CLASS_MOCK_HANDLE_INDEX(name, \
+ struct_name, \
+ 0, \
+ return_type, \
+ param_types)
+
+/**
+ * DECLARE_STRUCT_CLASS_MOCK_VOID_RETURN()
+ * @name: method name
+ * @struct_name: name of the class/struct
+ * @param_types: parameters of the method
+ *
+ * Same as DEFINE_STRUCT_CLASS_MOCK_VOID_RETURN(), but only makes header
+ * compatible declarations.
+ */
+#define DECLARE_STRUCT_CLASS_MOCK_VOID_RETURN(name, \
+ struct_name, \
+ param_types...) \
+ DECLARE_STRUCT_CLASS_MOCK_HANDLE_INDEX(name, \
+ struct_name, \
+ 0, \
+ void, \
+ param_types)
+
+/**
+ * DECLARE_STRUCT_CLASS_MOCK_INIT()
+ * @struct_name: name of the class/struct
+ *
+ * Same as DEFINE_STRUCT_CLASS_MOCK_INIT(), but only makes header compatible
+ * declarations.
+ */
+#define DECLARE_STRUCT_CLASS_MOCK_INIT(struct_name) \
+ struct MOCK(struct_name) *MOCK_INIT_ID(struct_name)( \
+ struct kunit *test)
+
+/**
+ * CONSTRUCT_MOCK()
+ * @struct_name: name of the class
+ * @test: associated test
+ *
+ * Constructs and allocates a test managed ``struct MOCK(struct_name)`` given
+ * the name of the class for which the mock is defined and a test object.
+ *
+ * See KUNIT_EXPECT_CALL() for example usage.
+ */
+#define CONSTRUCT_MOCK(struct_name, test) MOCK_INIT_ID(struct_name)(test)
+
+#define DEFINE_MOCK_CLIENT_COMMON(name, \
+ handle_index, \
+ MOCK_SOURCE, \
+ mock_source_ctx, \
+ return_type, \
+ RETURN, \
+ param_types...) \
+ return_type name(PARAM_LIST_FROM_TYPES(param_types)) \
+ { \
+ struct mock *mock = MOCK_SOURCE(mock_source_ctx, \
+ handle_index); \
+ static const char * const param_type_names[] = { \
+ TYPE_NAMES_FROM_TYPES(handle_index, \
+ param_types) \
+ }; \
+ const void *params[] = { \
+ PTR_TO_ARG_FROM_TYPES(handle_index, \
+ param_types) \
+ }; \
+ const void *retval; \
+ \
+ retval = mock->do_expect(mock, \
+ #name, \
+ name, \
+ param_type_names, \
+ params, \
+ ARRAY_SIZE(params)); \
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(mock->test, retval); \
+ if (!retval) { \
+ kunit_info(mock->test, \
+ "no action installed for "#name"\n");\
+ BUG(); \
+ } \
+ RETURN(return_type, retval); \
+ }
+
+#define CLASS_MOCK_CLIENT_SOURCE(ctx, handle_index) ctx(arg##handle_index)
+#define DEFINE_MOCK_METHOD_CLIENT_COMMON(name, \
+ handle_index, \
+ mock_converter, \
+ return_type, \
+ RETURN, \
+ param_types...) \
+ DEFINE_MOCK_CLIENT_COMMON(name, \
+ handle_index, \
+ CLASS_MOCK_CLIENT_SOURCE, \
+ mock_converter, \
+ return_type, \
+ RETURN, \
+ param_types)
+
+#define CAST_AND_RETURN(return_type, retval) return *((return_type *) retval)
+#define NO_RETURN(return_type, retval)
+
+#define DEFINE_MOCK_METHOD_CLIENT(name, \
+ handle_index, \
+ mock_converter, \
+ return_type, \
+ param_types...) \
+ DEFINE_MOCK_METHOD_CLIENT_COMMON(name, \
+ handle_index, \
+ mock_converter, \
+ return_type, \
+ CAST_AND_RETURN, \
+ param_types)
+
+#define DEFINE_MOCK_METHOD_CLIENT_VOID_RETURN(name, \
+ handle_index, \
+ mock_converter, \
+ param_types...) \
+ DEFINE_MOCK_METHOD_CLIENT_COMMON(name, \
+ handle_index, \
+ mock_converter, \
+ void, \
+ NO_RETURN, \
+ param_types)
+
+#define DEFINE_MOCK_MASTER_COMMON_INTERNAL(name, \
+ ctrl_index, \
+ MOCK_SOURCE, \
+ param_types...) \
+ struct mock_expectation *mock_master_##name( \
+ MATCHER_PARAM_LIST_FROM_TYPES(ctrl_index, \
+ param_types)) \
+ { \
+ struct mock_param_matcher *matchers[] = { \
+ ARG_NAMES_FROM_TYPES(ctrl_index, param_types) \
+ }; \
+ \
+ return mock_add_matcher(MOCK_SOURCE(ctrl_index), \
+ #name, \
+ (const void *) name, \
+ matchers, \
+ ARRAY_SIZE(matchers)); \
+ }
+#define DEFINE_MOCK_MASTER_COMMON(name, \
+ ctrl_index, \
+ MOCK_SOURCE, \
+ param_types...) \
+ DEFINE_MOCK_MASTER_COMMON_INTERNAL(name, \
+ ctrl_index, \
+ MOCK_SOURCE, \
+ param_types)
+
+#define CLASS_MOCK_MASTER_SOURCE(ctrl_index) arg##ctrl_index
+#define DEFINE_MOCK_METHOD_MASTER(name, ctrl_index, param_types...) \
+ DEFINE_MOCK_MASTER_COMMON(name, \
+ ctrl_index, \
+ CLASS_MOCK_MASTER_SOURCE, \
+ param_types)
+
+#define DEFINE_MOCK_COMMON(name, \
+ handle_index, \
+ mock_converter, \
+ return_type, \
+ param_types...) \
+ DEFINE_MOCK_METHOD_CLIENT(name, \
+ handle_index, \
+ mock_converter, \
+ return_type, \
+ param_types); \
+ DEFINE_MOCK_METHOD_MASTER(name, handle_index, param_types)
+
+#define DEFINE_MOCK_COMMON_VOID_RETURN(name, \
+ handle_index, \
+ mock_converter, \
+ param_types...) \
+ DEFINE_MOCK_METHOD_CLIENT_VOID_RETURN(name, \
+ handle_index, \
+ mock_converter, \
+ param_types); \
+ DEFINE_MOCK_METHOD_MASTER(name, handle_index, param_types)
+
+#define DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX_INTERNAL(name, \
+ struct_name, \
+ handle_index, \
+ return_type, \
+ param_types...) \
+ DEFINE_MOCK_COMMON(name, \
+ handle_index, \
+ from_##struct_name##_to_mock, \
+ return_type, param_types)
+#define DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX(name, \
+ struct_name, \
+ handle_index, \
+ return_type, \
+ param_types...) \
+ DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX_INTERNAL(name, \
+ struct_name, \
+ handle_index, \
+ return_type, \
+ param_types)
+
+#define DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX_VOID_RETURN_INTERNAL( \
+ name, \
+ struct_name, \
+ handle_index, \
+ param_types...) \
+ DEFINE_MOCK_COMMON_VOID_RETURN(name, \
+ handle_index, \
+ from_##struct_name##_to_mock, \
+ param_types)
+#define DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX_VOID_RETURN(name, \
+ struct_name, \
+ handle_index, \
+ param_types...) \
+ DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX_VOID_RETURN_INTERNAL( \
+ name, \
+ struct_name, \
+ handle_index, \
+ param_types)
+
+/**
+ * DEFINE_STRUCT_CLASS_MOCK()
+ * @name: name of the method
+ * @struct_name: name of the class of which the method belongs
+ * @return_type: return type of the method to be created. **Must not be void.**
+ * @param_types: parameters to method to be created.
+ *
+ * See KUNIT_EXPECT_CALL() for example usage.
+ */
+#define DEFINE_STRUCT_CLASS_MOCK(name, \
+ struct_name, \
+ return_type, \
+ param_types...) \
+ DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX(name, \
+ struct_name, \
+ 0, \
+ return_type, \
+ param_types)
+
+/**
+ * DEFINE_STRUCT_CLASS_MOCK_VOID_RETURN()
+ * @name: name of the method
+ * @struct_name: name of the class of which the method belongs
+ * @param_types: parameters to method to be created.
+ *
+ * Same as DEFINE_STRUCT_CLASS_MOCK() except the method has a ``void`` return
+ * type.
+ */
+#define DEFINE_STRUCT_CLASS_MOCK_VOID_RETURN(name, struct_name, param_types...)\
+ DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX_VOID_RETURN(name, \
+ struct_name, \
+ 0, \
+ param_types)
+
+/**
+ * DEFINE_STRUCT_CLASS_MOCK_INIT()
+ * @struct_name: name of the class
+ * @init_func: a function of type ``int (*)(struct kunit *, struct MOCK(struct_name) *)``.
+ * The function is passed a pointer to an allocated, *but not
+ * initialized*, ``struct MOCK(struct_name)``. The job of this user
+ * provided function is to perform remaining initialization. Usually
+ * this entails assigning mock methods to the function pointers in
+ * the parent struct.
+ *
+ * See KUNIT_EXPECT_CALL() for example usage.
+ */
+#define DEFINE_STRUCT_CLASS_MOCK_INIT(struct_name, init_func) \
+ struct MOCK(struct_name) *MOCK_INIT_ID(struct_name)( \
+ struct kunit *test) \
+ { \
+ struct MOCK(struct_name) *mock_obj; \
+ \
+ mock_obj = kunit_kzalloc(test, \
+ sizeof(*mock_obj), \
+ GFP_KERNEL); \
+ if (!mock_obj) \
+ return NULL; \
+ \
+ mock_init_ctrl(test, mock_get_ctrl(mock_obj)); \
+ \
+ if (init_func(test, mock_obj)) \
+ return NULL; \
+ \
+ return mock_obj; \
+ }
+
#define CONVERT_TO_ACTUAL_TYPE(type, ptr) (*((type *) ptr))
/**
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index a7a3c5e0a8bf..649e1c1f0d00 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -13,11 +13,12 @@ kunit-objs += debugfs.o
endif
obj-$(CONFIG_KUNIT_TEST) += kunit-test.o \
- mock-macro-test.o
+ mock-macro-test.o \
+ mock-test.o
# string-stream-test compiles built-in only.
ifeq ($(CONFIG_KUNIT_TEST),y)
obj-$(CONFIG_KUNIT_TEST) += string-stream-test.o
endif
-obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += kunit-example-test.o
+obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += kunit-example-test.o
diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c
index be1164ecc476..d6fff5a961ce 100644
--- a/lib/kunit/kunit-example-test.c
+++ b/lib/kunit/kunit-example-test.c
@@ -7,6 +7,7 @@
*/
#include <kunit/test.h>
+#include <kunit/mock.h>
/*
* This is the most fundamental element of KUnit, the test case. A test case
@@ -29,6 +30,92 @@ static void example_simple_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, 1 + 1, 2);
}
+struct example_ops;
+
+struct example {
+ struct example_ops *ops;
+};
+
+/*
+ * A lot of times, we embed "ops structs", which acts an abstraction over
+ * hardware, a file system implementation, or some other subsystem that you
+ * want to reason about in a generic way.
+ */
+struct example_ops {
+ int (*foo)(struct example *example, int num);
+};
+
+static int example_bar(struct example *example, int num)
+{
+ return example->ops->foo(example, num);
+}
+
+/*
+ * KUnit allows such a class to be "mocked out" with the following:
+ */
+
+/*
+ * This macro creates a mock subclass of the specified class.
+ */
+DECLARE_STRUCT_CLASS_MOCK_PREREQS(example);
+
+/*
+ * This macro creates a mock implementation of the specified method of the
+ * specified class.
+ */
+DEFINE_STRUCT_CLASS_MOCK(METHOD(foo), CLASS(example),
+ RETURNS(int),
+ PARAMS(struct example *, int));
+
+/*
+ * This tells KUnit how to initialize the parts of the mock that come from the
+ * parent. In this example, all we have to do is populate the member functions
+ * of the parent class with the mock versions we defined.
+ */
+static int example_init(struct kunit *test, struct MOCK(example) *mock_example)
+{
+ /* This is how you get a pointer to the parent class of a mock. */
+ struct example *example = mock_get_trgt(mock_example);
+
+ /*
+ * Here we create an ops struct containing our mock method instead.
+ */
+ example->ops = kunit_kzalloc(test, sizeof(*example->ops), GFP_KERNEL);
+ example->ops->foo = foo;
+
+ return 0;
+}
+
+/*
+ * This registers our parent init function above, allowing KUnit to create a
+ * constructor for the mock.
+ */
+DEFINE_STRUCT_CLASS_MOCK_INIT(example, example_init);
+
+/*
+ * This is a test case where we use our mock.
+ */
+static void example_mock_test(struct kunit *test)
+{
+ struct MOCK(example) *mock_example = test->priv;
+ struct example *example = mock_get_trgt(mock_example);
+ struct mock_expectation *handle;
+
+ /*
+ * Here we make an expectation that our mock method will be called with
+ * a parameter equal to 5 passed in.
+ */
+ handle = KUNIT_EXPECT_CALL(foo(mock_get_ctrl(mock_example),
+ kunit_int_eq(test, 5)));
+ /*
+ * We specify that when our mock is called in this way, we want it to
+ * return 2.
+ */
+ handle->action = kunit_int_return(test, 2);
+
+ KUNIT_EXPECT_EQ(test, 2, example_bar(example, 5));
+}
+
/*
* This is run once before each test case, see the comment on
* example_test_suite for more information.
@@ -37,6 +124,16 @@ static int example_test_init(struct kunit *test)
{
kunit_info(test, "initializing\n");
+ /*
+ * Here we construct the mock and store it in test's `priv` field; this
+ * field is for KUnit users. You can put whatever you want here, but
+ * most often it is a place that the init function can put stuff to be
+ * used by test cases.
+ */
+ test->priv = CONSTRUCT_MOCK(example, test);
+ if (!test->priv)
+ return -EINVAL;
+
return 0;
}
@@ -52,6 +149,7 @@ static struct kunit_case example_test_cases[] = {
* test suite.
*/
KUNIT_CASE(example_simple_test),
+ KUNIT_CASE(example_mock_test),
{}
};
diff --git a/lib/kunit/mock-macro-test.c b/lib/kunit/mock-macro-test.c
index 6c3dc2193edb..d196dbee2407 100644
--- a/lib/kunit/mock-macro-test.c
+++ b/lib/kunit/mock-macro-test.c
@@ -8,6 +8,55 @@
#include <kunit/test.h>
#include <kunit/params.h>
+#include <kunit/mock.h>
+
+struct mock_macro_dummy_struct {
+ int (*one_param)(struct mock_macro_dummy_struct *test_struct);
+ int (*two_param)(struct mock_macro_dummy_struct *test_struct, int num);
+ int (*non_first_slot_param)(
+ int num,
+ struct mock_macro_dummy_struct *test_struct);
+ void *(*void_ptr_return)(struct mock_macro_dummy_struct *test_struct);
+};
+
+DECLARE_STRUCT_CLASS_MOCK_PREREQS(mock_macro_dummy_struct);
+
+DEFINE_STRUCT_CLASS_MOCK(METHOD(one_param), CLASS(mock_macro_dummy_struct),
+ RETURNS(int),
+ PARAMS(struct mock_macro_dummy_struct *));
+
+DEFINE_STRUCT_CLASS_MOCK(METHOD(two_param), CLASS(mock_macro_dummy_struct),
+ RETURNS(int),
+ PARAMS(struct mock_macro_dummy_struct *, int));
+
+DEFINE_STRUCT_CLASS_MOCK_HANDLE_INDEX(METHOD(non_first_slot_param),
+ CLASS(mock_macro_dummy_struct), HANDLE_INDEX(1),
+ RETURNS(int),
+ PARAMS(int, struct mock_macro_dummy_struct *));
+
+DEFINE_STRUCT_CLASS_MOCK(METHOD(void_ptr_return),
+ CLASS(mock_macro_dummy_struct),
+ RETURNS(void *),
+ PARAMS(struct mock_macro_dummy_struct *));
+
+static int mock_macro_dummy_struct_init(
+ struct MOCK(mock_macro_dummy_struct) *mock_test_struct)
+{
+ struct mock_macro_dummy_struct *test_struct =
+ mock_get_trgt(mock_test_struct);
+
+ test_struct->one_param = one_param;
+ test_struct->two_param = two_param;
+ test_struct->non_first_slot_param = non_first_slot_param;
+ return 0;
+}
+
+DEFINE_STRUCT_CLASS_MOCK_INIT(mock_macro_dummy_struct,
+ mock_macro_dummy_struct_init);
+
+struct mock_macro_context {
+ struct MOCK(mock_macro_dummy_struct) *mock_test_struct;
+};
#define TO_STR_INTERNAL(...) #__VA_ARGS__
#define TO_STR(...) TO_STR_INTERNAL(__VA_ARGS__)
@@ -132,6 +181,46 @@ static void mock_macro_arg_names_from_types(struct kunit *test)
type15)));
}
+static void mock_macro_test_generated_method_code_works(struct kunit *test)
+{
+ struct mock_macro_context *ctx = test->priv;
+ struct MOCK(mock_macro_dummy_struct) *mock_test_struct =
+ ctx->mock_test_struct;
+ struct mock_macro_dummy_struct *test_struct =
+ mock_get_trgt(mock_test_struct);
+ struct mock_expectation *handle;
+
+ handle = KUNIT_EXPECT_CALL(one_param(mock_get_ctrl(mock_test_struct)));
+ handle->action = kunit_int_return(test, 0);
+ handle = KUNIT_EXPECT_CALL(two_param(mock_get_ctrl(mock_test_struct),
+ kunit_int_eq(test, 5)));
+ handle->action = kunit_int_return(test, 1);
+ handle = KUNIT_EXPECT_CALL(non_first_slot_param(
+ kunit_int_eq(test, 5),
+ mock_get_ctrl(mock_test_struct)));
+ handle->action = kunit_int_return(test, 1);
+
+ test_struct->one_param(test_struct);
+ test_struct->two_param(test_struct, 5);
+ test_struct->non_first_slot_param(5, test_struct);
+}
+
+static int mock_macro_test_init(struct kunit *test)
+{
+ struct mock_macro_context *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ test->priv = ctx;
+
+ ctx->mock_test_struct = CONSTRUCT_MOCK(mock_macro_dummy_struct, test);
+ if (!ctx->mock_test_struct)
+ return -EINVAL;
+
+ return 0;
+}
+
static struct kunit_case mock_macro_test_cases[] = {
KUNIT_CASE(mock_macro_is_equal),
KUNIT_CASE(mock_macro_if),
@@ -140,11 +229,13 @@ static struct kunit_case mock_macro_test_cases[] = {
KUNIT_CASE(mock_macro_for_each_param),
KUNIT_CASE(mock_macro_param_list_from_types_basic),
KUNIT_CASE(mock_macro_arg_names_from_types),
+ KUNIT_CASE(mock_macro_test_generated_method_code_works),
{}
};
static struct kunit_suite mock_macro_test_suite = {
.name = "mock-macro-test",
+ .init = mock_macro_test_init,
.test_cases = mock_macro_test_cases,
};
kunit_test_suite(mock_macro_test_suite);
diff --git a/lib/kunit/mock-test.c b/lib/kunit/mock-test.c
new file mode 100644
index 000000000000..8a0fa33d087c
--- /dev/null
+++ b/lib/kunit/mock-test.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test for mock.h.
+ *
+ * Copyright (C) 2020, Google LLC.
+ * Author: Brendan Higgins <brendanhiggins@google.com>
+ */
+
+#include <kunit/test.h>
+#include <kunit/mock.h>
+
+// A simple class for unit-testing/example purposes.
+struct adder {
+ int (*add)(struct adder *adder, int x, int y);
+};
+
+static int real_add(struct adder *adder, int x, int y)
+{
+ return x + y;
+}
+
+static void adder_real_init(struct adder *adder)
+{
+ adder->add = real_add;
+}
+
+DECLARE_STRUCT_CLASS_MOCK_PREREQS(adder);
+DEFINE_STRUCT_CLASS_MOCK(METHOD(mock_add), CLASS(adder), RETURNS(int),
+ PARAMS(struct adder*, int, int));
+DECLARE_STRUCT_CLASS_MOCK_INIT(adder);
+
+// This would normally live in the .c file.
+static int adder_mock_init(struct MOCK(adder) *mock_adder)
+{
+ struct adder *real = mock_get_trgt(mock_adder);
+
+ adder_real_init(real);
+
+ real->add = mock_add;
+ mock_set_default_action(mock_get_ctrl(mock_adder),
+ "mock_add",
+ mock_add,
+ kunit_int_return(mock_get_test(mock_adder), 0));
+ return 0;
+}
+DEFINE_STRUCT_CLASS_MOCK_INIT(adder, adder_mock_init);
+
+
+/*
+ * Note: we create a new `failing_test` so we can validate that failed mock
+ * expectations mark tests as failed.
+ * Marking the real `test` as failed is obviously problematic.
+ *
+ * See mock_test_failed_expect_call_fails_test for an example.
+ */
+struct mock_test_context {
+ struct kunit *failing_test;
+ struct mock *mock;
+};
+
+static void mock_test_do_expect_basic(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct mock *mock = ctx->mock;
+ int param0 = 5, param1 = -4;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+ struct mock_param_matcher *matchers_any_two[] = {
+ kunit_any(test), kunit_any(test)
+ };
+ struct mock_expectation *expectation;
+ const void *ret;
+
+ expectation = mock_add_matcher(mock,
+ "",
+ NULL,
+ matchers_any_two,
+ ARRAY_SIZE(matchers_any_two));
+ expectation->action = kunit_int_return(test, 5);
+ KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
+
+ ret = mock->do_expect(mock,
+ "",
+ NULL,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ret);
+ KUNIT_EXPECT_EQ(test, 5, *((int *) ret));
+ KUNIT_EXPECT_EQ(test, 1, expectation->times_called);
+}
+
+static void mock_test_ptr_eq(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+ void *param0 = ctx, *param1 = failing_test;
+ static const char * const two_param_types[] = {"void *", "void *"};
+ const void *two_params[] = {¶m0, ¶m1};
+ struct mock_param_matcher *matchers_two_ptrs[] = {
+ kunit_ptr_eq(test, param0), kunit_ptr_eq(test, param1)
+ };
+ struct mock_expectation *expectation;
+ const void *ret;
+
+ expectation = mock_add_matcher(mock,
+ "",
+ NULL,
+ matchers_two_ptrs,
+ ARRAY_SIZE(matchers_two_ptrs));
+ expectation->action = kunit_int_return(test, 0);
+ KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
+
+ ret = mock->do_expect(mock,
+ "",
+ NULL,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ret);
+ KUNIT_EXPECT_EQ(test, 1, expectation->times_called);
+}
+
+static void mock_test_ptr_eq_not_equal(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+
+ /* Pick some two pointers, but pass in different values. */
+ void *param0 = test, *param1 = failing_test;
+ static const char * const two_param_types[] = {"void *", "void *"};
+ const void *two_params[] = {¶m0, ¶m1};
+ struct mock_param_matcher *matchers_two_ptrs[] = {
+ kunit_ptr_eq(failing_test, param0),
+ kunit_ptr_eq(failing_test, param1 - 1)
+ };
+ struct mock_expectation *expectation;
+ const void *ret;
+
+ expectation = mock_add_matcher(mock,
+ "",
+ NULL,
+ matchers_two_ptrs,
+ ARRAY_SIZE(matchers_two_ptrs));
+ expectation->action = kunit_int_return(failing_test, 0);
+ KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
+
+ ret = mock->do_expect(mock,
+ "",
+ NULL,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ KUNIT_EXPECT_FALSE(test, ret);
+ KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
+
+ KUNIT_EXPECT_FALSE(test, failing_test->success);
+}
+
+/*
+ * In order for us to be able to rely on KUNIT_EXPECT_CALL to validate other
+ * behavior, we need to test that unsatisfied KUNIT_EXPECT_CALL causes a test
+ * failure.
+ *
+ * In order to understand what this test is testing we must first understand how
+ * KUNIT_EXPECT_CALL() works conceptually. In theory, a test specifies that it
+ * expects some function to be called some number of times (can be zero), with
+ * some particular arguments. Hence, KUNIT_EXPECT_CALL() must do two things:
+ *
+ * 1) Determine whether a function call matches the expectation.
+ *
+ * 2) Fail if there are too many or too few matches.
+ */
+static void mock_test_failed_expect_call_fails_test(struct kunit *test)
+{
+ /*
+ * We do not want to fail the real `test` object used to run this test.
+ * So we use a separate `failing_test` for KUNIT_EXPECT_CALL().
+ */
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+
+ /*
+ * Put an expectation on mock, which we won't satisify.
+ *
+ * NOTE: it does not actually matter what function we expect here.
+ * `mock` does not represent an actual mock on anything; we just need to
+ * create some expectation, that we won't satisfy.
+ */
+ KUNIT_EXPECT_CALL(mock_add(mock,
+ kunit_any(failing_test),
+ kunit_any(failing_test)));
+
+ /*
+ * Validate the unsatisfied expectation that we just created. This
+ * should cause `failing_test` to fail.
+ */
+ mock_validate_expectations(mock);
+
+ /* Verify that `failing_test` has actually failed. */
+ KUNIT_EXPECT_FALSE(test, failing_test->success);
+}
+
+static void mock_test_do_expect_default_return(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct mock *mock = ctx->mock;
+ int param0 = 5, param1 = -5;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+ struct mock_param_matcher *matchers[] = {
+ kunit_int_eq(test, 5),
+ kunit_int_eq(test, -4)
+ };
+ struct mock_expectation *expectation;
+ const void *ret;
+
+ expectation = mock_add_matcher(mock,
+ "add",
+ mock_add,
+ matchers,
+ ARRAY_SIZE(matchers));
+ expectation->action = kunit_int_return(test, 5);
+ KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
+
+ KUNIT_EXPECT_FALSE(test,
+ mock_set_default_action(mock,
+ "add",
+ mock_add,
+ kunit_int_return(test, -4)));
+
+ ret = mock->do_expect(mock,
+ "add",
+ mock_add,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ret);
+ KUNIT_EXPECT_EQ(test, -4, *((int *) ret));
+ KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
+}
+
+static void mock_test_mock_validate_expectations(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+
+ struct mock_param_matcher *matchers[] = {
+ kunit_int_eq(failing_test, 5),
+ kunit_int_eq(failing_test, -4)
+ };
+ struct mock_expectation *expectation;
+
+
+ expectation = mock_add_matcher(mock,
+ "add",
+ mock_add,
+ matchers,
+ ARRAY_SIZE(matchers));
+ expectation->times_called = 0;
+ expectation->min_calls_expected = 1;
+ expectation->max_calls_expected = 1;
+
+ mock_validate_expectations(mock);
+
+ KUNIT_EXPECT_FALSE(test, failing_test->success);
+}
+
+static int mock_test_init(struct kunit *test)
+{
+ struct mock_test_context *ctx;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ test->priv = ctx;
+
+ ctx->failing_test = kunit_kzalloc(test, sizeof(*ctx->failing_test),
+ GFP_KERNEL);
+ if (!ctx->failing_test)
+ return -EINVAL;
+ kunit_init_test(ctx->failing_test, NULL, NULL);
+
+ ctx->mock = kunit_kzalloc(test, sizeof(*ctx->mock), GFP_KERNEL);
+ if (!ctx->mock)
+ return -ENOMEM;
+ mock_init_ctrl(ctx->failing_test, ctx->mock);
+
+ return 0;
+}
+
+static void mock_test_exit(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+
+ kunit_cleanup(ctx->failing_test);
+}
+
+static struct kunit_case mock_test_cases[] = {
+ KUNIT_CASE(mock_test_do_expect_basic),
+ KUNIT_CASE(mock_test_ptr_eq),
+ KUNIT_CASE(mock_test_ptr_eq_not_equal),
+ KUNIT_CASE(mock_test_failed_expect_call_fails_test),
+ KUNIT_CASE(mock_test_do_expect_default_return),
+ KUNIT_CASE(mock_test_mock_validate_expectations),
+ {}
+};
+
+static struct kunit_suite mock_test_suite = {
+ .name = "mock-test",
+ .init = mock_test_init,
+ .exit = mock_test_exit,
+ .test_cases = mock_test_cases,
+};
+
+kunit_test_suite(mock_test_suite);
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 11/12] kunit: mock: add struct param matcher
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (9 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 10/12] kunit: mock: add class mocking support Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-10-12 22:20 ` [RFC v2 12/12] kunit: mock: implement nice, strict and naggy mock distinctions Daniel Latypov
2020-11-02 18:00 ` [RFC v2 00/12] kunit: introduce class mocking support Brendan Higgins
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan
From: Brendan Higgins <brendanhiggins@google.com>
Add parameter matcher builder for matching struct values.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/mock.h | 58 +++++++++++++++++++
lib/kunit/common-mocks.c | 117 +++++++++++++++++++++++++++++++++++++++
lib/kunit/mock-test.c | 40 +++++++++++++
3 files changed, 215 insertions(+)
diff --git a/include/kunit/mock.h b/include/kunit/mock.h
index 9252a0abd295..df99ae5ac721 100644
--- a/include/kunit/mock.h
+++ b/include/kunit/mock.h
@@ -758,8 +758,18 @@ struct mock_param_matcher *kunit_ptr_ge(struct kunit *test, void *expected);
struct mock_param_matcher *kunit_memeq(struct kunit *test,
const void *buf,
size_t size);
+
struct mock_param_matcher *kunit_streq(struct kunit *test, const char *str);
+struct mock_param_matcher *kunit_str_contains(struct kunit *test,
+ const char *needle);
+
+/* Matches var-arg arguments. */
+struct mock_param_matcher *kunit_va_format_cmp(
+ struct kunit *test,
+ struct mock_param_matcher *fmt_matcher,
+ struct mock_param_matcher *va_matcher);
+
struct mock_action *kunit_u8_return(struct kunit *test, u8 ret);
struct mock_action *kunit_u16_return(struct kunit *test, u16 ret);
struct mock_action *kunit_u32_return(struct kunit *test, u32 ret);
@@ -778,4 +788,52 @@ struct mock_action *kunit_ulonglong_return(struct kunit *test,
unsigned long long ret);
struct mock_action *kunit_ptr_return(struct kunit *test, void *ret);
+/**
+ * struct mock_struct_matcher_entry - composed with other &struct
+ * mock_struct_matcher_entry to make a
+ * &struct struct_matcher
+ * @member_offset: offset of this member
+ * @matcher: matcher for this particular member
+ *
+ * This is used for struct_cmp() matchers.
+ */
+struct mock_struct_matcher_entry {
+ size_t member_offset;
+ struct mock_param_matcher *matcher;
+};
+
+static inline void init_mock_struct_matcher_entry_internal(
+ struct mock_struct_matcher_entry *entry,
+ size_t offset,
+ struct mock_param_matcher *matcher)
+{
+ entry->member_offset = offset;
+ entry->matcher = matcher;
+}
+
+/**
+ * INIT_MOCK_STRUCT_MATCHER_ENTRY()
+ * @entry: the &struct mock_struct_matcher_entry to initialize
+ * @type: the struct being matched
+ * @member: the member of the struct being matched, used to calculate the offset
+ * @matcher: matcher to match that member
+ *
+ * Initializes ``entry`` to match ``type->member`` with ``matcher``.
+ */
+#define INIT_MOCK_STRUCT_MATCHER_ENTRY(entry, type, member, matcher) \
+ init_mock_struct_matcher_entry_internal(entry, \
+ offsetof(type, member),\
+ matcher)
+
+static inline void INIT_MOCK_STRUCT_MATCHER_ENTRY_LAST(
+ struct mock_struct_matcher_entry *entry)
+{
+ entry->matcher = NULL;
+}
+
+struct mock_param_matcher *kunit_struct_cmp(
+ struct kunit *test,
+ const char *struct_name,
+ struct mock_struct_matcher_entry *entries);
+
#endif /* _KUNIT_MOCK_H */
diff --git a/lib/kunit/common-mocks.c b/lib/kunit/common-mocks.c
index ce8929157ded..6a4cc9c60427 100644
--- a/lib/kunit/common-mocks.c
+++ b/lib/kunit/common-mocks.c
@@ -228,6 +228,123 @@ struct mock_param_matcher *kunit_streq(struct kunit *test, const char *str)
return &matcher->matcher;
}
+struct mock_str_contains_matcher {
+ struct mock_param_matcher matcher;
+ const char *needle;
+};
+
+static bool match_str_contains(struct mock_param_matcher *pmatcher,
+ struct kunit_stream *stream,
+ const void *phaystack)
+{
+ struct mock_str_contains_matcher *matcher =
+ container_of(pmatcher,
+ struct mock_str_contains_matcher,
+ matcher);
+ const char *haystack = CONVERT_TO_ACTUAL_TYPE(const char *, phaystack);
+ bool matches = strstr(haystack, matcher->needle);
+
+ if (matches)
+ kunit_stream_add(stream,
+ "'%s' found in '%s'",
+ matcher->needle,
+ haystack);
+ else
+ kunit_stream_add(stream,
+ "'%s' not found in '%s'",
+ matcher->needle,
+ haystack);
+ return matches;
+}
+
+struct mock_param_matcher *kunit_str_contains(struct kunit *test,
+ const char *str)
+{
+ struct mock_str_contains_matcher *matcher;
+
+ matcher = kunit_kzalloc(test, sizeof(*matcher), GFP_KERNEL);
+ if (!matcher)
+ return NULL;
+
+ matcher->matcher.match = match_str_contains;
+ matcher->needle = str;
+
+ return &matcher->matcher;
+}
+
+struct mock_param_matcher *kunit_va_format_cmp(
+ struct kunit *test,
+ struct mock_param_matcher *fmt_matcher,
+ struct mock_param_matcher *va_matcher)
+{
+ struct mock_struct_matcher_entry *entries;
+
+ entries = kunit_kzalloc(test, sizeof(*entries) * 3, GFP_KERNEL);
+ if (!entries)
+ return NULL;
+
+ INIT_MOCK_STRUCT_MATCHER_ENTRY(&entries[0],
+ struct va_format,
+ fmt,
+ fmt_matcher);
+ INIT_MOCK_STRUCT_MATCHER_ENTRY(&entries[1],
+ struct va_format,
+ va,
+ va_matcher);
+ INIT_MOCK_STRUCT_MATCHER_ENTRY_LAST(&entries[2]);
+
+ return kunit_struct_cmp(test, "va_format", entries);
+}
+
+struct mock_struct_matcher {
+ struct mock_param_matcher matcher;
+ const char *struct_name;
+ struct mock_struct_matcher_entry *entries;
+};
+
+static bool match_struct(struct mock_param_matcher *pmatcher,
+ struct kunit_stream *stream,
+ const void *pactual)
+{
+ struct mock_struct_matcher *matcher =
+ container_of(pmatcher,
+ struct mock_struct_matcher,
+ matcher);
+ struct mock_struct_matcher_entry *entry;
+ const char *actual = CONVERT_TO_ACTUAL_TYPE(const char *, pactual);
+ const char *member_ptr;
+ bool matches = true, tmp;
+
+ kunit_stream_add(stream, "struct %s {", matcher->struct_name);
+ for (entry = matcher->entries; entry->matcher; entry++) {
+ member_ptr = actual + entry->member_offset;
+ tmp = entry->matcher->match(entry->matcher, stream, member_ptr);
+ matches = matches && tmp;
+ kunit_stream_add(stream, ", ");
+ }
+ kunit_stream_add(stream, "}");
+
+ return matches;
+}
+
+struct mock_param_matcher *kunit_struct_cmp(
+ struct kunit *test,
+ const char *struct_name,
+ struct mock_struct_matcher_entry *entries)
+{
+ struct mock_struct_matcher *matcher;
+
+ matcher = kunit_kzalloc(test, sizeof(*matcher), GFP_KERNEL);
+ if (!matcher)
+ return NULL;
+
+ matcher->matcher.match = match_struct;
+ matcher->struct_name = struct_name;
+ matcher->entries = entries;
+
+ return &matcher->matcher;
+}
+
#define DEFINE_RETURN_ACTION_STRUCT(type_name, type) \
struct mock_##type_name##_action { \
struct mock_action action; \
diff --git a/lib/kunit/mock-test.c b/lib/kunit/mock-test.c
index 8a0fa33d087c..df0969b43ade 100644
--- a/lib/kunit/mock-test.c
+++ b/lib/kunit/mock-test.c
@@ -243,6 +243,45 @@ static void mock_test_do_expect_default_return(struct kunit *test)
KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
}
+/*
+ * Method called on naggy mock with no expectations will not fail, but will show
+ * a warning message
+ */
+static void mock_test_naggy_no_expectations_no_fail(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+
+ int param0 = 5, param1 = -5;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+
+ mock_set_default_action(mock,
+ "add",
+ real_add,
+ kunit_int_return(failing_test, -4));
+
+
+ KUNIT_EXPECT_CALL(mock_add(
+ mock,
+ kunit_any(failing_test),
+ kunit_va_format_cmp(failing_test,
+ kunit_str_contains(failing_test,
+ "Method was called with no expectations declared"),
+ kunit_any(failing_test))));
+
+ mock->do_expect(mock,
+ "add",
+ real_add,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ mock_validate_expectations(mock);
+
+ KUNIT_EXPECT_FALSE(test, failing_test->success);
+}
+
static void mock_test_mock_validate_expectations(struct kunit *test)
{
struct mock_test_context *ctx = test->priv;
@@ -307,6 +346,7 @@ static struct kunit_case mock_test_cases[] = {
KUNIT_CASE(mock_test_failed_expect_call_fails_test),
KUNIT_CASE(mock_test_do_expect_default_return),
KUNIT_CASE(mock_test_mock_validate_expectations),
+ KUNIT_CASE(mock_test_naggy_no_expectations_no_fail),
{}
};
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC v2 12/12] kunit: mock: implement nice, strict and naggy mock distinctions
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (10 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 11/12] kunit: mock: add struct param matcher Daniel Latypov
@ 2020-10-12 22:20 ` Daniel Latypov
2020-11-02 18:00 ` [RFC v2 00/12] kunit: introduce class mocking support Brendan Higgins
12 siblings, 0 replies; 14+ messages in thread
From: Daniel Latypov @ 2020-10-12 22:20 UTC (permalink / raw)
To: dlatypov
Cc: alan.maguire, brendanhiggins, davidgow, keescook, kunit-dev,
linux-kernel, linux-kselftest, mcgrof, sboyd, skhan, Felix Guo
From: Brendan Higgins <brendanhiggins@google.com>
Nice mocks only fail when there is an expectation on a method, but none
match a given call. Strict mocks only pass when there is a matching
expectation for every call. Naggy mocks have the same pass/fail behavior
as nice, but report a warning in any case a strict mock would fail.
Signed-off-by: Felix Guo <felixguoxiuping@gmail.com>
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Daniel Latypov <dlatypov@google.com>
---
include/kunit/mock.h | 63 ++++++++++++++++
lib/kunit/mock-test.c | 171 ++++++++++++++++++++++++++++++++++++++++++
lib/kunit/mock.c | 10 ++-
3 files changed, 242 insertions(+), 2 deletions(-)
diff --git a/include/kunit/mock.h b/include/kunit/mock.h
index df99ae5ac721..12e70a3f82c5 100644
--- a/include/kunit/mock.h
+++ b/include/kunit/mock.h
@@ -95,10 +95,17 @@ struct mock_method {
struct list_head expectations;
};
+enum mock_type {
+ MOCK_TYPE_NICE,
+ MOCK_TYPE_NAGGY,
+ MOCK_TYPE_STRICT
+};
+
struct mock {
struct kunit_post_condition parent;
struct kunit *test;
struct list_head methods;
+ enum mock_type type;
/* TODO(brendanhiggins@google.com): add locking to do_expect. */
const void *(*do_expect)(struct mock *mock,
const char *method_name,
@@ -108,6 +115,8 @@ struct mock {
int len);
};
+#define DEFAULT_MOCK_TYPE MOCK_TYPE_NAGGY
+
void mock_init_ctrl(struct kunit *test, struct mock *mock);
void mock_validate_expectations(struct mock *mock);
@@ -125,6 +134,60 @@ struct mock_expectation *mock_add_matcher(struct mock *mock,
#define MOCK(name) name##_mock
+/**
+ * STRICT_MOCK() - sets the mock to be strict and returns the mock
+ * @mock: the mock
+ *
+ * For an example, see ``The Nice, the Strict, and the Naggy`` under
+ * ``Using KUnit``.
+ */
+#define STRICT_MOCK(mock) \
+({ \
+ mock_get_ctrl(mock)->type = MOCK_TYPE_STRICT; \
+ mock; \
+})
+
+static inline bool is_strict_mock(struct mock *mock)
+{
+ return mock->type == MOCK_TYPE_STRICT;
+}
+
+/**
+ * NICE_MOCK() - sets the mock to be nice and returns the mock
+ * @mock: the mock
+ *
+ * For an example, see ``The Nice, the Strict, and the Naggy`` under
+ * ``Using KUnit``.
+ */
+#define NICE_MOCK(mock) \
+({ \
+ mock_get_ctrl(mock)->type = MOCK_TYPE_NICE; \
+ mock; \
+})
+
+static inline bool is_nice_mock(struct mock *mock)
+{
+ return mock->type == MOCK_TYPE_NICE;
+}
+
+/**
+ * NAGGY_MOCK() - sets the mock to be naggy and returns the mock
+ * @mock: the mock
+ *
+ * For an example, see ``The Nice, the Strict, and the Naggy`` under
+ * ``Using KUnit``.
+ */
+#define NAGGY_MOCK(mock) \
+({ \
+ mock_get_ctrl(mock)->type = MOCK_TYPE_NAGGY; \
+ mock; \
+})
+
+static inline bool is_naggy_mock(struct mock *mock)
+{
+ return mock->type == MOCK_TYPE_NAGGY;
+}
+
/**
* KUNIT_EXPECT_CALL() - Declares a *call expectation* on a mock function.
* @expectation_call: a mocked method or function with parameters replaced with
diff --git a/lib/kunit/mock-test.c b/lib/kunit/mock-test.c
index df0969b43ade..1c2aa2aa9c1b 100644
--- a/lib/kunit/mock-test.c
+++ b/lib/kunit/mock-test.c
@@ -243,6 +243,50 @@ static void mock_test_do_expect_default_return(struct kunit *test)
KUNIT_EXPECT_EQ(test, 0, expectation->times_called);
}
+/**
+ * DOC: Testing the failure condition of different mock types.
+ *
+ * The following tests will test the behaviour of expectations under different
+ * conditions. For example, what happens when an expectation:
+ * - is not satisfied at the end of the test
+ * - is fulfilled but the expected function is called again
+ * - a function is called without expectations set on it
+ *
+ * For each of these conditions, there may be variations between the different
+ * types of mocks: nice mocks, naggy mocks (the default) and strict mocks.
+ *
+ * More information about these mocks can be found in the kernel documentation
+ * under Documentation/test/api/class-and-function-mocking
+ */
+
+/* Method called on strict mock with no expectations will fail */
+static void mock_test_strict_no_expectations_will_fail(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+ int param0 = 5, param1 = -5;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+
+ mock->type = MOCK_TYPE_STRICT;
+
+ mock_set_default_action(mock,
+ "add",
+ mock_add,
+ kunit_int_return(failing_test, -4));
+
+ mock->do_expect(mock,
+ "add",
+ mock_add,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ mock_validate_expectations(mock);
+
+ KUNIT_EXPECT_FALSE(test, failing_test->success);
+}
+
/*
* Method called on naggy mock with no expectations will not fail, but will show
* a warning message
@@ -257,6 +301,8 @@ static void mock_test_naggy_no_expectations_no_fail(struct kunit *test)
static const char * const two_param_types[] = {"int", "int"};
const void *two_params[] = {¶m0, ¶m1};
+ mock->type = MOCK_TYPE_NAGGY;
+
mock_set_default_action(mock,
"add",
real_add,
@@ -282,6 +328,77 @@ static void mock_test_naggy_no_expectations_no_fail(struct kunit *test)
KUNIT_EXPECT_FALSE(test, failing_test->success);
}
+/* Method called on nice mock with no expectations will do nothing. */
+static void mock_test_nice_no_expectations_do_nothing(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+ int param0 = 5, param1 = -5;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+
+ mock->type = MOCK_TYPE_NICE;
+
+ mock->do_expect(mock,
+ "add",
+ mock_add,
+ two_param_types,
+ two_params,
+ ARRAY_SIZE(two_params));
+ mock_validate_expectations(mock);
+
+ KUNIT_EXPECT_TRUE(test, failing_test->success);
+}
+
+/* Test that method called on a mock (of any type) with no matching expectations
+ * will fail test and print all the tried expectations.
+ */
+static void
+run_method_called_but_no_matching_expectation_test(struct kunit *test,
+ enum mock_type mock_type)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+ int param0 = 5, param1 = -5;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+ struct mock_param_matcher *two_matchers[] = {
+ kunit_int_eq(failing_test, 100),
+ kunit_int_eq(failing_test, 100)
+ };
+
+ mock_add_matcher(mock, "add", mock_add, two_matchers,
+ ARRAY_SIZE(two_matchers));
+
+ mock->type = mock_type;
+
+ mock->do_expect(mock, "add", mock_add, two_param_types, two_params,
+ ARRAY_SIZE(two_params));
+
+ /* Even nice mocks should fail if there's an unmet expectation. */
+ KUNIT_EXPECT_FALSE(test, failing_test->success);
+}
+
+static void mock_test_naggy_no_matching_expectations_fail(struct kunit *test)
+{
+ run_method_called_but_no_matching_expectation_test(test,
+ MOCK_TYPE_NAGGY);
+}
+
+static void mock_test_strict_no_matching_expectations_fail(struct kunit *test)
+{
+ run_method_called_but_no_matching_expectation_test(test,
+ MOCK_TYPE_STRICT);
+}
+
+static void mock_test_nice_no_matching_expectations_fail(struct kunit *test)
+{
+ run_method_called_but_no_matching_expectation_test(test,
+ MOCK_TYPE_NICE);
+}
+
static void mock_test_mock_validate_expectations(struct kunit *test)
{
struct mock_test_context *ctx = test->priv;
@@ -309,6 +426,54 @@ static void mock_test_mock_validate_expectations(struct kunit *test)
KUNIT_EXPECT_FALSE(test, failing_test->success);
}
+static void mock_test_validate_clears_expectations(struct kunit *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct kunit *failing_test = ctx->failing_test;
+ struct mock *mock = ctx->mock;
+ struct mock_param_matcher *matchers[] = {
+ kunit_int_eq(failing_test, 5),
+ kunit_int_eq(failing_test, -4)
+ };
+ int param0 = 5, param1 = -4;
+ static const char * const two_param_types[] = {"int", "int"};
+ const void *two_params[] = {¶m0, ¶m1};
+
+ struct mock_expectation *expectation;
+
+ mock->type = MOCK_TYPE_STRICT;
+
+ /* Add an arbitrary matcher for 0 calls */
+ expectation = mock_add_matcher(mock, "add", mock_add,
+ matchers, ARRAY_SIZE(matchers));
+ expectation->times_called = 0;
+ expectation->min_calls_expected = 0;
+ expectation->max_calls_expected = 0;
+
+ /* Should have 0 calls and should clear the previous expectation */
+ mock_validate_expectations(mock);
+
+ /* Add a new matcher for 1 call */
+ expectation = mock_add_matcher(mock, "add", mock_add,
+ matchers, ARRAY_SIZE(matchers));
+ expectation->times_called = 0;
+ expectation->min_calls_expected = 1;
+ expectation->max_calls_expected = 1;
+
+ /* Satisfy previous matcher */
+ mock->do_expect(mock, "add", mock_add, two_param_types, two_params,
+ ARRAY_SIZE(two_params));
+
+ /*
+ * Validate previous satisfy; if we didn't clear the previous
+ * expectation, it would fail the mock_test.
+ */
+ mock_validate_expectations(mock);
+
+ /* If all goes well, shouldn't fail the test. */
+ KUNIT_EXPECT_TRUE(test, failing_test->success);
+}
+
static int mock_test_init(struct kunit *test)
{
struct mock_test_context *ctx;
@@ -346,7 +511,13 @@ static struct kunit_case mock_test_cases[] = {
KUNIT_CASE(mock_test_failed_expect_call_fails_test),
KUNIT_CASE(mock_test_do_expect_default_return),
KUNIT_CASE(mock_test_mock_validate_expectations),
+ KUNIT_CASE(mock_test_strict_no_expectations_will_fail),
KUNIT_CASE(mock_test_naggy_no_expectations_no_fail),
+ KUNIT_CASE(mock_test_nice_no_expectations_do_nothing),
+ KUNIT_CASE(mock_test_strict_no_matching_expectations_fail),
+ KUNIT_CASE(mock_test_naggy_no_matching_expectations_fail),
+ KUNIT_CASE(mock_test_nice_no_matching_expectations_fail),
+ KUNIT_CASE(mock_test_validate_clears_expectations),
{}
};
diff --git a/lib/kunit/mock.c b/lib/kunit/mock.c
index 12fb88899451..f1fa7a5b9dd4 100644
--- a/lib/kunit/mock.c
+++ b/lib/kunit/mock.c
@@ -85,6 +85,7 @@ void mock_init_ctrl(struct kunit *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);
}
@@ -283,7 +284,12 @@ static struct mock_expectation *mock_apply_expectations(
mock_add_method_expectation_error(test, stream,
"Method was called with no expectations declared: ",
mock, method, type_names, params, len);
- kunit_stream_commit(stream);
+ if (is_strict_mock(mock))
+ fail_and_flush(test, stream);
+ else if (is_naggy_mock(mock))
+ kunit_stream_commit(stream);
+ else
+ kunit_stream_clear(stream);
return NULL;
}
@@ -313,7 +319,7 @@ static struct mock_expectation *mock_apply_expectations(
}
}
- if (expectations_all_saturated) {
+ 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);
--
2.28.0.1011.ga647a8990f-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [RFC v2 00/12] kunit: introduce class mocking support.
2020-10-12 22:20 [RFC v2 00/12] kunit: introduce class mocking support Daniel Latypov
` (11 preceding siblings ...)
2020-10-12 22:20 ` [RFC v2 12/12] kunit: mock: implement nice, strict and naggy mock distinctions Daniel Latypov
@ 2020-11-02 18:00 ` Brendan Higgins
12 siblings, 0 replies; 14+ messages in thread
From: Brendan Higgins @ 2020-11-02 18:00 UTC (permalink / raw)
To: Daniel Latypov, Joel Stanley, Daniel Vetter
Cc: Alan Maguire, David Gow, Kees Cook, KUnit Development,
Linux Kernel Mailing List, open list:KERNEL SELFTEST FRAMEWORK,
Luis Chamberlain, Stephen Boyd, Shuah Khan
+Joel Stanley +Daniel Vetter
If I remember correctly, both of you said you were interested in
mocking on KUnit. This RFC only has some of the mocking features that
I mentioned previously, but I would still like to get your thoughts.
On Mon, Oct 12, 2020 at 3:21 PM Daniel Latypov <dlatypov@google.com> wrote:
>
> # Background
> KUnit currently lacks any first-class support for mocking.
> For an overview and discussion on the pros and cons, see
> https://martinfowler.com/articles/mocksArentStubs.html
>
> This patch set introduces the basic machinery needed for mocking:
> setting and validating expectations, setting default actions, etc.
>
> Using that basic infrastructure, we add macros for "class mocking", as
> it's probably the easiest type of mocking to start with.
>
> ## Class mocking
>
> By "class mocking", we're referring mocking out function pointers stored
> in structs like:
> struct sender {
> int (*send)(struct sender *sender, int data);
> };
> or in ops structs
> struct sender {
> struct send_ops *ops; // contains `send`
> };
>
> After the necessary DEFINE_* macros, we can then write code like
> struct MOCK(sender) mock_sender = CONSTRUCT_MOCK(sender, test);
>
> /* Fake an error for a specific input. */
> handle = KUNIT_EXPECT_CALL(send(<omitted>, kunit_int_eq(42)));
> handle->action = kunit_int_return(test, -EINVAL);
>
> /* Pass the mocked object to some code under test. */
> KUNIT_EXPECT_EQ(test, -EINVAL, send_message(...));
>
> I.e. the goal is to make it easier to test
> 1) with less dependencies (we don't need to setup a real `sender`)
> 2) unusual/error conditions more easily.
>
> In the future, we hope to build upon this to support mocking in more
> contexts, e.g. standalone funcs, etc.
>
> # TODOs
>
> ## Naming
> This introduces a number of new macros for dealing with mocks,
> e.g:
> DEFINE_STRUCT_CLASS_MOCK(METHOD(foo), CLASS(example),
> RETURNS(int),
> PARAMS(struct example *, int));
> ...
> KUNIT_EXPECT_CALL(foo(mock_get_ctrl(mock_example), ...);
> For consistency, we could prefix everything with KUNIT, e.g.
> `KUNIT_DEFINE_STRUCT_CLASS_MOCK` and `kunit_mock_get_ctrl`, but it feels
> like the names might be long enough that they would hinder readability.
>
> ## Usage
> For now the only use of class mocking is in kunit-example-test.c
> As part of changing this from an RFC to a real patch set, we're hoping
> to include at least one example.
>
> Pointers to bits of code where this would be useful that aren't too
> hairy would be appreciated.
> E.g. could easily add a test for tools/perf/ui/progress.h, e.g. that
> ui_progress__init() calls ui_progress_ops.init(), but that likely isn't
> useful to anyone.
>
> ---
> v2:
> * Pass `struct kunit *` to mock init's to allow allocating ops structs.
> * Update kunit-example-test.cc to do so as a more realistic example.
> v1: https://lore.kernel.org/linux-kselftest/20200918183114.2571146-1-dlatypov@google.com/
> ---
>
> Brendan Higgins (9):
> kunit: test: add kunit_stream a std::stream like logger
> kunit: test: add concept of post conditions
> checkpatch: add support for struct MOCK(foo) syntax
> kunit: mock: add parameter list manipulation macros
> kunit: mock: add internal mock infrastructure
> kunit: mock: add basic matchers and actions
> kunit: mock: add class mocking support
> kunit: mock: add struct param matcher
> kunit: mock: implement nice, strict and naggy mock distinctions
>
> Daniel Latypov (2):
> Revert "kunit: move string-stream.h to lib/kunit"
> kunit: expose kunit_set_failure() for use by mocking
>
> Marcelo Schmitt (1):
> kunit: mock: add macro machinery to pick correct format args
>
> include/kunit/assert.h | 3 +-
> include/kunit/kunit-stream.h | 94 +++
> include/kunit/mock.h | 902 +++++++++++++++++++++++++
> include/kunit/params.h | 305 +++++++++
> {lib => include}/kunit/string-stream.h | 2 +
> include/kunit/test.h | 9 +
> lib/kunit/Makefile | 9 +-
> lib/kunit/assert.c | 2 -
> lib/kunit/common-mocks.c | 409 +++++++++++
> lib/kunit/kunit-example-test.c | 98 +++
> lib/kunit/kunit-stream.c | 110 +++
> lib/kunit/mock-macro-test.c | 241 +++++++
> lib/kunit/mock-test.c | 531 +++++++++++++++
> lib/kunit/mock.c | 370 ++++++++++
> lib/kunit/string-stream-test.c | 3 +-
> lib/kunit/string-stream.c | 5 +-
> lib/kunit/test.c | 15 +-
> scripts/checkpatch.pl | 4 +
> 18 files changed, 3099 insertions(+), 13 deletions(-)
> create mode 100644 include/kunit/kunit-stream.h
> create mode 100644 include/kunit/mock.h
> create mode 100644 include/kunit/params.h
> rename {lib => include}/kunit/string-stream.h (95%)
> create mode 100644 lib/kunit/common-mocks.c
> create mode 100644 lib/kunit/kunit-stream.c
> create mode 100644 lib/kunit/mock-macro-test.c
> create mode 100644 lib/kunit/mock-test.c
> create mode 100644 lib/kunit/mock.c
>
>
> base-commit: 10b82d5176488acee2820e5a2cf0f2ec5c3488b6
> --
> 2.28.0.1011.ga647a8990f-goog
>
^ permalink raw reply [flat|nested] 14+ messages in thread