This patch set proposes KUnit, a lightweight unit testing and mocking framework for the Linux kernel.
Unlike Autotest and kselftest, KUnit is a true unit testing framework; it does not require installing the kernel on a test machine or in a VM and does not require tests to be written in userspace running on a host kernel. Additionally, KUnit is fast: From invocation to completion KUnit can run several dozen tests in under a second. Currently, the entire KUnit test suite for KUnit runs in under a second from the initial invocation (build time excluded).
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
## What's so special about unit testing?
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
## Is KUnit trying to replace other testing frameworks for the kernel?
No. Most existing tests for the Linux kernel are end-to-end tests, which have their place. A well tested system has lots of unit tests, a reasonable number of integration tests, and some end-to-end tests. KUnit is just trying to address the unit test space which is currently not being addressed.
## More information on KUnit
There is a bunch of documentation near the end of this patch set that describes how to use KUnit and best practices for writing unit tests. For convenience I am hosting the compiled docs here: https://google.github.io/kunit-docs/third_party/kernel/docs/
## Changes Since Last Version
- Updated patchset to apply cleanly on 4.19. - Stripped down patchset to focus on just the core features (I dropped mocking, spying, and the MMIO stuff for now; you can find these patches here: https://kunit-review.googlesource.com/c/linux/+/1132), as suggested by Rob. - Cleaned up some of the commit messages and tweaked commit order a bit based on suggestions.
Add core facilities for defining unit tests; this provides a common way to define test cases, functions that execute code which is under test and determine whether the code under test behaves as expected; this also provides a way to group together related test cases in test suites (here we call them test_modules).
Just define test cases and how to execute them for now; setting expectations on code will be defined later.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- include/kunit/test.h | 165 ++++++++++++++++++++++++++++++++++++++++++ kunit/Kconfig | 17 +++++ kunit/Makefile | 1 + kunit/test.c | 168 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 include/kunit/test.h create mode 100644 kunit/Kconfig create mode 100644 kunit/Makefile create mode 100644 kunit/test.c
diff --git a/include/kunit/test.h b/include/kunit/test.h new file mode 100644 index 0000000000000..e0b14b227ac44 --- /dev/null +++ b/include/kunit/test.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Base unit test (KUnit) API. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#ifndef _KUNIT_TEST_H +#define _KUNIT_TEST_H + +#include <linux/types.h> +#include <linux/slab.h> + +struct test; + +/** + * struct test_case - represents an individual test case. + * @run_case: the function representing the actual test case. + * @name: the name of the test case. + * + * A test case is a function with the signature, ``void (*)(struct test *)`` + * that makes expectations (see TEST_EXPECT_TRUE()) about code under test. Each + * test case is associated with a &struct test_module and will be run after the + * module's init function and followed by the module's exit function. + * + * A test case should be static and should only be created with the TEST_CASE() + * macro; additionally, every array of test cases should be terminated with an + * empty test case. + * + * Example: + * + * .. code-block:: c + * + * void add_test_basic(struct test *test) + * { + * TEST_EXPECT_EQ(test, 1, add(1, 0)); + * TEST_EXPECT_EQ(test, 2, add(1, 1)); + * TEST_EXPECT_EQ(test, 0, add(-1, 1)); + * TEST_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX)); + * TEST_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN)); + * } + * + * static struct test_case example_test_cases[] = { + * TEST_CASE(add_test_basic), + * {}, + * }; + * + */ +struct test_case { + void (*run_case)(struct test *test); + const char name[256]; + + /* private: internal use only. */ + bool success; +}; + +/** + * TEST_CASE - A helper for creating a &struct test_case + * @test_name: a reference to a test case function. + * + * Takes a symbol for a function representing a test case and creates a &struct + * test_case object from it. See the documentation for &struct test_case for an + * example on how to use it. + */ +#define TEST_CASE(test_name) { .run_case = test_name, .name = #test_name } + +/** + * struct test_module - describes a related collection of &struct test_case s. + * @name: the name of the test. Purely informational. + * @init: called before every test case. + * @exit: called after every test case. + * @test_cases: a null terminated array of test cases. + * + * A test_module is a collection of related &struct test_case s, such that + * @init is called before every test case and @exit is called after every test + * case, similar to the notion of a *test fixture* or a *test class* in other + * unit testing frameworks like JUnit or Googletest. + * + * Every &struct test_case must be associated with a test_module for KUnit to + * run it. + */ +struct test_module { + const char name[256]; + int (*init)(struct test *test); + void (*exit)(struct test *test); + struct test_case *test_cases; +}; + +/** + * struct test - represents a running instance of a test. + * @priv: for user to store arbitrary data. Commonly used to pass data created + * in the init function (see &struct test_module). + * + * Used to store information about the current context under which the test is + * running. Most of this data is private and should only be accessed indirectly + * via public functions; the one exception is @priv which can be used by the + * test writer to store arbitrary data. + */ +struct test { + void *priv; + + /* private: internal use only. */ + const char *name; /* Read only after initialization! */ + spinlock_t lock; /* Gaurds all mutable test state. */ + bool success; /* Protected by lock. */ + void (*vprintk)(const struct test *test, + const char *level, + struct va_format *vaf); +}; + +int test_init_test(struct test *test, const char *name); + +int test_run_tests(struct test_module *module); + +/** + * module_test() - used to register a &struct test_module with KUnit. + * @module: a statically allocated &struct test_module. + * + * Registers @module with the test framework. See &struct test_module for more + * information. + */ +#define module_test(module) \ + static int module_test_init##module(void) \ + { \ + return test_run_tests(&module); \ + } \ + late_initcall(module_test_init##module) + +void __printf(3, 4) test_printk(const char *level, + const struct test *test, + const char *fmt, ...); + +/** + * test_info() - Prints an INFO level message associated with the current test. + * @test: The test context object. + * @fmt: A printk() style format string. + * + * Prints an info level message associated with the test module being run. Takes + * a variable number of format parameters just like printk(). + */ +#define test_info(test, fmt, ...) \ + test_printk(KERN_INFO, test, fmt, ##__VA_ARGS__) + +/** + * test_warn() - Prints a WARN level message associated with the current test. + * @test: The test context object. + * @fmt: A printk() style format string. + * + * See test_info(). + */ +#define test_warn(test, fmt, ...) \ + test_printk(KERN_WARNING, test, fmt, ##__VA_ARGS__) + +/** + * test_err() - Prints an ERROR level message associated with the current test. + * @test: The test context object. + * @fmt: A printk() style format string. + * + * See test_info(). + */ +#define test_err(test, fmt, ...) \ + test_printk(KERN_ERR, test, fmt, ##__VA_ARGS__) + +#endif /* _KUNIT_TEST_H */ diff --git a/kunit/Kconfig b/kunit/Kconfig new file mode 100644 index 0000000000000..49b44c4f6630a --- /dev/null +++ b/kunit/Kconfig @@ -0,0 +1,17 @@ +# +# KUnit base configuration +# + +menu "KUnit support" + +config KUNIT + bool "Enable support for unit tests (KUnit)" + depends on UML + help + Enables support for kernel unit tests (KUnit), a lightweight unit + testing and mocking framework for the Linux kernel. These tests are + able to be run locally on a developer's workstation without a VM or + special hardware. For more information, please see + Documentation/kunit/ + +endmenu diff --git a/kunit/Makefile b/kunit/Makefile new file mode 100644 index 0000000000000..dd7a0299514b7 --- /dev/null +++ b/kunit/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_KUNIT) += test.o diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..4732e5f0d7575 --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Base unit test (KUnit) API. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <linux/sched.h> +#include <linux/sched/debug.h> +#include <os.h> +#include <kunit/test.h> + +static bool test_get_success(struct test *test) +{ + unsigned long flags; + bool success; + + spin_lock_irqsave(&test->lock, flags); + success = test->success; + spin_unlock_irqrestore(&test->lock, flags); + + return success; +} + +static void test_set_success(struct test *test, bool success) +{ + unsigned long flags; + + spin_lock_irqsave(&test->lock, flags); + test->success = success; + spin_unlock_irqrestore(&test->lock, flags); +} + +static int test_vprintk_emit(const struct test *test, + int level, + const char *fmt, + va_list args) +{ + return vprintk_emit(0, level, NULL, 0, fmt, args); +} + +static int test_printk_emit(const struct test *test, + int level, + const char *fmt, ...) +{ + va_list args; + int ret; + + va_start(args, fmt); + ret = test_vprintk_emit(test, level, fmt, args); + va_end(args); + + return ret; +} + +static void test_vprintk(const struct test *test, + const char *level, + struct va_format *vaf) +{ + test_printk_emit(test, + level[1] - '0', + "kunit %s: %pV", test->name, vaf); +} + +int test_init_test(struct test *test, const char *name) +{ + spin_lock_init(&test->lock); + test->name = name; + test->vprintk = test_vprintk; + + return 0; +} + +/* + * Initializes and runs test case. Does not clean up or do post validations. + */ +static void test_run_case_internal(struct test *test, + struct test_module *module, + struct test_case *test_case) +{ + int ret; + + if (module->init) { + ret = module->init(test); + if (ret) { + test_err(test, "failed to initialize: %d", ret); + test_set_success(test, false); + return; + } + } + + test_case->run_case(test); +} + +/* + * Performs post validations and cleanup after a test case was run. + * XXX: Should ONLY BE CALLED AFTER test_run_case_internal! + */ +static void test_run_case_cleanup(struct test *test, + struct test_module *module, + struct test_case *test_case) +{ + if (module->exit) + module->exit(test); +} + +/* + * Performs all logic to run a test case. + */ +static bool test_run_case(struct test *test, + struct test_module *module, + struct test_case *test_case) +{ + test_set_success(test, true); + + test_run_case_internal(test, module, test_case); + test_run_case_cleanup(test, module, test_case); + + return test_get_success(test); +} + +int test_run_tests(struct test_module *module) +{ + bool all_passed = true, success; + struct test_case *test_case; + struct test test; + int ret; + + ret = test_init_test(&test, module->name); + if (ret) + return ret; + + for (test_case = module->test_cases; test_case->run_case; test_case++) { + success = test_run_case(&test, module, test_case); + if (!success) + all_passed = false; + + test_info(&test, + "%s %s", + test_case->name, + success ? "passed" : "failed"); + } + + if (all_passed) + test_info(&test, "all tests passed"); + else + test_info(&test, "one or more tests failed"); + + return 0; +} + +void test_printk(const char *level, + const struct test *test, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + test->vprintk(test, level, &vaf); + + va_end(args); +}
On 10/23/2018 05:57 PM, Brendan Higgins wrote:
Add core facilities for defining unit tests; this provides a common way to define test cases, functions that execute code which is under test and determine whether the code under test behaves as expected; this also provides a way to group together related test cases in test suites (here we call them test_modules).
Just define test cases and how to execute them for now; setting expectations on code will be defined later.
Signed-off-by: Brendan Higgins brendanhiggins@google.com
include/kunit/test.h | 165 ++++++++++++++++++++++++++++++++++++++++++ kunit/Kconfig | 17 +++++ kunit/Makefile | 1 + kunit/test.c | 168 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 include/kunit/test.h create mode 100644 kunit/Kconfig create mode 100644 kunit/Makefile create mode 100644 kunit/test.c
diff --git a/include/kunit/test.h b/include/kunit/test.h new file mode 100644 index 0000000000000..e0b14b227ac44 --- /dev/null +++ b/include/kunit/test.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- Base unit test (KUnit) API.
- Copyright (C) 2018, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#ifndef _KUNIT_TEST_H +#define _KUNIT_TEST_H
+#include <linux/types.h> +#include <linux/slab.h>
+struct test;
+/**
- struct test_case - represents an individual test case.
- @run_case: the function representing the actual test case.
- @name: the name of the test case.
- A test case is a function with the signature, ``void (*)(struct test *)``
- that makes expectations (see TEST_EXPECT_TRUE()) about code under test. Each
- test case is associated with a &struct test_module and will be run after the
- module's init function and followed by the module's exit function.
- A test case should be static and should only be created with the TEST_CASE()
- macro; additionally, every array of test cases should be terminated with an
- empty test case.
- Example:
- .. code-block:: c
- void add_test_basic(struct test *test)
- {
TEST_EXPECT_EQ(test, 1, add(1, 0));
TEST_EXPECT_EQ(test, 2, add(1, 1));
TEST_EXPECT_EQ(test, 0, add(-1, 1));
TEST_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
TEST_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
- }
- static struct test_case example_test_cases[] = {
TEST_CASE(add_test_basic),
{},
- };
- */
+struct test_case {
- void (*run_case)(struct test *test);
- const char name[256];
- /* private: internal use only. */
- bool success;
+};
Introducing a prefix kunit_* might be a good idea for the API. This comment applies to the rest of patches as well.
thanks, -- Shuah
On Fri, Nov 2, 2018 at 11:44 AM Shuah Khan shuah@kernel.org wrote:
On 10/23/2018 05:57 PM, Brendan Higgins wrote:
<snip>
- Example:
- .. code-block:: c
- void add_test_basic(struct test *test)
- {
TEST_EXPECT_EQ(test, 1, add(1, 0));
TEST_EXPECT_EQ(test, 2, add(1, 1));
TEST_EXPECT_EQ(test, 0, add(-1, 1));
TEST_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
TEST_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
- }
- static struct test_case example_test_cases[] = {
TEST_CASE(add_test_basic),
{},
- };
- */
+struct test_case {
void (*run_case)(struct test *test);
const char name[256];
/* private: internal use only. */
bool success;
+};
Introducing a prefix kunit_* might be a good idea for the API. This comment applies to the rest of patches as well.
What about kunit_* instead of test_* and kmock_* instead of mock_*? Does that seem reasonable?
On 11/06/2018 06:28 PM, Brendan Higgins wrote:
On Fri, Nov 2, 2018 at 11:44 AM Shuah Khan shuah@kernel.org wrote:
On 10/23/2018 05:57 PM, Brendan Higgins wrote:
<snip> >> + * Example: >> + * >> + * .. code-block:: c >> + * >> + * void add_test_basic(struct test *test) >> + * { >> + * TEST_EXPECT_EQ(test, 1, add(1, 0)); >> + * TEST_EXPECT_EQ(test, 2, add(1, 1)); >> + * TEST_EXPECT_EQ(test, 0, add(-1, 1)); >> + * TEST_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX)); >> + * TEST_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN)); >> + * } >> + * >> + * static struct test_case example_test_cases[] = { >> + * TEST_CASE(add_test_basic), >> + * {}, >> + * }; >> + * >> + */ >> +struct test_case { >> + void (*run_case)(struct test *test); >> + const char name[256]; >> + >> + /* private: internal use only. */ >> + bool success; >> +}; >> + > > Introducing a prefix kunit_* might be a good idea for the API. > This comment applies to the rest of patches as well.
What about kunit_* instead of test_* and kmock_* instead of mock_*? Does that seem reasonable?
kunit_* would work well.
thanks, -- Shuah
Create a common API for test managed resources like memory and test objects. A lot of times a test will want to set up infrastructure to be used in test cases; this could be anything from just wanting to allocate some memory to setting up a driver stack; this defines facilities for creating "test resources" which are managed by the test infrastructure and are automatically cleaned up at the conclusion of the test.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- include/kunit/test.h | 109 +++++++++++++++++++++++++++++++++++++++++++ kunit/test.c | 95 +++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+)
diff --git a/include/kunit/test.h b/include/kunit/test.h index e0b14b227ac44..1c116a20063da 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -12,6 +12,69 @@ #include <linux/types.h> #include <linux/slab.h>
+struct test_resource; + +typedef int (*test_resource_init_t)(struct test_resource *, void *); +typedef void (*test_resource_free_t)(struct test_resource *); + +/** + * struct test_resource - represents a *test managed resource* + * @allocation: for the user to store arbitrary data. + * @free: a user supplied function to free the resource. Populated by + * test_alloc_resource(). + * + * Represents a *test managed resource*, a resource which will automatically be + * cleaned up at the end of a test case. + * + * Example: + * + * .. code-block:: c + * + * struct test_kmalloc_params { + * size_t size; + * gfp_t gfp; + * }; + * + * static int test_kmalloc_init(struct test_resource *res, void *context) + * { + * struct test_kmalloc_params *params = context; + * res->allocation = kmalloc(params->size, params->gfp); + * + * if (!res->allocation) + * return -ENOMEM; + * + * return 0; + * } + * + * static void test_kmalloc_free(struct test_resource *res) + * { + * kfree(res->allocation); + * } + * + * void *test_kmalloc(struct test *test, size_t size, gfp_t gfp) + * { + * struct test_kmalloc_params params; + * struct test_resource *res; + * + * params.size = size; + * params.gfp = gfp; + * + * res = test_alloc_resource(test, test_kmalloc_init, + * test_kmalloc_free, ¶ms); + * if (res) + * return res->allocation; + * else + * return NULL; + * } + */ +struct test_resource { + void *allocation; + test_resource_free_t free; + + /* private: internal use only. */ + struct list_head node; +}; + struct test;
/** @@ -104,6 +167,7 @@ struct test { const char *name; /* Read only after initialization! */ spinlock_t lock; /* Gaurds all mutable test state. */ bool success; /* Protected by lock. */ + struct list_head resources; /* Protected by lock. */ void (*vprintk)(const struct test *test, const char *level, struct va_format *vaf); @@ -127,6 +191,51 @@ int test_run_tests(struct test_module *module); } \ late_initcall(module_test_init##module)
+/** + * test_alloc_resource() - Allocates a *test managed resource*. + * @test: The test context object. + * @init: a user supplied function to initialize the resource. + * @free: a user supplied function to free the resource. + * @context: for the user to pass in arbitrary data. + * + * Allocates a *test managed resource*, a resource which will automatically be + * cleaned up at the end of a test case. See &struct test_resource for an + * example. + */ +struct test_resource *test_alloc_resource(struct test *test, + test_resource_init_t init, + test_resource_free_t free, + void *context); + +void test_free_resource(struct test *test, struct test_resource *res); + +/** + * test_kmalloc() - Just like kmalloc() except the allocation is *test managed*. + * @test: The test context object. + * @size: The size in bytes of the desired memory. + * @gfp: flags passed to underlying kmalloc(). + * + * Just like `kmalloc(...)`, except the allocation is managed by the test case + * and is automatically cleaned up after the test case concludes. See &struct + * test_resource for more information. + */ +void *test_kmalloc(struct test *test, size_t size, gfp_t gfp); + +/** + * test_kzalloc() - Just like test_kmalloc(), but zeroes the allocation. + * @test: The test context object. + * @size: The size in bytes of the desired memory. + * @gfp: flags passed to underlying kmalloc(). + * + * See kzalloc() and test_kmalloc() for more information. + */ +static inline void *test_kzalloc(struct test *test, size_t size, gfp_t gfp) +{ + return test_kmalloc(test, size, gfp | __GFP_ZERO); +} + +void test_cleanup(struct test *test); + void __printf(3, 4) test_printk(const char *level, const struct test *test, const char *fmt, ...); diff --git a/kunit/test.c b/kunit/test.c index 4732e5f0d7575..fd0a51245215e 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -66,6 +66,7 @@ static void test_vprintk(const struct test *test, int test_init_test(struct test *test, const char *name) { spin_lock_init(&test->lock); + INIT_LIST_HEAD(&test->resources); test->name = name; test->vprintk = test_vprintk;
@@ -93,6 +94,11 @@ static void test_run_case_internal(struct test *test, test_case->run_case(test); }
+static void test_case_internal_cleanup(struct test *test) +{ + test_cleanup(test); +} + /* * Performs post validations and cleanup after a test case was run. * XXX: Should ONLY BE CALLED AFTER test_run_case_internal! @@ -103,6 +109,8 @@ static void test_run_case_cleanup(struct test *test, { if (module->exit) module->exit(test); + + test_case_internal_cleanup(test); }
/* @@ -150,6 +158,93 @@ int test_run_tests(struct test_module *module) return 0; }
+struct test_resource *test_alloc_resource(struct test *test, + test_resource_init_t init, + test_resource_free_t free, + void *context) +{ + struct test_resource *res; + unsigned long flags; + int ret; + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) + return NULL; + + ret = init(res, context); + if (ret) + return NULL; + + res->free = free; + spin_lock_irqsave(&test->lock, flags); + list_add_tail(&res->node, &test->resources); + spin_unlock_irqrestore(&test->lock, flags); + + return res; +} + +void test_free_resource(struct test *test, struct test_resource *res) +{ + res->free(res); + list_del(&res->node); + kfree(res); +} + +struct test_kmalloc_params { + size_t size; + gfp_t gfp; +}; + +static int test_kmalloc_init(struct test_resource *res, void *context) +{ + struct test_kmalloc_params *params = context; + + res->allocation = kmalloc(params->size, params->gfp); + if (!res->allocation) + return -ENOMEM; + + return 0; +} + +static void test_kmalloc_free(struct test_resource *res) +{ + kfree(res->allocation); +} + +void *test_kmalloc(struct test *test, size_t size, gfp_t gfp) +{ + struct test_kmalloc_params params; + struct test_resource *res; + + params.size = size; + params.gfp = gfp; + + res = test_alloc_resource(test, + test_kmalloc_init, + test_kmalloc_free, + ¶ms); + + if (res) + return res->allocation; + else + return NULL; +} + +void test_cleanup(struct test *test) +{ + struct test_resource *resource, *resource_safe; + unsigned long flags; + + spin_lock_irqsave(&test->lock, flags); + list_for_each_entry_safe(resource, + resource_safe, + &test->resources, + node) { + test_free_resource(test, resource); + } + spin_unlock_irqrestore(&test->lock, flags); +} + void test_printk(const char *level, const struct test *test, const char *fmt, ...)
A number of test features need to do pretty complicated string printing where it may not be possible to rely on a single preallocated string with parameters.
So provide a library for constructing the string as you go similar to C++'s std::string.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- include/kunit/string-stream.h | 44 ++++++++++ kunit/Makefile | 2 +- kunit/string-stream.c | 149 ++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 include/kunit/string-stream.h create mode 100644 kunit/string-stream.c
diff --git a/include/kunit/string-stream.h b/include/kunit/string-stream.h new file mode 100644 index 0000000000000..933ed5740cf07 --- /dev/null +++ b/include/kunit/string-stream.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#ifndef _KUNIT_STRING_STREAM_H +#define _KUNIT_STRING_STREAM_H + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/kref.h> +#include <stdarg.h> + +struct string_stream_fragment { + struct list_head node; + char *fragment; +}; + +struct string_stream { + size_t length; + struct list_head fragments; + + /* length and fragments are protected by this lock */ + spinlock_t lock; + struct kref refcount; + int (*add)(struct string_stream *this, const char *fmt, ...); + int (*vadd)(struct string_stream *this, const char *fmt, va_list args); + char *(*get_string)(struct string_stream *this); + void (*clear)(struct string_stream *this); + bool (*is_empty)(struct string_stream *this); +}; + +struct string_stream *new_string_stream(void); + +void destroy_string_stream(struct string_stream *stream); + +void string_stream_get(struct string_stream *stream); + +int string_stream_put(struct string_stream *stream); + +#endif /* _KUNIT_STRING_STREAM_H */ diff --git a/kunit/Makefile b/kunit/Makefile index dd7a0299514b7..7fc613a9b383b 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1 +1 @@ -obj-$(CONFIG_KUNIT) += test.o +obj-$(CONFIG_KUNIT) += test.o string-stream.o diff --git a/kunit/string-stream.c b/kunit/string-stream.c new file mode 100644 index 0000000000000..1e7efa630cc35 --- /dev/null +++ b/kunit/string-stream.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <linux/list.h> +#include <linux/slab.h> +#include <kunit/string-stream.h> + +static int string_stream_vadd(struct string_stream *this, + const char *fmt, + va_list args) +{ + struct string_stream_fragment *fragment; + int len; + va_list args_for_counting; + unsigned long flags; + + /* Make a copy because `vsnprintf` could change it */ + va_copy(args_for_counting, args); + + /* Need space for null byte. */ + len = vsnprintf(NULL, 0, fmt, args_for_counting) + 1; + + va_end(args_for_counting); + + fragment = kmalloc(sizeof(*fragment), GFP_KERNEL); + if (!fragment) + return -ENOMEM; + + fragment->fragment = kmalloc(len, GFP_KERNEL); + if (!fragment->fragment) { + kfree(fragment); + return -ENOMEM; + } + + len = vsnprintf(fragment->fragment, len, fmt, args); + spin_lock_irqsave(&this->lock, flags); + this->length += len; + list_add_tail(&fragment->node, &this->fragments); + spin_unlock_irqrestore(&this->lock, flags); + return 0; +} + +static int string_stream_add(struct string_stream *this, const char *fmt, ...) +{ + va_list args; + int result; + + va_start(args, fmt); + result = string_stream_vadd(this, fmt, args); + va_end(args); + return result; +} + +static void string_stream_clear(struct string_stream *this) +{ + struct string_stream_fragment *fragment, *fragment_safe; + unsigned long flags; + + spin_lock_irqsave(&this->lock, flags); + list_for_each_entry_safe(fragment, + fragment_safe, + &this->fragments, + node) { + list_del(&fragment->node); + kfree(fragment->fragment); + kfree(fragment); + } + this->length = 0; + spin_unlock_irqrestore(&this->lock, flags); +} + +static char *string_stream_get_string(struct string_stream *this) +{ + struct string_stream_fragment *fragment; + size_t buf_len = this->length + 1; /* +1 for null byte. */ + char *buf; + unsigned long flags; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return NULL; + + spin_lock_irqsave(&this->lock, flags); + list_for_each_entry(fragment, &this->fragments, node) + strlcat(buf, fragment->fragment, buf_len); + spin_unlock_irqrestore(&this->lock, flags); + + return buf; +} + +static bool string_stream_is_empty(struct string_stream *this) +{ + bool is_empty; + unsigned long flags; + + spin_lock_irqsave(&this->lock, flags); + is_empty = list_empty(&this->fragments); + spin_unlock_irqrestore(&this->lock, flags); + + return is_empty; +} + +void destroy_string_stream(struct string_stream *stream) +{ + stream->clear(stream); + kfree(stream); +} + +static void string_stream_destroy(struct kref *kref) +{ + struct string_stream *stream = container_of(kref, + struct string_stream, + refcount); + destroy_string_stream(stream); +} + +struct string_stream *new_string_stream(void) +{ + struct string_stream *stream = kzalloc(sizeof(*stream), GFP_KERNEL); + + if (!stream) + return NULL; + + INIT_LIST_HEAD(&stream->fragments); + spin_lock_init(&stream->lock); + kref_init(&stream->refcount); + stream->add = string_stream_add; + stream->vadd = string_stream_vadd; + stream->get_string = string_stream_get_string; + stream->clear = string_stream_clear; + stream->is_empty = string_stream_is_empty; + return stream; +} + +void string_stream_get(struct string_stream *stream) +{ + kref_get(&stream->refcount); +} + +int string_stream_put(struct string_stream *stream) +{ + return kref_put(&stream->refcount, &string_stream_destroy); +} +
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.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- include/kunit/test-stream.h | 49 ++++++++++++ include/kunit/test.h | 2 + kunit/Makefile | 2 +- kunit/test-stream.c | 153 ++++++++++++++++++++++++++++++++++++ kunit/test.c | 8 ++ 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 include/kunit/test-stream.h create mode 100644 kunit/test-stream.c
diff --git a/include/kunit/test-stream.h b/include/kunit/test-stream.h new file mode 100644 index 0000000000000..c5dfd95ef21bb --- /dev/null +++ b/include/kunit/test-stream.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * C++ stream style string formatter and printer used in KUnit for outputting + * KUnit messages. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#ifndef _KUNIT_TEST_STREAM_H +#define _KUNIT_TEST_STREAM_H + +#include <linux/types.h> +#include <kunit/string-stream.h> + +struct test; + +/** + * struct test_stream - a std::stream style string builder. + * @set_level: sets the level that this string should be printed at. + * @add: adds the formatted input to the internal buffer. + * @commit: prints out the internal buffer to the user. + * @clear: clears the internal buffer. + * + * A std::stream style string builder. Allows messages to be built up and + * printed all at once. + */ +struct test_stream { + void (*set_level)(struct test_stream *this, const char *level); + void (*add)(struct test_stream *this, const char *fmt, ...); + void (*append)(struct test_stream *this, struct test_stream *other); + void (*commit)(struct test_stream *this); + void (*clear)(struct test_stream *this); + /* private: internal use only. */ + struct test *test; + spinlock_t lock; /* Guards level. */ + const char *level; + struct string_stream *internal_stream; +}; + +/** + * test_new_stream() - constructs a new &struct test_stream. + * @test: The test context object. + * + * Constructs a new test managed &struct test_stream. + */ +struct test_stream *test_new_stream(struct test *test); + +#endif /* _KUNIT_TEST_STREAM_H */ diff --git a/include/kunit/test.h b/include/kunit/test.h index 1c116a20063da..68320fa2452de 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -11,6 +11,7 @@
#include <linux/types.h> #include <linux/slab.h> +#include <kunit/test-stream.h>
struct test_resource;
@@ -171,6 +172,7 @@ struct test { void (*vprintk)(const struct test *test, const char *level, struct va_format *vaf); + void (*fail)(struct test *test, struct test_stream *stream); };
int test_init_test(struct test *test, const char *name); diff --git a/kunit/Makefile b/kunit/Makefile index 7fc613a9b383b..5b4562ea7f322 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1 +1 @@ -obj-$(CONFIG_KUNIT) += test.o string-stream.o +obj-$(CONFIG_KUNIT) += test.o string-stream.o test-stream.o diff --git a/kunit/test-stream.c b/kunit/test-stream.c new file mode 100644 index 0000000000000..392966864a708 --- /dev/null +++ b/kunit/test-stream.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * C++ stream style string formatter and printer used in KUnit for outputting + * KUnit messages. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <kunit/test.h> +#include <kunit/test-stream.h> +#include <kunit/string-stream.h> + +static const char *test_stream_get_level(struct test_stream *this) +{ + unsigned long flags; + const char *level; + + spin_lock_irqsave(&this->lock, flags); + level = this->level; + spin_unlock_irqrestore(&this->lock, flags); + + return level; +} + +static void test_stream_set_level(struct test_stream *this, const char *level) +{ + unsigned long flags; + + spin_lock_irqsave(&this->lock, flags); + this->level = level; + spin_unlock_irqrestore(&this->lock, flags); +} + +static void test_stream_add(struct test_stream *this, const char *fmt, ...) +{ + va_list args; + struct string_stream *stream = this->internal_stream; + + va_start(args, fmt); + if (stream->vadd(stream, fmt, args) < 0) + test_err(this->test, "Failed to allocate fragment: %s", fmt); + + va_end(args); +} + +static void test_stream_append(struct test_stream *this, + struct test_stream *other) +{ + struct string_stream *other_stream = other->internal_stream; + const char *other_content; + + other_content = other_stream->get_string(other_stream); + + if (!other_content) { + test_err(this->test, + "Failed to get string from second argument for appending."); + return; + } + + this->add(this, other_content); +} + +static void test_stream_clear(struct test_stream *this) +{ + this->internal_stream->clear(this->internal_stream); +} + +static void test_stream_commit(struct test_stream *this) +{ + struct string_stream *stream = this->internal_stream; + struct string_stream_fragment *fragment; + const char *level; + char *buf; + + level = test_stream_get_level(this); + if (!level) { + test_err(this->test, + "Stream was committed without a specified log level."); + level = KERN_ERR; + this->set_level(this, level); + } + + buf = stream->get_string(stream); + if (!buf) { + test_err(this->test, + "Could not allocate buffer, dumping stream:"); + list_for_each_entry(fragment, &stream->fragments, node) { + test_err(this->test, fragment->fragment); + } + goto cleanup; + } + + test_printk(level, this->test, buf); + kfree(buf); + +cleanup: + this->clear(this); +} + +static int test_stream_init(struct test_resource *res, void *context) +{ + struct test *test = context; + struct test_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + res->allocation = stream; + stream->test = test; + spin_lock_init(&stream->lock); + stream->internal_stream = new_string_stream(); + + if (!stream->internal_stream) + return -ENOMEM; + + stream->set_level = test_stream_set_level; + stream->add = test_stream_add; + stream->append = test_stream_append; + stream->commit = test_stream_commit; + stream->clear = test_stream_clear; + + return 0; +} + +static void test_stream_free(struct test_resource *res) +{ + struct test_stream *stream = res->allocation; + + if (!stream->internal_stream->is_empty(stream->internal_stream)) { + test_err(stream->test, + "End of test case reached with uncommitted stream entries."); + stream->commit(stream); + } + + destroy_string_stream(stream->internal_stream); + kfree(stream); +} + +struct test_stream *test_new_stream(struct test *test) +{ + struct test_resource *res; + + res = test_alloc_resource(test, + test_stream_init, + test_stream_free, + test); + + if (res) + return res->allocation; + else + return NULL; +} diff --git a/kunit/test.c b/kunit/test.c index fd0a51245215e..f798183533c8d 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -63,12 +63,20 @@ static void test_vprintk(const struct test *test, "kunit %s: %pV", test->name, vaf); }
+static void test_fail(struct test *test, struct test_stream *stream) +{ + test_set_success(test, false); + stream->set_level(stream, KERN_ERR); + stream->commit(stream); +} + int test_init_test(struct test *test, const char *name) { spin_lock_init(&test->lock); INIT_LIST_HEAD(&test->resources); test->name = name; test->vprintk = test_vprintk; + test->fail = test_fail;
return 0; }
Add support for expectations, which allow properties to be specified and then verified in tests.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- include/kunit/test.h | 259 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 68320fa2452de..d652825d7296f 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -273,4 +273,263 @@ void __printf(3, 4) test_printk(const char *level, #define test_err(test, fmt, ...) \ test_printk(KERN_ERR, test, fmt, ##__VA_ARGS__)
+static inline struct test_stream *test_expect_start(struct test *test, + const char *file, + const char *line) +{ + struct test_stream *stream = test_new_stream(test); + + stream->add(stream, "EXPECTATION FAILED at %s:%s\n\t", file, line); + + return stream; +} + +static inline void test_expect_end(struct test *test, + bool success, + struct test_stream *stream) +{ + if (!success) + test->fail(test, stream); + else + stream->clear(stream); +} + +#define TEST_EXPECT_START(test) \ + test_expect_start(test, __FILE__, __stringify(__LINE__)) + +#define TEST_EXPECT_END(test, success, stream) \ + test_expect_end(test, success, stream) + +#define TEST_EXPECT(test, success, message) do { \ + struct test_stream *__stream = TEST_EXPECT_START(test); \ + \ + __stream->add(__stream, message); \ + TEST_EXPECT_END(test, success, __stream); \ +} while (0) + +/** + * TEST_SUCCEED() - A no-op expectation. Only exists for code clarity. + * @test: The test context object. + * + * The opposite of TEST_FAIL(), it is an expectation that cannot fail. In other + * words, it does nothing and only exists for code clarity. See + * TEST_EXPECT_TRUE() for more information. + */ +#define TEST_SUCCEED(test) do {} while (0) + +/** + * TEST_FAIL() - Always causes a test to fail when evaluated. + * @test: The test context object. + * @message: an informational message to be printed when the assertion is made. + * + * The opposite of TEST_SUCCEED(), it is an expectation that always fails. In + * other words, it always results in a failed expectation, and consequently + * always causes the test case to fail when evaluated. See TEST_EXPECT_TRUE() + * for more information. + */ +#define TEST_FAIL(test, message) TEST_EXPECT(test, false, message) + +/** + * TEST_EXPECT_TRUE() - Causes a test failure when the expression is not true. + * @test: The test context object. + * @condition: an arbitrary boolean expression. The test fails when this does + * not evaluate to true. + * + * This and expectations of the form `TEST_EXPECT_*` will cause the test case to + * fail when the specified condition is not met; however, it will not prevent + * the test case from continuing to run; this is otherwise known as an + * *expectation failure*. + */ +#define TEST_EXPECT_TRUE(test, condition) \ + TEST_EXPECT(test, (condition), \ + "Expected " #condition " is true, but is false.") + +/** + * TEST_EXPECT_FALSE() - Causes a test failure when the expression is not false. + * @test: The test context object. + * @condition: an arbitrary boolean expression. The test fails when this does + * not evaluate to false. + * + * Sets an expectation that @condition evaluates to false. See + * TEST_EXPECT_TRUE() for more information. + */ +#define TEST_EXPECT_FALSE(test, condition) \ + TEST_EXPECT(test, !(condition), \ + "Expected " #condition " is false, but is true.") + +static inline void test_expect_binary(struct test *test, + long long left, const char *left_name, + long long right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line) +{ + struct test_stream *stream = test_expect_start(test, file, line); + + stream->add(stream, + "Expected %s %s %s, but\n", + left_name, compare_name, right_name); + stream->add(stream, "\t\t%s == %lld\n", left_name, left); + stream->add(stream, "\t\t%s == %lld", right_name, right); + + test_expect_end(test, compare_result, stream); +} + +/* + * A factory macro for defining the expectations for the basic comparisons + * defined for the built in types. + * + * Unfortunately, there is no common type that all types can be promoted to for + * which all the binary operators behave the same way as for the actual types + * (for example, there is no type that long long and unsigned long long can + * both be cast to where the comparison result is preserved for all values). So + * the best we can do is do the comparison in the original types and then coerce + * everything to long long for printing; this way, the comparison behaves + * correctly and the printed out value usually makes sense without + * interpretation, but can always be interpretted to figure out the actual + * value. + */ +#define TEST_EXPECT_BINARY(test, left, condition, right) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + test_expect_binary(test, \ + (long long) __left, #left, \ + (long long) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__)); \ +} while (0) + +/** + * TEST_EXPECT_EQ() - Sets an expectation that @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an expectation that the values that @left and @right evaluate to are + * equal. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, (@left) == (@right)). See TEST_EXPECT_TRUE() for more + * information. + */ +#define TEST_EXPECT_EQ(test, left, right) \ + TEST_EXPECT_BINARY(test, left, ==, right) + +/** + * TEST_EXPECT_NE() - An expectation that @left and @right are not equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an expectation that the values that @left and @right evaluate to are not + * equal. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, (@left) != (@right)). See TEST_EXPECT_TRUE() for more + * information. + */ +#define TEST_EXPECT_NE(test, left, right) \ + TEST_EXPECT_BINARY(test, left, !=, right) + +/** + * TEST_EXPECT_LT() - An expectation that @left is less than @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an expectation that the value that @left evaluates to is less than the + * value that @right evaluates to. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, (@left) < (@right)). See TEST_EXPECT_TRUE() for more + * information. + */ +#define TEST_EXPECT_LT(test, left, right) \ + TEST_EXPECT_BINARY(test, left, <, right) + +/** + * TEST_EXPECT_LE() - An expectation that @left is less than or equal to @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an expectation that the value that @left evaluates to is less than or + * equal to the value that @right evaluates to. Semantically this is equivalent + * to TEST_EXPECT_TRUE(@test, (@left) <= (@right)). See TEST_EXPECT_TRUE() for + * more information. + */ +#define TEST_EXPECT_LE(test, left, right) \ + TEST_EXPECT_BINARY(test, left, <=, right) + +/** + * TEST_EXPECT_GT() - An expectation that @left is greater than @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an expectation that the value that @left evaluates to is greater than + * the value that @right evaluates to. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, (@left) > (@right)). See TEST_EXPECT_TRUE() for more + * information. + */ +#define TEST_EXPECT_GT(test, left, right) \ + TEST_EXPECT_BINARY(test, left, >, right) + +/** + * TEST_EXPECT_GE() - Expects that @left is greater than or equal to @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an expectation that the value that @left evaluates to is greater than + * the value that @right evaluates to. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, (@left) >= (@right)). See TEST_EXPECT_TRUE() for more + * information. + */ +#define TEST_EXPECT_GE(test, left, right) \ + TEST_EXPECT_BINARY(test, left, >=, right) + +/** + * TEST_EXPECT_STREQ() - An expectation that strings @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a null terminated string. + * @right: an arbitrary expression that evaluates to a null terminated string. + * + * Sets an expectation that the values that @left and @right evaluate to are + * equal. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, !strcmp((@left), (@right))). See TEST_EXPECT_TRUE() + * for more information. + */ +#define TEST_EXPECT_STREQ(test, left, right) do { \ + struct test_stream *__stream = TEST_EXPECT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + __stream->add(__stream, "Expected " #left " == " #right ", but\n"); \ + __stream->add(__stream, "\t\t%s == %s\n", #left, __left); \ + __stream->add(__stream, "\t\t%s == %s\n", #right, __right); \ + \ + TEST_EXPECT_END(test, !strcmp(left, right), __stream); \ +} while (0) + +/** + * TEST_EXPECT_NOT_ERR_OR_NULL() - Expects that @ptr is not null and not err. + * @test: The test context object. + * @ptr: an arbitrary pointer. + * + * Sets an expectation that the value that @ptr evaluates to is not null and not + * an errno stored in a pointer. This is semantically equivalent to + * TEST_EXPECT_TRUE(@test, !IS_ERR_OR_NULL(@ptr)). See TEST_EXPECT_TRUE() for + * more information. + */ +#define TEST_EXPECT_NOT_ERR_OR_NULL(test, ptr) do { \ + struct test_stream *__stream = TEST_EXPECT_START(test); \ + typeof(ptr) __ptr = (ptr); \ + \ + if (!__ptr) \ + __stream->add(__stream, \ + "Expected " #ptr " is not null, but is."); \ + if (IS_ERR(__ptr)) \ + __stream->add(__stream, \ + "Expected " #ptr " is not error, but is: %ld", \ + PTR_ERR(__ptr)); \ + \ + TEST_EXPECT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ +} while (0) + #endif /* _KUNIT_TEST_H */
Make minimum number of changes outside of the KUnit directories for KUnit to build and run using UML.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- Kconfig | 2 ++ Makefile | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/Kconfig b/Kconfig index 48a80beab6853..10428501edb78 100644 --- a/Kconfig +++ b/Kconfig @@ -30,3 +30,5 @@ source "crypto/Kconfig" source "lib/Kconfig"
source "lib/Kconfig.debug" + +source "kunit/Kconfig" diff --git a/Makefile b/Makefile index 69fa5c0310d83..197f01cbddf03 100644 --- a/Makefile +++ b/Makefile @@ -966,7 +966,7 @@ endif
ifeq ($(KBUILD_EXTMOD),) -core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ +core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ kunit/
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
Add a test for string stream along with a simpler example.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- kunit/Kconfig | 12 ++++++ kunit/Makefile | 4 +- kunit/example-test.c | 88 ++++++++++++++++++++++++++++++++++++++ kunit/string-stream-test.c | 61 ++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 kunit/example-test.c create mode 100644 kunit/string-stream-test.c
diff --git a/kunit/Kconfig b/kunit/Kconfig index 49b44c4f6630a..c3dc7bca83f9d 100644 --- a/kunit/Kconfig +++ b/kunit/Kconfig @@ -14,4 +14,16 @@ config KUNIT special hardware. For more information, please see Documentation/kunit/
+config KUNIT_TEST + bool "KUnit test for KUnit" + depends on KUNIT + help + Enables KUnit test to test KUnit. + +config KUNIT_EXAMPLE_TEST + bool "Example test for KUnit" + depends on KUNIT + help + Enables example KUnit test to demo features of KUnit. + endmenu diff --git a/kunit/Makefile b/kunit/Makefile index 5b4562ea7f322..b437bd546ab70 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1 +1,3 @@ -obj-$(CONFIG_KUNIT) += test.o string-stream.o test-stream.o +obj-$(CONFIG_KUNIT) += test.o string-stream.o test-stream.o +obj-$(CONFIG_KUNIT_TEST) += string-stream-test.o +obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += example-test.o diff --git a/kunit/example-test.c b/kunit/example-test.c new file mode 100644 index 0000000000000..e9bd2b41c5fd2 --- /dev/null +++ b/kunit/example-test.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Example KUnit test to show how to use KUnit. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <kunit/test.h> + +/* + * This is the most fundamental element of KUnit, the test case. A test case + * makes a set EXPECTATIONs and ASSERTIONs about the behavior of some code; if + * any expectations or assertions are not met, the test fails; otherwise, the + * test passes. + * + * In KUnit, a test case is just a function with the signature + * `void (*)(struct test *)`. `struct test` is a context object that stores + * information about the current test. + */ +static void example_simple_test(struct test *test) +{ + /* + * This is an EXPECTATION; it is how KUnit tests things. When you want + * to test a piece of code, you set some expectations about what the + * code should do. KUnit then runs the test and verifies that the code's + * behavior matched what was expected. + */ + TEST_EXPECT_EQ(test, 1 + 1, 2); +} + +/* + * This is run once before each test case, see the comment on + * example_test_module for more information. + */ +static int example_test_init(struct test *test) +{ + test_info(test, "initializing"); + + return 0; +} + +/* + * Here we make a list of all the test cases we want to add to the test module + * below. + */ +static struct test_case example_test_cases[] = { + /* + * This is a helper to create a test case object from a test case + * function; its exact function is not important to understand how to + * use KUnit, just know that this is how you associate test cases with a + * test module. + */ + TEST_CASE(example_simple_test), + {}, +}; + +/* + * This defines a suite or grouping of tests. + * + * Test cases are defined as belonging to the suite by adding them to + * `test_cases`. + * + * Often it is desirable to run some function which will set up things which + * will be used by every test; this is accomplished with an `init` function + * which runs before each test case is invoked. Similarly, an `exit` function + * may be specified which runs after every test case and can be used to for + * cleanup. For clarity, running tests in a test module would behave as follows: + * + * module.init(test); + * module.test_case[0](test); + * module.exit(test); + * module.init(test); + * module.test_case[1](test); + * module.exit(test); + * ...; + */ +static struct test_module example_test_module = { + .name = "example", + .init = example_test_init, + .test_cases = example_test_cases, +}; + +/* + * This registers the above test module telling KUnit that this is a suite of + * tests that need to be run. + */ +module_test(example_test_module); diff --git a/kunit/string-stream-test.c b/kunit/string-stream-test.c new file mode 100644 index 0000000000000..07c626cbfffbf --- /dev/null +++ b/kunit/string-stream-test.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for struct string_stream. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <linux/slab.h> +#include <kunit/test.h> +#include <kunit/string-stream.h> + +static void string_stream_test_get_string(struct test *test) +{ + struct string_stream *stream = new_string_stream(); + char *output; + + stream->add(stream, "Foo"); + stream->add(stream, " %s", "bar"); + + output = stream->get_string(stream); + TEST_EXPECT_STREQ(test, output, "Foo bar"); + kfree(output); + destroy_string_stream(stream); +} + +static void string_stream_test_add_and_clear(struct test *test) +{ + struct string_stream *stream = new_string_stream(); + char *output; + int i; + + for (i = 0; i < 10; i++) + stream->add(stream, "A"); + + output = stream->get_string(stream); + TEST_EXPECT_STREQ(test, output, "AAAAAAAAAA"); + TEST_EXPECT_EQ(test, stream->length, 10); + TEST_EXPECT_FALSE(test, stream->is_empty(stream)); + kfree(output); + + stream->clear(stream); + + output = stream->get_string(stream); + TEST_EXPECT_STREQ(test, output, ""); + TEST_EXPECT_TRUE(test, stream->is_empty(stream)); + destroy_string_stream(stream); +} + +static struct test_case string_stream_test_cases[] = { + TEST_CASE(string_stream_test_get_string), + TEST_CASE(string_stream_test_add_and_clear), + {} +}; + +static struct test_module string_stream_test_module = { + .name = "string-stream-test", + .test_cases = string_stream_test_cases +}; +module_test(string_stream_test_module); +
Add context to current thread that allows a test to specify that it wants to skip the normal checks to run an installed fault catcher.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- arch/um/include/asm/processor-generic.h | 4 +++- arch/um/kernel/trap.c | 15 +++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/arch/um/include/asm/processor-generic.h b/arch/um/include/asm/processor-generic.h index b58b746d3f2ca..d566cd416ff02 100644 --- a/arch/um/include/asm/processor-generic.h +++ b/arch/um/include/asm/processor-generic.h @@ -27,6 +27,7 @@ struct thread_struct { struct task_struct *prev_sched; struct arch_thread arch; jmp_buf switch_buf; + bool is_running_test; struct { int op; union { @@ -51,7 +52,8 @@ struct thread_struct { .fault_addr = NULL, \ .prev_sched = NULL, \ .arch = INIT_ARCH_THREAD, \ - .request = { 0 } \ + .request = { 0 }, \ + .is_running_test = false, \ }
static inline void release_thread(struct task_struct *task) diff --git a/arch/um/kernel/trap.c b/arch/um/kernel/trap.c index cced829460427..bf90e678b3d71 100644 --- a/arch/um/kernel/trap.c +++ b/arch/um/kernel/trap.c @@ -201,6 +201,12 @@ void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs) segv(*fi, UPT_IP(regs), UPT_IS_USER(regs), regs); }
+static void segv_run_catcher(jmp_buf *catcher, void *fault_addr) +{ + current->thread.fault_addr = fault_addr; + UML_LONGJMP(catcher, 1); +} + /* * We give a *copy* of the faultinfo in the regs to segv. * This must be done, since nesting SEGVs could overwrite @@ -219,7 +225,10 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user, if (!is_user && regs) current->thread.segv_regs = container_of(regs, struct pt_regs, regs);
- if (!is_user && (address >= start_vm) && (address < end_vm)) { + catcher = current->thread.fault_catcher; + if (catcher && current->thread.is_running_test) + segv_run_catcher(catcher, (void *) address); + else if (!is_user && (address >= start_vm) && (address < end_vm)) { flush_tlb_kernel_vm(); goto out; } @@ -246,12 +255,10 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user, address = 0; }
- catcher = current->thread.fault_catcher; if (!err) goto out; else if (catcher != NULL) { - current->thread.fault_addr = (void *) address; - UML_LONGJMP(catcher, 1); + segv_run_catcher(catcher, (void *) address); } else if (current->thread.fault_addr != NULL) panic("fault_addr set but no fault catcher");
Add support for assertions which are like expectations except the test terminates if the assertion is not satisfied.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- include/kunit/test.h | 272 ++++++++++++++++++++++++++++++++++++- kunit/Makefile | 2 +- kunit/string-stream-test.c | 12 +- kunit/test-test.c | 37 +++++ kunit/test.c | 131 ++++++++++++++++-- 5 files changed, 436 insertions(+), 18 deletions(-) create mode 100644 kunit/test-test.c
diff --git a/include/kunit/test.h b/include/kunit/test.h index d652825d7296f..49a9d6e43992c 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -84,9 +84,10 @@ struct test; * @name: the name of the test case. * * A test case is a function with the signature, ``void (*)(struct test *)`` - * that makes expectations (see TEST_EXPECT_TRUE()) about code under test. Each - * test case is associated with a &struct test_module and will be run after the - * module's init function and followed by the module's exit function. + * that makes expectations and assertions (see TEST_EXPECT_TRUE() and + * TEST_ASSERT_TRUE()) about code under test. Each test case is associated with + * a &struct test_module and will be run after the module's init function and + * followed by the module's exit function. * * A test case should be static and should only be created with the TEST_CASE() * macro; additionally, every array of test cases should be terminated with an @@ -168,11 +169,14 @@ struct test { const char *name; /* Read only after initialization! */ spinlock_t lock; /* Gaurds all mutable test state. */ bool success; /* Protected by lock. */ + bool death_test; /* Protected by lock. */ struct list_head resources; /* Protected by lock. */ + void (*set_death_test)(struct test *test, bool death_test); void (*vprintk)(const struct test *test, const char *level, struct va_format *vaf); void (*fail)(struct test *test, struct test_stream *stream); + void (*abort)(struct test *test); };
int test_init_test(struct test *test, const char *name); @@ -532,4 +536,266 @@ static inline void test_expect_binary(struct test *test, TEST_EXPECT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ } while (0)
+static inline struct test_stream *test_assert_start(struct test *test, + const char *file, + const char *line) +{ + struct test_stream *stream = test_new_stream(test); + + stream->add(stream, "ASSERTION FAILED at %s:%s\n\t", file, line); + + return stream; +} + +static inline void test_assert_end(struct test *test, + bool success, + struct test_stream *stream) +{ + if (!success) { + test->fail(test, stream); + test->abort(test); + } else { + stream->clear(stream); + } +} + +#define TEST_ASSERT_START(test) \ + test_assert_start(test, __FILE__, __stringify(__LINE__)) + +#define TEST_ASSERT_END(test, success, stream) \ + test_assert_end(test, success, stream) + +#define TEST_ASSERT(test, success, message) do { \ + struct test_stream *__stream = TEST_ASSERT_START(test); \ + \ + __stream->add(__stream, message); \ + TEST_ASSERT_END(test, success, __stream); \ +} while (0) + +#define TEST_ASSERT_FAILURE(test, message) TEST_ASSERT(test, false, message) + +/** + * TEST_ASSERT_TRUE() - Causes an assertion failure when expression is not true. + * @test: The test context object. + * @condition: an arbitrary boolean expression. The test fails and aborts when + * this does not evaluate to true. + * + * This and assertions of the form `TEST_ASSERT_*` will cause the test case to + * fail *and immediately abort* when the specified condition is not met. Unlike + * an expectation failure, it will prevent the test case from continuing to run; + * this is otherwise known as an *assertion failure*. + */ +#define TEST_ASSERT_TRUE(test, condition) \ + TEST_ASSERT(test, (condition), \ + "Asserted " #condition " is true, but is false.") + +/** + * TEST_ASSERT_FALSE() - Sets an assertion that @condition is false. + * @test: The test context object. + * @condition: an arbitrary boolean expression. + * + * Sets an assertion that the value that @condition evaluates to is false. This + * is the same as TEST_EXPECT_FALSE(), except it causes an assertion failure + * (see TEST_ASSERT_TRUE()) when the assertion is not met. + */ +#define TEST_ASSERT_FALSE(test, condition) \ + TEST_ASSERT(test, !(condition), \ + "Asserted " #condition " is false, but is true.") + +static inline void test_assert_binary(struct test *test, + long long left, const char *left_name, + long long right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line) +{ + struct test_stream *stream = test_assert_start(test, file, line); + + stream->add(stream, + "Asserted %s %s %s, but\n", + left_name, compare_name, right_name); + stream->add(stream, "\t\t%s == %lld\n", left_name, left); + stream->add(stream, "\t\t%s == %lld", right_name, right); + + test_assert_end(test, compare_result, stream); +} + +/* + * A factory macro for defining the expectations for the basic comparisons + * defined for the built in types. + * + * Unfortunately, there is no common type that all types can be promoted to for + * which all the binary operators behave the same way as for the actual types + * (for example, there is no type that long long and unsigned long long can + * both be cast to where the comparison result is preserved for all values). So + * the best we can do is do the comparison in the original types and then coerce + * everything to long long for printing; this way, the comparison behaves + * correctly and the printed out value usually makes sense without + * interpretation, but can always be interpretted to figure out the actual + * value. + */ +#define TEST_ASSERT_BINARY(test, left, condition, right) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + test_assert_binary(test, \ + (long long) __left, #left, \ + (long long) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__)); \ +} while (0) + +/** + * TEST_ASSERT_EQ() - Sets an assertion that @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an assertion that the values that @left and @right evaluate to are + * equal. This is the same as TEST_EXPECT_EQ(), except it causes an assertion + * failure (see TEST_ASSERT_TRUE()) when the assertion is not met. + */ +#define TEST_ASSERT_EQ(test, left, right) \ + TEST_ASSERT_BINARY(test, left, ==, right) + +/** + * TEST_ASSERT_NE() - An assertion that @left and @right are not equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an assertion that the values that @left and @right evaluate to are not + * equal. This is the same as TEST_EXPECT_NE(), except it causes an assertion + * failure (see TEST_ASSERT_TRUE()) when the assertion is not met. + */ +#define TEST_ASSERT_NE(test, left, right) \ + TEST_ASSERT_BINARY(test, left, !=, right) + +/** + * TEST_ASSERT_LT() - An assertion that @left is less than @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an assertion that the value that @left evaluates to is less than the + * value that @right evaluates to. This is the same as TEST_EXPECT_LT(), except + * it causes an assertion failure (see TEST_ASSERT_TRUE()) when the assertion is + * not met. + */ +#define TEST_ASSERT_LT(test, left, right) \ + TEST_ASSERT_BINARY(test, left, <, right) + +/** + * TEST_ASSERT_LE() - An assertion that @left is less than or equal to @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an assertion that the value that @left evaluates to is less than or + * equal to the value that @right evaluates to. This is the same as + * TEST_EXPECT_LE(), except it causes an assertion failure (see + * TEST_ASSERT_TRUE()) when the assertion is not met. + */ +#define TEST_ASSERT_LE(test, left, right) \ + TEST_ASSERT_BINARY(test, left, <=, right) + +/** + * TEST_ASSERT_GT() - An assertion that @left is greater than @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an assertion that the value that @left evaluates to is greater than the + * value that @right evaluates to. This is the same as TEST_EXPECT_GT(), except + * it causes an assertion failure (see TEST_ASSERT_TRUE()) when the assertion is + * not met. + */ +#define TEST_ASSERT_GT(test, left, right) \ + TEST_ASSERT_BINARY(test, left, >, right) + +/** + * TEST_ASSERT_GE() - Assertion that @left is greater than or equal to @right. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a primitive C type. + * @right: an arbitrary expression that evaluates to a primitive C type. + * + * Sets an assertion that the value that @left evaluates to is greater than the + * value that @right evaluates to. This is the same as TEST_EXPECT_GE(), except + * it causes an assertion failure (see TEST_ASSERT_TRUE()) when the assertion is + * not met. + */ +#define TEST_ASSERT_GE(test, left, right) \ + TEST_ASSERT_BINARY(test, left, >=, right) + +/** + * TEST_ASSERT_STREQ() - An assertion that strings @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a null terminated string. + * @right: an arbitrary expression that evaluates to a null terminated string. + * + * Sets an assertion that the values that @left and @right evaluate to are + * equal. This is the same as TEST_EXPECT_STREQ(), except it causes an + * assertion failure (see TEST_ASSERT_TRUE()) when the assertion is not met. + */ +#define TEST_ASSERT_STREQ(test, left, right) do { \ + struct test_stream *__stream = TEST_ASSERT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + __stream->add(__stream, "Asserted " #left " == " #right ", but\n"); \ + __stream->add(__stream, "\t\t%s == %s\n", #left, __left); \ + __stream->add(__stream, "\t\t%s == %s\n", #right, __right); \ + \ + TEST_ASSERT_END(test, !strcmp(left, right), __stream); \ +} while (0) + +/** + * TEST_ASSERT_NOT_ERR_OR_NULL() - Assertion that @ptr is not null and not err. + * @test: The test context object. + * @ptr: an arbitrary pointer. + * + * Sets an assertion that the value that @ptr evaluates to is not null and not + * an errno stored in a pointer. This is the same as + * TEST_EXPECT_NOT_ERR_OR_NULL(), except it causes an assertion failure (see + * TEST_ASSERT_TRUE()) when the assertion is not met. + */ +#define TEST_ASSERT_NOT_ERR_OR_NULL(test, ptr) do { \ + struct test_stream *__stream = TEST_ASSERT_START(test); \ + typeof(ptr) __ptr = (ptr); \ + \ + if (!__ptr) \ + __stream->add(__stream, \ + "Asserted " #ptr " is not null, but is."); \ + if (IS_ERR(__ptr)) \ + __stream->add(__stream, \ + "Asserted " #ptr " is not error, but is: %ld", \ + PTR_ERR(__ptr)); \ + \ + TEST_ASSERT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ +} while (0) + +/** + * TEST_ASSERT_SIGSEGV() - An assertion that @expr will cause a segfault. + * @test: The test context object. + * @expr: an arbitrary block of code. + * + * Sets an assertion that @expr, when evaluated, will cause a segfault. + * Currently this assertion is only really useful for testing the KUnit + * framework, as a segmentation fault in normal kernel code is always incorrect. + * However, the plan is to replace this assertion with an arbitrary death + * assertion similar to + * https://github.com/google/googletest/blob/master/googletest/docs/advanced.md... + * which will probably be massaged to make sense in the context of the kernel + * (maybe assert that a panic occurred, or that BUG() was called). + * + * NOTE: no code after this assertion will ever be executed. + */ +#define TEST_ASSERT_SIGSEGV(test, expr) do { \ + test->set_death_test(test, true); \ + expr; \ + test->set_death_test(test, false); \ + TEST_ASSERT_FAILURE(test, \ + "Asserted that " #expr " would cause death, but did not.");\ +} while (0) + #endif /* _KUNIT_TEST_H */ diff --git a/kunit/Makefile b/kunit/Makefile index b437bd546ab70..919fa6faf9940 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1,3 +1,3 @@ obj-$(CONFIG_KUNIT) += test.o string-stream.o test-stream.o -obj-$(CONFIG_KUNIT_TEST) += string-stream-test.o +obj-$(CONFIG_KUNIT_TEST) += test-test.o string-stream-test.o obj-$(CONFIG_KUNIT_EXAMPLE_TEST) += example-test.o diff --git a/kunit/string-stream-test.c b/kunit/string-stream-test.c index 07c626cbfffbf..5947fada67d96 100644 --- a/kunit/string-stream-test.c +++ b/kunit/string-stream-test.c @@ -19,7 +19,7 @@ static void string_stream_test_get_string(struct test *test) stream->add(stream, " %s", "bar");
output = stream->get_string(stream); - TEST_EXPECT_STREQ(test, output, "Foo bar"); + TEST_ASSERT_STREQ(test, output, "Foo bar"); kfree(output); destroy_string_stream(stream); } @@ -34,16 +34,16 @@ static void string_stream_test_add_and_clear(struct test *test) stream->add(stream, "A");
output = stream->get_string(stream); - TEST_EXPECT_STREQ(test, output, "AAAAAAAAAA"); - TEST_EXPECT_EQ(test, stream->length, 10); - TEST_EXPECT_FALSE(test, stream->is_empty(stream)); + TEST_ASSERT_STREQ(test, output, "AAAAAAAAAA"); + TEST_ASSERT_EQ(test, stream->length, 10); + TEST_ASSERT_FALSE(test, stream->is_empty(stream)); kfree(output);
stream->clear(stream);
output = stream->get_string(stream); - TEST_EXPECT_STREQ(test, output, ""); - TEST_EXPECT_TRUE(test, stream->is_empty(stream)); + TEST_ASSERT_STREQ(test, output, ""); + TEST_ASSERT_TRUE(test, stream->is_empty(stream)); destroy_string_stream(stream); }
diff --git a/kunit/test-test.c b/kunit/test-test.c new file mode 100644 index 0000000000000..fd4b90208f0c3 --- /dev/null +++ b/kunit/test-test.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for core test infrastructure. + * + * Copyright (C) 2018, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ +#include <kunit/test.h> + +static void test_test_catches_segfault(struct test *test) +{ + void (*invalid_func)(void) = (void (*)(void)) SIZE_MAX; + + TEST_ASSERT_SIGSEGV(test, invalid_func()); +} + +static int test_test_init(struct test *test) +{ + return 0; +} + +static void test_test_exit(struct test *test) +{ +} + +static struct test_case test_test_cases[] = { + TEST_CASE(test_test_catches_segfault), + {}, +}; + +static struct test_module test_test_module = { + .name = "test-test", + .init = test_test_init, + .exit = test_test_exit, + .test_cases = test_test_cases, +}; +module_test(test_test_module); diff --git a/kunit/test.c b/kunit/test.c index f798183533c8d..f89cfaaf5eb79 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -32,6 +32,27 @@ static void test_set_success(struct test *test, bool success) spin_unlock_irqrestore(&test->lock, flags); }
+static bool test_get_death_test(struct test *test) +{ + unsigned long flags; + bool death_test; + + spin_lock_irqsave(&test->lock, flags); + death_test = test->death_test; + spin_unlock_irqrestore(&test->lock, flags); + + return death_test; +} + +static void test_set_death_test(struct test *test, bool death_test) +{ + unsigned long flags; + + spin_lock_irqsave(&test->lock, flags); + test->death_test = death_test; + spin_unlock_irqrestore(&test->lock, flags); +} + static int test_vprintk_emit(const struct test *test, int level, const char *fmt, @@ -70,13 +91,34 @@ static void test_fail(struct test *test, struct test_stream *stream) stream->commit(stream); }
+static void __noreturn test_abort(struct test *test) +{ + test_set_death_test(test, true); + if (current->thread.fault_catcher && current->thread.is_running_test) + UML_LONGJMP(current->thread.fault_catcher, 1); + + /* + * Attempted to abort from a not properly initialized test context. + */ + test_err(test, + "Attempted to abort from a not properly initialized test context!"); + if (!current->thread.fault_catcher) + test_err(test, "No fault_catcher present!"); + if (!current->thread.is_running_test) + test_err(test, "is_running_test not set!"); + show_stack(NULL, NULL); + BUG(); +} + int test_init_test(struct test *test, const char *name) { spin_lock_init(&test->lock); INIT_LIST_HEAD(&test->resources); test->name = name; + test->set_death_test = test_set_death_test; test->vprintk = test_vprintk; test->fail = test_fail; + test->abort = test_abort;
return 0; } @@ -122,16 +164,89 @@ static void test_run_case_cleanup(struct test *test, }
/* - * Performs all logic to run a test case. + * Handles an unexpected crash in a test case. */ -static bool test_run_case(struct test *test, - struct test_module *module, - struct test_case *test_case) +static void test_handle_test_crash(struct test *test, + struct test_module *module, + struct test_case *test_case) { - test_set_success(test, true); + test_err(test, "%s crashed", test_case->name); + /* + * TODO(brendanhiggins@google.com): This prints the stack trace up + * through this frame, not up to the frame that caused the crash. + */ + show_stack(NULL, NULL); + + test_case_internal_cleanup(test); +}
- test_run_case_internal(test, module, test_case); - test_run_case_cleanup(test, module, test_case); +/* + * Performs all logic to run a test case. It also catches most errors that + * occurs in a test case and reports them as failures. + * + * XXX: THIS DOES NOT FOLLOW NORMAL CONTROL FLOW. READ CAREFULLY!!! + */ +static bool test_run_case_catch_errors(struct test *test, + struct test_module *module, + struct test_case *test_case) +{ + jmp_buf fault_catcher; + int faulted; + + test_set_success(test, true); + test_set_death_test(test, false); + + /* + * Tell the trap subsystem that we want to catch any segfaults that + * occur. + */ + current->thread.is_running_test = true; + current->thread.fault_catcher = &fault_catcher; + + /* + * ENTER HANDLER: If a failure occurs, we enter here. + */ + faulted = UML_SETJMP(&fault_catcher); + if (faulted == 0) { + /* + * NORMAL CASE: we have not run test_run_case_internal yet. + * + * test_run_case_internal may encounter a fatal error; if it + * does, we will jump to ENTER_HANDLER above instead of + * continuing normal control flow. + */ + test_run_case_internal(test, module, test_case); + /* + * This line may never be reached. + */ + test_run_case_cleanup(test, module, test_case); + } else if (test_get_death_test(test)) { + /* + * EXPECTED DEATH: test_run_case_internal encountered + * anticipated fatal error. Everything should be in a safe + * state. + */ + test_run_case_cleanup(test, module, test_case); + } else { + /* + * UNEXPECTED DEATH: test_run_case_internal encountered an + * unanticipated fatal error. We have no idea what the state of + * the test case is in. + */ + test_handle_test_crash(test, module, test_case); + test_set_success(test, false); + } + /* + * EXIT HANDLER: test case has been run and all possible errors have + * been handled. + */ + + /* + * Tell the trap subsystem that we no longer want to catch any + * segfaults. + */ + current->thread.fault_catcher = NULL; + current->thread.is_running_test = false;
return test_get_success(test); } @@ -148,7 +263,7 @@ int test_run_tests(struct test_module *module) return ret;
for (test_case = module->test_cases; test_case->run_case; test_case++) { - success = test_run_case(&test, module, test_case); + success = test_run_case_catch_errors(&test, module, test_case); if (!success) all_passed = false;
The ultimate goal is to create minimal isolated test binaries; in the meantime we are using UML to provide the infrastructure to run tests, so define an abstract way to configure and run tests that allow us to change the context in which tests are built without affecting the user. This also makes pretty and dynamic error reporting, and a lot of other nice features easier.
kunit_config.py: - parse .config and Kconfig files.
kunit_kernel.py: provides helper functions to: - configure the kernel using kunitconfig. - build the kernel with the appropriate configuration. - provide function to invoke the kernel and stream the output back.
Signed-off-by: Felix Guo felixguoxiuping@gmail.com Signed-off-by: Brendan Higgins brendanhiggins@google.com --- tools/testing/kunit/.gitignore | 3 + tools/testing/kunit/kunit_config.py | 60 ++++++++++++++ tools/testing/kunit/kunit_kernel.py | 123 ++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 tools/testing/kunit/.gitignore create mode 100644 tools/testing/kunit/kunit_config.py create mode 100644 tools/testing/kunit/kunit_kernel.py
diff --git a/tools/testing/kunit/.gitignore b/tools/testing/kunit/.gitignore new file mode 100644 index 0000000000000..c791ff59a37a9 --- /dev/null +++ b/tools/testing/kunit/.gitignore @@ -0,0 +1,3 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] \ No newline at end of file diff --git a/tools/testing/kunit/kunit_config.py b/tools/testing/kunit/kunit_config.py new file mode 100644 index 0000000000000..183bd5e758762 --- /dev/null +++ b/tools/testing/kunit/kunit_config.py @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 + +import collections +import re + +CONFIG_IS_NOT_SET_PATTERN = r'^# CONFIG_\w+ is not set$' +CONFIG_PATTERN = r'^CONFIG_\w+=\S+$' + +KconfigEntryBase = collections.namedtuple('KconfigEntry', ['raw_entry']) + + +class KconfigEntry(KconfigEntryBase): + + def __str__(self) -> str: + return self.raw_entry + + +class KconfigParseError(Exception): + """Error parsing Kconfig defconfig or .config.""" + + +class Kconfig(object): + """Represents defconfig or .config specified using the Kconfig language.""" + + def __init__(self): + self._entries = [] + + def entries(self): + return set(self._entries) + + def add_entry(self, entry: KconfigEntry) -> None: + self._entries.append(entry) + + def is_subset_of(self, other: "Kconfig") -> bool: + return self.entries().issubset(other.entries()) + + def write_to_file(self, path: str) -> None: + with open(path, 'w') as f: + for entry in self.entries(): + f.write(str(entry) + '\n') + + def parse_from_string(self, blob: str) -> None: + """Parses a string containing KconfigEntrys and populates this Kconfig.""" + self._entries = [] + is_not_set_matcher = re.compile(CONFIG_IS_NOT_SET_PATTERN) + config_matcher = re.compile(CONFIG_PATTERN) + for line in blob.split('\n'): + line = line.strip() + if not line: + continue + elif config_matcher.match(line) or is_not_set_matcher.match(line): + self._entries.append(KconfigEntry(line)) + elif line[0] == '#': + continue + else: + raise KconfigParseError('Failed to parse: ' + line) + + def read_from_file(self, path: str) -> None: + with open(path, 'r') as f: + self.parse_from_string(f.read()) diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py new file mode 100644 index 0000000000000..87abaede50513 --- /dev/null +++ b/tools/testing/kunit/kunit_kernel.py @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: GPL-2.0 + +import logging +import subprocess +import os + +import kunit_config + +KCONFIG_PATH = '.config' + +class ConfigError(Exception): + """Represents an error trying to configure the Linux kernel.""" + + +class BuildError(Exception): + """Represents an error trying to build the Linux kernel.""" + + +class LinuxSourceTreeOperations(object): + """An abstraction over command line operations performed on a source tree.""" + + def make_mrproper(self): + try: + subprocess.check_output(['make', 'mrproper']) + except OSError as e: + raise ConfigError('Could not call make command: ' + e) + except subprocess.CalledProcessError as e: + raise ConfigError(e.output) + + def make_olddefconfig(self): + try: + subprocess.check_output(['make', 'ARCH=um', 'olddefconfig']) + except OSError as e: + raise ConfigError('Could not call make command: ' + e) + except subprocess.CalledProcessError as e: + raise ConfigError(e.output) + + def make(self): + try: + subprocess.check_output(['make', 'ARCH=um']) + except OSError as e: + raise BuildError('Could not call execute make: ' + e) + except subprocess.CalledProcessError as e: + raise BuildError(e.output) + + def linux_bin(self, params, timeout): + """Runs the Linux UML binary. Must be named 'linux'.""" + process = subprocess.Popen( + ['./linux'] + params, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + process.wait(timeout=timeout) + return process + + +class LinuxSourceTree(object): + """Represents a Linux kernel source tree with KUnit tests.""" + + def __init__(self): + self._kconfig = kunit_config.Kconfig() + self._kconfig.read_from_file('kunitconfig') + self._ops = LinuxSourceTreeOperations() + + def clean(self): + try: + self._ops.make_mrproper() + except ConfigError as e: + logging.error(e) + return False + return True + + def build_config(self): + self._kconfig.write_to_file(KCONFIG_PATH) + try: + self._ops.make_olddefconfig() + except ConfigError as e: + logging.error(e) + return False + validated_kconfig = kunit_config.Kconfig() + validated_kconfig.read_from_file(KCONFIG_PATH) + if not self._kconfig.is_subset_of(validated_kconfig): + logging.error('Provided Kconfig is not contained in validated .config!') + return False + return True + + def build_reconfig(self): + """Creates a new .config if it is not a subset of the kunitconfig.""" + if os.path.exists(KCONFIG_PATH): + existing_kconfig = kunit_config.Kconfig() + existing_kconfig.read_from_file(KCONFIG_PATH) + if not self._kconfig.is_subset_of(existing_kconfig): + print('Regenerating .config ...') + os.remove(KCONFIG_PATH) + return self.build_config() + else: + return True + else: + print('Generating .config ...') + return self.build_config() + + def build_um_kernel(self): + try: + self._ops.make_olddefconfig() + self._ops.make() + except (ConfigError, BuildError) as e: + logging.error(e) + return False + used_kconfig = kunit_config.Kconfig() + used_kconfig.read_from_file(KCONFIG_PATH) + if not self._kconfig.is_subset_of(used_kconfig): + logging.error('Provided Kconfig is not contained in final config!') + return False + return True + + def run_kernel(self, args=[]): + timeout = None + args.extend(['mem=256M']) + process = self._ops.linux_bin(args, timeout) + with open('test.log', 'w') as f: + for line in process.stdout: + f.write(line.rstrip().decode('ascii') + '\n') + yield line.rstrip().decode('ascii')
The KUnit wrapper script interfaces with the two modules (kunit_config.py and kunit_kernel.py) and provides a command line interface for running KUnit tests. This interface allows the caller to specify options like test timeouts. The script handles configuring, building and running the kernel and tests.
The output parser (kunit_parser.py) simply strips out all the output from the kernel that is outputted as part of it's initialization sequence. This ensures that only the output from KUnit is displayed on the screen.
A full version of the output is written to test.log, or can be seen by passing --raw_output to the wrapper script.
Signed-off-by: Felix Guo felixguoxiuping@gmail.com Signed-off-by: Brendan Higgins brendanhiggins@google.com --- tools/testing/kunit/kunit.py | 40 +++++++++++++++++++++++++++++ tools/testing/kunit/kunit_kernel.py | 3 +-- tools/testing/kunit/kunit_parser.py | 24 +++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100755 tools/testing/kunit/kunit.py create mode 100644 tools/testing/kunit/kunit_parser.py
diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py new file mode 100755 index 0000000000000..1356be404996b --- /dev/null +++ b/tools/testing/kunit/kunit.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 + +# A thin wrapper on top of the KUnit Kernel + +import argparse +import sys +import os + +import kunit_config +import kunit_kernel +import kunit_parser + +parser = argparse.ArgumentParser(description='Runs KUnit tests.') + +parser.add_argument('--raw_output', help='don't format output from kernel', + action='store_true') + +parser.add_argument('--timeout', help='maximum number of seconds to allow for ' + 'all tests to run. This does not include time taken to ' + 'build the tests.', type=int, default=300, + metavar='timeout') + +cli_args = parser.parse_args() +linux = kunit_kernel.LinuxSourceTree() + +success = linux.build_reconfig() +if not success: + quit() + +print('Building KUnit Kernel ...') +success = linux.build_um_kernel() +if not success: + quit() + +print('Starting KUnit Kernel ...') +if cli_args.raw_output: + kunit_parser.raw_output(linux.run_kernel(timeout=cli_args.timeout)) +else: + kunit_parser.parse_run_tests(linux.run_kernel(timeout=cli_args.timeout)) diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py index 87abaede50513..c1259b174f7d4 100644 --- a/tools/testing/kunit/kunit_kernel.py +++ b/tools/testing/kunit/kunit_kernel.py @@ -113,8 +113,7 @@ class LinuxSourceTree(object): return False return True
- def run_kernel(self, args=[]): - timeout = None + def run_kernel(self, args=[], timeout=None): args.extend(['mem=256M']) process = self._ops.linux_bin(args, timeout) with open('test.log', 'w') as f: diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py new file mode 100644 index 0000000000000..1dff3adb73bd3 --- /dev/null +++ b/tools/testing/kunit/kunit_parser.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +import re + +kunit_start_re = re.compile('console .* enabled') +kunit_end_re = re.compile('List of all partitions:') + +def isolate_kunit_output(kernel_output): + started = False + for line in kernel_output: + if kunit_start_re.match(line): + started = True + elif kunit_end_re.match(line): + break + elif started: + yield line + +def raw_output(kernel_output): + for line in kernel_output: + print(line) + +def parse_run_tests(kernel_output): + for output in isolate_kunit_output(kernel_output): + print(output)
- add colors to displayed output - add timing and summary
Signed-off-by: Felix Guo felixguoxiuping@gmail.com Signed-off-by: Brendan Higgins brendanhiggins@google.com --- tools/testing/kunit/kunit.py | 20 ++++++- tools/testing/kunit/kunit_parser.py | 93 ++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 4 deletions(-)
diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index 1356be404996b..b36c7b7924567 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -6,6 +6,7 @@ import argparse import sys import os +import time
import kunit_config import kunit_kernel @@ -24,17 +25,32 @@ parser.add_argument('--timeout', help='maximum number of seconds to allow for ' cli_args = parser.parse_args() linux = kunit_kernel.LinuxSourceTree()
+config_start = time.time() success = linux.build_reconfig() +config_end = time.time() if not success: quit()
-print('Building KUnit Kernel ...') +kunit_parser.print_with_timestamp('Building KUnit Kernel ...') + +build_start = time.time() success = linux.build_um_kernel() +build_end = time.time() if not success: quit()
-print('Starting KUnit Kernel ...') +kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') +test_start = time.time() + if cli_args.raw_output: kunit_parser.raw_output(linux.run_kernel(timeout=cli_args.timeout)) else: kunit_parser.parse_run_tests(linux.run_kernel(timeout=cli_args.timeout)) + +test_end = time.time() + +kunit_parser.print_with_timestamp(( + "Elapsed time: %.3fs total, %.3fs configuring, %.3fs " + + "building, %.3fs running.\n") % (test_end - config_start, + config_end - config_start, build_end - build_start, + test_end - test_start)) diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py index 1dff3adb73bd3..d9051e407d5a7 100644 --- a/tools/testing/kunit/kunit_parser.py +++ b/tools/testing/kunit/kunit_parser.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0
import re +from datetime import datetime
kunit_start_re = re.compile('console .* enabled') kunit_end_re = re.compile('List of all partitions:') @@ -19,6 +20,94 @@ def raw_output(kernel_output): for line in kernel_output: print(line)
+DIVIDER = "=" * 30 + +RESET = '\033[0;0m' + +def red(text): + return '\033[1;31m' + text + RESET + +def yellow(text): + return '\033[1;33m' + text + RESET + +def green(text): + return '\033[1;32m' + text + RESET + +def print_with_timestamp(message): + print('[%s] %s' % (datetime.now().strftime('%H:%M:%S'), message)) + +def print_log(log): + for m in log: + print_with_timestamp(m) + def parse_run_tests(kernel_output): - for output in isolate_kunit_output(kernel_output): - print(output) + test_case_output = re.compile('^kunit .*?: (.*)$') + + test_module_success = re.compile('^kunit .*: all tests passed') + test_module_fail = re.compile('^kunit .*: one or more tests failed') + + test_case_success = re.compile('^kunit (.*): (.*) passed') + test_case_fail = re.compile('^kunit (.*): (.*) failed') + test_case_crash = re.compile('^kunit (.*): (.*) crashed') + + total_tests = set() + failed_tests = set() + crashed_tests = set() + + def get_test_name(match): + return match.group(1) + ":" + match.group(2) + + current_case_log = [] + def end_one_test(match, log): + log.clear() + total_tests.add(get_test_name(match)) + + print_with_timestamp(DIVIDER) + for line in isolate_kunit_output(kernel_output): + # Ignore module output: + if (test_module_success.match(line) or + test_module_fail.match(line)): + print_with_timestamp(DIVIDER) + continue + + match = re.match(test_case_success, line) + if match: + print_with_timestamp(green("[PASSED] ") + + get_test_name(match)) + end_one_test(match, current_case_log) + continue + + match = re.match(test_case_fail, line) + # Crashed tests will report as both failed and crashed. We only + # want to show and count it once. + if match and get_test_name(match) not in crashed_tests: + failed_tests.add(get_test_name(match)) + print_with_timestamp(red("[FAILED] " + + get_test_name(match))) + print_log(map(yellow, current_case_log)) + print_with_timestamp("") + end_one_test(match, current_case_log) + continue + + match = re.match(test_case_crash, line) + if match: + crashed_tests.add(get_test_name(match)) + print_with_timestamp(yellow("[CRASH] " + + get_test_name(match))) + print_log(current_case_log) + print_with_timestamp("") + end_one_test(match, current_case_log) + continue + + # Strip off the `kunit module-name:` prefix + match = re.match(test_case_output, line) + if match: + current_case_log.append(match.group(1)) + else: + current_case_log.append(line) + + fmt = green if (len(failed_tests) + len(crashed_tests) == 0) else red + print_with_timestamp( + fmt("Testing complete. %d tests run. %d failed. %d crashed." % + (len(total_tests), len(failed_tests), len(crashed_tests)))) +
Add documentation for KUnit, the Linux kernel unit testing framework. - Add intro and usage guide for KUnit - Add API reference
Signed-off-by: Felix Guo felixguoxiuping@gmail.com Signed-off-by: Brendan Higgins brendanhiggins@google.com --- Documentation/index.rst | 1 + Documentation/kunit/api/index.rst | 16 ++ Documentation/kunit/api/test.rst | 15 + Documentation/kunit/faq.rst | 46 +++ Documentation/kunit/index.rst | 80 ++++++ Documentation/kunit/start.rst | 180 ++++++++++++ Documentation/kunit/usage.rst | 447 ++++++++++++++++++++++++++++++ 7 files changed, 785 insertions(+) create mode 100644 Documentation/kunit/api/index.rst create mode 100644 Documentation/kunit/api/test.rst create mode 100644 Documentation/kunit/faq.rst create mode 100644 Documentation/kunit/index.rst create mode 100644 Documentation/kunit/start.rst create mode 100644 Documentation/kunit/usage.rst
diff --git a/Documentation/index.rst b/Documentation/index.rst index 5db7e87c7cb1d..275ef4db79f61 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -68,6 +68,7 @@ merged much easier. kernel-hacking/index trace/index maintainer/index + kunit/index
Kernel API documentation ------------------------ diff --git a/Documentation/kunit/api/index.rst b/Documentation/kunit/api/index.rst new file mode 100644 index 0000000000000..c31c530088153 --- /dev/null +++ b/Documentation/kunit/api/index.rst @@ -0,0 +1,16 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============= +API Reference +============= +.. toctree:: + + test + +This section documents the KUnit kernel testing API. It is divided into 3 +sections: + +================================= ============================================== +:doc:`test` documents all of the standard testing API + excluding mocking or mocking related features. +================================= ============================================== diff --git a/Documentation/kunit/api/test.rst b/Documentation/kunit/api/test.rst new file mode 100644 index 0000000000000..7f22db32536eb --- /dev/null +++ b/Documentation/kunit/api/test.rst @@ -0,0 +1,15 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======== +Test API +======== + +This file documents all of the standard testing API excluding mocking or mocking +related features. + +.. kernel-doc:: include/kunit/test.h + :internal: + +.. kernel-doc:: include/kunit/test-stream.h + :internal: + diff --git a/Documentation/kunit/faq.rst b/Documentation/kunit/faq.rst new file mode 100644 index 0000000000000..cb8e4fb2257a0 --- /dev/null +++ b/Documentation/kunit/faq.rst @@ -0,0 +1,46 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================= +Frequently Asked Questions +========================================= + +How is this different from Autotest, kselftest, etc? +==================================================== +KUnit is a unit testing framework. Autotest, kselftest (and some others) are +not. + +A `unit test https://martinfowler.com/bliki/UnitTest.html`_ is supposed to +test a single unit of code in isolation, hence the name. A unit test should be +the finest granularity of testing and as such should allow all possible code +paths to be tested in the code under test; this is only possible if the code +under test is very small and does not have any external dependencies outside of +the test's control like hardware. + +There are no testing frameworks currently available for the kernel that do not +require installing the kernel on a test machine or in a VM and all require +tests to be written in userspace and run on the kernel under test; this is true +for Autotest, kselftest, and some others, disqualifying any of them from being +considered unit testing frameworks. + +What is the difference between a unit test and these other kinds of tests? +========================================================================== +Most existing tests for the Linux kernel would be categorized as an integration +test, or an end-to-end test. + +- A unit test is supposed to test a single unit of code in isolation, hence the + name. A unit test should be the finest granularity of testing and as such + should allow all possible code paths to be tested in the code under test; this + is only possible if the code under test is very small and does not have any + external dependencies outside of the test's control like hardware. +- An integration test tests the interaction between a minimal set of components, + usually just two or three. For example, someone might write an integration + test to test the interaction between a driver and a piece of hardware, or to + test the interaction between the userspace libraries the kernel provides and + the kernel itself; however, one of these tests would probably not test the + entire kernel along with hardware interactions and interactions with the + userspace. +- An end-to-end test usually tests the entire system from the perspective of the + code under test. For example, someone might write an end-to-end test for the + kernel by installing a production configuration of the kernel on production + hardware with a production userspace and then trying to exercise some behavior + that depends on interactions between the hardware, the kernel, and userspace. diff --git a/Documentation/kunit/index.rst b/Documentation/kunit/index.rst new file mode 100644 index 0000000000000..c6710211b647f --- /dev/null +++ b/Documentation/kunit/index.rst @@ -0,0 +1,80 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================= +KUnit - Unit Testing for the Linux Kernel +========================================= + +.. toctree:: + :maxdepth: 2 + + start + usage + api/index + faq + +What is KUnit? +============== + +KUnit is a lightweight unit testing and mocking framework for the Linux kernel. +These tests are able to be run locally on a developer's workstation without a VM +or special hardware. + +KUnit is heavily inspired by JUnit, Python's unittest.mock, and +Googletest/Googlemock for C++. KUnit provides facilities for defining unit test +cases, grouping related test cases into test suites, providing common +infrastructure for running tests, and much more. + +Get started now: :doc:`start` + +Why KUnit? +========== + +A unit test is supposed to test a single unit of code in isolation, hence the +name. A unit test should be the finest granularity of testing and as such should +allow all possible code paths to be tested in the code under test; this is only +possible if the code under test is very small and does not have any external +dependencies outside of the test's control like hardware. + +Outside of KUnit, there are no testing frameworks currently +available for the kernel that do not require installing the kernel on a test +machine or in a VM and all require tests to be written in userspace running on +the kernel; this is true for Autotest, and kselftest, disqualifying +any of them from being considered unit testing frameworks. + +KUnit addresses the problem of being able to run tests without needing a virtual +machine or actual hardware with User Mode Linux. User Mode Linux is a Linux +architecture, like ARM or x86; however, unlike other architectures it compiles +to a standalone program that can be run like any other program directly inside +of a host operating system; to be clear, it does not require any virtualization +support; it is just a regular program. + +KUnit is fast. Excluding build time, from invocation to completion KUnit can run +several dozen tests in only 10 to 20 seconds; this might not sound like a big +deal to some people, but having such fast and easy to run tests fundamentally +changes the way you go about testing and even writing code in the first place. +Linus himself said in his `git talk at Google +https://gist.github.com/lorn/1272686/revisions#diff-53c65572127855f1b003db4064a94573R874`_: + + "... a lot of people seem to think that performance is about doing the + same thing, just doing it faster, and that is not true. That is not what + performance is all about. If you can do something really fast, really + well, people will start using it differently." + +In this context Linus was talking about branching and merging, +but this point also applies to testing. If your tests are slow, unreliable, are +difficult to write, and require a special setup or special hardware to run, +then you wait a lot longer to write tests, and you wait a lot longer to run +tests; this means that tests are likely to break, unlikely to test a lot of +things, and are unlikely to be rerun once they pass. If your tests are really +fast, you run them all the time, every time you make a change, and every time +someone sends you some code. Why trust that someone ran all their tests +correctly on every change when you can just run them yourself in less time than +it takes to read his / her test log? + +How do I use it? +=================== + +* :doc:`start` - for new users of KUnit +* :doc:`usage` - for a more detailed explanation of KUnit features +* :doc:`api/index` - for the list of KUnit APIs used for testing + diff --git a/Documentation/kunit/start.rst b/Documentation/kunit/start.rst new file mode 100644 index 0000000000000..bdae8e7f84b04 --- /dev/null +++ b/Documentation/kunit/start.rst @@ -0,0 +1,180 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============== +Getting Started +=============== + +Installing dependencies +======================= +KUnit has the same dependencies as the Linux kernel. As long as you can build +the kernel, you can run KUnit. + +KUnit Wrapper +============= +Included with KUnit is a simple Python wrapper that helps format the output to +easily use and read KUnit output. It handles building and running the kernel, as +well as formatting the output. + +The wrapper can be run with: + +.. code-block:: bash + + ./tools/testing/kunit/kunit.py + +Creating a kunitconfig +====================== +The Python script is a thin wrapper around Kbuild as such, it needs to be +configured with a ``kunitconfig`` file. This file essentially contains the +regular Kernel config, with the specific test targets as well. + +.. code-block:: bash + + git clone -b master https://kunit.googlesource.com/kunitconfig $PATH_TO_KUNITCONFIG_REPO + cd $PATH_TO_LINUX_REPO + ln -s $PATH_TO_KUNIT_CONFIG_REPO/kunitconfig kunitconfig + +You may want to add kunitconfig to your local gitignore. + +Verifying KUnit Works +------------------------- + +To make sure that everything is set up correctly, simply invoke the Python +wrapper from your kernel repo: + +.. code-block:: bash + + ./tools/testing/kunit/kunit.py + +.. note:: + You may want to run ``make mrproper`` first. + +If everything worked correctly, you should see the following: + +.. code-block:: bash + + Generating .config ... + Building KUnit Kernel ... + Starting KUnit Kernel ... + +followed by a list of tests that are run. All of them should be passing. + +.. note:: + Because it is building a lot of sources for the first time, the ``Building + kunit kernel`` step may take a while. + +Writing your first test +========================== + +In your kernel repo let's add some code that we can test. Create a file +``drivers/misc/example.h`` with the contents: + +.. code-block:: c + + int misc_example_add(int left, int right); + +create a file ``drivers/misc/example.c``: + +.. code-block:: c + + #include <linux/errno.h> + + #include "example.h" + + int misc_example_add(int left, int right) + { + return left + right; + } + +Now add the following lines to ``drivers/misc/Kconfig``: + +.. code-block:: kconfig + + config MISC_EXAMPLE + bool "My example" + +and the following lines to ``drivers/misc/Makefile``: + +.. code-block:: make + + obj-$(CONFIG_MISC_EXAMPLE) += example.o + +Now we are ready to write the test. The test will be in +``drivers/misc/example-test.c``: + +.. code-block:: c + + #include <kunit/test.h> + #include "example.h" + + /* Define the test cases. */ + + static void misc_example_add_test_basic(struct test *test) + { + TEST_EXPECT_EQ(test, 1, misc_example_add(1, 0)); + TEST_EXPECT_EQ(test, 2, misc_example_add(1, 1)); + TEST_EXPECT_EQ(test, 0, misc_example_add(-1, 1)); + TEST_EXPECT_EQ(test, INT_MAX, misc_example_add(0, INT_MAX)); + TEST_EXPECT_EQ(test, -1, misc_example_add(INT_MAX, INT_MIN)); + } + + static void misc_example_test_failure(struct test *test) + { + TEST_FAIL(test, "This test never passes."); + } + + static struct test_case misc_example_test_cases[] = { + TEST_CASE(misc_example_add_test_basic), + TEST_CASE(misc_example_test_failure), + {}, + }; + + static struct test_module misc_example_test_module = { + .name = "misc-example", + .test_cases = misc_example_test_cases, + }; + module_test(misc_example_test_module); + +Now add the following to ``drivers/misc/Kconfig``: + +.. code-block:: kconfig + + config MISC_EXAMPLE_TEST + bool "Test for my example" + depends on MISC_EXAMPLE && KUNIT + +and the following to ``drivers/misc/Makefile``: + +.. code-block:: make + + obj-$(CONFIG_MISC_EXAMPLE_TEST) += example-test.o + +Now add it to your ``kunitconfig``: + +.. code-block:: none + + CONFIG_MISC_EXAMPLE=y + CONFIG_MISC_EXAMPLE_TEST=y + +Now you can run the test: + +.. code-block:: bash + + ./tools/testing/kunit/kunit.py + +You should see the following failure: + +.. code-block:: none + + ... + [16:08:57] [PASSED] misc-example:misc_example_add_test_basic + [16:08:57] [FAILED] misc-example:misc_example_test_failure + [16:08:57] EXPECTATION FAILED at drivers/misc/example-test.c:17 + [16:08:57] This test never passes. + ... + +Congrats! You just wrote your first KUnit test! + +Next Steps +============= +* Check out the :doc:`usage` page for a more + in-depth explanation of KUnit. diff --git a/Documentation/kunit/usage.rst b/Documentation/kunit/usage.rst new file mode 100644 index 0000000000000..491466d4d4ec3 --- /dev/null +++ b/Documentation/kunit/usage.rst @@ -0,0 +1,447 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============= +Using KUnit +============= + +The purpose of this document is to describe what KUnit is, how it works, how it +is intended to be used, and all the concepts and terminology that are needed to +understand it. This guide assumes a working knowledge of the Linux kernel and +some basic knowledge of testing. + +For a high level introduction to KUnit, including setting up KUnit for your +project, see :doc:`start`. + +Organization of this document +================================= + +This document is organized into two main sections: Testing and Isolating +Behavior. The first covers what a unit test is and how to use KUnit to write +them. The second covers how to use KUnit to isolate code and make it possible +to unit test code that was otherwise un-unit-testable. + +Testing +========== + +What is KUnit? +------------------ + +"K" is short for "kernel" so "KUnit" is the "(Linux) Kernel Unit Testing +Framework." KUnit is intended first and foremost for writing unit tests; it is +general enough that it can be used to write integration tests; however, this is +a secondary goal. KUnit has no ambition of being the only testing framework for +the kernel; for example, it does not intend to be an end-to-end testing +framework. + +What is Unit Testing? +------------------------- + +A `unit test https://martinfowler.com/bliki/UnitTest.html`_ is a test that +tests code at the smallest possible scope, a *unit* of code. In the C +programming language that's a function. + +Unit tests should be written for all the publicly exposed functions in a +compilation unit; so that is all the functions that are exported in either a +*class* (defined below) or all functions which are **not** static. + +Writing Tests +------------- + +Test Cases +~~~~~~~~~~ + +The fundamental unit in KUnit is the test case. A test case is a function with +the signature ``void (*)(struct test *test)``. It calls a function to be tested +and then sets *expectations* for what should happen. For example: + +.. code-block:: c + + void example_test_success(struct test *test) + { + } + + void example_test_failure(struct test *test) + { + TEST_FAIL(test, "This test never passes."); + } + +In the above example ``example_test_success`` always passes because it does +nothing; no expectations are set, so all expectations pass. On the other hand +``example_test_failure`` always fails because it calls ``TEST_FAIL``, which is a +special expectation that logs a message and causes the test case to fail. + +Expectations +~~~~~~~~~~~~ +An *expectation* is a way to specify that you expect a piece of code to do +something in a test. An expectation is called like a function. A test is made +by setting expectations about the behavior of a piece of code under test; when +one or more of the expectations fail, the test case fails and information about +the failure is logged. For example: + +.. code-block:: c + + void add_test_basic(struct test *test) + { + TEST_EXPECT_EQ(test, 1, add(1, 0)); + TEST_EXPECT_EQ(test, 2, add(1, 1)); + } + +In the above example ``add_test_basic`` makes a number of assertions about the +behavior of a function called ``add``; the first parameter is always of type +``struct test *``, which contains information about the current test context; +the second parameter, in this case, is what the value is expected to be; the +last value is what the value actually is. If ``add`` passes all of these +expectations, the test case, ``add_test_basic`` will pass; if any one of these +expectations fail, the test case will fail. + +It is important to understand that a test case *fails* when any expectation is +violated; however, the test will continue running, potentially trying other +expectations until the test case ends or is otherwise terminated. This is as +opposed to *assertions* which are discussed later. + +To learn about more expectations supported by KUnit, see :doc:`api/test`. + +.. note:: + A single test case should be pretty short, pretty easy to understand, + focused on a single behavior. + +For example, if we wanted to properly test the add function above, we would +create additional tests cases which would each test a different property that an +add function should have like this: + +.. code-block:: c + + void add_test_basic(struct test *test) + { + TEST_EXPECT_EQ(test, 1, add(1, 0)); + TEST_EXPECT_EQ(test, 2, add(1, 1)); + } + + void add_test_negative(struct test *test) + { + TEST_EXPECT_EQ(test, 0, add(-1, 1)); + } + + void add_test_max(struct test *test) + { + TEST_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX)); + TEST_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN)); + } + + void add_test_overflow(struct test *test) + { + TEST_EXPECT_EQ(test, INT_MIN, add(INT_MAX, 1)); + } + +Notice how it is immediately obvious what all the properties that we are testing +for are. + +Assertions +~~~~~~~~~~ + +KUnit also has the concept of an *assertion*. An assertion is just like an +expectation except the assertion immediately terminates the test case if it is +not satisfied. + +For example: + +.. code-block:: c + + static void mock_test_do_expect_default_return(struct test *test) + { + struct mock_test_context *ctx = test->priv; + struct mock *mock = ctx->mock; + int param0 = 5, param1 = -5; + const char *two_param_types[] = {"int", "int"}; + const void *two_params[] = {¶m0, ¶m1}; + const void *ret; + + ret = mock->do_expect(mock, + "test_printk", test_printk, + two_param_types, two_params, + ARRAY_SIZE(two_params)); + TEST_ASSERT_NOT_ERR_OR_NULL(test, ret); + TEST_EXPECT_EQ(test, -4, *((int *) ret)); + } + +In this example, the method under test should return a pointer to a value, so +if the pointer returned by the method is null or an errno, we don't want to +bother continuing the test since the following expectation could crash the test +case. `ASSERT_NOT_ERR_OR_NULL(...)` allows us to bail out of the test case if +the appropriate conditions have not been satisfied to complete the test. + +Modules / Test Suites +~~~~~~~~~~~~~~~~~~~~~ + +Now obviously one unit test isn't very helpful; the power comes from having +many test cases covering all of your behaviors. Consequently it is common to +have many *similar* tests; in order to reduce duplication in these closely +related tests most unit testing frameworks provide the concept of a *test +suite*, in KUnit we call it a *test module*; all it is is just a collection of +test cases for a unit of code with a set up function that gets invoked before +every test cases and then a tear down function that gets invoked after every +test case completes. + +Example: + +.. code-block:: c + + static struct test_case example_test_cases[] = { + TEST_CASE(example_test_foo), + TEST_CASE(example_test_bar), + TEST_CASE(example_test_baz), + {}, + }; + + static struct test_module example_test_module[] = { + .name = "example", + .init = example_test_init, + .exit = example_test_exit, + .test_cases = example_test_cases, + }; + module_test(example_test_module); + +In the above example the test suite, ``example_test_module``, would run the test +cases ``example_test_foo``, ``example_test_bar``, and ``example_test_baz``, each +would have ``example_test_init`` called immediately before it and would have +``example_test_exit`` called immediately after it. +``module_test(example_test_module)`` registers the test suite with the KUnit +test framework. + +.. note:: + A test case will only be run if it is associated with a test suite. + +For a more information on these types of things see the :doc:`api/test`. + +Isolating Behavior +================== + +The most important aspect of unit testing that other forms of testing do not +provide is the ability to limit the amount of code under test to a single unit. +In practice, this is only possible by being able to control what code gets run +when the unit under test calls a function and this is usually accomplished +through some sort of indirection where a function is exposed as part of an API +such that the definition of that function can be changed without affecting the +rest of the code base. In the kernel this primarily comes from two constructs, +classes, structs that contain function pointers that are provided by the +implementer, and architecture specific functions which have definitions selected +at compile time. + +Classes +------- + +Classes are not a construct that is built into the C programming language; +however, it is an easily derived concept. Accordingly, pretty much every project +that does not use a standardized object oriented library (like GNOME's GObject) +has their own slightly different way of doing object oriented programming; the +Linux kernel is no exception. + +The central concept in kernel object oriented programming is the class. In the +kernel, a *class* is a struct that contains function pointers. This creates a +contract between *implementers* and *users* since it forces them to use the +same function signature without having to call the function directly. In order +for it to truly be a class, the function pointers must specify that a pointer +to the class, known as a *class handle*, be one of the parameters; this makes +it possible for the member functions (also known as *methods*) to have access +to member variables (more commonly known as *fields*) allowing the same +implementation to have multiple *instances*. + +Typically a class can be *overridden* by *child classes* by embedding the +*parent class* in the child class. Then when a method provided by the child +class is called, the child implementation knows that the pointer passed to it is +of a parent contained within the child; because of this, the child can compute +the pointer to itself because the pointer to the parent is always a fixed offset +from the pointer to the child; this offset is the offset of the parent contained +in the child struct. For example: + +.. code-block:: c + + struct shape { + int (*area)(struct shape *this); + }; + + struct rectangle { + struct shape parent; + int length; + int width; + }; + + int rectangle_area(struct shape *this) + { + struct rectangle *self = container_of(this, struct shape, parent); + + return self->length * self->width; + }; + + void rectangle_new(struct rectangle *self, int length, int width) + { + self->parent.area = rectangle_area; + self->length = length; + self->width = width; + } + +In this example (as in most kernel code) the operation of computing the pointer +to the child from the pointer to the parent is done by ``container_of``. + +Faking Classes +~~~~~~~~~~~~~~ + +In order to unit test a piece of code that calls a method in a class, the +behavior of the method must be controllable, otherwise the test ceases to be a +unit test and becomes an integration test. + +A fake just provides an implementation of a piece of code that is different than +what runs in a production instance, but behaves identically from the standpoint +of the callers; this is usually done to replace a dependency that is hard to +deal with, or is slow. + +A good example for this might be implementing a fake EEPROM that just stores the +"contents" in an internal buffer. For example, let's assume we have a class that +represents an EEPROM: + +.. code-block:: c + + struct eeprom { + ssize_t (*read)(struct eeprom *this, size_t offset, char *buffer, size_t count); + ssize_t (*write)(struct eeprom *this, size_t offset, const char *buffer, size_t count); + }; + +And we want to test some code that buffers writes to the EEPROM: + +.. code-block:: c + + struct eeprom_buffer { + ssize_t (*write)(struct eeprom_buffer *this, const char *buffer, size_t count); + int flush(struct eeprom_buffer *this); + size_t flush_count; /* Flushes when buffer exceeds flush_count. */ + }; + + struct eeprom_buffer *new_eeprom_buffer(struct eeprom *eeprom); + void destroy_eeprom_buffer(struct eeprom *eeprom); + +We can easily test this code by *faking out* the underlying EEPROM: + +.. code-block:: c + + struct fake_eeprom { + struct eeprom parent; + char contents[FAKE_EEPROM_CONTENTS_SIZE]; + }; + + ssize_t fake_eeprom_read(struct eeprom *parent, size_t offset, char *buffer, size_t count) + { + struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent); + + count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset); + memcpy(buffer, this->contents + offset, count); + + return count; + } + + ssize_t fake_eeprom_write(struct eeprom *this, size_t offset, const char *buffer, size_t count) + { + struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent); + + count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset); + memcpy(this->contents + offset, buffer, count); + + return count; + } + + void fake_eeprom_init(struct fake_eeprom *this) + { + this->parent.read = fake_eeprom_read; + this->parent.write = fake_eeprom_write; + memset(this->contents, 0, FAKE_EEPROM_CONTENTS_SIZE); + } + +We can now use it to test ``struct eeprom_buffer``: + +.. code-block:: c + + struct eeprom_buffer_test { + struct fake_eeprom *fake_eeprom; + struct eeprom_buffer *eeprom_buffer; + }; + + static void eeprom_buffer_test_does_not_write_until_flush(struct test *test) + { + struct eeprom_buffer_test *ctx = test->priv; + struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer; + struct fake_eeprom *fake_eeprom = ctx->fake_eeprom; + char buffer[] = {0xff}; + + eeprom_buffer->flush_count = SIZE_MAX; + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + TEST_EXPECT_EQ(test, fake_eeprom->contents[0], 0); + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + TEST_EXPECT_EQ(test, fake_eeprom->contents[1], 0); + + eeprom_buffer->flush(eeprom_buffer); + TEST_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff); + TEST_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff); + } + + static void eeprom_buffer_test_flushes_after_flush_count_met(struct test *test) + { + struct eeprom_buffer_test *ctx = test->priv; + struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer; + struct fake_eeprom *fake_eeprom = ctx->fake_eeprom; + char buffer[] = {0xff}; + + eeprom_buffer->flush_count = 2; + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + TEST_EXPECT_EQ(test, fake_eeprom->contents[0], 0); + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + TEST_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff); + TEST_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff); + } + + static void eeprom_buffer_test_flushes_increments_of_flush_count(struct test *test) + { + struct eeprom_buffer_test *ctx = test->priv; + struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer; + struct fake_eeprom *fake_eeprom = ctx->fake_eeprom; + char buffer[] = {0xff, 0xff}; + + eeprom_buffer->flush_count = 2; + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + TEST_EXPECT_EQ(test, fake_eeprom->contents[0], 0); + + eeprom_buffer->write(eeprom_buffer, buffer, 2); + TEST_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff); + TEST_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff); + /* Should have only flushed the first two bytes. */ + TEST_EXPECT_EQ(test, fake_eeprom->contents[2], 0); + } + + static int eeprom_buffer_test_init(struct test *test) + { + struct eeprom_buffer_test *ctx; + + ctx = test_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->fake_eeprom = test_kzalloc(test, sizeof(*ctx->fake_eeprom), GFP_KERNEL); + ASSERT_NOT_ERR_OR_NULL(test, ctx->fake_eeprom); + + ctx->eeprom_buffer = new_eeprom_buffer(&ctx->fake_eeprom->parent); + ASSERT_NOT_ERR_OR_NULL(test, ctx->eeprom_buffer); + + test->priv = ctx; + + return 0; + } + + static void eeprom_buffer_test_exit(struct test *test) + { + struct eeprom_buffer_test *ctx = test->priv; + + destroy_eeprom_buffer(ctx->eeprom_buffer); + } +
Add myself as maintainer of KUnit, the Linux kernel's unit testing framework.
Signed-off-by: Brendan Higgins brendanhiggins@google.com --- MAINTAINERS | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index b2f710eee67a7..8c9b56dbc9645 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7988,6 +7988,16 @@ S: Maintained F: tools/testing/selftests/ F: Documentation/dev-tools/kselftest*
+KERNEL UNIT TESTING FRAMEWORK (KUnit) +M: Brendan Higgins brendanhiggins@google.com +L: kunit-dev@googlegroups.com +W: https://google.github.io/kunit-docs/third_party/kernel/docs/ +S: Maintained +F: Documentation/kunit/ +F: include/kunit/ +F: kunit/ +F: tools/testing/kunit/ + KERNEL USERMODE HELPER M: "Luis R. Rodriguez" mcgrof@kernel.org L: linux-kernel@vger.kernel.org
On Tue, Oct 23, 2018 at 04:57:36PM -0700, Brendan Higgins wrote:
This patch set proposes KUnit, a lightweight unit testing and mocking framework for the Linux kernel.
Unlike Autotest and kselftest, KUnit is a true unit testing framework; it does not require installing the kernel on a test machine or in a VM and does not require tests to be written in userspace running on a host kernel. Additionally, KUnit is fast: From invocation to completion KUnit can run several dozen tests in under a second. Currently, the entire KUnit test suite for KUnit runs in under a second from the initial invocation (build time excluded).
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
## What's so special about unit testing?
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
## Is KUnit trying to replace other testing frameworks for the kernel?
No. Most existing tests for the Linux kernel are end-to-end tests, which have their place. A well tested system has lots of unit tests, a reasonable number of integration tests, and some end-to-end tests. KUnit is just trying to address the unit test space which is currently not being addressed.
## More information on KUnit
There is a bunch of documentation near the end of this patch set that describes how to use KUnit and best practices for writing unit tests. For convenience I am hosting the compiled docs here: https://google.github.io/kunit-docs/third_party/kernel/docs/
## Changes Since Last Version
- Updated patchset to apply cleanly on 4.19.
- Stripped down patchset to focus on just the core features (I dropped mocking, spying, and the MMIO stuff for now; you can find these patches here: https://kunit-review.googlesource.com/c/linux/+/1132), as suggested by Rob.
- Cleaned up some of the commit messages and tweaked commit order a bit based on suggestions.
Do you have some example unit tests somewhere? The docs are all neat, but real example helps a lot with the tried&true art of copypasting :-)
I'd like to give this a test spin with some of the unit tests we already have in drm. And especially figuring out how we could integrate this with our existing infrastructure. -Daniel
On Wed, Oct 24, 2018 at 2:14 AM Daniel Vetter daniel@ffwll.ch wrote:
On Tue, Oct 23, 2018 at 04:57:36PM -0700, Brendan Higgins wrote:
<snip>
## Changes Since Last Version
- Updated patchset to apply cleanly on 4.19.
- Stripped down patchset to focus on just the core features (I dropped mocking, spying, and the MMIO stuff for now; you can find these patches here: https://kunit-review.googlesource.com/c/linux/+/1132), as suggested by Rob.
- Cleaned up some of the commit messages and tweaked commit order a bit based on suggestions.
Do you have some example unit tests somewhere? The docs are all neat, but real example helps a lot with the tried&true art of copypasting :-)
Yes! So I have quite a few tests for KUnit itself, but they are mostly in the patches that I cut from the previous version of the RFC: https://kunit.googlesource.com/linux/+/c58019fb4fe15f820e51f857ae4ff14cd3407... https://kunit.googlesource.com/linux/+/c58019fb4fe15f820e51f857ae4ff14cd3407... https://kunit.googlesource.com/linux/+/c58019fb4fe15f820e51f857ae4ff14cd3407... https://kunit.googlesource.com/linux/+/c58019fb4fe15f820e51f857ae4ff14cd3407... https://kunit.googlesource.com/linux/+/c58019fb4fe15f820e51f857ae4ff14cd3407...
There are some in this patchset (like here: https://lore.kernel.org/patchwork/patch/1002904/), but I should probably include some more...
In any case, if you are looking for a more "realistic" example, you could look at these sets of tests, which I think is pretty good, and actually found a couple bugs in existing code: https://kunit.googlesource.com/linux/+/e10484ad2f9fc7926412ec84739fe105981b4... https://kunit.googlesource.com/linux/+/4b41b1594ffeab70f1b4364721940cb909509...
I'd like to give this a test spin with some of the unit tests we already have in drm. And especially figuring out how we could integrate this with our existing infrastructure.
Awesome! I am looking forward to hearing how it goes!
Cheers
On 10/23/2018 05:57 PM, Brendan Higgins wrote:
This patch set proposes KUnit, a lightweight unit testing and mocking framework for the Linux kernel.
Unlike Autotest and kselftest, KUnit is a true unit testing framework; it does not require installing the kernel on a test machine or in a VM and does not require tests to be written in userspace running on a host kernel. Additionally, KUnit is fast: From invocation to completion KUnit can run several dozen tests in under a second. Currently, the entire KUnit test suite for KUnit runs in under a second from the initial invocation (build time excluded).
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
## What's so special about unit testing?
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
## Is KUnit trying to replace other testing frameworks for the kernel?
No. Most existing tests for the Linux kernel are end-to-end tests, which have their place. A well tested system has lots of unit tests, a reasonable number of integration tests, and some end-to-end tests. KUnit is just trying to address the unit test space which is currently not being addressed.
## More information on KUnit
There is a bunch of documentation near the end of this patch set that describes how to use KUnit and best practices for writing unit tests. For convenience I am hosting the compiled docs here: https://google.github.io/kunit-docs/third_party/kernel/docs/
## Changes Since Last Version
- Updated patchset to apply cleanly on 4.19.
- Stripped down patchset to focus on just the core features (I dropped mocking, spying, and the MMIO stuff for now; you can find these patches here: https://kunit-review.googlesource.com/c/linux/+/1132), as suggested by Rob.
- Cleaned up some of the commit messages and tweaked commit order a bit based on suggestions.
I am a bit behind on reviewing this patch series.
Just a quick note that I will start looking at these early next week.
thanks, -- Shuah
Hi Brendan,
On 10/23/2018 05:57 PM, Brendan Higgins wrote:
This patch set proposes KUnit, a lightweight unit testing and mocking framework for the Linux kernel.
Unlike Autotest and kselftest, KUnit is a true unit testing framework; it does not require installing the kernel on a test machine or in a VM and does not require tests to be written in userspace running on a host kernel. Additionally, KUnit is fast: From invocation to completion KUnit can run several dozen tests in under a second. Currently, the entire KUnit test suite for KUnit runs in under a second from the initial invocation (build time excluded).
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
## What's so special about unit testing?
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
## Is KUnit trying to replace other testing frameworks for the kernel?
No. Most existing tests for the Linux kernel are end-to-end tests, which have their place. A well tested system has lots of unit tests, a reasonable number of integration tests, and some end-to-end tests. KUnit is just trying to address the unit test space which is currently not being addressed.
## More information on KUnit
There is a bunch of documentation near the end of this patch set that describes how to use KUnit and best practices for writing unit tests. For convenience I am hosting the compiled docs here: https://google.github.io/kunit-docs/third_party/kernel/docs/
## Changes Since Last Version
- Updated patchset to apply cleanly on 4.19.
- Stripped down patchset to focus on just the core features (I dropped mocking, spying, and the MMIO stuff for now; you can find these patches here: https://kunit-review.googlesource.com/c/linux/+/1132), as suggested by Rob.
- Cleaned up some of the commit messages and tweaked commit order a bit based on suggestions.
Framework looks good. I think it would be helpful to include a real test in the patch series to get a feel for how effective it is.
On one hand, KUnit stands on its own as its own and maybe it should be placed in under tools/testing/KUnit, however I am wondering would it be beneficial for the framework to under selftests.
I am a bit concerned about the number of test framework we have at the moment and are we running the risk of fragmenting the landscape. I am concerned if this would lead to developer confusion as to where to add tests.
That being said, I don't have a strong opinion one way or the other.
btw I started playing with kunit following the instructions and ran into problems:
./tools/testing/kunit/kunit.py usage: kunit.py [-h] {run,new} ...
Helps writing and running KUnit tests.
positional arguments: {run,new} run Runs KUnit tests. new Prints out boilerplate for writing new tests.
optional arguments: -h, --help show this help message and exit
./tools/testing/kunit/kunit.py run Regenerating .config ... ERROR:root:Provided Kconfig is not contained in validated .config!
thanks, -- Shuah
On Fri, Nov 2, 2018 at 11:23 AM Shuah Khan shuah@kernel.org wrote:
Hi Brendan,
<snip>
Framework looks good. I think it would be helpful to include a real test
Great to hear!
in the patch series to get a feel for how effective it is.
Alright, will do. Rob suggested converting https://elixir.bootlin.com/linux/v4.19/source/drivers/of/unittest.c to KUnit, so that might offer a good comparison.
On one hand, KUnit stands on its own as its own and maybe it should be placed in under tools/testing/KUnit, however I am wondering would it be beneficial for the framework to under selftests.
I am a bit concerned about the number of test framework we have at the moment and are we running the risk of fragmenting the landscape. I am concerned if this would lead to developer confusion as to where to add tests.
On one hand separating them makes sense because they are different things. kselftest seems to do its job, writing end-to-end tests, pretty well. Trying to associate KUnit with it could also be confusing since they have different requirements and should be used for different things. But on the other hand, you are right, it would be nice to have a coherent experience for developers. In either case, we will still need developers to understand when they should be writing unit tests and when they should be writing end-to-end tests. So in the end, I don't think it will matter too much, we will still need to address this confusion either way.
The main reason I made it separate was just because I thought people would find it strange to have essentially normal kernel code depend on something in `tools/testing/`. You, me, and Greg had talked about this elsewhere, in summary, I am trying to do in-tree unit testing, so tests should live side-by-side with the code it tests, and the tests run at the same level of abstraction as the code that is under test. The tests do not run as userspace programs on an installed kernel under test. The goal is to be able to run arbitrary kernel code in isolation.
Although it would be *possible* to put KUnit under `tools/testing/`, my understanding is that `tools/testing/` is for programs that compile independently of the kernel, rather they do not link against anything built-in to the kernel. (I saw there are some test drivers, but they seem to be modeled as out of tree drivers that have no option to be built into the kernel.) KUnit does not fall into this category as KUnit links directly against arbitrary kernel code, and actually reuses kernel infrastructure to provide an environment to run kernel code in.
I do not feel strongly about where KUnit lives, but by my understanding, putting KUnit in `tools/testing/` might break assumptions about the relationship between `tools/` and the rest of the kernel.
I know you previously said that there are unit tests under kselftest (like maybe this one: https://elixir.bootlin.com/linux/v4.19/source/lib/locking-selftest.c ?). I agree that something like this example is trying to be a unit test, but the kselftest infrastructure is built around the idea of booting kernels and running tests against them; I know that some kselftests load test modules, but the point is the intention is to run the kernel somewhere. My end goal with KUnit is that we should not need to run a kernel anywhere (I know right now I am using UML, but the goal is to get away from that). So I think that decidedly makes them two different things.
The number of test frameworks is a problem, I definitely agree with that. Most of the other tests I have seen for the kernel are indisputably end-to-end tests; it seems that kselftest is decidedly the favorite, maybe we should move those under kselftest?
I know there are other things that don't fit. Maybe we could start off with some suggestions for best practices for what to use and when?
That being said, I don't have a strong opinion one way or the other.
I think it is pretty common on other projects to have unit tests separated from end-to-end tests in some way or another. You want to be able to run unit tests quickly and all the time, whereas you usually only want to run your end-to-end tests when you make a submission or a release. Regardless of where we put KUnit and what its relationship to kselftest becomes, we should definitely make it easy to run unit tests separate from all the end-to-end tests.
btw I started playing with kunit following the instructions and ran into problems:
./tools/testing/kunit/kunit.py usage: kunit.py [-h] {run,new} ...
Helps writing and running KUnit tests.
positional arguments: {run,new} run Runs KUnit tests. new Prints out boilerplate for writing new tests.
optional arguments: -h, --help show this help message and exit
./tools/testing/kunit/kunit.py run Regenerating .config ... ERROR:root:Provided Kconfig is not contained in validated .config!
Oh sorry, I need to write some documentation for that.
I take it you checked out https://kunit.googlesource.com/linux/+/kunit/alpha/master ?
If so, you need a "kunitconfig" in the root of your kernel source with the following Kconfig options: CONFIG_TEST=y CONFIG_TEST_TEST=y CONFIG_EXAMPLE_TEST=y
I am guessing what happened is that you used the "stable" branch which we have been using internally, but follow the instructions I posted for this patchset. Some of the Kconfig options have deviated between them. Sorry about the confusion.
thanks, -- Shuah
Thank you!
On 11/6/18 5:17 PM, Brendan Higgins wrote:
On Fri, Nov 2, 2018 at 11:23 AM Shuah Khan shuah@kernel.org wrote:
Hi Brendan,
<snip> > Framework looks good. I think it would be helpful to include a real test
Great to hear!
in the patch series to get a feel for how effective it is.
Alright, will do. Rob suggested converting https://elixir.bootlin.com/linux/v4.19/source/drivers/of/unittest.c to KUnit, so that might offer a good comparison.
drivers/of/unittest.c might be a bit bigger and more complex test than you want to start with.
-Frank
< snip >
On Wed, Nov 7, 2018 at 9:46 AM Frank Rowand frowand.list@gmail.com wrote:
On 11/6/18 5:17 PM, Brendan Higgins wrote:
On Fri, Nov 2, 2018 at 11:23 AM Shuah Khan shuah@kernel.org wrote:
Hi Brendan,
<snip> > Framework looks good. I think it would be helpful to include a real test
Great to hear!
in the patch series to get a feel for how effective it is.
Alright, will do. Rob suggested converting https://elixir.bootlin.com/linux/v4.19/source/drivers/of/unittest.c to KUnit, so that might offer a good comparison.
drivers/of/unittest.c might be a bit bigger and more complex test than you want to start with.
I already got it working under KUnit. Admittedly, structurally it is not so different, so it will require some work to make it pretty.
I think now it is a question of whether you want me to start here or not.
Probably best just to share what I have now along with Shuah's requested changes and we can go from there.
Let me know what you think.
Cheers
On Tue, 2018-10-23 at 16:57 -0700, Brendan Higgins wrote:
This patch set proposes KUnit, a lightweight unit testing and mocking framework for the Linux kernel.
Unlike Autotest and kselftest, KUnit is a true unit testing framework;
First thanks to Hidenori Yamaji for making me aware of these threads!
I'd like to kindly remind Brendan, and inform others who might have missed out on it, about our (somewhat different approach) to this space at Oracle: KTF (Kernel Test Framework).
KTF is a product of our experience with driver testing within Oracle since 2011, developed as part of a project that was not made public until 2016. During the project, we experimented with multiple approaches to enable more test driven work with kernel code. KTF is the "testing within the kernel" part of this. While we realize there are quite a few testing frameworks out there, KTF makes it easy to run selected tests in kernel context directly, and as such provides a valuable approach to unit testing.
Brendan, I regret you weren't at this year's testing and fuzzing workshop at LPC last week so we could have continued our discussions from last year there!
I hope we can work on this for a while longer before anything gets merged. Maybe it can be topic for a longer session in a future test related workshop?
Links to more info about KTF: ------ Git repo: https://github.com/oracle/ktf Formatted docs: http://heim.ifi.uio.no/~knuto/ktf/
LWN mention from my presentation at LPC'17: https://lwn.net/Articles/735034/ Oracle blog post: https://blogs.oracle.com/linux/oracles-new-kernel-test-framework-for-linux-v... OSS'18 presentation slides: https://events.linuxfoundation.org/wp-content/uploads/2017/12/Test-Driven-Ke...
In the documentation (see http://heim.ifi.uio.no/~knuto/ktf/introduction.html) we present some more motivation for choices made with KTF. As described in that introduction, we believe in a more pragmatic approach to unit testing for the kernel than the classical "mock everything" approach, except for typical heavily algorithmic components that has interfaces simple to mock, such as container implementations, or components like page table traversal algorithms or memory allocators, where the benefit of being able to "listen" on the mock interfaces needed pays handsomely off.
We also used strategies to compile kernel code in user mode, for parts of the code which seemed easy enough to mock interfaces for. I also looked at UML back then, but dismissed it in favor of the more lightweight approach of just compiling the code under test directly in user mode, with a minimal partly hand crafted, flat mock layer.
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
I am curious, with the intention of only running in user mode anyway, why not try to build upon Googletest/Googlemock (or a similar C unit test framework if C is desired), instead of "reinventing" specific kernel macros for the tests?
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
I think it is clearly a trade-off here: Tests run in an isolated, mocked environment are subject to fewer external components. But the more complex the mock environment gets, the more likely it also is to be a source of errors, nondeterminism and capacity limits itself, also the mock code would typically be less well tested than the mocked parts of the kernel, so it is by no means any silver bullet, precise testing with a real kernel on real hardware is still often necessary and desired.
If the code under test is fairly standalone and complex enough, building a mock environment for it and test it independently may be worth it, but pragmatically, if the same functionality can be relatively easily exercised within the kernel, that would be my first choice.
I like to think about all sorts of testing and assertion making as adding more redundancy. When errors surface you can never be sure whether it is a problem with the test, the test framework, the environment, or an actual error, and all places have to be fixed before the test can pass.
Thanks, Knut
On Fri, Nov 23, 2018 at 9:15 PM Knut Omang knut.omang@oracle.com wrote:
On Tue, 2018-10-23 at 16:57 -0700, Brendan Higgins wrote:
<snip>
Brendan, I regret you weren't at this year's testing and fuzzing workshop at LPC last week so we could have continued our discussions from last year there!
Likewise! Unfortunately, I could not make it. So it goes.
I hope we can work on this for a while longer before anything gets merged. Maybe it can be topic for a longer session in a future test related workshop?
I don't see why we cannot just discuss it here as we are already doing. Besides, you are mostly interested in out of tree testing, right? I don't see how this precludes anything that you are trying to do with KTF.
I think the best way to develop something like what I am trying to do with KUnit is gradually, in tree, and with the active input and participation of the Linux kernel community.
Links to more info about KTF:
Git repo: https://github.com/oracle/ktf Formatted docs: http://heim.ifi.uio.no/~knuto/ktf/
LWN mention from my presentation at LPC'17: https://lwn.net/Articles/735034/ Oracle blog post: https://blogs.oracle.com/linux/oracles-new-kernel-test-framework-for-linux-v... OSS'18 presentation slides: https://events.linuxfoundation.org/wp-content/uploads/2017/12/Test-Driven-Ke...
In the documentation (see http://heim.ifi.uio.no/~knuto/ktf/introduction.html) we present some more motivation for choices made with KTF. As described in that introduction, we believe in a more pragmatic approach to unit testing for the kernel than the classical "mock everything" approach, except for typical heavily algorithmic components that has interfaces simple to mock, such as container implementations, or components like page table traversal algorithms or memory allocators, where the benefit of being able to "listen" on the mock interfaces needed pays handsomely off.
I am not advocating that we mock everything. Using as much real code dependencies as possible for code under test is a pretty common position, and one which I adhere to myself.
We also used strategies to compile kernel code in user mode, for parts of the code which seemed easy enough to mock interfaces for. I also looked at UML back then, but dismissed it in favor of the more lightweight approach of just compiling the code under test directly in user mode, with a minimal partly hand crafted, flat mock layer.
Is this new? When I tried your code out, I had to install the kernel objects into my host kernel. Indeed, your documentation references having to install kernel modules on the host: http://heim.ifi.uio.no/~knuto/ktf/installation.html
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
I am curious, with the intention of only running in user mode anyway,
I made it possible to "port" KUnit to other architectures. Nevertheless, I believe all unit tests should be able to run without depending on hardware or some special test harness. If I see a unit test, I should not need to know anything about it just to run it. Since there is no way to have all possible hardware configurations a priori, all tests must be able to be run in a place that doesn't depend in hardware; hence they should all be runnable as just normal plane old user space programs with no dependency on a host kernel or host hardware.
why not try to build upon Googletest/Googlemock (or a similar C unit test framework if C is desired), instead of "reinventing" specific kernel macros for the tests?
I would love to reuse Googletest/Googlemock if it were possible; I have used it a lot on other projects that I have worked on and think it is great, but I need something I can check into the Linux kernel; this requirement rules out Googletest/Googlemock since it depends on C++. There are existing frameworks for C, true, but we then need to check that into the Linux kernel or have the kernel depend on that; to me that seemed like a lot more work than just reimplementing what we need, which isn't much. Most of the hard parts are specific to the kernel anyway.
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
I think it is clearly a trade-off here: Tests run in an isolated, mocked environment are subject to fewer external components. But the more complex the mock environment gets, the more likely it also is to be a source of errors, nondeterminism and capacity limits itself, also the mock code would typically be less well tested than the mocked parts of the kernel, so it is by no means any silver bullet, precise testing with a real kernel on real hardware is still often necessary and desired.
I think you are misunderstand me. By isolation, I just mean no code under test should depend on anything outside of the control of the test environment. As I mention above, reusing real code for test dependencies is highly encouraged.
As for running against hardware, yes, we need tests for that too, but that falls under integration testing; it is possible to use what I have here as a basis for that, but for right now, I just want to focus on the problem of unit testing: I think this patchset is large enough as it is.
If the code under test is fairly standalone and complex enough, building a mock environment for it and test it independently may be worth it, but pragmatically, if the same functionality can be relatively easily exercised within the kernel, that would be my first choice.
I like to think about all sorts of testing and assertion making as adding more redundancy. When errors surface you can never be sure whether it is a problem with the test, the test framework, the environment, or an actual error, and all places have to be fixed before the test can pass.
Yep, I totally agree, but this is why I think test isolation is so important. If one test, or one piece of code is running that doesn't need to be, it makes debugging tests that much more complicated.
Cheers!
On Mon, 2018-11-26 at 17:41 -0800, Brendan Higgins wrote:
On Fri, Nov 23, 2018 at 9:15 PM Knut Omang knut.omang@oracle.com wrote:
On Tue, 2018-10-23 at 16:57 -0700, Brendan Higgins wrote:
<snip> > > Brendan, I regret you weren't at this year's testing and fuzzing workshop at > LPC last week so we could have continued our discussions from last year there!
Likewise! Unfortunately, I could not make it. So it goes.
I hope we can work on this for a while longer before anything gets merged. Maybe it can be topic for a longer session in a future test related workshop?
I don't see why we cannot just discuss it here as we are already doing.
Yes, as long as we are not wasting all the Cc:'ed people's valuable time.
Besides, you are mostly interested in out of tree testing, right? I don't see how this precludes anything that you are trying to do with KTF.
Both approaches provide assertion macros for running tests inside the kernel, I doubt the kernel community would like to see yet two such sets of macros, with differing syntax merged. One of the good reasons to have a *framework* is that it is widely used and understood, so that people coming from one part of the kernel can easily run, understand and relate to selected tests from another part.
The goal with KTF is to allow this for any kernel, old or new, not just kernels built specifically for testing purposes. We felt that providing it as a separate git module (still open source, for anyone to contribute to) is a more efficient approach until we have more examples and experience with using it in different parts of the kernel. We can definitely post the kernel side of KTF as a patch series fairly soon if the community wants us to. Except for hybrid tests, the ktf.ko module works fine independently of any user side support, just using the debugfs interface to run and examine tests.
I think there are good uses cases for having the ability to maintain a single source for tests that can be run against multiple kernels, also distro kernels that the test framework cannot expect to be able to modify, except from using the module interfaces.
And there are good arguments for having (at least parts of) the test framework easily available within the kernel under test.
Links to more info about KTF:
Git repo: https://github.com/oracle/ktf Formatted docs: http://heim.ifi.uio.no/~knuto/ktf/
LWN mention from my presentation at LPC'17: https://lwn.net/Articles/735034/ Oracle blog post: https://blogs.oracle.com/linux/oracles-new-kernel-test-framework-for
-linux-v2
OSS'18 presentation slides: https://events.linuxfoundation.org/wp-content/uploads/2017
/12/Test-Driven-Kernel-Development-Knut-Omang-Oracle.pdf
In the documentation (see http://heim.ifi.uio.no/~knuto/ktf/introduction.html) we present some more motivation for choices made with KTF. As described in that introduction, we believe in a more pragmatic approach to unit testing for the kernel than the classical "mock everything" approach, except for typical heavily algorithmic components that has interfaces simple to mock, such as container implementations, or components like page table traversal algorithms or memory allocators, where the benefit of being able to "listen" on the mock interfaces needed pays handsomely off.
I am not advocating that we mock everything. Using as much real code dependencies as possible for code under test is a pretty common position, and one which I adhere to myself.
We also used strategies to compile kernel code in user mode, for parts of the code which seemed easy enough to mock interfaces for. I also looked at UML back then, but dismissed it in favor of the more lightweight approach of just compiling the code under test directly in user mode, with a minimal partly hand crafted, flat mock layer.
Is this new? When I tried your code out, I had to install the kernel objects into my host kernel. Indeed, your documentation references having to install kernel modules on the host: http://heim.ifi.uio.no/~knuto/ktf/installation.html
That is correct. The user land testing is a separate code base that need some more care still to make it generic enough to serve as an RFC, so you haven't seen it (yet).
KUnit is heavily inspired by JUnit, Python's unittest.mock, and Googletest/Googlemock for C++. KUnit provides facilities for defining unit test cases, grouping related test cases into test suites, providing common infrastructure for running tests, mocking, spying, and much more.
I am curious, with the intention of only running in user mode anyway,
I made it possible to "port" KUnit to other architectures. Nevertheless, I believe all unit tests should be able to run without depending on hardware or some special test harness. If I see a unit test, I should not need to know anything about it just to run it. Since there is no way to have all possible hardware configurations a priori, all tests must be able to be run in a place that doesn't depend in hardware; hence they should all be runnable as just normal plane old user space programs with no dependency on a host kernel or host hardware.
why not try to build upon Googletest/Googlemock (or a similar C unit test framework if C is desired), instead of "reinventing" specific kernel macros for the tests?
I would love to reuse Googletest/Googlemock if it were possible;
I have done it with googletest, so it *is* possible ;-)
I have used it a lot on other projects that I have worked on and think it is great, but I need something I can check into the Linux kernel; this requirement rules out Googletest/Googlemock since it depends on C++. There are existing frameworks for C, true, but we then need to check that into the Linux kernel or have the kernel depend on that;
I think that is limiting the scope of it. Certainly developing the kernel already depends on a lot of user land tools, such as git for instance. Having another package to install for running tests might not be such a big deal, and saves this list from even more traffic.
So figuring out what to put in the kernel repo and what to leave outside is IMHO part of the challenge.
to me that seemed like a lot more work than just reimplementing what we need, which isn't much. Most of the hard parts are specific to the kernel anyway.
A unit test is supposed to test a single unit of code in isolation, hence the name. There should be no dependencies outside the control of the test; this means no external dependencies, which makes tests orders of magnitudes faster. Likewise, since there are no external dependencies, there are no hoops to jump through to run the tests. Additionally, this makes unit tests deterministic: a failing unit test always indicates a problem. Finally, because unit tests necessarily have finer granularity, they are able to test all code paths easily solving the classic problem of difficulty in exercising error handling code.
I think it is clearly a trade-off here: Tests run in an isolated, mocked environment are subject to fewer external components. But the more complex the mock environment gets, the more likely it also is to be a source of errors, nondeterminism and capacity limits itself, also the mock code would typically be less well tested than the mocked parts of the kernel, so it is by no means any silver bullet, precise testing with a real kernel on real hardware is still often necessary and desired.
I think you are misunderstand me. By isolation, I just mean no code under test should depend on anything outside of the control of the test environment.
And this approach is good, but it only covers the needs for a limited part of the kernel code. It can also be done entirely in user space, using user land test frameworks, the biggest challenge is in the mocking. Lots of the code in the kernel behave based on interaction with other subsystems, and with hardware.
As I mention above, reusing real code for test dependencies is highly encouraged.
As for running against hardware, yes, we need tests for that too, but that falls under integration testing; it is possible to use what I have here as a basis for that, but for right now, I just want to focus on the problem of unit testing: I think this patchset is large enough as it is.
If the code under test is fairly standalone and complex enough, building a mock environment for it and test it independently may be worth it, but pragmatically, if the same functionality can be relatively easily exercised within the kernel, that would be my first choice.
I like to think about all sorts of testing and assertion making as adding more redundancy. When errors surface you can never be sure whether it is a problem with the test, the test framework, the environment, or an actual error, and all places have to be fixed before the test can pass.
Yep, I totally agree, but this is why I think test isolation is so important. If one test, or one piece of code is running that doesn't need to be, it makes debugging tests that much more complicated.
Yes, and another dimension to this that we have focused on with KTF, and where the Googletest frontend gives additional value, is that the tests should be both usable for smoke test and continuous integration needs, but at the same time be easy to execute standalone, test by test, with extra debugging, to allow them to be used by developers as part of a short cycle development process.
I think the solution needs to allow a pragmatic approach, time an resources are limited. Sometimes an isolated test is possible, sometimes a test that executes inside a real environment is a better return on investment!
Cheers, Knut
On 11/28/18 12:54 PM, Knut Omang wrote:
On Mon, 2018-11-26 at 17:41 -0800, Brendan Higgins wrote:
On Fri, Nov 23, 2018 at 9:15 PM Knut Omang knut.omang@oracle.com wrote:
On Tue, 2018-10-23 at 16:57 -0700, Brendan Higgins wrote:
<snip> > > Brendan, I regret you weren't at this year's testing and fuzzing workshop at > LPC last week so we could have continued our discussions from last year there!
Likewise! Unfortunately, I could not make it. So it goes.
I hope we can work on this for a while longer before anything gets merged. Maybe it can be topic for a longer session in a future test related workshop?
I don't see why we cannot just discuss it here as we are already doing.
Yes, as long as we are not wasting all the Cc:'ed people's valuable time.
Besides, you are mostly interested in out of tree testing, right? I don't see how this precludes anything that you are trying to do with KTF.
Both approaches provide assertion macros for running tests inside the kernel, I doubt the kernel community would like to see yet two such sets of macros, with differing syntax merged. One of the good reasons to have a *framework* is that it is widely used and understood, so that people coming from one part of the kernel can easily run, understand and relate to selected tests from another part.
The goal with KTF is to allow this for any kernel, old or new, not just kernels built specifically for testing purposes. We felt that providing it as a separate git module (still open source, for anyone to contribute to) is a more efficient approach until we have more examples and experience with using it in different parts of the kernel. We can definitely post the kernel side of KTF as a patch series fairly soon if the community wants us to. Except for hybrid tests, the ktf.ko module works fine independently of any user side support, just using the debugfs interface to run and examine tests.
Having test framework in the kernel sources tree has benefits. It allows us (kernel developers) to do co-development of kernel features and tests for these features.
It encourages developers to write regression tests. More importantly, kernel features and tests for these features are included in the same release in most cases and/or allows us freedom to do so if test framework and tests are part of the kernel sources.
We have seen this with our experience with kselftest. It would not have see the same level of attention and growth if it stayed outside the kernel sources.
Most kernel developers would not want to include a externally maintained module for testing. As a general rule, it has to be easy to run tests.
I think there are good uses cases for having the ability to maintain a single source for tests that can be run against multiple kernels, also distro kernels that the test framework cannot expect to be able to modify, except from using the module interfaces.
Same reasons as above. Having the tests included in the kernel sources makes it easier for distros to run those tests and include running them during their qualification.
And there are good arguments for having (at least parts of) the test framework easily available within the kernel under test.
When Kernel unit, functional, and regressions tests reside in the kernel sources, they evolve quicker as kernel developers contribute tests as part of their kernel work-flow. Maintaining tests and framework separately will make it harder to maintain them and keep them updated for us the kernel community.
thanks, -- Shuah
On Wed, Nov 28, 2018 at 01:50:01PM -0700, shuah wrote:
On 11/28/18 12:54 PM, Knut Omang wrote:
On Mon, 2018-11-26 at 17:41 -0800, Brendan Higgins wrote: Both approaches provide assertion macros for running tests inside the kernel, I doubt the kernel community would like to see yet two such sets of macros, with differing syntax merged. One of the good reasons to have a *framework* is that it is widely used and understood, so that people coming from one part of the kernel can easily run, understand and relate to selected tests from another part.
The goal with KTF is to allow this for any kernel, old or new, not just kernels built specifically for testing purposes. We felt that providing it as a separate git module (still open source, for anyone to contribute to) is a more efficient approach until we have more examples and experience with using it in different parts of the kernel. We can definitely post the kernel side of KTF as a patch series fairly soon if the community wants us to. Except for hybrid tests, the ktf.ko module works fine independently of any user side support, just using the debugfs interface to run and examine tests.
Having test framework in the kernel sources tree has benefits. It allows us (kernel developers) to do co-development of kernel features and tests for these features.
Agreed!
It encourages developers to write regression tests. More importantly, kernel features and tests for these features are included in the same release in most cases and/or allows us freedom to do so if test framework and tests are part of the kernel sources.
We have seen this with our experience with kselftest. It would not have see the same level of attention and growth if it stayed outside the kernel sources.
Most kernel developers would not want to include a externally maintained module for testing. As a general rule, it has to be easy to run tests.
I think there are good uses cases for having the ability to maintain a single source for tests that can be run against multiple kernels, also distro kernels that the test framework cannot expect to be able to modify, except from using the module interfaces.
Same reasons as above. Having the tests included in the kernel sources makes it easier for distros to run those tests and include running them during their qualification.
Also... selftests are an example of tests which *are* upstream and yet there are teams out there using them to test these tests on older kernels. So the scripts for instance are supposed to work with older kernels. So if you expand on a feature your selftest script should detect if the new mechanism is present or not, and also be backward compatible with older kernels.
And there are good arguments for having (at least parts of) the test framework easily available within the kernel under test.
When Kernel unit, functional, and regressions tests reside in the kernel sources, they evolve quicker as kernel developers contribute tests as part of their kernel work-flow. Maintaining tests and framework separately will make it harder to maintain them and keep them updated for us the kernel community.
Agreed!
Also, I actually see no issue with having *both* kunit / ktest merged upstream. IMHO we should not be forcing people to pick one or the other but rather we should: let the best test framework win. Similar as we did with LSMs. Each test framework has its own gains / advantages.
Luis
linux-kselftest-mirror@lists.linaro.org