## TL;DR
A not so quick follow-up to Stephen's suggestions on PATCH v4. Nothing that really changes any functionality or usage with the minor exception of a couple public functions that Stephen asked me to rename. Nevertheless, a good deal of clean up and fixes. See changes below.
As for our current status, right now we got Reviewed-bys on all patches except:
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
However, it would probably be good to get reviews/acks from the subsystem maintainers on:
- [PATCH v5 06/18] kbuild: enable building KUnit - [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list - [PATCH v5 15/18] Documentation: kunit: add documentation for KUnit - [PATCH v5 17/18] kernel/sysctl-test: Add null pointer test for sysctl.c:proc_dointvec() - [PATCH v5 18/18] MAINTAINERS: add proc sysctl KUnit test to PROC SYSCTL section
Other than that, I think we should be good to go.
One last thing, I updated the background to include my thoughts on KUnit vs. in kernel testing with kselftest in the background sections as suggested by Frank in the discussion on PATCH v2.
## Background
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 (however, KUnit still allows you to run tests on test machines or in VMs if you want[1]) 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.
### But wait! Doesn't kselftest support in kernel testing?!
In a previous version of this patchset Frank pointed out that kselftest already supports writing a test that resides in the kernel using the test module feature[2]. LWN did a really great summary on this discussion here[3].
Kselftest has a feature that allows a test module to be loaded into a kernel using the kselftest framework; this does allow someone to write tests against kernel code not directly exposed to userland; however, it does not provide much of a framework around how to structure the tests. The kselftest test module feature just provides a header which has a standardized way of reporting test failures, and then provides infrastructure to load and run the tests using the kselftest test harness.
The kselftest test module does not seem to be opinionated at all in regards to how tests are structured, how they check for failures, how tests are organized. Even in the method it provides for reporting failures is pretty simple; it doesn't have any more advanced failure reporting or logging features. Given what's there, I think it is fair to say that it is not actually a framework, but a feature that makes it possible for someone to do some checks in kernel space.
Furthermore, kselftest test module has very few users. I checked for all the tests that use it using the following grep command:
grep -Hrn -e 'kselftest_module.h'
and only got three results: lib/test_strscpy.c, lib/test_printf.c, and lib/test_bitmap.c.
So despite kselftest test module's existence, there really is no feature overlap between kselftest and KUnit, save one: that you can use either to write an in-kernel test, but this is a very small feature in comparison to everything that KUnit allows you to do. KUnit is a full x-unit style unit testing framework, whereas kselftest looks a lot more like an end-to-end/functional testing framework, with a feature that makes it possible to write in-kernel tests.
### 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[4].
Additionally for convenience, I have applied these patches to a branch[5]. The repo may be cloned with: git clone https://kunit.googlesource.com/linux This patchset is on the kunit/rfc/v5.2-rc4/v5 branch.
## Changes Since Last Version
Aside from a couple public function renames, there isn't really anything in here that changes any functionality.
- Went through and fixed a couple of anti-patterns suggested by Stephen Boyd. Things like: - Dropping an else clause at the end of a function. - Dropping the comma on the closing sentinel, `{}`, of a list. - Inlines a bunch of functions in the test case running logic in patch 01/18 to make it more readable as suggested by Stephen Boyd - Found and fixed bug in resource deallocation logic in patch 02/18. Bug was discovered as a result of making a change suggested by Stephen Boyd. This does not substantially change how any of the code works conceptually. - Renamed new_string_stream() to alloc_string_stream() as suggested by Stephen Boyd. - Made string-stream a KUnit managed object - based on a suggestion made by Stephen Boyd. - Renamed kunit_new_stream() to alloc_kunit_stream() as suggested by Stephen Boyd. - Removed the ability to set log level after allocating a kunit_stream, as suggested by Stephen Boyd.
[1] https://google.github.io/kunit-docs/third_party/kernel/docs/usage.html#kunit... [2] https://www.kernel.org/doc/html/latest/dev-tools/kselftest.html#test-module [3] https://lwn.net/Articles/790235/ [4] https://google.github.io/kunit-docs/third_party/kernel/docs/ [5] https://kunit.googlesource.com/linux/+/kunit/rfc/v5.2-rc4/v5
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 Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Changes Since Last Revision: - Mostly minor fixes suggested by Stephen Boyd - Biggest change inlines a bunch of functions in the test case running logic to make it more readable as suggested by Stephen Boyd --- include/kunit/test.h | 161 +++++++++++++++++++++++++++++++++ kunit/Kconfig | 17 ++++ kunit/Makefile | 1 + kunit/test.c | 210 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 389 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..8476b3d371cb9 --- /dev/null +++ b/include/kunit/test.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Base unit test (KUnit) API. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#ifndef _KUNIT_TEST_H +#define _KUNIT_TEST_H + +#include <linux/types.h> + +struct kunit; + +/** + * struct kunit_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 kunit *)`` + * that makes expectations (see KUNIT_EXPECT_TRUE()) about code under test. Each + * test case is associated with a &struct kunit_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 KUNIT_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 kunit *test) + * { + * KUNIT_EXPECT_EQ(test, 1, add(1, 0)); + * KUNIT_EXPECT_EQ(test, 2, add(1, 1)); + * KUNIT_EXPECT_EQ(test, 0, add(-1, 1)); + * KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX)); + * KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN)); + * } + * + * static struct kunit_case example_test_cases[] = { + * KUNIT_CASE(add_test_basic), + * {} + * }; + * + */ +struct kunit_case { + void (*run_case)(struct kunit *test); + const char *name; + + /* private: internal use only. */ + bool success; +}; + +/** + * KUNIT_CASE - A helper for creating a &struct kunit_case + * @test_name: a reference to a test case function. + * + * Takes a symbol for a function representing a test case and creates a + * &struct kunit_case object from it. See the documentation for + * &struct kunit_case for an example on how to use it. + */ +#define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name } + +/** + * struct kunit_module - describes a related collection of &struct kunit_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 kunit_module is a collection of related &struct kunit_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 kunit_case must be associated with a kunit_module for KUnit to + * run it. + */ +struct kunit_module { + const char name[256]; + int (*init)(struct kunit *test); + void (*exit)(struct kunit *test); + struct kunit_case *test_cases; +}; + +/** + * struct kunit - 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 kunit_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 kunit { + 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 kunit_init_test(struct kunit *test, const char *name); + +int kunit_run_tests(struct kunit_module *module); + +/** + * module_test() - used to register a &struct kunit_module with KUnit. + * @module: a statically allocated &struct kunit_module. + * + * Registers @module with the test framework. See &struct kunit_module for more + * information. + */ +#define module_test(module) \ + static int module_kunit_init##module(void) \ + { \ + return kunit_run_tests(&module); \ + } \ + late_initcall(module_kunit_init##module) + +void __printf(3, 4) kunit_printk(const char *level, + const struct kunit *test, + const char *fmt, ...); + +/** + * kunit_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 kunit_info(test, fmt, ...) \ + kunit_printk(KERN_INFO, test, fmt, ##__VA_ARGS__) + +/** + * kunit_warn() - Prints a WARN level message associated with the current test. + * @test: The test context object. + * @fmt: A printk() style format string. + * + * Prints a warning level message. + */ +#define kunit_warn(test, fmt, ...) \ + kunit_printk(KERN_WARNING, test, fmt, ##__VA_ARGS__) + +/** + * kunit_err() - Prints an ERROR level message associated with the current test. + * @test: The test context object. + * @fmt: A printk() style format string. + * + * Prints an error level message. + */ +#define kunit_err(test, fmt, ...) \ + kunit_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..330ae83527c23 --- /dev/null +++ b/kunit/Kconfig @@ -0,0 +1,17 @@ +# +# KUnit base configuration +# + +menu "KUnit support" + +config KUNIT + bool "Enable support for unit tests (KUnit)" + 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 when using UML. Can also be used on most other + architectures. For more information, please see + Documentation/dev-tools/kunit/. + +endmenu diff --git a/kunit/Makefile b/kunit/Makefile new file mode 100644 index 0000000000000..5efdc4dea2c08 --- /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..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Base unit test (KUnit) API. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <linux/sched/debug.h> +#include <kunit/test.h> + +static bool kunit_get_success(struct kunit *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 kunit_set_success(struct kunit *test, bool success) +{ + unsigned long flags; + + spin_lock_irqsave(&test->lock, flags); + test->success = success; + spin_unlock_irqrestore(&test->lock, flags); +} + +static int kunit_vprintk_emit(int level, const char *fmt, va_list args) +{ + return vprintk_emit(0, level, NULL, 0, fmt, args); +} + +static int kunit_printk_emit(int level, const char *fmt, ...) +{ + va_list args; + int ret; + + va_start(args, fmt); + ret = kunit_vprintk_emit(level, fmt, args); + va_end(args); + + return ret; +} + +static void kunit_vprintk(const struct kunit *test, + const char *level, + struct va_format *vaf) +{ + kunit_printk_emit(level[1] - '0', "\t# %s: %pV", test->name, vaf); +} + +static bool kunit_has_printed_tap_version; + +static void kunit_print_tap_version(void) +{ + if (!kunit_has_printed_tap_version) { + kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n"); + kunit_has_printed_tap_version = true; + } +} + +static size_t kunit_test_cases_len(struct kunit_case *test_cases) +{ + struct kunit_case *test_case; + size_t len = 0; + + for (test_case = test_cases; test_case->run_case; test_case++) + len++; + + return len; +} + +static void kunit_print_subtest_start(struct kunit_module *module) +{ + kunit_print_tap_version(); + kunit_printk_emit(LOGLEVEL_INFO, "\t# Subtest: %s\n", module->name); + kunit_printk_emit(LOGLEVEL_INFO, + "\t1..%zd\n", + kunit_test_cases_len(module->test_cases)); +} + +static void kunit_print_ok_not_ok(bool should_indent, + bool is_ok, + size_t test_number, + const char *description) +{ + const char *indent, *ok_not_ok; + + if (should_indent) + indent = "\t"; + else + indent = ""; + + if (is_ok) + ok_not_ok = "ok"; + else + ok_not_ok = "not ok"; + + kunit_printk_emit(LOGLEVEL_INFO, + "%s%s %zd - %s\n", + indent, ok_not_ok, test_number, description); +} + +static bool kunit_module_has_succeeded(struct kunit_module *module) +{ + const struct kunit_case *test_case; + bool success = true; + + for (test_case = module->test_cases; test_case->run_case; test_case++) + if (!test_case->success) { + success = false; + break; + } + + return success; +} + +static size_t kunit_module_counter = 1; + +static void kunit_print_subtest_end(struct kunit_module *module) +{ + kunit_print_ok_not_ok(false, + kunit_module_has_succeeded(module), + kunit_module_counter++, + module->name); +} + +static void kunit_print_test_case_ok_not_ok(struct kunit_case *test_case, + size_t test_number) +{ + kunit_print_ok_not_ok(true, + test_case->success, + test_number, + test_case->name); +} + +void kunit_init_test(struct kunit *test, const char *name) +{ + spin_lock_init(&test->lock); + test->name = name; + test->success = true; +} + +/* + * Performs all logic to run a test case. + */ +static void kunit_run_case(struct kunit_module *module, + struct kunit_case *test_case) +{ + struct kunit test; + int ret = 0; + + kunit_init_test(&test, test_case->name); + + if (module->init) { + ret = module->init(&test); + if (ret) { + kunit_err(&test, "failed to initialize: %d\n", ret); + kunit_set_success(&test, false); + return; + } + } + + if (!ret) + test_case->run_case(&test); + + if (module->exit) + module->exit(&test); + + test_case->success = kunit_get_success(&test); +} + +int kunit_run_tests(struct kunit_module *module) +{ + struct kunit_case *test_case; + size_t test_case_count = 1; + + kunit_print_subtest_start(module); + + for (test_case = module->test_cases; test_case->run_case; test_case++) { + kunit_run_case(module, test_case); + kunit_print_test_case_ok_not_ok(test_case, test_case_count++); + } + + kunit_print_subtest_end(module); + + return 0; +} + +void kunit_printk(const char *level, + const struct kunit *test, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + kunit_vprintk(test, level, &vaf); + + va_end(args); +}
Quoting Brendan Higgins (2019-06-17 01:25:56)
diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Base unit test (KUnit) API.
- Copyright (C) 2019, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#include <linux/sched/debug.h> +#include <kunit/test.h>
+static bool kunit_get_success(struct kunit *test) +{
unsigned long flags;
bool success;
spin_lock_irqsave(&test->lock, flags);
success = test->success;
spin_unlock_irqrestore(&test->lock, flags);
I still don't understand the locking scheme in this code. Is the intention to make getter and setter APIs that are "safe" by adding in a spinlock that is held around getting and setting various members in the kunit structure?
In what situation is there more than one thread reading or writing the kunit struct? Isn't it only a single process that is going to be operating on this structure? And why do we need to disable irqs? Are we expecting to be modifying the unit tests from irq contexts?
return success;
+}
+static void kunit_set_success(struct kunit *test, bool success) +{
unsigned long flags;
spin_lock_irqsave(&test->lock, flags);
test->success = success;
spin_unlock_irqrestore(&test->lock, flags);
+}
+static int kunit_vprintk_emit(int level, const char *fmt, va_list args) +{
return vprintk_emit(0, level, NULL, 0, fmt, args);
+}
+static int kunit_printk_emit(int level, const char *fmt, ...) +{
va_list args;
int ret;
va_start(args, fmt);
ret = kunit_vprintk_emit(level, fmt, args);
va_end(args);
return ret;
+}
+static void kunit_vprintk(const struct kunit *test,
const char *level,
struct va_format *vaf)
+{
kunit_printk_emit(level[1] - '0', "\t# %s: %pV", test->name, vaf);
+}
+static bool kunit_has_printed_tap_version;
Can you please move this into function local scope in the function below?
+static void kunit_print_tap_version(void) +{
if (!kunit_has_printed_tap_version) {
kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n");
kunit_has_printed_tap_version = true;
}
+}
[...]
+static bool kunit_module_has_succeeded(struct kunit_module *module) +{
const struct kunit_case *test_case;
bool success = true;
for (test_case = module->test_cases; test_case->run_case; test_case++)
if (!test_case->success) {
success = false;
break;
Why not 'return false'?
}
return success;
And 'return true'?
+}
+static size_t kunit_module_counter = 1;
+static void kunit_print_subtest_end(struct kunit_module *module) +{
kunit_print_ok_not_ok(false,
kunit_module_has_succeeded(module),
kunit_module_counter++,
module->name);
+}
+static void kunit_print_test_case_ok_not_ok(struct kunit_case *test_case,
size_t test_number)
+{
kunit_print_ok_not_ok(true,
test_case->success,
test_number,
test_case->name);
+}
+void kunit_init_test(struct kunit *test, const char *name) +{
spin_lock_init(&test->lock);
test->name = name;
test->success = true;
+}
+/*
- Performs all logic to run a test case.
- */
+static void kunit_run_case(struct kunit_module *module,
struct kunit_case *test_case)
+{
struct kunit test;
int ret = 0;
kunit_init_test(&test, test_case->name);
if (module->init) {
ret = module->init(&test);
if (ret) {
kunit_err(&test, "failed to initialize: %d\n", ret);
kunit_set_success(&test, false);
return;
}
}
if (!ret)
test_case->run_case(&test);
Do we need this if condition? ret can only be set to non-zero above but then we'll exit the function early so it seems unnecessary. Given that, ret should probably be moved into the module->init path.
if (module->exit)
module->exit(&test);
test_case->success = kunit_get_success(&test);
+}
On Wed, Jun 19, 2019 at 5:15 PM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-17 01:25:56)
diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Base unit test (KUnit) API.
- Copyright (C) 2019, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#include <linux/sched/debug.h> +#include <kunit/test.h>
+static bool kunit_get_success(struct kunit *test) +{
unsigned long flags;
bool success;
spin_lock_irqsave(&test->lock, flags);
success = test->success;
spin_unlock_irqrestore(&test->lock, flags);
I still don't understand the locking scheme in this code. Is the intention to make getter and setter APIs that are "safe" by adding in a spinlock that is held around getting and setting various members in the kunit structure?
Yes, your understanding is correct. It is possible for a user to write a test such that certain elements may be updated in different threads; this would most likely happen in the case where someone wants to make an assertion or an expectation in a thread created by a piece of code under test. Although this should generally be avoided, it is possible, and there are occasionally good reasons to do so, so it is functionality that we should support.
Do you think I should add a comment to this effect?
In what situation is there more than one thread reading or writing the kunit struct? Isn't it only a single process that is going to be
As I said above, it is possible that the code under test may spawn a new thread that may make an expectation or an assertion. It is not a super common use case, but it is possible.
operating on this structure? And why do we need to disable irqs? Are we expecting to be modifying the unit tests from irq contexts?
There are instances where someone may want to test a driver which has an interrupt handler in it. I actually have (not the greatest) example here. Now in these cases, I expect someone to use a mock irqchip or some other fake mechanism to trigger the interrupt handler and not actual hardware; technically speaking in this case, it is not going to be accessed from a "real" irq context; however, the code under test should think that it is in an irq context; given that, I figured it is best to just treat it as a real irq context. Does that make sense?
return success;
+}
+static void kunit_set_success(struct kunit *test, bool success) +{
unsigned long flags;
spin_lock_irqsave(&test->lock, flags);
test->success = success;
spin_unlock_irqrestore(&test->lock, flags);
+}
+static int kunit_vprintk_emit(int level, const char *fmt, va_list args) +{
return vprintk_emit(0, level, NULL, 0, fmt, args);
+}
+static int kunit_printk_emit(int level, const char *fmt, ...) +{
va_list args;
int ret;
va_start(args, fmt);
ret = kunit_vprintk_emit(level, fmt, args);
va_end(args);
return ret;
+}
+static void kunit_vprintk(const struct kunit *test,
const char *level,
struct va_format *vaf)
+{
kunit_printk_emit(level[1] - '0', "\t# %s: %pV", test->name, vaf);
+}
+static bool kunit_has_printed_tap_version;
Can you please move this into function local scope in the function below?
Sure, that makes sense.
+static void kunit_print_tap_version(void) +{
if (!kunit_has_printed_tap_version) {
kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n");
kunit_has_printed_tap_version = true;
}
+}
[...]
+static bool kunit_module_has_succeeded(struct kunit_module *module) +{
const struct kunit_case *test_case;
bool success = true;
for (test_case = module->test_cases; test_case->run_case; test_case++)
if (!test_case->success) {
success = false;
break;
Why not 'return false'?
Also a good point. Will fix.
}
return success;
And 'return true'?
Will fix.
+}
+static size_t kunit_module_counter = 1;
+static void kunit_print_subtest_end(struct kunit_module *module) +{
kunit_print_ok_not_ok(false,
kunit_module_has_succeeded(module),
kunit_module_counter++,
module->name);
+}
+static void kunit_print_test_case_ok_not_ok(struct kunit_case *test_case,
size_t test_number)
+{
kunit_print_ok_not_ok(true,
test_case->success,
test_number,
test_case->name);
+}
+void kunit_init_test(struct kunit *test, const char *name) +{
spin_lock_init(&test->lock);
test->name = name;
test->success = true;
+}
+/*
- Performs all logic to run a test case.
- */
+static void kunit_run_case(struct kunit_module *module,
struct kunit_case *test_case)
+{
struct kunit test;
int ret = 0;
kunit_init_test(&test, test_case->name);
if (module->init) {
ret = module->init(&test);
if (ret) {
kunit_err(&test, "failed to initialize: %d\n", ret);
kunit_set_success(&test, false);
return;
}
}
if (!ret)
test_case->run_case(&test);
Do we need this if condition? ret can only be set to non-zero above but then we'll exit the function early so it seems unnecessary. Given that, ret should probably be moved into the module->init path.
Whoops. Sorry, another instance of how it evolved over time and I forgot why I did the check. Will fix.
if (module->exit)
module->exit(&test);
test_case->success = kunit_get_success(&test);
+}
Thanks!
On Tue, Jun 25, 2019 at 01:28:25PM -0700, Brendan Higgins wrote:
On Wed, Jun 19, 2019 at 5:15 PM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-17 01:25:56)
diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Base unit test (KUnit) API.
- Copyright (C) 2019, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#include <linux/sched/debug.h> +#include <kunit/test.h>
+static bool kunit_get_success(struct kunit *test) +{
unsigned long flags;
bool success;
spin_lock_irqsave(&test->lock, flags);
success = test->success;
spin_unlock_irqrestore(&test->lock, flags);
I still don't understand the locking scheme in this code. Is the intention to make getter and setter APIs that are "safe" by adding in a spinlock that is held around getting and setting various members in the kunit structure?
Yes, your understanding is correct. It is possible for a user to write a test such that certain elements may be updated in different threads; this would most likely happen in the case where someone wants to make an assertion or an expectation in a thread created by a piece of code under test. Although this should generally be avoided, it is possible, and there are occasionally good reasons to do so, so it is functionality that we should support.
Do you think I should add a comment to this effect?
In what situation is there more than one thread reading or writing the kunit struct? Isn't it only a single process that is going to be
As I said above, it is possible that the code under test may spawn a new thread that may make an expectation or an assertion. It is not a super common use case, but it is possible.
I wonder if it is worth to have then different types of tests based on locking requirements. One with no locking, since it seems you imply most tests would fall under this category, then locking, and another with IRQ context.
If no locking is done at all for all tests which do not require locking, is there any gains at run time? I'm sure it might be minimum but curious.
operating on this structure? And why do we need to disable irqs? Are we expecting to be modifying the unit tests from irq contexts?
There are instances where someone may want to test a driver which has an interrupt handler in it. I actually have (not the greatest) example here. Now in these cases, I expect someone to use a mock irqchip or some other fake mechanism to trigger the interrupt handler and not actual hardware; technically speaking in this case, it is not going to be accessed from a "real" irq context; however, the code under test should think that it is in an irq context; given that, I figured it is best to just treat it as a real irq context. Does that make sense?
Since its a new architecture and since you seem to imply most tests don't require locking or even IRQs disabled, I think its worth to consider the impact of adding such extreme locking requirements for an initial ramp up.
Luis
On Tue, Jun 25, 2019 at 2:44 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Tue, Jun 25, 2019 at 01:28:25PM -0700, Brendan Higgins wrote:
On Wed, Jun 19, 2019 at 5:15 PM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-17 01:25:56)
diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Base unit test (KUnit) API.
- Copyright (C) 2019, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#include <linux/sched/debug.h> +#include <kunit/test.h>
+static bool kunit_get_success(struct kunit *test) +{
unsigned long flags;
bool success;
spin_lock_irqsave(&test->lock, flags);
success = test->success;
spin_unlock_irqrestore(&test->lock, flags);
I still don't understand the locking scheme in this code. Is the intention to make getter and setter APIs that are "safe" by adding in a spinlock that is held around getting and setting various members in the kunit structure?
Yes, your understanding is correct. It is possible for a user to write a test such that certain elements may be updated in different threads; this would most likely happen in the case where someone wants to make an assertion or an expectation in a thread created by a piece of code under test. Although this should generally be avoided, it is possible, and there are occasionally good reasons to do so, so it is functionality that we should support.
Do you think I should add a comment to this effect?
In what situation is there more than one thread reading or writing the kunit struct? Isn't it only a single process that is going to be
As I said above, it is possible that the code under test may spawn a new thread that may make an expectation or an assertion. It is not a super common use case, but it is possible.
I wonder if it is worth to have then different types of tests based on locking requirements. One with no locking, since it seems you imply most tests would fall under this category, then locking, and another with IRQ context.
If no locking is done at all for all tests which do not require locking, is there any gains at run time? I'm sure it might be minimum but curious.
Yeah, I don't think it is worth it.
I don't think we need to be squeezing every ounce of performance out of unit tests, since they are inherently a cost and are not intended to be run in a production deployed kernel as part of normal production usage.
operating on this structure? And why do we need to disable irqs? Are we expecting to be modifying the unit tests from irq contexts?
There are instances where someone may want to test a driver which has an interrupt handler in it. I actually have (not the greatest) example here. Now in these cases, I expect someone to use a mock irqchip or some other fake mechanism to trigger the interrupt handler and not actual hardware; technically speaking in this case, it is not going to be accessed from a "real" irq context; however, the code under test should think that it is in an irq context; given that, I figured it is best to just treat it as a real irq context. Does that make sense?
Since its a new architecture and since you seem to imply most tests don't require locking or even IRQs disabled, I think its worth to consider the impact of adding such extreme locking requirements for an initial ramp up.
Fair enough, I can see the point of not wanting to use irq disabled until we get someone complaining about it, but I think making it thread safe is reasonable. It means there is one less thing to confuse a KUnit user and the only penalty paid is some very minor performance.
On Tue, Jun 25, 2019 at 03:14:45PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 2:44 PM Luis Chamberlain mcgrof@kernel.org wrote:
Since its a new architecture and since you seem to imply most tests don't require locking or even IRQs disabled, I think its worth to consider the impact of adding such extreme locking requirements for an initial ramp up.
Fair enough, I can see the point of not wanting to use irq disabled until we get someone complaining about it, but I think making it thread safe is reasonable. It means there is one less thing to confuse a KUnit user and the only penalty paid is some very minor performance.
One reason I'm really excited about kunit is speed... so by all means I think we're at a good point to analyze performance optimizationsm if they do make sense.
While on the topic of parallization, what about support for running different test cases in parallel? Or at the very least different kunit modules in parallel. Few questions come up based on this prospect:
* Why not support parallelism from the start? * Are you opposed to eventually having this added? For instance, there is enough code on lib/test_kmod.c for batching tons of kthreads each one running its own thing for testing purposes which could be used as template. * If we eventually *did* support it: - Would logs be skewed? - Could we have a way to query: give me log for only kunit module named "foo"?
Luis
On Tue, Jun 25, 2019 at 4:02 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Tue, Jun 25, 2019 at 03:14:45PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 2:44 PM Luis Chamberlain mcgrof@kernel.org wrote:
Since its a new architecture and since you seem to imply most tests don't require locking or even IRQs disabled, I think its worth to consider the impact of adding such extreme locking requirements for an initial ramp up.
Fair enough, I can see the point of not wanting to use irq disabled until we get someone complaining about it, but I think making it thread safe is reasonable. It means there is one less thing to confuse a KUnit user and the only penalty paid is some very minor performance.
One reason I'm really excited about kunit is speed... so by all means I think we're at a good point to analyze performance optimizationsm if they do make sense.
Yeah, but I think there are much lower hanging fruit than this (as you point out below). I am all for making/keeping KUnit super fast, but I also don't want to waste time with premature optimizations and I think having thread safe expectations and non-thread safe expectations hurts usability.
Still, I am on board with making this a mutex instead of a spinlock for now.
While on the topic of parallization, what about support for running different test cases in parallel? Or at the very least different kunit modules in parallel. Few questions come up based on this prospect:
- Why not support parallelism from the start?
Just because it is more work and there isn't much to gain from it right now.
Some numbers: I currently have a collection of 86 test cases in the branch that this patchset is from. I turned on PRINTK_TIME and looked at the first KUnit output and the last. On UML, start time was 0.090000, and end time was 0.090000. Looks like sched_clock is not very good on UML.
Still it seems quite likely that all of these tests run around 0.01 seconds or less on UML: I ran KUnit with only 2 test cases enabled three times and got an average runtime of 1.55867 seconds with a standard deviation of 0.0346747. I then ran it another three times with all test cases enabled and got an average runtime of 1.535 seconds with a standard deviation of 0.0150997. The second average is less, but that doesn't really mean anything because it is well within one standard deviation with a very small sample size. Nevertheless, we can conclude that the actual runtime of those 84 test cases is most likely within one standard deviation, so on the order of 0.01 seconds.
On x86 running on QEMU, first message from KUnit was printed at 0.194251 and the last KUnit message was printed at 0.340915, meaning that all 86 test cases ran in about 0.146664 seconds.
In any case, running KUnit tests in parallel is definitely something I plan on adding it eventually, but it just doesn't really seem worth it right now. I find the incremental build time of the kernel to typically be between 3 and 30 seconds, and a clean build to be between 30 seconds to several minutes, depending on the number of available cores, so I don't think most users would even notice the amount of runtime contributed by the actual unit tests until we start getting into the 1000s of test cases. I don't suspect it will become an issue until we get into the 10,000s of test cases. I think we are a pretty long way off from that.
- Are you opposed to eventually having this added? For instance, there is enough code on lib/test_kmod.c for batching tons of kthreads each one running its own thing for testing purposes which could be used as template.
I am not opposed to adding it eventually at all. I actually plan on doing so, just not in this patchset. There are a lot of additional features, improvements, and sugar that I really want to add, so much so that most of it doesn't belong in this patchset; I just think this is one of those things that belongs in a follow up. I tried to boil down this patchset to as small as I could while still being useful; this is basically an MVP. Maybe after this patchset gets merged I should post a list of things I have ready for review, or would like to work on, and people can comment on what things they want to see next.
- If we eventually *did* support it:
- Would logs be skewed?
Probably, before I went with the TAP approach, I was tagging each message with the test case it came from and I could have parsed it and assembled a coherent view of the logs using that; now that I am using TAP conforming output, that won't work. I haven't really thought too hard about how to address it, but there are ways. For the UML users, I am planning on adding a feature to guarantee hermeticity between tests running in different modules by adding a feature that allows a single kernel to be built with all tests included, and then determine which tests get run by passing in command line arguments or something. This way you can get the isolation from running tests in separate environments without increasing the build cost. We could also use this method to achieve parallelism by dispatching multiple kernels at once. That only works for UML, but I imagine you could do something similar for users running tests under qemu.
- Could we have a way to query: give me log for only kunit module named "foo"?
Yeah, I think that would make sense as part of the hermeticity thing I mentioned above.
Hope that seems reasonable!
On Tue, Jun 25, 2019 at 11:41:47PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 4:02 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Tue, Jun 25, 2019 at 03:14:45PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 2:44 PM Luis Chamberlain mcgrof@kernel.org wrote:
Since its a new architecture and since you seem to imply most tests don't require locking or even IRQs disabled, I think its worth to consider the impact of adding such extreme locking requirements for an initial ramp up.
Fair enough, I can see the point of not wanting to use irq disabled until we get someone complaining about it, but I think making it thread safe is reasonable. It means there is one less thing to confuse a KUnit user and the only penalty paid is some very minor performance.
One reason I'm really excited about kunit is speed... so by all means I think we're at a good point to analyze performance optimizationsm if they do make sense.
Yeah, but I think there are much lower hanging fruit than this (as you point out below). I am all for making/keeping KUnit super fast, but I also don't want to waste time with premature optimizations and I think having thread safe expectations and non-thread safe expectations hurts usability.
Still, I am on board with making this a mutex instead of a spinlock for now.
While on the topic of parallization, what about support for running different test cases in parallel? Or at the very least different kunit modules in parallel. Few questions come up based on this prospect:
- Why not support parallelism from the start?
Just because it is more work and there isn't much to gain from it right now.
Some numbers: I currently have a collection of 86 test cases in the branch that this patchset is from.
Impressive, imagine 86 tests and kunit is not even *merged yet*.
I turned on PRINTK_TIME and looked at the first KUnit output and the last. On UML, start time was 0.090000, and end time was 0.090000. Looks like sched_clock is not very good on UML.
Since you have a python thing that kicks tests, what if you just run time on it?
Still it seems quite likely that all of these tests run around 0.01 seconds or less on UML: I ran KUnit with only 2 test cases enabled three times and got an average runtime of 1.55867 seconds with a standard deviation of 0.0346747. I then ran it another three times with all test cases enabled and got an average runtime of 1.535 seconds with a standard deviation of 0.0150997. The second average is less, but that doesn't really mean anything because it is well within one standard deviation with a very small sample size. Nevertheless, we can conclude that the actual runtime of those 84 test cases is most likely within one standard deviation, so on the order of 0.01 seconds.
On x86 running on QEMU, first message from KUnit was printed at 0.194251 and the last KUnit message was printed at 0.340915, meaning that all 86 test cases ran in about 0.146664 seconds.
Pretty impressive numbers. But can you blame me for expressing the desire to possibly being able to do better? I am not saying -- let's definitely have parallelism in place *now*. Just wanted to hear out tangibles of why we *don't* want it now.
And.. also, since we are reviewing now, try to target so that the code can later likely get a face lift to support parallelism without requiring much changes.
In any case, running KUnit tests in parallel is definitely something I plan on adding it eventually, but it just doesn't really seem worth it right now.
Makes sense!
I find the incremental build time of the kernel to typically be between 3 and 30 seconds, and a clean build to be between 30 seconds to several minutes, depending on the number of available cores, so I don't think most users would even notice the amount of runtime contributed by the actual unit tests until we start getting into the 1000s of test cases. I don't suspect it will become an issue until we get into the 10,000s of test cases. I think we are a pretty long way off from that.
All makes sense, and agreed based on the numbers you are providing. Thanks for the details!
- Are you opposed to eventually having this added? For instance, there is enough code on lib/test_kmod.c for batching tons of kthreads each one running its own thing for testing purposes which could be used as template.
I am not opposed to adding it eventually at all. I actually plan on doing so, just not in this patchset. There are a lot of additional features, improvements, and sugar that I really want to add, so much so that most of it doesn't belong in this patchset; I just think this is one of those things that belongs in a follow up. I tried to boil down this patchset to as small as I could while still being useful; this is basically an MVP. Maybe after this patchset gets merged I should post a list of things I have ready for review, or would like to work on, and people can comment on what things they want to see next.
Groovy.
- If we eventually *did* support it:
- Would logs be skewed?
Probably, before I went with the TAP approach, I was tagging each message with the test case it came from and I could have parsed it and assembled a coherent view of the logs using that; now that I am using TAP conforming output, that won't work. I haven't really thought too hard about how to address it, but there are ways. For the UML users, I am planning on adding a feature to guarantee hermeticity between tests running in different modules by adding a feature that allows a single kernel to be built with all tests included, and then determine which tests get run by passing in command line arguments or something. This way you can get the isolation from running tests in separate environments without increasing the build cost. We could also use this method to achieve parallelism by dispatching multiple kernels at once.
Indeed. Or... wait for it... containers... There tools for these sorts of things already. And I'm evaluating such prospects now for some other tests I care for.
That only works for UML, but I imagine you could do something similar for users running tests under qemu.
Or containers.
- Could we have a way to query: give me log for only kunit module named "foo"?
Yeah, I think that would make sense as part of the hermeticity thing I mentioned above.
Hope that seems reasonable!
All groovy. Thanks for the details!
Luis
On Wed, Jun 26, 2019 at 3:02 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Tue, Jun 25, 2019 at 11:41:47PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 4:02 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Tue, Jun 25, 2019 at 03:14:45PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 2:44 PM Luis Chamberlain mcgrof@kernel.org wrote:
Since its a new architecture and since you seem to imply most tests don't require locking or even IRQs disabled, I think its worth to consider the impact of adding such extreme locking requirements for an initial ramp up.
Fair enough, I can see the point of not wanting to use irq disabled until we get someone complaining about it, but I think making it thread safe is reasonable. It means there is one less thing to confuse a KUnit user and the only penalty paid is some very minor performance.
One reason I'm really excited about kunit is speed... so by all means I think we're at a good point to analyze performance optimizationsm if they do make sense.
Yeah, but I think there are much lower hanging fruit than this (as you point out below). I am all for making/keeping KUnit super fast, but I also don't want to waste time with premature optimizations and I think having thread safe expectations and non-thread safe expectations hurts usability.
Still, I am on board with making this a mutex instead of a spinlock for now.
While on the topic of parallization, what about support for running different test cases in parallel? Or at the very least different kunit modules in parallel. Few questions come up based on this prospect:
- Why not support parallelism from the start?
Just because it is more work and there isn't much to gain from it right now.
Some numbers: I currently have a collection of 86 test cases in the branch that this patchset is from.
Impressive, imagine 86 tests and kunit is not even *merged yet*.
Full disclaimer, about half of them are KUnit tests for KUnit - to make sure KUnit works, so I don't know if you consider that cheating.
I turned on PRINTK_TIME and looked at the first KUnit output and the last. On UML, start time was 0.090000, and end time was 0.090000. Looks like sched_clock is not very good on UML.
Since you have a python thing that kicks tests, what if you just run time on it?
That's what I did on the this following paragraph (I just couldn't time the tests by themselves in this case):
Still it seems quite likely that all of these tests run around 0.01 seconds or less on UML: I ran KUnit with only 2 test cases enabled three times and got an average runtime of 1.55867 seconds with a standard deviation of 0.0346747. I then ran it another three times with all test cases enabled and got an average runtime of 1.535 seconds with a standard deviation of 0.0150997. The second average is less, but that doesn't really mean anything because it is well within one standard deviation with a very small sample size. Nevertheless, we can conclude that the actual runtime of those 84 test cases is most likely within one standard deviation, so on the order of 0.01 seconds.
On x86 running on QEMU, first message from KUnit was printed at 0.194251 and the last KUnit message was printed at 0.340915, meaning that all 86 test cases ran in about 0.146664 seconds.
Pretty impressive numbers. But can you blame me for expressing the desire to possibly being able to do better? I am not saying -- let's definitely have parallelism in place *now*. Just wanted to hear out tangibles of why we *don't* want it now.
I agree, faster is almost always better in these types of things, and certainly is in this case.
In fairness to you, I also short sold the speed of KUnit in the cover letter. I was too lazy to do this complete of an analysis back when I wrote it (even if I did a complete timing like this, I would have to put a bunch of asterisks since it wouldn't include the time to "boot" the kernel or to build it, which vastly outstrip the speed of individual test cases). And given the original numbers, I think speeding things up would probably seem more urgent. So no, I really cannot blame you.
Sorry if it came across that I was frustrated or impatient, but I am actually glad you asked because I now have this public email where I did the full analysis of how fast KUnit really is which I can refer to in the future.
And.. also, since we are reviewing now, try to target so that the code can later likely get a face lift to support parallelism without requiring much changes.
Also fair.
In any case, running KUnit tests in parallel is definitely something I plan on adding it eventually, but it just doesn't really seem worth it right now.
Makes sense!
I find the incremental build time of the kernel to typically be between 3 and 30 seconds, and a clean build to be between 30 seconds to several minutes, depending on the number of available cores, so I don't think most users would even notice the amount of runtime contributed by the actual unit tests until we start getting into the 1000s of test cases. I don't suspect it will become an issue until we get into the 10,000s of test cases. I think we are a pretty long way off from that.
All makes sense, and agreed based on the numbers you are providing. Thanks for the details!
- Are you opposed to eventually having this added? For instance, there is enough code on lib/test_kmod.c for batching tons of kthreads each one running its own thing for testing purposes which could be used as template.
I am not opposed to adding it eventually at all. I actually plan on doing so, just not in this patchset. There are a lot of additional features, improvements, and sugar that I really want to add, so much so that most of it doesn't belong in this patchset; I just think this is one of those things that belongs in a follow up. I tried to boil down this patchset to as small as I could while still being useful; this is basically an MVP. Maybe after this patchset gets merged I should post a list of things I have ready for review, or would like to work on, and people can comment on what things they want to see next.
Groovy.
Cool, I will do that then!
- If we eventually *did* support it:
- Would logs be skewed?
Probably, before I went with the TAP approach, I was tagging each message with the test case it came from and I could have parsed it and assembled a coherent view of the logs using that; now that I am using TAP conforming output, that won't work. I haven't really thought too hard about how to address it, but there are ways. For the UML users, I am planning on adding a feature to guarantee hermeticity between tests running in different modules by adding a feature that allows a single kernel to be built with all tests included, and then determine which tests get run by passing in command line arguments or something. This way you can get the isolation from running tests in separate environments without increasing the build cost. We could also use this method to achieve parallelism by dispatching multiple kernels at once.
Indeed. Or... wait for it... containers... There tools for these sorts of things already. And I'm evaluating such prospects now for some other tests I care for.
Containers could definitely be useful in the long run. I have a long term goal to build and run just parts of the kernel as I have mentioned to you, and doing so in a totally hermetic environment could provide a lot of value; in this case I would probably only want a chroot, but if I want to deploy tests to run on different machines containers could be very useful.
Actually, on the topic of containers for running tests, the presubmit system I have set up for KUnit uses containers for deploying KUnit on servers for testing[1]. Actually, I have some experimental patches to make it work with LKML instead of Gerrit, but I am not sure whether it makes more sense to go that route, with one of the many patchworks clones that support presubmit, or something else.
That only works for UML, but I imagine you could do something similar for users running tests under qemu.
Or containers.
- Could we have a way to query: give me log for only kunit module named "foo"?
Yeah, I think that would make sense as part of the hermeticity thing I mentioned above.
Hope that seems reasonable!
All groovy. Thanks for the details!
[1] https://kunit-review.googlesource.com/c/linux/+/1809/2#message-c243e1c9086d9...
Quoting Brendan Higgins (2019-06-25 13:28:25)
On Wed, Jun 19, 2019 at 5:15 PM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-17 01:25:56)
diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Base unit test (KUnit) API.
- Copyright (C) 2019, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#include <linux/sched/debug.h> +#include <kunit/test.h>
+static bool kunit_get_success(struct kunit *test) +{
unsigned long flags;
bool success;
spin_lock_irqsave(&test->lock, flags);
success = test->success;
spin_unlock_irqrestore(&test->lock, flags);
I still don't understand the locking scheme in this code. Is the intention to make getter and setter APIs that are "safe" by adding in a spinlock that is held around getting and setting various members in the kunit structure?
Yes, your understanding is correct. It is possible for a user to write a test such that certain elements may be updated in different threads; this would most likely happen in the case where someone wants to make an assertion or an expectation in a thread created by a piece of code under test. Although this should generally be avoided, it is possible, and there are occasionally good reasons to do so, so it is functionality that we should support.
Do you think I should add a comment to this effect?
No, I think the locking should be removed.
In what situation is there more than one thread reading or writing the kunit struct? Isn't it only a single process that is going to be
As I said above, it is possible that the code under test may spawn a new thread that may make an expectation or an assertion. It is not a super common use case, but it is possible.
Sure, sounds super possible and OK.
operating on this structure? And why do we need to disable irqs? Are we expecting to be modifying the unit tests from irq contexts?
There are instances where someone may want to test a driver which has an interrupt handler in it. I actually have (not the greatest) example here. Now in these cases, I expect someone to use a mock irqchip or some other fake mechanism to trigger the interrupt handler and not actual hardware; technically speaking in this case, it is not going to be accessed from a "real" irq context; however, the code under test should think that it is in an irq context; given that, I figured it is best to just treat it as a real irq context. Does that make sense?
Can you please describe the scenario in which grabbing the lock here, updating a single variable, and then releasing the lock right after does anything useful vs. not having the lock? I'm looking for a two CPU scenario like below, but where it is a problem. There could be three CPUs, or even one CPU and three threads if you want to describe the extra thread scenario.
Here's my scenario where it isn't needed:
CPU0 CPU1 ---- ---- kunit_run_test(&test) test_case_func() .... [mock hardirq] kunit_set_success(&test) [hardirq ends] ... complete(&test_done) wait_for_completion(&test_done) kunit_get_success(&test)
We don't need to care about having locking here because success or failure only happens in one place and it's synchronized with the completion.
return success;
+}
+static void kunit_set_success(struct kunit *test, bool success) +{
unsigned long flags;
spin_lock_irqsave(&test->lock, flags);
test->success = success;
spin_unlock_irqrestore(&test->lock, flags);
+}
On Tue, Jun 25, 2019 at 8:41 PM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-25 13:28:25)
On Wed, Jun 19, 2019 at 5:15 PM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-17 01:25:56)
diff --git a/kunit/test.c b/kunit/test.c new file mode 100644 index 0000000000000..d05d254f1521f --- /dev/null +++ b/kunit/test.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Base unit test (KUnit) API.
- Copyright (C) 2019, Google LLC.
- Author: Brendan Higgins brendanhiggins@google.com
- */
+#include <linux/sched/debug.h> +#include <kunit/test.h>
+static bool kunit_get_success(struct kunit *test) +{
unsigned long flags;
bool success;
spin_lock_irqsave(&test->lock, flags);
success = test->success;
spin_unlock_irqrestore(&test->lock, flags);
I still don't understand the locking scheme in this code. Is the intention to make getter and setter APIs that are "safe" by adding in a spinlock that is held around getting and setting various members in the kunit structure?
Yes, your understanding is correct. It is possible for a user to write a test such that certain elements may be updated in different threads; this would most likely happen in the case where someone wants to make an assertion or an expectation in a thread created by a piece of code under test. Although this should generally be avoided, it is possible, and there are occasionally good reasons to do so, so it is functionality that we should support.
Do you think I should add a comment to this effect?
No, I think the locking should be removed.
In what situation is there more than one thread reading or writing the kunit struct? Isn't it only a single process that is going to be
As I said above, it is possible that the code under test may spawn a new thread that may make an expectation or an assertion. It is not a super common use case, but it is possible.
Sure, sounds super possible and OK.
operating on this structure? And why do we need to disable irqs? Are we expecting to be modifying the unit tests from irq contexts?
There are instances where someone may want to test a driver which has an interrupt handler in it. I actually have (not the greatest) example here. Now in these cases, I expect someone to use a mock irqchip or some other fake mechanism to trigger the interrupt handler and not actual hardware; technically speaking in this case, it is not going to be accessed from a "real" irq context; however, the code under test should think that it is in an irq context; given that, I figured it is best to just treat it as a real irq context. Does that make sense?
Can you please describe the scenario in which grabbing the lock here, updating a single variable, and then releasing the lock right after does anything useful vs. not having the lock? I'm looking for a two CPU
Sure.
scenario like below, but where it is a problem. There could be three CPUs, or even one CPU and three threads if you want to describe the extra thread scenario.
Here's my scenario where it isn't needed:
CPU0 CPU1 ---- ---- kunit_run_test(&test) test_case_func() .... [mock hardirq] kunit_set_success(&test) [hardirq ends] ... complete(&test_done) wait_for_completion(&test_done) kunit_get_success(&test)
We don't need to care about having locking here because success or failure only happens in one place and it's synchronized with the completion.
Here is the scenario I am concerned about:
CPU0 CPU1 CPU2 ---- ---- ---- kunit_run_test(&test) test_case_func() .... schedule_work(foo_func) [mock hardirq] foo_func() ... ... kunit_set_success(false) kunit_set_success(false) [hardirq ends] ... ... complete(&test_done) wait_for_completion(...) kunit_get_success(&test)
In my scenario, since both CPU1 and CPU2 update the success status of the test simultaneously, even though they are setting it to the same value. If my understanding is correct, this could result in a write-tear on some architectures in some circumstances. I suppose we could just make it an atomic boolean, but I figured locking is also fine, and generally preferred.
Also, to be clear, I am onboard with dropping then IRQ stuff for now. I am fine moving to a mutex for the time being.
return success;
+}
+static void kunit_set_success(struct kunit *test, bool success) +{
unsigned long flags;
spin_lock_irqsave(&test->lock, flags);
test->success = success;
spin_unlock_irqrestore(&test->lock, flags);
+}
Quoting Brendan Higgins (2019-06-26 16:00:40)
On Tue, Jun 25, 2019 at 8:41 PM Stephen Boyd sboyd@kernel.org wrote:
scenario like below, but where it is a problem. There could be three CPUs, or even one CPU and three threads if you want to describe the extra thread scenario.
Here's my scenario where it isn't needed:
CPU0 CPU1 ---- ---- kunit_run_test(&test) test_case_func() .... [mock hardirq] kunit_set_success(&test) [hardirq ends] ... complete(&test_done) wait_for_completion(&test_done) kunit_get_success(&test)
We don't need to care about having locking here because success or failure only happens in one place and it's synchronized with the completion.
Here is the scenario I am concerned about:
CPU0 CPU1 CPU2
kunit_run_test(&test) test_case_func() .... schedule_work(foo_func) [mock hardirq] foo_func() ... ... kunit_set_success(false) kunit_set_success(false) [hardirq ends] ... ... complete(&test_done) wait_for_completion(...) kunit_get_success(&test)
In my scenario, since both CPU1 and CPU2 update the success status of the test simultaneously, even though they are setting it to the same value. If my understanding is correct, this could result in a write-tear on some architectures in some circumstances. I suppose we could just make it an atomic boolean, but I figured locking is also fine, and generally preferred.
This is what we have WRITE_ONCE() and READ_ONCE() for. Maybe you could just use that in the getter and setters and remove the lock if it isn't used for anything else.
It may also be a good idea to have a kunit_fail_test() API that fails the test passed in with a WRITE_ONCE(false). Otherwise, the test is assumed successful and it isn't even possible for a test to change the state from failure to success due to a logical error because the API isn't available. Then we don't really need to have a generic kunit_set_success() function at all. We could have a kunit_test_failed() function too that replaces the kunit_get_success() function. That would read better in an if condition.
Also, to be clear, I am onboard with dropping then IRQ stuff for now. I am fine moving to a mutex for the time being.
Ok.
On Thu, Jun 27, 2019 at 11:16 AM Stephen Boyd sboyd@kernel.org wrote:
Quoting Brendan Higgins (2019-06-26 16:00:40)
On Tue, Jun 25, 2019 at 8:41 PM Stephen Boyd sboyd@kernel.org wrote:
scenario like below, but where it is a problem. There could be three CPUs, or even one CPU and three threads if you want to describe the extra thread scenario.
Here's my scenario where it isn't needed:
CPU0 CPU1 ---- ---- kunit_run_test(&test) test_case_func() .... [mock hardirq] kunit_set_success(&test) [hardirq ends] ... complete(&test_done) wait_for_completion(&test_done) kunit_get_success(&test)
We don't need to care about having locking here because success or failure only happens in one place and it's synchronized with the completion.
Here is the scenario I am concerned about:
CPU0 CPU1 CPU2
kunit_run_test(&test) test_case_func() .... schedule_work(foo_func) [mock hardirq] foo_func() ... ... kunit_set_success(false) kunit_set_success(false) [hardirq ends] ... ... complete(&test_done) wait_for_completion(...) kunit_get_success(&test)
In my scenario, since both CPU1 and CPU2 update the success status of the test simultaneously, even though they are setting it to the same value. If my understanding is correct, this could result in a write-tear on some architectures in some circumstances. I suppose we could just make it an atomic boolean, but I figured locking is also fine, and generally preferred.
This is what we have WRITE_ONCE() and READ_ONCE() for. Maybe you could just use that in the getter and setters and remove the lock if it isn't used for anything else.
It may also be a good idea to have a kunit_fail_test() API that fails the test passed in with a WRITE_ONCE(false). Otherwise, the test is assumed successful and it isn't even possible for a test to change the state from failure to success due to a logical error because the API isn't available. Then we don't really need to have a generic kunit_set_success() function at all. We could have a kunit_test_failed() function too that replaces the kunit_get_success() function. That would read better in an if condition.
You know what, I think you are right.
Sorry, for not realizing this earlier, I think you mentioned something along these lines a long time ago.
Thanks for your patience!
Also, to be clear, I am onboard with dropping then IRQ stuff for now. I am fine moving to a mutex for the time being.
Ok.
Thanks!
On Mon, Jun 17, 2019 at 01:25:56AM -0700, Brendan Higgins wrote:
+/**
- module_test() - used to register a &struct kunit_module with KUnit.
- @module: a statically allocated &struct kunit_module.
- Registers @module with the test framework. See &struct kunit_module for more
- information.
- */
+#define module_test(module) \
static int module_kunit_init##module(void) \
{ \
return kunit_run_tests(&module); \
} \
late_initcall(module_kunit_init##module)
Becuase late_initcall() is used, if these modules are built-in, this would preclude the ability to test things prior to this part of the kernel under UML or whatever architecture runs the tests. So, this limits the scope of testing. Small detail but the scope whould be documented.
+static void kunit_print_tap_version(void) +{
- if (!kunit_has_printed_tap_version) {
kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n");
What is this TAP thing? Why should we care what version it is on? Why are we printing this?
kunit_has_printed_tap_version = true;
- }
+}
+static size_t kunit_test_cases_len(struct kunit_case *test_cases) +{
- struct kunit_case *test_case;
- size_t len = 0;
- for (test_case = test_cases; test_case->run_case; test_case++)
If we make the last test case NULL, we'd just check for test_case here, and save ourselves an extra few bytes per test module. Any reason why the last test case cannot be NULL?
+void kunit_init_test(struct kunit *test, const char *name) +{
- spin_lock_init(&test->lock);
- test->name = name;
- test->success = true;
+}
+/*
- Performs all logic to run a test case.
- */
+static void kunit_run_case(struct kunit_module *module,
struct kunit_case *test_case)
+{
- struct kunit test;
- int ret = 0;
- kunit_init_test(&test, test_case->name);
- if (module->init) {
ret = module->init(&test);
I believe if we used struct kunit_module *kmodule it would be much clearer who's init this is.
Luis
On Tue, Jun 25, 2019 at 3:33 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:25:56AM -0700, Brendan Higgins wrote:
+/**
- module_test() - used to register a &struct kunit_module with KUnit.
- @module: a statically allocated &struct kunit_module.
- Registers @module with the test framework. See &struct kunit_module for more
- information.
- */
+#define module_test(module) \
static int module_kunit_init##module(void) \
{ \
return kunit_run_tests(&module); \
} \
late_initcall(module_kunit_init##module)
Becuase late_initcall() is used, if these modules are built-in, this would preclude the ability to test things prior to this part of the kernel under UML or whatever architecture runs the tests. So, this limits the scope of testing. Small detail but the scope whould be documented.
You aren't the first person to complain about this (and I am not sure it is the first time you have complained about it). Anyway, I have some follow on patches that will improve the late_initcall thing, and people seemed okay with discussing the follow on patches as part of a subsequent patchset after this gets merged.
I will nevertheless document the restriction until then.
+static void kunit_print_tap_version(void) +{
if (!kunit_has_printed_tap_version) {
kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n");
What is this TAP thing? Why should we care what version it is on? Why are we printing this?
It's part of the TAP specification[1]. Greg and Frank asked me to make the intermediate format conform to TAP. Seems like something else I should probable document...
kunit_has_printed_tap_version = true;
}
+}
+static size_t kunit_test_cases_len(struct kunit_case *test_cases) +{
struct kunit_case *test_case;
size_t len = 0;
for (test_case = test_cases; test_case->run_case; test_case++)
If we make the last test case NULL, we'd just check for test_case here, and save ourselves an extra few bytes per test module. Any reason why the last test case cannot be NULL?
Is there anyway to make that work with a statically defined array?
Basically, I want to be able to do something like:
static struct kunit_case example_test_cases[] = { KUNIT_CASE(example_simple_test), KUNIT_CASE(example_mock_test), {} };
FYI, #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
In order to do what you are proposing, I think I need an array of pointers to test cases, which is not ideal.
+void kunit_init_test(struct kunit *test, const char *name) +{
spin_lock_init(&test->lock);
test->name = name;
test->success = true;
+}
+/*
- Performs all logic to run a test case.
- */
+static void kunit_run_case(struct kunit_module *module,
struct kunit_case *test_case)
+{
struct kunit test;
int ret = 0;
kunit_init_test(&test, test_case->name);
if (module->init) {
ret = module->init(&test);
I believe if we used struct kunit_module *kmodule it would be much clearer who's init this is.
That's reasonable. I will fix in next revision.
Cheers!
[1] https://github.com/TestAnything/Specification/blob/tap-14-specification/spec...
On Tue, Jun 25, 2019 at 05:07:32PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 3:33 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:25:56AM -0700, Brendan Higgins wrote:
+/**
- module_test() - used to register a &struct kunit_module with KUnit.
- @module: a statically allocated &struct kunit_module.
- Registers @module with the test framework. See &struct kunit_module for more
- information.
- */
+#define module_test(module) \
static int module_kunit_init##module(void) \
{ \
return kunit_run_tests(&module); \
} \
late_initcall(module_kunit_init##module)
Becuase late_initcall() is used, if these modules are built-in, this would preclude the ability to test things prior to this part of the kernel under UML or whatever architecture runs the tests. So, this limits the scope of testing. Small detail but the scope whould be documented.
You aren't the first person to complain about this (and I am not sure it is the first time you have complained about it). Anyway, I have some follow on patches that will improve the late_initcall thing, and people seemed okay with discussing the follow on patches as part of a subsequent patchset after this gets merged.
I will nevertheless document the restriction until then.
To be clear, I am not complaining about it. I just find it simply critical to document its limitations, so folks don't try to invest time and energy on kunit right away for an early init test, if it cannot support it.
If support for that requires some work, it may be worth mentioning as well.
+static void kunit_print_tap_version(void) +{
if (!kunit_has_printed_tap_version) {
kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n");
What is this TAP thing? Why should we care what version it is on? Why are we printing this?
It's part of the TAP specification[1]. Greg and Frank asked me to make the intermediate format conform to TAP. Seems like something else I should probable document...
Yes thanks!
kunit_has_printed_tap_version = true;
}
+}
+static size_t kunit_test_cases_len(struct kunit_case *test_cases) +{
struct kunit_case *test_case;
size_t len = 0;
for (test_case = test_cases; test_case->run_case; test_case++)
If we make the last test case NULL, we'd just check for test_case here, and save ourselves an extra few bytes per test module. Any reason why the last test case cannot be NULL?
Is there anyway to make that work with a statically defined array?
No you're right.
Basically, I want to be able to do something like:
static struct kunit_case example_test_cases[] = { KUNIT_CASE(example_simple_test), KUNIT_CASE(example_mock_test), {} };
FYI, #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
In order to do what you are proposing, I think I need an array of pointers to test cases, which is not ideal.
Yeah, you're right. The only other alternative is to have a:
struct kunit_module { const char name[256]; int (*init)(struct kunit *test); void (*exit)(struct kunit *test); struct kunit_case *test_cases; + unsigned int num_cases; };
And then something like:
#define KUNIT_MODULE(name, init, exit, cases) { \ .name = name, \ .init = init, \ .exit = exit, \ .test_cases = cases, num_cases = ARRAY_SIZE(cases), \ }
Let's evaluate which is better: one extra test case per all test cases, or an extra unsigned int for each kunit module.
Luis
On Tue, Jun 25, 2019 at 8:36 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Tue, Jun 25, 2019 at 05:07:32PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 3:33 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:25:56AM -0700, Brendan Higgins wrote:
+/**
- module_test() - used to register a &struct kunit_module with KUnit.
- @module: a statically allocated &struct kunit_module.
- Registers @module with the test framework. See &struct kunit_module for more
- information.
- */
+#define module_test(module) \
static int module_kunit_init##module(void) \
{ \
return kunit_run_tests(&module); \
} \
late_initcall(module_kunit_init##module)
Becuase late_initcall() is used, if these modules are built-in, this would preclude the ability to test things prior to this part of the kernel under UML or whatever architecture runs the tests. So, this limits the scope of testing. Small detail but the scope whould be documented.
You aren't the first person to complain about this (and I am not sure it is the first time you have complained about it). Anyway, I have some follow on patches that will improve the late_initcall thing, and people seemed okay with discussing the follow on patches as part of a subsequent patchset after this gets merged.
I will nevertheless document the restriction until then.
To be clear, I am not complaining about it. I just find it simply critical to document its limitations, so folks don't try to invest time and energy on kunit right away for an early init test, if it cannot support it.
If support for that requires some work, it may be worth mentioning as well.
Makes sense. And in anycase, it is something I do want to do, just not right now. I will put a TODO here in the next revision.
+static void kunit_print_tap_version(void) +{
if (!kunit_has_printed_tap_version) {
kunit_printk_emit(LOGLEVEL_INFO, "TAP version 14\n");
What is this TAP thing? Why should we care what version it is on? Why are we printing this?
It's part of the TAP specification[1]. Greg and Frank asked me to make the intermediate format conform to TAP. Seems like something else I should probable document...
Yes thanks!
kunit_has_printed_tap_version = true;
}
+}
+static size_t kunit_test_cases_len(struct kunit_case *test_cases) +{
struct kunit_case *test_case;
size_t len = 0;
for (test_case = test_cases; test_case->run_case; test_case++)
If we make the last test case NULL, we'd just check for test_case here, and save ourselves an extra few bytes per test module. Any reason why the last test case cannot be NULL?
Is there anyway to make that work with a statically defined array?
No you're right.
Basically, I want to be able to do something like:
static struct kunit_case example_test_cases[] = { KUNIT_CASE(example_simple_test), KUNIT_CASE(example_mock_test), {} };
FYI, #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
In order to do what you are proposing, I think I need an array of pointers to test cases, which is not ideal.
Yeah, you're right. The only other alternative is to have a:
struct kunit_module { const char name[256]; int (*init)(struct kunit *test); void (*exit)(struct kunit *test); struct kunit_case *test_cases;
unsigned int num_cases;
};
And then something like:
#define KUNIT_MODULE(name, init, exit, cases) { \ .name = name, \ .init = init, \ .exit = exit, \ .test_cases = cases, num_cases = ARRAY_SIZE(cases), \ }
Let's evaluate which is better: one extra test case per all test cases, or an extra unsigned int for each kunit module.
I am in favor of the current method since init and exit are optional arguments. I could see myself (actually I am planning on) adding more optional things to the kunit_module, so having optional arguments will make my life a lot easier since I won't have to go through big refactorings around the kernel to support new features that tie in here.
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 Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Changes Since Last Revision: - Found and fixed bug in resource deallocation logic. Bug was discovered as a result of making a change suggested by Stephen Boyd. This does not substantially change how any of the code works conceptually. --- include/kunit/test.h | 110 +++++++++++++++++++++++++++++++++++++++++++ kunit/test.c | 95 +++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 8476b3d371cb9..27bd95b6b5523 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -10,6 +10,70 @@ #define _KUNIT_TEST_H
#include <linux/types.h> +#include <linux/slab.h> + +struct kunit_resource; + +typedef int (*kunit_resource_init_t)(struct kunit_resource *, void *); +typedef void (*kunit_resource_free_t)(struct kunit_resource *); + +/** + * struct kunit_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 + * kunit_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 kunit_kmalloc_params { + * size_t size; + * gfp_t gfp; + * }; + * + * static int kunit_kmalloc_init(struct kunit_resource *res, void *context) + * { + * struct kunit_kmalloc_params *params = context; + * res->allocation = kmalloc(params->size, params->gfp); + * + * if (!res->allocation) + * return -ENOMEM; + * + * return 0; + * } + * + * static void kunit_kmalloc_free(struct kunit_resource *res) + * { + * kfree(res->allocation); + * } + * + * void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp) + * { + * struct kunit_kmalloc_params params; + * struct kunit_resource *res; + * + * params.size = size; + * params.gfp = gfp; + * + * res = kunit_alloc_resource(test, kunit_kmalloc_init, + * kunit_kmalloc_free, ¶ms); + * if (res) + * return res->allocation; + * + * return NULL; + * } + */ +struct kunit_resource { + void *allocation; + kunit_resource_free_t free; + + /* private: internal use only. */ + struct list_head node; +};
struct kunit;
@@ -103,6 +167,7 @@ struct kunit { 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 kunit_init_test(struct kunit *test, const char *name); @@ -123,6 +188,51 @@ int kunit_run_tests(struct kunit_module *module); } \ late_initcall(module_kunit_init##module)
+/** + * kunit_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 to the init function. + * + * Allocates a *test managed resource*, a resource which will automatically be + * cleaned up at the end of a test case. See &struct kunit_resource for an + * example. + */ +struct kunit_resource *kunit_alloc_resource(struct kunit *test, + kunit_resource_init_t init, + kunit_resource_free_t free, + void *context); + +void kunit_free_resource(struct kunit *test, struct kunit_resource *res); + +/** + * kunit_kmalloc() - 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 + * kunit_resource for more information. + */ +void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp); + +/** + * kunit_kzalloc() - Just like kunit_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 kunit_kmalloc() for more information. + */ +static inline void *kunit_kzalloc(struct kunit *test, size_t size, gfp_t gfp) +{ + return kunit_kmalloc(test, size, gfp | __GFP_ZERO); +} + +void kunit_cleanup(struct kunit *test); + void __printf(3, 4) kunit_printk(const char *level, const struct kunit *test, const char *fmt, ...); diff --git a/kunit/test.c b/kunit/test.c index d05d254f1521f..53838f5394303 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -142,6 +142,7 @@ static void kunit_print_test_case_ok_not_ok(struct kunit_case *test_case, void kunit_init_test(struct kunit *test, const char *name) { spin_lock_init(&test->lock); + INIT_LIST_HEAD(&test->resources); test->name = name; test->success = true; } @@ -172,6 +173,8 @@ static void kunit_run_case(struct kunit_module *module, if (module->exit) module->exit(&test);
+ kunit_cleanup(&test); + test_case->success = kunit_get_success(&test); }
@@ -192,6 +195,98 @@ int kunit_run_tests(struct kunit_module *module) return 0; }
+struct kunit_resource *kunit_alloc_resource(struct kunit *test, + kunit_resource_init_t init, + kunit_resource_free_t free, + void *context) +{ + struct kunit_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 kunit_free_resource(struct kunit *test, struct kunit_resource *res) +{ + res->free(res); + list_del(&res->node); + kfree(res); +} + +struct kunit_kmalloc_params { + size_t size; + gfp_t gfp; +}; + +static int kunit_kmalloc_init(struct kunit_resource *res, void *context) +{ + struct kunit_kmalloc_params *params = context; + + res->allocation = kmalloc(params->size, params->gfp); + if (!res->allocation) + return -ENOMEM; + + return 0; +} + +static void kunit_kmalloc_free(struct kunit_resource *res) +{ + kfree(res->allocation); +} + +void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp) +{ + struct kunit_kmalloc_params params; + struct kunit_resource *res; + + params.size = size; + params.gfp = gfp; + + res = kunit_alloc_resource(test, + kunit_kmalloc_init, + kunit_kmalloc_free, + ¶ms); + + if (res) + return res->allocation; + + return NULL; +} + +void kunit_cleanup(struct kunit *test) +{ + struct kunit_resource *resource, *resource_safe; + unsigned long flags; + + spin_lock_irqsave(&test->lock, flags); + /* + * test->resources is a stack - each allocation must be freed in the + * reverse order from which it was added since one resource may depend + * on another for its entire lifetime. + */ + list_for_each_entry_safe_reverse(resource, + resource_safe, + &test->resources, + node) { + kunit_free_resource(test, resource); + } + spin_unlock_irqrestore(&test->lock, flags); +} + void kunit_printk(const char *level, const struct kunit *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 Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Changes Since Last Revision: - Renamed new_string_stream() to alloc_string_stream() as suggested by Stephen Boyd. - Made string-stream a KUnit managed object - based on a suggestion made by Stephen Boyd. - Made a number of other small changes like renaming parameters as suggested by Stephen Boyd. --- include/kunit/string-stream.h | 49 ++++++++++++ kunit/Makefile | 3 +- kunit/string-stream.c | 147 ++++++++++++++++++++++++++++++++++ 3 files changed, 198 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..0552a05781afe --- /dev/null +++ b/include/kunit/string-stream.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2019, 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 kunit; + +struct string_stream *alloc_string_stream(struct kunit *test); + +void string_stream_get(struct string_stream *stream); + +int string_stream_put(struct string_stream *stream); + +int string_stream_add(struct string_stream *stream, const char *fmt, ...); + +int string_stream_vadd(struct string_stream *stream, + const char *fmt, + va_list args); + +char *string_stream_get_string(struct string_stream *stream); + +void string_stream_clear(struct string_stream *stream); + +bool string_stream_is_empty(struct string_stream *stream); + +#endif /* _KUNIT_STRING_STREAM_H */ diff --git a/kunit/Makefile b/kunit/Makefile index 5efdc4dea2c08..275b565a0e81f 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1 +1,2 @@ -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..0463a92dad74b --- /dev/null +++ b/kunit/string-stream.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * C++ stream style string builder used in KUnit for building messages. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <linux/list.h> +#include <linux/slab.h> +#include <kunit/string-stream.h> +#include <kunit/test.h> + +int string_stream_vadd(struct string_stream *stream, + const char *fmt, + va_list args) +{ + struct string_stream_fragment *frag_container; + 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); + + frag_container = kmalloc(sizeof(*frag_container), GFP_KERNEL); + if (!frag_container) + return -ENOMEM; + + frag_container->fragment = kmalloc(len, GFP_KERNEL); + if (!frag_container->fragment) { + kfree(frag_container); + return -ENOMEM; + } + + len = vsnprintf(frag_container->fragment, len, fmt, args); + spin_lock_irqsave(&stream->lock, flags); + stream->length += len; + list_add_tail(&frag_container->node, &stream->fragments); + spin_unlock_irqrestore(&stream->lock, flags); + + return 0; +} + +int string_stream_add(struct string_stream *stream, const char *fmt, ...) +{ + va_list args; + int result; + + va_start(args, fmt); + result = string_stream_vadd(stream, fmt, args); + va_end(args); + + return result; +} + +void string_stream_clear(struct string_stream *stream) +{ + struct string_stream_fragment *frag_container, *frag_container_safe; + unsigned long flags; + + spin_lock_irqsave(&stream->lock, flags); + list_for_each_entry_safe(frag_container, + frag_container_safe, + &stream->fragments, + node) { + list_del(&frag_container->node); + kfree(frag_container->fragment); + kfree(frag_container); + } + stream->length = 0; + spin_unlock_irqrestore(&stream->lock, flags); +} + +char *string_stream_get_string(struct string_stream *stream) +{ + struct string_stream_fragment *frag_container; + size_t buf_len = stream->length + 1; /* +1 for null byte. */ + char *buf; + unsigned long flags; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return NULL; + + spin_lock_irqsave(&stream->lock, flags); + list_for_each_entry(frag_container, &stream->fragments, node) + strlcat(buf, frag_container->fragment, buf_len); + spin_unlock_irqrestore(&stream->lock, flags); + + return buf; +} + +bool string_stream_is_empty(struct string_stream *stream) +{ + bool is_empty; + unsigned long flags; + + spin_lock_irqsave(&stream->lock, flags); + is_empty = list_empty(&stream->fragments); + spin_unlock_irqrestore(&stream->lock, flags); + + return is_empty; +} + +static int string_stream_init(struct kunit_resource *res, void *context) +{ + struct string_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + res->allocation = stream; + INIT_LIST_HEAD(&stream->fragments); + spin_lock_init(&stream->lock); + + return 0; +} + +static void string_stream_free(struct kunit_resource *res) +{ + struct string_stream *stream = res->allocation; + + string_stream_clear(stream); + kfree(stream); +} + +struct string_stream *alloc_string_stream(struct kunit *test) +{ + struct kunit_resource *res; + + res = kunit_alloc_resource(test, + string_stream_init, + string_stream_free, + NULL); + + if (!res) + return NULL; + + return res->allocation; +}
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 Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Changes Since Last Revision: - Renamed kunit_new_stream() to alloc_kunit_stream() as suggested by Stephen Boyd. - Removed the ability to set log level after allocating a stream, as suggested by Stephen Boyd. - Removed locking, as it is now unnecessary with the previous change. - Made a number of other small changes like renaming parameters as suggested by Stephen Boyd. --- include/kunit/kunit-stream.h | 81 +++++++++++++++++++++++ include/kunit/test.h | 3 + kunit/Makefile | 3 +- kunit/kunit-stream.c | 123 +++++++++++++++++++++++++++++++++++ kunit/test.c | 6 ++ 5 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 include/kunit/kunit-stream.h create mode 100644 kunit/kunit-stream.c
diff --git a/include/kunit/kunit-stream.h b/include/kunit/kunit-stream.h new file mode 100644 index 0000000000000..a7b53eabf6be4 --- /dev/null +++ b/include/kunit/kunit-stream.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * C++ stream style string formatter and printer used in KUnit for outputting + * KUnit messages. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#ifndef _KUNIT_KUNIT_STREAM_H +#define _KUNIT_KUNIT_STREAM_H + +#include <linux/types.h> +#include <kunit/string-stream.h> + +struct kunit; + +/** + * struct kunit_stream - a std::stream style string builder. + * + * A std::stream style string builder. Allows messages to be built up and + * printed all at once. + */ +struct kunit_stream { + /* private: internal use only. */ + struct kunit *test; + const char *level; + struct string_stream *internal_stream; +}; + +/** + * alloc_kunit_stream() - constructs a new &struct kunit_stream. + * @test: The test context object. + * @level: The log level at which to print out the message. + * + * Constructs a new test managed &struct kunit_stream. + */ +struct kunit_stream *alloc_kunit_stream(struct kunit *test, const char *level); + +/** + * kunit_stream_add(): adds the formatted input to the internal buffer. + * @kstream: the stream being operated on. + * @fmt: printf style format string to append to stream. + * + * Appends the formatted string, @fmt, to the internal buffer. + */ +void __printf(2, 3) kunit_stream_add(struct kunit_stream *kstream, + const char *fmt, ...); + +/** + * kunit_stream_append(): appends the contents of @other to @kstream. + * @kstream: the stream to which @other is appended. + * @other: the stream whose contents are appended to @kstream. + * + * Appends the contents of @other to @kstream. + */ +void kunit_stream_append(struct kunit_stream *kstream, + struct kunit_stream *other); + +/** + * kunit_stream_commit(): prints out the internal buffer to the user. + * @kstream: the stream being operated on. + * + * Outputs the contents of the internal buffer as a kunit_printk formatted + * output. KUNIT_STREAM ONLY OUTPUTS ITS BUFFER TO THE USER IF COMMIT IS + * CALLED!!! The reason for this is that it allows us to construct a message + * before we know whether we want to print it out; this can be extremely handy + * if there is information you might need for a failure message that is easiest + * to collect in the steps leading up to the actual check. + */ +void kunit_stream_commit(struct kunit_stream *kstream); + +/** + * kunit_stream_clear(): clears the internal buffer. + * @kstream: the stream being operated on. + * + * Clears the contents of the internal buffer. + */ +void kunit_stream_clear(struct kunit_stream *kstream); + +#endif /* _KUNIT_KUNIT_STREAM_H */ diff --git a/include/kunit/test.h b/include/kunit/test.h index 27bd95b6b5523..83fb7ace1b320 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/kunit-stream.h>
struct kunit_resource;
@@ -172,6 +173,8 @@ struct kunit {
void kunit_init_test(struct kunit *test, const char *name);
+void kunit_fail(struct kunit *test, struct kunit_stream *stream); + int kunit_run_tests(struct kunit_module *module);
/** diff --git a/kunit/Makefile b/kunit/Makefile index 275b565a0e81f..6ddc622ee6b1c 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_KUNIT) += test.o \ - string-stream.o + string-stream.o \ + kunit-stream.o diff --git a/kunit/kunit-stream.c b/kunit/kunit-stream.c new file mode 100644 index 0000000000000..8bea1f22eafb5 --- /dev/null +++ b/kunit/kunit-stream.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * C++ stream style string formatter and printer used in KUnit for outputting + * KUnit messages. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <kunit/test.h> +#include <kunit/kunit-stream.h> +#include <kunit/string-stream.h> + +void kunit_stream_add(struct kunit_stream *kstream, const char *fmt, ...) +{ + va_list args; + struct string_stream *stream = kstream->internal_stream; + + va_start(args, fmt); + + if (string_stream_vadd(stream, fmt, args) < 0) + kunit_err(kstream->test, + "Failed to allocate fragment: %s\n", + fmt); + + va_end(args); +} + +void kunit_stream_append(struct kunit_stream *kstream, + struct kunit_stream *other) +{ + struct string_stream *other_stream = other->internal_stream; + const char *other_content; + + other_content = string_stream_get_string(other_stream); + + if (!other_content) { + kunit_err(kstream->test, + "Failed to get string from second argument for appending\n"); + return; + } + + kunit_stream_add(kstream, other_content); +} + +void kunit_stream_clear(struct kunit_stream *kstream) +{ + string_stream_clear(kstream->internal_stream); +} + +void kunit_stream_commit(struct kunit_stream *kstream) +{ + struct string_stream *stream = kstream->internal_stream; + struct string_stream_fragment *fragment; + struct kunit *test = kstream->test; + char *buf; + + buf = string_stream_get_string(stream); + if (!buf) { + kunit_err(test, + "Could not allocate buffer, dumping stream:\n"); + list_for_each_entry(fragment, &stream->fragments, node) { + kunit_err(test, fragment->fragment); + } + kunit_err(test, "\n"); + goto cleanup; + } + + kunit_printk(kstream->level, test, buf); + kfree(buf); + +cleanup: + kunit_stream_clear(kstream); +} + +static int kunit_stream_init(struct kunit_resource *res, void *context) +{ + struct kunit *test = context; + struct kunit_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + res->allocation = stream; + stream->test = test; + stream->internal_stream = alloc_string_stream(test); + + if (!stream->internal_stream) + return -ENOMEM; + + return 0; +} + +static void kunit_stream_free(struct kunit_resource *res) +{ + struct kunit_stream *stream = res->allocation; + + if (!string_stream_is_empty(stream->internal_stream)) { + kunit_err(stream->test, + "End of test case reached with uncommitted stream entries\n"); + kunit_stream_commit(stream); + } +} + +struct kunit_stream *alloc_kunit_stream(struct kunit *test, const char *level) +{ + struct kunit_stream *kstream; + struct kunit_resource *res; + + res = kunit_alloc_resource(test, + kunit_stream_init, + kunit_stream_free, + test); + + if (!res) + return NULL; + + kstream = res->allocation; + kstream->level = level; + + return kstream; +} diff --git a/kunit/test.c b/kunit/test.c index 53838f5394303..9404e4d34fa3f 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -139,6 +139,12 @@ static void kunit_print_test_case_ok_not_ok(struct kunit_case *test_case, test_case->name); }
+void kunit_fail(struct kunit *test, struct kunit_stream *stream) +{ + kunit_set_success(test, false); + kunit_stream_commit(stream); +} + void kunit_init_test(struct kunit *test, const char *name) { spin_lock_init(&test->lock);
Add support for expectations, which allow properties to be specified and then verified in tests.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Changes Since Last Revision: - Added typechecking to expectations that compare two things of arbitrary types (it just verifies that the two compared types match), as suggested by Stephen Boyd. --- include/kunit/test.h | 525 +++++++++++++++++++++++++++++++++++++++++++ kunit/test.c | 66 ++++++ 2 files changed, 591 insertions(+)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 83fb7ace1b320..e4c760ccc8717 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -9,6 +9,7 @@ #ifndef _KUNIT_TEST_H #define _KUNIT_TEST_H
+#include <linux/kernel.h> #include <linux/types.h> #include <linux/slab.h> #include <kunit/kunit-stream.h> @@ -271,4 +272,528 @@ void __printf(3, 4) kunit_printk(const char *level, #define kunit_err(test, fmt, ...) \ kunit_printk(KERN_ERR, test, fmt, ##__VA_ARGS__)
+/* + * Generates a compile-time warning in case of comparing incompatible types. + */ +#define __kunit_typecheck(lhs, rhs) \ + ((void) __typecheck(lhs, rhs)) + +static inline struct kunit_stream *kunit_expect_start(struct kunit *test, + const char *file, + const char *line) +{ + struct kunit_stream *stream = alloc_kunit_stream(test, KERN_ERR); + + kunit_stream_add(stream, "EXPECTATION FAILED at %s:%s\n\t", file, line); + + return stream; +} + +static inline void kunit_expect_end(struct kunit *test, + bool success, + struct kunit_stream *stream) +{ + if (!success) + kunit_fail(test, stream); + else + kunit_stream_clear(stream); +} + +#define KUNIT_EXPECT_START(test) \ + kunit_expect_start(test, __FILE__, __stringify(__LINE__)) + +#define KUNIT_EXPECT_END(test, success, stream) \ + kunit_expect_end(test, success, stream) + +#define KUNIT_EXPECT_MSG(test, success, message, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + \ + kunit_stream_add(__stream, message); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + KUNIT_EXPECT_END(test, success, __stream); \ +} while (0) + +#define KUNIT_EXPECT(test, success, message) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + \ + kunit_stream_add(__stream, message); \ + KUNIT_EXPECT_END(test, success, __stream); \ +} while (0) + +/** + * KUNIT_SUCCEED() - A no-op expectation. Only exists for code clarity. + * @test: The test context object. + * + * The opposite of KUNIT_FAIL(), it is an expectation that cannot fail. In other + * words, it does nothing and only exists for code clarity. See + * KUNIT_EXPECT_TRUE() for more information. + */ +#define KUNIT_SUCCEED(test) do {} while (0) + +/** + * KUNIT_FAIL() - Always causes a test to fail when evaluated. + * @test: The test context object. + * @fmt: an informational message to be printed when the assertion is made. + * @...: string format arguments. + * + * The opposite of KUNIT_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 KUNIT_EXPECT_TRUE() + * for more information. + */ +#define KUNIT_FAIL(test, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + KUNIT_EXPECT_END(test, false, __stream); \ +} while (0) + +/** + * KUNIT_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 `KUNIT_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 KUNIT_EXPECT_TRUE(test, condition) \ + KUNIT_EXPECT(test, (condition), \ + "Expected " #condition " is true, but is false\n") + +#define KUNIT_EXPECT_TRUE_MSG(test, condition, fmt, ...) \ + KUNIT_EXPECT_MSG(test, (condition), \ + "Expected " #condition " is true, but is false\n",\ + fmt, ##__VA_ARGS__) + +/** + * KUNIT_EXPECT_FALSE() - Makes 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 + * KUNIT_EXPECT_TRUE() for more information. + */ +#define KUNIT_EXPECT_FALSE(test, condition) \ + KUNIT_EXPECT(test, !(condition), \ + "Expected " #condition " is false, but is true\n") + +#define KUNIT_EXPECT_FALSE_MSG(test, condition, fmt, ...) \ + KUNIT_EXPECT_MSG(test, !(condition), \ + "Expected " #condition " is false, but is true\n",\ + fmt, ##__VA_ARGS__) + +void kunit_expect_binary_msg(struct kunit *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, + const char *fmt, ...); + +static inline void kunit_expect_binary(struct kunit *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) +{ + kunit_expect_binary_msg(test, + left, left_name, + right, right_name, + compare_result, + compare_name, + file, + line, + NULL); +} + +void kunit_expect_ptr_binary_msg(struct kunit *test, + void *left, const char *left_name, + void *right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line, + const char *fmt, ...); + +static inline void kunit_expect_ptr_binary(struct kunit *test, + void *left, const char *left_name, + void *right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line) +{ + kunit_expect_ptr_binary_msg(test, + left, left_name, + right, right_name, + compare_result, + compare_name, + file, + line, + NULL); +} + +/* + * 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 KUNIT_EXPECT_BINARY(test, left, condition, right) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_expect_binary(test, \ + (long long) __left, #left, \ + (long long) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__)); \ +} while (0) + +#define KUNIT_EXPECT_BINARY_MSG(test, left, condition, right, fmt, ...) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_expect_binary_msg(test, \ + (long long) __left, #left, \ + (long long) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__), \ + fmt, ##__VA_ARGS__); \ +} while (0) + +/* + * Just like KUNIT_EXPECT_BINARY, but for comparing pointer types. + */ +#define KUNIT_EXPECT_PTR_BINARY(test, left, condition, right) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_expect_ptr_binary(test, \ + (void *) __left, #left, \ + (void *) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__)); \ +} while (0) + +#define KUNIT_EXPECT_PTR_BINARY_MSG(test, left, condition, right, fmt, ...) \ +do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_expect_ptr_binary_msg(test, \ + (void *) __left, #left, \ + (void *) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__), \ + fmt, ##__VA_ARGS__); \ +} while (0) + +/** + * KUNIT_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 + * KUNIT_EXPECT_TRUE(@test, (@left) == (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_EQ(test, left, right) \ + KUNIT_EXPECT_BINARY(test, left, ==, right) + +#define KUNIT_EXPECT_EQ_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_BINARY_MSG(test, \ + left, \ + ==, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_EXPECT_PTR_EQ() - Expects that pointers @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a pointer. + * @right: an arbitrary expression that evaluates to a pointer. + * + * Sets an expectation that the values that @left and @right evaluate to are + * equal. This is semantically equivalent to + * KUNIT_EXPECT_TRUE(@test, (@left) == (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_PTR_EQ(test, left, right) \ + KUNIT_EXPECT_PTR_BINARY(test, left, ==, right) + +#define KUNIT_EXPECT_PTR_EQ_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_PTR_BINARY_MSG(test, \ + left, \ + ==, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 + * KUNIT_EXPECT_TRUE(@test, (@left) != (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_NE(test, left, right) \ + KUNIT_EXPECT_BINARY(test, left, !=, right) + +#define KUNIT_EXPECT_NE_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_BINARY_MSG(test, \ + left, \ + !=, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_EXPECT_PTR_NE() - Expects that pointers @left and @right are not equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a pointer. + * @right: an arbitrary expression that evaluates to a pointer. + * + * Sets an expectation that the values that @left and @right evaluate to are not + * equal. This is semantically equivalent to + * KUNIT_EXPECT_TRUE(@test, (@left) != (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_PTR_NE(test, left, right) \ + KUNIT_EXPECT_PTR_BINARY(test, left, !=, right) + +#define KUNIT_EXPECT_PTR_NE_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_PTR_BINARY_MSG(test, \ + left, \ + !=, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 + * KUNIT_EXPECT_TRUE(@test, (@left) < (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_LT(test, left, right) \ + KUNIT_EXPECT_BINARY(test, left, <, right) + +#define KUNIT_EXPECT_LT_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_BINARY_MSG(test, \ + left, \ + <, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_EXPECT_LE() - Expects 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 KUNIT_EXPECT_TRUE(@test, (@left) <= (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_LE(test, left, right) \ + KUNIT_EXPECT_BINARY(test, left, <=, right) + +#define KUNIT_EXPECT_LE_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_BINARY_MSG(test, \ + left, \ + <=, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 + * KUNIT_EXPECT_TRUE(@test, (@left) > (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_GT(test, left, right) \ + KUNIT_EXPECT_BINARY(test, left, >, right) + +#define KUNIT_EXPECT_GT_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_BINARY_MSG(test, \ + left, \ + >, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 + * KUNIT_EXPECT_TRUE(@test, (@left) >= (@right)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_GE(test, left, right) \ + KUNIT_EXPECT_BINARY(test, left, >=, right) + +#define KUNIT_EXPECT_GE_MSG(test, left, right, fmt, ...) \ + KUNIT_EXPECT_BINARY_MSG(test, \ + left, \ + >=, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_EXPECT_STREQ() - Expects 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 + * KUNIT_EXPECT_TRUE(@test, !strcmp((@left), (@right))). See KUNIT_EXPECT_TRUE() + * for more information. + */ +#define KUNIT_EXPECT_STREQ(test, left, right) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Expected " #left " == " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + \ + KUNIT_EXPECT_END(test, !strcmp(left, right), __stream); \ +} while (0) + +#define KUNIT_EXPECT_STREQ_MSG(test, left, right, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Expected " #left " == " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + \ + KUNIT_EXPECT_END(test, !strcmp(left, right), __stream); \ +} while (0) + +/** + * KUNIT_EXPECT_STRNEQ() - Expects that strings @left and @right are not 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 + * not equal. This is semantically equivalent to + * KUNIT_EXPECT_TRUE(@test, strcmp((@left), (@right))). See KUNIT_EXPECT_TRUE() + * for more information. + */ +#define KUNIT_EXPECT_STRNEQ(test, left, right) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Expected " #left " != " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + \ + KUNIT_EXPECT_END(test, strcmp(left, right), __stream); \ +} while (0) + +#define KUNIT_EXPECT_STRNEQ_MSG(test, left, right, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Expected " #left " != " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + \ + KUNIT_EXPECT_END(test, strcmp(left, right), __stream); \ +} while (0) + +/** + * KUNIT_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 + * KUNIT_EXPECT_TRUE(@test, !IS_ERR_OR_NULL(@ptr)). See KUNIT_EXPECT_TRUE() for + * more information. + */ +#define KUNIT_EXPECT_NOT_ERR_OR_NULL(test, ptr) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + typeof(ptr) __ptr = (ptr); \ + \ + if (!__ptr) \ + kunit_stream_add(__stream, \ + "Expected " #ptr " is not null, but is\n"); \ + if (IS_ERR(__ptr)) \ + kunit_stream_add(__stream, \ + "Expected " #ptr " is not error, but is: %ld", \ + PTR_ERR(__ptr)); \ + \ + KUNIT_EXPECT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ +} while (0) + +#define KUNIT_EXPECT_NOT_ERR_OR_NULL_MSG(test, ptr, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_EXPECT_START(test); \ + typeof(ptr) __ptr = (ptr); \ + \ + if (!__ptr) { \ + kunit_stream_add(__stream, \ + "Expected " #ptr " is not null, but is\n"); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + } \ + if (IS_ERR(__ptr)) { \ + kunit_stream_add(__stream, \ + "Expected " #ptr " is not error, but is: %ld", \ + PTR_ERR(__ptr)); \ + \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + } \ + KUNIT_EXPECT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ +} while (0) + #endif /* _KUNIT_TEST_H */ diff --git a/kunit/test.c b/kunit/test.c index 9404e4d34fa3f..0ba05467d525f 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -309,3 +309,69 @@ void kunit_printk(const char *level,
va_end(args); } + +void kunit_expect_binary_msg(struct kunit *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, + const char *fmt, ...) +{ + struct kunit_stream *stream = kunit_expect_start(test, file, line); + struct va_format vaf; + va_list args; + + kunit_stream_add(stream, + "Expected %s %s %s, but\n", + left_name, compare_name, right_name); + kunit_stream_add(stream, "\t\t%s == %lld\n", left_name, left); + kunit_stream_add(stream, "\t\t%s == %lld", right_name, right); + + if (fmt) { + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + kunit_stream_add(stream, "\n%pV", &vaf); + + va_end(args); + } + + kunit_expect_end(test, compare_result, stream); +} + +void kunit_expect_ptr_binary_msg(struct kunit *test, + void *left, const char *left_name, + void *right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line, + const char *fmt, ...) +{ + struct kunit_stream *stream = kunit_expect_start(test, file, line); + struct va_format vaf; + va_list args; + + kunit_stream_add(stream, + "Expected %s %s %s, but\n", + left_name, compare_name, right_name); + kunit_stream_add(stream, "\t\t%s == %pK\n", left_name, left); + kunit_stream_add(stream, "\t\t%s == %pK", right_name, right); + + if (fmt) { + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + kunit_stream_add(stream, "\n%pV", &vaf); + + va_end(args); + } + + kunit_expect_end(test, compare_result, stream); +}
KUnit is a new unit testing framework for the kernel and when used is built into the kernel as a part of it. Add KUnit to the root Kconfig and Makefile to allow it to be actually built.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.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 b81e172612507..4b544a8eebee4 100644 --- a/Makefile +++ b/Makefile @@ -991,7 +991,7 @@ endif PHONY += prepare0
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) \
On Mon, Jun 17, 2019 at 01:26:01AM -0700, Brendan Higgins wrote:
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"
This patch would break compilation as kunit/Kconfig is not introduced. This would would also break bisectability on this commit. This change should either be folded in to the next patch, or just be a separate patch after the next one.
Luis
On Tue, Jun 25, 2019 at 3:13 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:01AM -0700, Brendan Higgins wrote:
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"
This patch would break compilation as kunit/Kconfig is not introduced. This would would also break bisectability on this commit. This change should either be folded in to the next patch, or just be a separate patch after the next one.
Maybe my brain isn't working right now, but I am pretty darn sure that I introduce kunit/Kconfig in the very first patch of this series. Quoting from the change summary from the first commit:
include/kunit/test.h | 161 +++++++++++++++++++++++++++++++++ kunit/Kconfig | 17 ++++ kunit/Makefile | 1 + kunit/test.c | 210 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 include/kunit/test.h create mode 100644 kunit/Kconfig
I am not crazy, right?
create mode 100644 kunit/Makefile create mode 100644 kunit/test.c
On Tue, Jun 25, 2019 at 03:41:29PM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 3:13 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:01AM -0700, Brendan Higgins wrote:
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"
This patch would break compilation as kunit/Kconfig is not introduced. This would would also break bisectability on this commit. This change should either be folded in to the next patch, or just be a separate patch after the next one.
Maybe my brain isn't working right now, but I am pretty darn sure that I introduce kunit/Kconfig in the very first patch of this series. Quoting from the change summary from the first commit:
Indeed, my mistake, thanks!
Luis
Add a test for string stream along with a simpler example.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- kunit/Kconfig | 21 +++++++++ kunit/Makefile | 4 ++ kunit/example-test.c | 88 ++++++++++++++++++++++++++++++++++++++ kunit/string-stream-test.c | 75 ++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 kunit/example-test.c create mode 100644 kunit/string-stream-test.c
diff --git a/kunit/Kconfig b/kunit/Kconfig index 330ae83527c23..8541ef95b65ad 100644 --- a/kunit/Kconfig +++ b/kunit/Kconfig @@ -14,4 +14,25 @@ config KUNIT architectures. For more information, please see Documentation/dev-tools/kunit/.
+config KUNIT_TEST + bool "KUnit test for KUnit" + depends on KUNIT + help + Enables the unit tests for the KUnit test framework. These tests test + the KUnit test framework itself; the tests are both written using + KUnit and test KUnit. This option should only be enabled for testing + purposes by developers interested in testing that KUnit works as + expected. + +config KUNIT_EXAMPLE_TEST + bool "Example test for KUnit" + depends on KUNIT + help + Enables an example unit test that illustrates some of the basic + features of KUnit. This test only exists to help new users understand + what KUnit is and how it is used. Please refer to the example test + itself, kunit/example-test.c, for more information. This option is + intended for curious hackers who would like to understand how to use + KUnit for kernel development. + endmenu diff --git a/kunit/Makefile b/kunit/Makefile index 6ddc622ee6b1c..60a9ea6cb4697 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1,3 +1,7 @@ obj-$(CONFIG_KUNIT) += test.o \ string-stream.o \ kunit-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..f44b8ece488bb --- /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) 2019, 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 kunit *)`. `struct kunit` is a context object that stores + * information about the current test. + */ +static void example_simple_test(struct kunit *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. + */ + KUNIT_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 kunit *test) +{ + kunit_info(test, "initializing\n"); + + return 0; +} + +/* + * Here we make a list of all the test cases we want to add to the test module + * below. + */ +static struct kunit_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. + */ + KUNIT_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 + * `kunit_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 kunit_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..36f0b5769a5a4 --- /dev/null +++ b/kunit/string-stream-test.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for struct string_stream. + * + * Copyright (C) 2019, 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_empty_on_creation(struct kunit *test) +{ + struct string_stream *stream = alloc_string_stream(test); + + KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); +} + +static void string_stream_test_not_empty_after_add(struct kunit *test) +{ + struct string_stream *stream = alloc_string_stream(test); + + string_stream_add(stream, "Foo"); + + KUNIT_EXPECT_FALSE(test, string_stream_is_empty(stream)); +} +static void string_stream_test_get_string(struct kunit *test) +{ + struct string_stream *stream = alloc_string_stream(test); + char *output; + + string_stream_add(stream, "Foo"); + string_stream_add(stream, " %s", "bar"); + + output = string_stream_get_string(stream); + KUNIT_EXPECT_STREQ(test, output, "Foo bar"); + kfree(output); +} + +static void string_stream_test_add_and_clear(struct kunit *test) +{ + struct string_stream *stream = alloc_string_stream(test); + char *output; + int i; + + for (i = 0; i < 10; i++) + string_stream_add(stream, "A"); + + output = string_stream_get_string(stream); + KUNIT_EXPECT_STREQ(test, output, "AAAAAAAAAA"); + KUNIT_EXPECT_EQ(test, stream->length, (size_t)10); + KUNIT_EXPECT_FALSE(test, string_stream_is_empty(stream)); + kfree(output); + + string_stream_clear(stream); + + output = string_stream_get_string(stream); + KUNIT_EXPECT_STREQ(test, output, ""); + KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); +} + +static struct kunit_case string_stream_test_cases[] = { + KUNIT_CASE(string_stream_test_empty_on_creation), + KUNIT_CASE(string_stream_test_not_empty_after_add), + KUNIT_CASE(string_stream_test_get_string), + KUNIT_CASE(string_stream_test_add_and_clear), + {} +}; + +static struct kunit_module string_stream_test_module = { + .name = "string-stream-test", + .test_cases = string_stream_test_cases +}; +module_test(string_stream_test_module);
On Mon, Jun 17, 2019 at 01:26:02AM -0700, Brendan Higgins wrote:
diff --git a/kunit/example-test.c b/kunit/example-test.c new file mode 100644 index 0000000000000..f44b8ece488bb --- /dev/null +++ b/kunit/example-test.c
<-- snip -->
+/*
- This defines a suite or grouping of tests.
- Test cases are defined as belonging to the suite by adding them to
- `kunit_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:
To be clear this is not the kernel module init, but rather the kunit module init. I think using kmodule would make this clearer to a reader.
- module.init(test);
- module.test_case[0](test);
- module.exit(test);
- module.init(test);
- module.test_case[1](test);
- module.exit(test);
- ...;
- */
Luis
On Tue, Jun 25, 2019 at 4:22 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:02AM -0700, Brendan Higgins wrote:
diff --git a/kunit/example-test.c b/kunit/example-test.c new file mode 100644 index 0000000000000..f44b8ece488bb --- /dev/null +++ b/kunit/example-test.c
<-- snip -->
+/*
- This defines a suite or grouping of tests.
- Test cases are defined as belonging to the suite by adding them to
- `kunit_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:
To be clear this is not the kernel module init, but rather the kunit module init. I think using kmodule would make this clearer to a reader.
Seems reasonable. Will fix in next revision.
- module.init(test);
- module.test_case[0](test);
- module.exit(test);
- module.init(test);
- module.test_case[1](test);
- module.exit(test);
- ...;
- */
Luis
On Wed, Jun 26, 2019 at 12:53 AM Brendan Higgins brendanhiggins@google.com wrote:
On Tue, Jun 25, 2019 at 4:22 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:02AM -0700, Brendan Higgins wrote:
diff --git a/kunit/example-test.c b/kunit/example-test.c new file mode 100644 index 0000000000000..f44b8ece488bb --- /dev/null +++ b/kunit/example-test.c
<-- snip -->
+/*
- This defines a suite or grouping of tests.
- Test cases are defined as belonging to the suite by adding them to
- `kunit_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:
To be clear this is not the kernel module init, but rather the kunit module init. I think using kmodule would make this clearer to a reader.
Seems reasonable. Will fix in next revision.
- module.init(test);
- module.test_case[0](test);
- module.exit(test);
- module.init(test);
- module.test_case[1](test);
- module.exit(test);
- ...;
- */
Do you think it might be clearer yet to rename `struct kunit_module *module;` to `struct kunit_suite *suite;`?
On Tue, Jul 02, 2019 at 10:52:50AM -0700, Brendan Higgins wrote:
On Wed, Jun 26, 2019 at 12:53 AM Brendan Higgins brendanhiggins@google.com wrote:
On Tue, Jun 25, 2019 at 4:22 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:02AM -0700, Brendan Higgins wrote:
diff --git a/kunit/example-test.c b/kunit/example-test.c new file mode 100644 index 0000000000000..f44b8ece488bb --- /dev/null +++ b/kunit/example-test.c
<-- snip -->
+/*
- This defines a suite or grouping of tests.
- Test cases are defined as belonging to the suite by adding them to
- `kunit_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:
To be clear this is not the kernel module init, but rather the kunit module init. I think using kmodule would make this clearer to a reader.
Seems reasonable. Will fix in next revision.
- module.init(test);
- module.test_case[0](test);
- module.exit(test);
- module.init(test);
- module.test_case[1](test);
- module.exit(test);
- ...;
- */
Do you think it might be clearer yet to rename `struct kunit_module *module;` to `struct kunit_suite *suite;`?
Yes. Definitely. Or struct kunit_test. Up to you.
Luis
Fix the following warning seen on GCC 7.3: kunit/test-test.o: warning: objtool: kunit_test_unsuccessful_try() falls through to next function kunit_test_catch()
kunit_try_catch_throw is a function added in the following patch in this series; it allows KUnit, a unit testing framework for the kernel, to bail out of a broken test. As a consequence, it is a new __noreturn function that objtool thinks is broken (as seen above). So fix this warning by adding kunit_try_catch_throw to objtool's noreturn list.
Reported-by: kbuild test robot lkp@intel.com Signed-off-by: Brendan Higgins brendanhiggins@google.com Link: https://www.spinics.net/lists/linux-kbuild/msg21708.html --- tools/objtool/check.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 172f991957269..98db5fe85c797 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -134,6 +134,7 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func, "usercopy_abort", "machine_real_restart", "rewind_stack_do_exit", + "kunit_try_catch_throw", };
if (func->bind == STB_WEAK)
Add support for aborting/bailing out of test cases, which is needed for implementing assertions.
An assertion is like an expectation, but bails out of the test case early if the assertion is not met. The idea with assertions is that you use them to state all the preconditions for your test. Logically speaking, these are the premises of the test case, so if a premise isn't true, there is no point in continuing the test case because there are no conclusions that can be drawn without the premises. Whereas, the expectation is the thing you are trying to prove.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- include/kunit/test.h | 14 +++ include/kunit/try-catch.h | 69 +++++++++++++++ kunit/Makefile | 3 +- kunit/test.c | 177 ++++++++++++++++++++++++++++++++++---- kunit/try-catch.c | 95 ++++++++++++++++++++ 5 files changed, 342 insertions(+), 16 deletions(-) create mode 100644 include/kunit/try-catch.h create mode 100644 kunit/try-catch.c
diff --git a/include/kunit/test.h b/include/kunit/test.h index e4c760ccc8717..194c85afc69a8 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -13,6 +13,7 @@ #include <linux/types.h> #include <linux/slab.h> #include <kunit/kunit-stream.h> +#include <kunit/try-catch.h>
struct kunit_resource;
@@ -167,15 +168,28 @@ struct kunit {
/* private: internal use only. */ const char *name; /* Read only after initialization! */ + struct kunit_try_catch try_catch; 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. */ };
+static inline void kunit_set_death_test(struct kunit *test, bool death_test) +{ + unsigned long flags; + + spin_lock_irqsave(&test->lock, flags); + test->death_test = death_test; + spin_unlock_irqrestore(&test->lock, flags); +} + void kunit_init_test(struct kunit *test, const char *name);
void kunit_fail(struct kunit *test, struct kunit_stream *stream);
+void kunit_abort(struct kunit *test); + int kunit_run_tests(struct kunit_module *module);
/** diff --git a/include/kunit/try-catch.h b/include/kunit/try-catch.h new file mode 100644 index 0000000000000..8a414a9af0b64 --- /dev/null +++ b/include/kunit/try-catch.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * An API to allow a function, that may fail, to be executed, and recover in a + * controlled manner. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#ifndef _KUNIT_TRY_CATCH_H +#define _KUNIT_TRY_CATCH_H + +#include <linux/types.h> + +typedef void (*kunit_try_catch_func_t)(void *); + +struct kunit; + +/* + * struct kunit_try_catch - provides a generic way to run code which might fail. + * @context: used to pass user data to the try and catch functions. + * + * kunit_try_catch provides a generic, architecture independent way to execute + * an arbitrary function of type kunit_try_catch_func_t which may bail out by + * calling kunit_try_catch_throw(). If kunit_try_catch_throw() is called, @try + * is stopped at the site of invocation and @catch is catch is called. + * + * struct kunit_try_catch provides a generic interface for the functionality + * needed to implement kunit->abort() which in turn is needed for implementing + * assertions. Assertions allow stating a precondition for a test simplifying + * how test cases are written and presented. + * + * Assertions are like expectations, except they abort (call + * kunit_try_catch_throw()) when the specified condition is not met. This is + * useful when you look at a test case as a logical statement about some piece + * of code, where assertions are the premises for the test case, and the + * conclusion is a set of predicates, rather expectations, that must all be + * true. If your premises are violated, it does not makes sense to continue. + */ +struct kunit_try_catch { + /* private: internal use only. */ + struct kunit *test; + struct completion *try_completion; + int try_result; + kunit_try_catch_func_t try; + kunit_try_catch_func_t catch; + void *context; +}; + +void kunit_try_catch_init(struct kunit_try_catch *try_catch, + struct kunit *test, + kunit_try_catch_func_t try, + kunit_try_catch_func_t catch); + +void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context); + +void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch); + +static inline int kunit_try_catch_get_result(struct kunit_try_catch *try_catch) +{ + return try_catch->try_result; +} + +/* + * Exposed for testing only. + */ +void kunit_generic_try_catch_init(struct kunit_try_catch *try_catch); + +#endif /* _KUNIT_TRY_CATCH_H */ diff --git a/kunit/Makefile b/kunit/Makefile index 60a9ea6cb4697..1f7680cfa11ad 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_KUNIT) += test.o \ string-stream.o \ - kunit-stream.o + kunit-stream.o \ + try-catch.o
obj-$(CONFIG_KUNIT_TEST) += string-stream-test.o
diff --git a/kunit/test.c b/kunit/test.c index 0ba05467d525f..cb07e0916e901 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -8,6 +8,7 @@
#include <linux/sched/debug.h> #include <kunit/test.h> +#include <kunit/try-catch.h>
static bool kunit_get_success(struct kunit *test) { @@ -30,6 +31,18 @@ static void kunit_set_success(struct kunit *test, bool success) spin_unlock_irqrestore(&test->lock, flags); }
+static bool kunit_get_death_test(struct kunit *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 int kunit_vprintk_emit(int level, const char *fmt, va_list args) { return vprintk_emit(0, level, NULL, 0, fmt, args); @@ -145,41 +158,175 @@ void kunit_fail(struct kunit *test, struct kunit_stream *stream) kunit_stream_commit(stream); }
+void __noreturn kunit_abort(struct kunit *test) +{ + kunit_set_death_test(test, true); + + kunit_try_catch_throw(&test->try_catch); + + /* + * Throw could not abort from test. + * + * XXX: we should never reach this line! As kunit_try_catch_throw is + * marked __noreturn. + */ + WARN_ONCE(true, "Throw could not abort from test!\n"); +} + void kunit_init_test(struct kunit *test, const char *name) { spin_lock_init(&test->lock); INIT_LIST_HEAD(&test->resources); test->name = name; test->success = true; + test->death_test = false; }
/* - * Performs all logic to run a test case. + * Initializes and runs test case. Does not clean up or do post validations. */ -static void kunit_run_case(struct kunit_module *module, - struct kunit_case *test_case) +static void kunit_run_case_internal(struct kunit *test, + struct kunit_module *module, + struct kunit_case *test_case) { - struct kunit test; - int ret = 0; - - kunit_init_test(&test, test_case->name); + int ret;
if (module->init) { - ret = module->init(&test); + ret = module->init(test); if (ret) { - kunit_err(&test, "failed to initialize: %d\n", ret); - kunit_set_success(&test, false); + kunit_err(test, "failed to initialize: %d\n", ret); + kunit_set_success(test, false); return; } }
- if (!ret) - test_case->run_case(&test); + test_case->run_case(test); +} + +static void kunit_case_internal_cleanup(struct kunit *test) +{ + kunit_cleanup(test); +}
+/* + * Performs post validations and cleanup after a test case was run. + * XXX: Should ONLY BE CALLED AFTER kunit_run_case_internal! + */ +static void kunit_run_case_cleanup(struct kunit *test, + struct kunit_module *module) +{ if (module->exit) - module->exit(&test); + module->exit(test); + + kunit_case_internal_cleanup(test); +} + +/* + * Handles an unexpected crash in a test case. + */ +static void kunit_handle_test_crash(struct kunit *test, + struct kunit_module *module, + struct kunit_case *test_case) +{ + kunit_err(test, "kunit test case crashed!"); + /* + * 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); + + kunit_case_internal_cleanup(test); +} + +struct kunit_try_catch_context { + struct kunit *test; + struct kunit_module *module; + struct kunit_case *test_case; +}; + +static void kunit_try_run_case(void *data) +{ + struct kunit_try_catch_context *ctx = data; + struct kunit *test = ctx->test; + struct kunit_module *module = ctx->module; + struct kunit_case *test_case = ctx->test_case; + + /* + * kunit_run_case_internal may encounter a fatal error; if it does, + * abort will be called, this thread will exit, and finally the parent + * thread will resume control and handle any necessary clean up. + */ + kunit_run_case_internal(test, module, test_case); + /* This line may never be reached. */ + kunit_run_case_cleanup(test, module); +} + +static void kunit_catch_run_case(void *data) +{ + struct kunit_try_catch_context *ctx = data; + struct kunit *test = ctx->test; + struct kunit_module *module = ctx->module; + struct kunit_case *test_case = ctx->test_case; + int try_exit_code = kunit_try_catch_get_result(&test->try_catch); + + if (try_exit_code) { + kunit_set_success(test, false); + /* + * Test case could not finish, we have no idea what state it is + * in, so don't do clean up. + */ + if (try_exit_code == -ETIMEDOUT) + kunit_err(test, "test case timed out\n"); + /* + * Unknown internal error occurred preventing test case from + * running, so there is nothing to clean up. + */ + else + kunit_err(test, "internal error occurred preventing test case from running: %d\n", + try_exit_code); + return; + } + + if (kunit_get_death_test(test)) { + /* + * EXPECTED DEATH: kunit_run_case_internal encountered + * anticipated fatal error. Everything should be in a safe + * state. + */ + kunit_run_case_cleanup(test, module); + } else { + /* + * UNEXPECTED DEATH: kunit_run_case_internal encountered an + * unanticipated fatal error. We have no idea what the state of + * the test case is in. + */ + kunit_handle_test_crash(test, module, test_case); + kunit_set_success(test, false); + } +} + +/* + * Performs all logic to run a test case. It also catches most errors that + * occurs in a test case and reports them as failures. + */ +static void kunit_run_case_catch_errors(struct kunit_module *module, + struct kunit_case *test_case) +{ + struct kunit_try_catch_context context; + struct kunit_try_catch *try_catch; + struct kunit test; + + kunit_init_test(&test, test_case->name); + try_catch = &test.try_catch;
- kunit_cleanup(&test); + kunit_try_catch_init(try_catch, + &test, + kunit_try_run_case, + kunit_catch_run_case); + context.test = &test; + context.module = module; + context.test_case = test_case; + kunit_try_catch_run(try_catch, &context);
test_case->success = kunit_get_success(&test); } @@ -192,7 +339,7 @@ int kunit_run_tests(struct kunit_module *module) kunit_print_subtest_start(module);
for (test_case = module->test_cases; test_case->run_case; test_case++) { - kunit_run_case(module, test_case); + kunit_run_case_catch_errors(module, test_case); kunit_print_test_case_ok_not_ok(test_case, test_case_count++); }
diff --git a/kunit/try-catch.c b/kunit/try-catch.c new file mode 100644 index 0000000000000..de580f074387b --- /dev/null +++ b/kunit/try-catch.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An API to allow a function, that may fail, to be executed, and recover in a + * controlled manner. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ + +#include <kunit/try-catch.h> +#include <kunit/test.h> +#include <linux/completion.h> +#include <linux/kthread.h> + +void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch) +{ + try_catch->try_result = -EFAULT; + complete_and_exit(try_catch->try_completion, -EFAULT); +} + +static int kunit_generic_run_threadfn_adapter(void *data) +{ + struct kunit_try_catch *try_catch = data; + + try_catch->try(try_catch->context); + + complete_and_exit(try_catch->try_completion, 0); +} + +void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context) +{ + DECLARE_COMPLETION_ONSTACK(try_completion); + struct kunit *test = try_catch->test; + struct task_struct *task_struct; + int exit_code, status; + + try_catch->context = context; + try_catch->try_completion = &try_completion; + try_catch->try_result = 0; + task_struct = kthread_run(kunit_generic_run_threadfn_adapter, + try_catch, + "kunit_try_catch_thread"); + if (IS_ERR(task_struct)) { + try_catch->catch(try_catch->context); + return; + } + + /* + * TODO(brendanhiggins@google.com): We should probably have some type of + * variable timeout here. The only question is what that timeout value + * should be. + * + * The intention has always been, at some point, to be able to label + * tests with some type of size bucket (unit/small, integration/medium, + * large/system/end-to-end, etc), where each size bucket would get a + * default timeout value kind of like what Bazel does: + * https://docs.bazel.build/versions/master/be/common-definitions.html#test.siz... + * There is still some debate to be had on exactly how we do this. (For + * one, we probably want to have some sort of test runner level + * timeout.) + * + * For more background on this topic, see: + * https://mike-bland.com/2011/11/01/small-medium-large.html + */ + status = wait_for_completion_timeout(&try_completion, + 300 * MSEC_PER_SEC); /* 5 min */ + if (status < 0) { + kunit_err(test, "try timed out\n"); + try_catch->try_result = -ETIMEDOUT; + } + + exit_code = try_catch->try_result; + + if (!exit_code) + return; + + if (exit_code == -EFAULT) + try_catch->try_result = 0; + else if (exit_code == -EINTR) + kunit_err(test, "wake_up_process() was never called\n"); + else if (exit_code) + kunit_err(test, "Unknown error: %d\n", exit_code); + + try_catch->catch(try_catch->context); +} + +void kunit_try_catch_init(struct kunit_try_catch *try_catch, + struct kunit *test, + kunit_try_catch_func_t try, + kunit_try_catch_func_t catch) +{ + try_catch->test = test; + try_catch->try = try; + try_catch->catch = catch; +}
Add KUnit tests for the KUnit test abort mechanism (see preceding commit). Add tests both for general try catch mechanism as well as non-architecture specific mechanism.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- kunit/Makefile | 3 +- kunit/test-test.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 kunit/test-test.c
diff --git a/kunit/Makefile b/kunit/Makefile index 1f7680cfa11ad..533355867abd2 100644 --- a/kunit/Makefile +++ b/kunit/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_KUNIT) += test.o \ kunit-stream.o \ try-catch.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/test-test.c b/kunit/test-test.c new file mode 100644 index 0000000000000..5d14e1ae35ed5 --- /dev/null +++ b/kunit/test-test.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for core test infrastructure. + * + * Copyright (C) 2019, Google LLC. + * Author: Brendan Higgins brendanhiggins@google.com + */ +#include <kunit/test.h> + +struct kunit_try_catch_test_context { + struct kunit_try_catch *try_catch; + bool function_called; +}; + +void kunit_test_successful_try(void *data) +{ + struct kunit *test = data; + struct kunit_try_catch_test_context *ctx = test->priv; + + ctx->function_called = true; +} + +void kunit_test_no_catch(void *data) +{ + struct kunit *test = data; + + KUNIT_FAIL(test, "Catch should not be called\n"); +} + +static void kunit_test_try_catch_successful_try_no_catch(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_init(try_catch, + test, + kunit_test_successful_try, + kunit_test_no_catch); + kunit_try_catch_run(try_catch, test); + + KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +void kunit_test_unsuccessful_try(void *data) +{ + struct kunit *test = data; + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_throw(try_catch); + KUNIT_FAIL(test, "This line should never be reached\n"); +} + +void kunit_test_catch(void *data) +{ + struct kunit *test = data; + struct kunit_try_catch_test_context *ctx = test->priv; + + ctx->function_called = true; +} + +static void kunit_test_try_catch_unsuccessful_try_does_catch(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx = test->priv; + struct kunit_try_catch *try_catch = ctx->try_catch; + + kunit_try_catch_init(try_catch, + test, + kunit_test_unsuccessful_try, + kunit_test_catch); + kunit_try_catch_run(try_catch, test); + + KUNIT_EXPECT_TRUE(test, ctx->function_called); +} + +static int kunit_try_catch_test_init(struct kunit *test) +{ + struct kunit_try_catch_test_context *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + test->priv = ctx; + + ctx->try_catch = kunit_kmalloc(test, + sizeof(*ctx->try_catch), + GFP_KERNEL); + + return 0; +} + +static struct kunit_case kunit_try_catch_test_cases[] = { + KUNIT_CASE(kunit_test_try_catch_successful_try_no_catch), + KUNIT_CASE(kunit_test_try_catch_unsuccessful_try_does_catch), + {} +}; + +static struct kunit_module kunit_try_catch_test_module = { + .name = "kunit-try-catch-test", + .init = kunit_try_catch_test_init, + .test_cases = kunit_try_catch_test_cases, +}; +module_test(kunit_try_catch_test_module);
Add support for assertions which are like expectations except the test terminates if the assertion is not satisfied.
The idea with assertions is that you use them to state all the preconditions for your test. Logically speaking, these are the premises of the test case, so if a premise isn't true, there is no point in continuing the test case because there are no conclusions that can be drawn without the premises. Whereas, the expectation is the thing you are trying to prove. It is not used universally in x-unit style test frameworks, but I really like it as a convention. You could still express the idea of a premise using the above idiom, but I think KUNIT_ASSERT_* states the intended idea perfectly.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- include/kunit/test.h | 499 ++++++++++++++++++++++++++++++++++++- kunit/string-stream-test.c | 12 +- kunit/test-test.c | 2 + kunit/test.c | 66 +++++ 4 files changed, 570 insertions(+), 9 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 194c85afc69a8..92ddad36ab9b8 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -86,9 +86,10 @@ struct kunit; * @name: the name of the test case. * * A test case is a function with the signature, ``void (*)(struct kunit *)`` - * that makes expectations (see KUNIT_EXPECT_TRUE()) about code under test. Each - * test case is associated with a &struct kunit_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 KUNIT_EXPECT_TRUE() and + * KUNIT_ASSERT_TRUE()) about code under test. Each test case is associated with + * a &struct kunit_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 KUNIT_CASE() * macro; additionally, every array of test cases should be terminated with an @@ -810,4 +811,496 @@ do { \ KUNIT_EXPECT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ } while (0)
+static inline struct kunit_stream *kunit_assert_start(struct kunit *test, + const char *file, + const char *line) +{ + struct kunit_stream *stream = alloc_kunit_stream(test, KERN_ERR); + + kunit_stream_add(stream, "ASSERTION FAILED at %s:%s\n\t", file, line); + + return stream; +} + +static inline void kunit_assert_end(struct kunit *test, + bool success, + struct kunit_stream *stream) +{ + if (!success) { + kunit_fail(test, stream); + kunit_abort(test); + } else { + kunit_stream_clear(stream); + } +} + +#define KUNIT_ASSERT_START(test) \ + kunit_assert_start(test, __FILE__, __stringify(__LINE__)) + +#define KUNIT_ASSERT_END(test, success, stream) \ + kunit_assert_end(test, success, stream) + +#define KUNIT_ASSERT(test, success, message) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + \ + kunit_stream_add(__stream, message); \ + KUNIT_ASSERT_END(test, success, __stream); \ +} while (0) + +#define KUNIT_ASSERT_MSG(test, success, message, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + \ + kunit_stream_add(__stream, message); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + KUNIT_ASSERT_END(test, success, __stream); \ +} while (0) + +#define KUNIT_ASSERT_FAILURE(test, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + KUNIT_ASSERT_END(test, false, __stream); \ +} while (0) + +/** + * KUNIT_ASSERT_TRUE() - Sets an assertion that @condition is 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 `KUNIT_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 KUNIT_ASSERT_TRUE(test, condition) \ + KUNIT_ASSERT(test, (condition), \ + "Asserted " #condition " is true, but is false\n") + +#define KUNIT_ASSERT_TRUE_MSG(test, condition, fmt, ...) \ + KUNIT_ASSERT_MSG(test, (condition), \ + "Asserted " #condition " is true, but is false\n",\ + fmt, ##__VA_ARGS__) + +/** + * KUNIT_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 KUNIT_EXPECT_FALSE(), except it causes an assertion failure + * (see KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_FALSE(test, condition) \ + KUNIT_ASSERT(test, !(condition), \ + "Asserted " #condition " is false, but is true\n") + +#define KUNIT_ASSERT_FALSE_MSG(test, condition, fmt, ...) \ + KUNIT_ASSERT_MSG(test, !(condition), \ + "Asserted " #condition " is false, but is true\n",\ + fmt, ##__VA_ARGS__) + +void kunit_assert_binary_msg(struct kunit *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, + const char *fmt, ...); + +static inline void kunit_assert_binary(struct kunit *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) +{ + kunit_assert_binary_msg(test, + left, left_name, + right, right_name, + compare_result, + compare_name, + file, + line, + NULL); +} + +void kunit_assert_ptr_binary_msg(struct kunit *test, + void *left, const char *left_name, + void *right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line, + const char *fmt, ...); + +static inline void kunit_assert_ptr_binary(struct kunit *test, + void *left, const char *left_name, + void *right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line) +{ + kunit_assert_ptr_binary_msg(test, + left, left_name, + right, right_name, + compare_result, + compare_name, + file, + line, + NULL); +} + +/* + * 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 KUNIT_ASSERT_BINARY(test, left, condition, right) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_assert_binary(test, \ + (long long) __left, #left, \ + (long long) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__)); \ +} while (0) + +#define KUNIT_ASSERT_BINARY_MSG(test, left, condition, right, fmt, ...) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_assert_binary_msg(test, \ + (long long) __left, #left, \ + (long long) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__), \ + fmt, ##__VA_ARGS__); \ +} while (0) + +/* + * Just like KUNIT_EXPECT_BINARY, but for comparing pointer types. + */ +#define KUNIT_ASSERT_PTR_BINARY(test, left, condition, right) do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_assert_ptr_binary(test, \ + (void *) __left, #left, \ + (void *) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__)); \ +} while (0) + +#define KUNIT_ASSERT_PTR_BINARY_MSG(test, left, condition, right, fmt, ...) \ +do { \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + __kunit_typecheck(__left, __right); \ + kunit_assert_ptr_binary_msg(test, \ + (void *) __left, #left, \ + (void *) __right, #right, \ + __left condition __right, #condition, \ + __FILE__, __stringify(__LINE__), \ + fmt, ##__VA_ARGS__); \ +} while (0) + +/** + * KUNIT_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 KUNIT_EXPECT_EQ(), except it causes an assertion + * failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_EQ(test, left, right) \ + KUNIT_ASSERT_BINARY(test, left, ==, right) + +#define KUNIT_ASSERT_EQ_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_BINARY_MSG(test, \ + left, \ + ==, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_ASSERT_PTR_EQ() - Asserts that pointers @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a pointer. + * @right: an arbitrary expression that evaluates to a pointer. + * + * Sets an assertion that the values that @left and @right evaluate to are + * equal. This is the same as KUNIT_EXPECT_EQ(), except it causes an assertion + * failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_PTR_EQ(test, left, right) \ + KUNIT_ASSERT_PTR_BINARY(test, left, ==, right) + +#define KUNIT_ASSERT_PTR_EQ_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_PTR_BINARY_MSG(test, \ + left, \ + ==, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 KUNIT_EXPECT_NE(), except it causes an assertion + * failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_NE(test, left, right) \ + KUNIT_ASSERT_BINARY(test, left, !=, right) + +#define KUNIT_ASSERT_NE_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_BINARY_MSG(test, \ + left, \ + !=, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_ASSERT_PTR_NE() - Asserts that pointers @left and @right are not equal. + * KUNIT_ASSERT_PTR_EQ() - Asserts that pointers @left and @right are equal. + * @test: The test context object. + * @left: an arbitrary expression that evaluates to a pointer. + * @right: an arbitrary expression that evaluates to a pointer. + * + * Sets an assertion that the values that @left and @right evaluate to are not + * equal. This is the same as KUNIT_EXPECT_NE(), except it causes an assertion + * failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_PTR_NE(test, left, right) \ + KUNIT_ASSERT_PTR_BINARY(test, left, !=, right) + +#define KUNIT_ASSERT_PTR_NE_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_PTR_BINARY_MSG(test, \ + left, \ + !=, \ + right, \ + fmt, \ + ##__VA_ARGS__) +/** + * KUNIT_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 KUNIT_EXPECT_LT(), except + * it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the assertion + * is not met. + */ +#define KUNIT_ASSERT_LT(test, left, right) \ + KUNIT_ASSERT_BINARY(test, left, <, right) + +#define KUNIT_ASSERT_LT_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_BINARY_MSG(test, \ + left, \ + <, \ + right, \ + fmt, \ + ##__VA_ARGS__) +/** + * KUNIT_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 + * KUNIT_EXPECT_LE(), except it causes an assertion failure (see + * KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_LE(test, left, right) \ + KUNIT_ASSERT_BINARY(test, left, <=, right) + +#define KUNIT_ASSERT_LE_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_BINARY_MSG(test, \ + left, \ + <=, \ + right, \ + fmt, \ + ##__VA_ARGS__) +/** + * KUNIT_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 KUNIT_EXPECT_GT(), except + * it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the assertion + * is not met. + */ +#define KUNIT_ASSERT_GT(test, left, right) \ + KUNIT_ASSERT_BINARY(test, left, >, right) + +#define KUNIT_ASSERT_GT_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_BINARY_MSG(test, \ + left, \ + >, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 KUNIT_EXPECT_GE(), except + * it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the assertion + * is not met. + */ +#define KUNIT_ASSERT_GE(test, left, right) \ + KUNIT_ASSERT_BINARY(test, left, >=, right) + +#define KUNIT_ASSERT_GE_MSG(test, left, right, fmt, ...) \ + KUNIT_ASSERT_BINARY_MSG(test, \ + left, \ + >=, \ + right, \ + fmt, \ + ##__VA_ARGS__) + +/** + * KUNIT_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 KUNIT_EXPECT_STREQ(), except it causes an + * assertion failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_STREQ(test, left, right) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Asserted " #left " == " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + \ + KUNIT_ASSERT_END(test, !strcmp(left, right), __stream); \ +} while (0) + +#define KUNIT_ASSERT_STREQ_MSG(test, left, right, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Asserted " #left " == " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + \ + KUNIT_ASSERT_END(test, !strcmp(left, right), __stream); \ +} while (0) + +/** + * KUNIT_ASSERT_STRNEQ() - Expects that strings @left and @right are not 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 + * not equal. This is semantically equivalent to + * KUNIT_ASSERT_TRUE(@test, strcmp((@left), (@right))). See KUNIT_ASSERT_TRUE() + * for more information. + */ +#define KUNIT_ASSERT_STRNEQ(test, left, right) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Asserted " #left " == " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + \ + KUNIT_ASSERT_END(test, strcmp(left, right), __stream); \ +} while (0) + +#define KUNIT_ASSERT_STRNEQ_MSG(test, left, right, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + typeof(left) __left = (left); \ + typeof(right) __right = (right); \ + \ + kunit_stream_add(__stream, "Asserted " #left " == " #right ", but\n"); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #left, __left); \ + kunit_stream_add(__stream, "\t\t%s == %s\n", #right, __right); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + \ + KUNIT_ASSERT_END(test, strcmp(left, right), __stream); \ +} while (0) + +/** + * KUNIT_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 + * KUNIT_EXPECT_NOT_ERR_OR_NULL(), except it causes an assertion failure (see + * KUNIT_ASSERT_TRUE()) when the assertion is not met. + */ +#define KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + typeof(ptr) __ptr = (ptr); \ + \ + if (!__ptr) \ + kunit_stream_add(__stream, \ + "Asserted " #ptr " is not null, but is\n"); \ + if (IS_ERR(__ptr)) \ + kunit_stream_add(__stream, \ + "Asserted " #ptr " is not error, but is: %ld\n",\ + PTR_ERR(__ptr)); \ + \ + KUNIT_ASSERT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ +} while (0) + +#define KUNIT_ASSERT_NOT_ERR_OR_NULL_MSG(test, ptr, fmt, ...) do { \ + struct kunit_stream *__stream = KUNIT_ASSERT_START(test); \ + typeof(ptr) __ptr = (ptr); \ + \ + if (!__ptr) { \ + kunit_stream_add(__stream, \ + "Asserted " #ptr " is not null, but is\n"); \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + } \ + if (IS_ERR(__ptr)) { \ + kunit_stream_add(__stream, \ + "Asserted " #ptr " is not error, but is: %ld\n",\ + PTR_ERR(__ptr)); \ + \ + kunit_stream_add(__stream, fmt, ##__VA_ARGS__); \ + } \ + KUNIT_ASSERT_END(test, !IS_ERR_OR_NULL(__ptr), __stream); \ +} while (0) + #endif /* _KUNIT_TEST_H */ diff --git a/kunit/string-stream-test.c b/kunit/string-stream-test.c index 36f0b5769a5a4..a88ecef799a6b 100644 --- a/kunit/string-stream-test.c +++ b/kunit/string-stream-test.c @@ -34,7 +34,7 @@ static void string_stream_test_get_string(struct kunit *test) string_stream_add(stream, " %s", "bar");
output = string_stream_get_string(stream); - KUNIT_EXPECT_STREQ(test, output, "Foo bar"); + KUNIT_ASSERT_STREQ(test, output, "Foo bar"); kfree(output); }
@@ -48,16 +48,16 @@ static void string_stream_test_add_and_clear(struct kunit *test) string_stream_add(stream, "A");
output = string_stream_get_string(stream); - KUNIT_EXPECT_STREQ(test, output, "AAAAAAAAAA"); - KUNIT_EXPECT_EQ(test, stream->length, (size_t)10); - KUNIT_EXPECT_FALSE(test, string_stream_is_empty(stream)); + KUNIT_ASSERT_STREQ(test, output, "AAAAAAAAAA"); + KUNIT_ASSERT_EQ(test, stream->length, (size_t)10); + KUNIT_ASSERT_FALSE(test, string_stream_is_empty(stream)); kfree(output);
string_stream_clear(stream);
output = string_stream_get_string(stream); - KUNIT_EXPECT_STREQ(test, output, ""); - KUNIT_EXPECT_TRUE(test, string_stream_is_empty(stream)); + KUNIT_ASSERT_STREQ(test, output, ""); + KUNIT_ASSERT_TRUE(test, string_stream_is_empty(stream)); }
static struct kunit_case string_stream_test_cases[] = { diff --git a/kunit/test-test.c b/kunit/test-test.c index 5d14e1ae35ed5..43d1bedcf4592 100644 --- a/kunit/test-test.c +++ b/kunit/test-test.c @@ -78,11 +78,13 @@ static int kunit_try_catch_test_init(struct kunit *test) struct kunit_try_catch_test_context *ctx;
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); test->priv = ctx;
ctx->try_catch = kunit_kmalloc(test, sizeof(*ctx->try_catch), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->try_catch);
return 0; } diff --git a/kunit/test.c b/kunit/test.c index cb07e0916e901..e665a7e9a17d8 100644 --- a/kunit/test.c +++ b/kunit/test.c @@ -522,3 +522,69 @@ void kunit_expect_ptr_binary_msg(struct kunit *test,
kunit_expect_end(test, compare_result, stream); } + +void kunit_assert_binary_msg(struct kunit *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, + const char *fmt, ...) +{ + struct kunit_stream *stream = kunit_assert_start(test, file, line); + struct va_format vaf; + va_list args; + + kunit_stream_add(stream, + "Asserted %s %s %s, but\n", + left_name, compare_name, right_name); + kunit_stream_add(stream, "\t\t%s == %lld\n", left_name, left); + kunit_stream_add(stream, "\t\t%s == %lld\n", right_name, right); + + if (fmt) { + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + kunit_stream_add(stream, "\n%pV", &vaf); + + va_end(args); + } + + kunit_assert_end(test, compare_result, stream); +} + +void kunit_assert_ptr_binary_msg(struct kunit *test, + void *left, const char *left_name, + void *right, const char *right_name, + bool compare_result, + const char *compare_name, + const char *file, + const char *line, + const char *fmt, ...) +{ + struct kunit_stream *stream = kunit_assert_start(test, file, line); + struct va_format vaf; + va_list args; + + kunit_stream_add(stream, + "Asserted %s %s %s, but\n", + left_name, compare_name, right_name); + kunit_stream_add(stream, "\t\t%s == %pK\n", left_name, left); + kunit_stream_add(stream, "\t\t%s == %pK", right_name, right); + + if (fmt) { + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + kunit_stream_add(stream, "\n%pV", &vaf); + + va_end(args); + } + + kunit_assert_end(test, compare_result, stream); +}
From: Avinash Kondareddy akndr41@gmail.com
Add unit tests for KUnit managed resources. KUnit managed resources (struct kunit_resource) are resources that are automatically cleaned up at the end of a KUnit test, similar to the concept of devm_* managed resources.
Signed-off-by: Avinash Kondareddy akndr41@gmail.com Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- kunit/test-test.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+)
diff --git a/kunit/test-test.c b/kunit/test-test.c index 43d1bedcf4592..60ef508cda1c6 100644 --- a/kunit/test-test.c +++ b/kunit/test-test.c @@ -101,3 +101,222 @@ static struct kunit_module kunit_try_catch_test_module = { .test_cases = kunit_try_catch_test_cases, }; module_test(kunit_try_catch_test_module); + +/* + * Context for testing test managed resources + * is_resource_initialized is used to test arbitrary resources + */ +struct kunit_test_resource_context { + struct kunit test; + bool is_resource_initialized; + int allocate_order[2]; + int free_order[2]; +}; + +static int fake_resource_init(struct kunit_resource *res, void *context) +{ + struct kunit_test_resource_context *ctx = context; + + res->allocation = &ctx->is_resource_initialized; + ctx->is_resource_initialized = true; + return 0; +} + +static void fake_resource_free(struct kunit_resource *res) +{ + bool *is_resource_initialized = res->allocation; + + *is_resource_initialized = false; +} + +static void kunit_resource_test_init_resources(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + + kunit_init_test(&ctx->test, "testing_test_init_test"); + + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_alloc_resource(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *res; + kunit_resource_free_t free = fake_resource_free; + + res = kunit_alloc_resource(&ctx->test, + fake_resource_init, + fake_resource_free, + ctx); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res); + KUNIT_EXPECT_PTR_EQ(test, + &ctx->is_resource_initialized, + (bool *) res->allocation); + KUNIT_EXPECT_TRUE(test, list_is_last(&res->node, &ctx->test.resources)); + KUNIT_EXPECT_PTR_EQ(test, free, res->free); +} + +static void kunit_resource_test_free_resource(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *res = kunit_alloc_resource(&ctx->test, + fake_resource_init, + fake_resource_free, + ctx); + + kunit_free_resource(&ctx->test, res); + + KUNIT_EXPECT_FALSE(test, ctx->is_resource_initialized); + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_cleanup_resources(struct kunit *test) +{ + int i; + struct kunit_test_resource_context *ctx = test->priv; + struct kunit_resource *resources[5]; + + for (i = 0; i < ARRAY_SIZE(resources); i++) { + resources[i] = kunit_alloc_resource(&ctx->test, + fake_resource_init, + fake_resource_free, + ctx); + } + + kunit_cleanup(&ctx->test); + + KUNIT_EXPECT_TRUE(test, list_empty(&ctx->test.resources)); +} + +static void kunit_resource_test_mark_order(int order_array[], + size_t order_size, + int key) +{ + int i; + + for (i = 0; i < order_size && order_array[i]; i++) + ; + + order_array[i] = key; +} + +#define KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, order_field, key) \ + kunit_resource_test_mark_order(ctx->order_field, \ + ARRAY_SIZE(ctx->order_field), \ + key) + +static int fake_resource_2_init(struct kunit_resource *res, void *context) +{ + struct kunit_test_resource_context *ctx = context; + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, allocate_order, 2); + + res->allocation = ctx; + + return 0; +} + +static void fake_resource_2_free(struct kunit_resource *res) +{ + struct kunit_test_resource_context *ctx = res->allocation; + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, free_order, 2); +} + +static int fake_resource_1_init(struct kunit_resource *res, void *context) +{ + struct kunit_test_resource_context *ctx = context; + + kunit_alloc_resource(&ctx->test, + fake_resource_2_init, + fake_resource_2_free, + ctx); + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, allocate_order, 1); + + res->allocation = ctx; + + return 0; +} + +static void fake_resource_1_free(struct kunit_resource *res) +{ + struct kunit_test_resource_context *ctx = res->allocation; + + KUNIT_RESOURCE_TEST_MARK_ORDER(ctx, free_order, 1); +} + +/* + * TODO(brendanhiggins@google.com): replace the arrays that keep track of the + * order of allocation and freeing with strict mocks using the IN_SEQUENCE macro + * to assert allocation and freeing order when the feature becomes available. + */ +static void kunit_resource_test_proper_free_ordering(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + + /* fake_resource_1 allocates a fake_resource_2 in its init. */ + kunit_alloc_resource(&ctx->test, + fake_resource_1_init, + fake_resource_1_free, + ctx); + + /* + * Since fake_resource_2_init calls KUNIT_RESOURCE_TEST_MARK_ORDER + * before returning to fake_resource_1_init, it should be the first to + * put its key in the allocate_order array. + */ + KUNIT_EXPECT_EQ(test, ctx->allocate_order[0], 2); + KUNIT_EXPECT_EQ(test, ctx->allocate_order[1], 1); + + kunit_cleanup(&ctx->test); + + /* + * Because fake_resource_2 finishes allocation before fake_resource_1, + * fake_resource_1 should be freed first since it could depend on + * fake_resource_2. + */ + KUNIT_EXPECT_EQ(test, ctx->free_order[0], 1); + KUNIT_EXPECT_EQ(test, ctx->free_order[1], 2); +} + +static int kunit_resource_test_init(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = + kzalloc(sizeof(*ctx), GFP_KERNEL); + + if (!ctx) + return -ENOMEM; + + test->priv = ctx; + + kunit_init_test(&ctx->test, "test_test_context"); + + return 0; +} + +static void kunit_resource_test_exit(struct kunit *test) +{ + struct kunit_test_resource_context *ctx = test->priv; + + kunit_cleanup(&ctx->test); + kfree(ctx); +} + +static struct kunit_case kunit_resource_test_cases[] = { + KUNIT_CASE(kunit_resource_test_init_resources), + KUNIT_CASE(kunit_resource_test_alloc_resource), + KUNIT_CASE(kunit_resource_test_free_resource), + KUNIT_CASE(kunit_resource_test_cleanup_resources), + KUNIT_CASE(kunit_resource_test_proper_free_ordering), + {} +}; + +static struct kunit_module kunit_resource_test_module = { + .name = "kunit-resource-test", + .init = kunit_resource_test_init, + .exit = kunit_resource_test_exit, + .test_cases = kunit_resource_test_cases, +}; +module_test(kunit_resource_test_module);
From: Felix Guo felixguoxiuping@gmail.com
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 Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- tools/testing/kunit/.gitignore | 3 + tools/testing/kunit/kunit.py | 116 +++++++ tools/testing/kunit/kunit_config.py | 66 ++++ tools/testing/kunit/kunit_kernel.py | 148 +++++++++ tools/testing/kunit/kunit_parser.py | 290 ++++++++++++++++++ tools/testing/kunit/kunit_tool_test.py | 206 +++++++++++++ .../test_is_test_passed-all_passed.log | 32 ++ .../test_data/test_is_test_passed-crash.log | 69 +++++ .../test_data/test_is_test_passed-failure.log | 36 +++ .../test_is_test_passed-no_tests_run.log | 75 +++++ .../test_output_isolated_correctly.log | 106 +++++++ .../test_data/test_read_from_file.kconfig | 17 + 12 files changed, 1164 insertions(+) create mode 100644 tools/testing/kunit/.gitignore create mode 100755 tools/testing/kunit/kunit.py create mode 100644 tools/testing/kunit/kunit_config.py create mode 100644 tools/testing/kunit/kunit_kernel.py create mode 100644 tools/testing/kunit/kunit_parser.py create mode 100755 tools/testing/kunit/kunit_tool_test.py create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-all_passed.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-crash.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-failure.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log create mode 100644 tools/testing/kunit/test_data/test_output_isolated_correctly.log create mode 100644 tools/testing/kunit/test_data/test_read_from_file.kconfig
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.py b/tools/testing/kunit/kunit.py new file mode 100755 index 0000000000000..da11bd62a4b82 --- /dev/null +++ b/tools/testing/kunit/kunit.py @@ -0,0 +1,116 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 +# +# A thin wrapper on top of the KUnit Kernel +# +# Copyright (C) 2019, Google LLC. +# Author: Felix Guo felixguoxiuping@gmail.com +# Author: Brendan Higgins brendanhiggins@google.com + +import argparse +import sys +import os +import time + +from collections import namedtuple +from enum import Enum, auto + +import kunit_config +import kunit_kernel +import kunit_parser + +KunitResult = namedtuple('KunitResult', ['status','result']) + +KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 'build_dir']) + +class KunitStatus(Enum): + SUCCESS = auto() + CONFIG_FAILURE = auto() + BUILD_FAILURE = auto() + TEST_FAILURE = auto() + +def run_tests(linux: kunit_kernel.LinuxSourceTree, + request: KunitRequest) -> KunitResult: + config_start = time.time() + success = linux.build_reconfig(request.build_dir) + config_end = time.time() + if not success: + return KunitResult(KunitStatus.CONFIG_FAILURE, 'could not configure kernel') + + kunit_parser.print_with_timestamp('Building KUnit Kernel ...') + + build_start = time.time() + success = linux.build_um_kernel(request.jobs, request.build_dir) + build_end = time.time() + if not success: + return KunitResult(KunitStatus.BUILD_FAILURE, 'could not build kernel') + + kunit_parser.print_with_timestamp('Starting KUnit Kernel ...') + test_start = time.time() + + test_result = kunit_parser.TestResult(kunit_parser.TestStatus.SUCCESS, + [], + 'Tests not Parsed.') + if request.raw_output: + kunit_parser.raw_output( + linux.run_kernel(timeout=request.timeout)) + else: + kunit_output = linux.run_kernel(timeout=request.timeout) + test_result = kunit_parser.parse_run_tests(kunit_output) + 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)) + + if test_result.status != kunit_parser.TestStatus.SUCCESS: + return KunitResult(KunitStatus.TEST_FAILURE, test_result) + else: + return KunitResult(KunitStatus.SUCCESS, test_result) + +def main(argv, linux): + parser = argparse.ArgumentParser( + description='Helps writing and running KUnit tests.') + subparser = parser.add_subparsers(dest='subcommand') + + run_parser = subparser.add_parser('run', help='Runs KUnit tests.') + run_parser.add_argument('--raw_output', help='don't format output from kernel', + action='store_true') + + run_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') + + run_parser.add_argument('--jobs', + help='As in the make command, "Specifies the number of ' + 'jobs (commands) to run simultaneously."', + type=int, default=8, metavar='jobs') + + run_parser.add_argument('--build_dir', + help='As in the make command, it specifies the build ' + 'directory.', + type=str, default=None, metavar='build_dir') + + cli_args = parser.parse_args(argv) + + if cli_args.subcommand == 'run': + request = KunitRequest(cli_args.raw_output, + cli_args.timeout, + cli_args.jobs, + cli_args.build_dir) + result = run_tests(linux, request) + if result.status != KunitStatus.SUCCESS: + sys.exit(1) + else: + parser.print_help() + +if __name__ == '__main__': + main(sys.argv[1:], kunit_kernel.LinuxSourceTree()) diff --git a/tools/testing/kunit/kunit_config.py b/tools/testing/kunit/kunit_config.py new file mode 100644 index 0000000000000..ebf3942b23f51 --- /dev/null +++ b/tools/testing/kunit/kunit_config.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Builds a .config from a kunitconfig. +# +# Copyright (C) 2019, Google LLC. +# Author: Felix Guo felixguoxiuping@gmail.com +# Author: Brendan Higgins brendanhiggins@google.com + +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..07c0abf2f47df --- /dev/null +++ b/tools/testing/kunit/kunit_kernel.py @@ -0,0 +1,148 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Runs UML kernel, collects output, and handles errors. +# +# Copyright (C) 2019, Google LLC. +# Author: Felix Guo felixguoxiuping@gmail.com +# Author: Brendan Higgins brendanhiggins@google.com + + +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, build_dir): + command = ['make', 'ARCH=um', 'olddefconfig'] + if build_dir: + command += ['O=' + build_dir] + try: + subprocess.check_output(command) + except OSError as e: + raise ConfigError('Could not call make command: ' + e) + except subprocess.CalledProcessError as e: + raise ConfigError(e.output) + + def make(self, jobs, build_dir): + command = ['make', 'ARCH=um', '--jobs=' + str(jobs)] + if build_dir: + command += ['O=' + build_dir] + try: + subprocess.check_output(command) + 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, build_dir): + """Runs the Linux UML binary. Must be named 'linux'.""" + linux_bin = './linux' + if build_dir: + linux_bin = os.path.join(build_dir, 'linux') + process = subprocess.Popen( + [linux_bin] + params, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + process.wait(timeout=timeout) + return process + + +def get_kconfig_path(build_dir): + kconfig_path = KCONFIG_PATH + if build_dir: + kconfig_path = os.path.join(build_dir, KCONFIG_PATH) + return kconfig_path + +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, build_dir): + kconfig_path = get_kconfig_path(build_dir) + if build_dir and not os.path.exists(build_dir): + os.mkdir(build_dir) + self._kconfig.write_to_file(kconfig_path) + try: + self._ops.make_olddefconfig(build_dir) + 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, build_dir): + """Creates a new .config if it is not a subset of the kunitconfig.""" + kconfig_path = get_kconfig_path(build_dir) + 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(build_dir) + else: + return True + else: + print('Generating .config ...') + return self.build_config(build_dir) + + def build_um_kernel(self, jobs, build_dir): + try: + self._ops.make_olddefconfig(build_dir) + self._ops.make(jobs, build_dir) + except (ConfigError, BuildError) as e: + logging.error(e) + return False + used_kconfig = kunit_config.Kconfig() + used_kconfig.read_from_file(get_kconfig_path(build_dir)) + 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, build_dir=None): + args.extend(['mem=256M']) + process = self._ops.linux_bin(args, timeout, build_dir) + with open('test.log', 'w') as f: + for line in process.stdout: + f.write(line.rstrip().decode('ascii') + '\n') + yield line.rstrip().decode('ascii') diff --git a/tools/testing/kunit/kunit_parser.py b/tools/testing/kunit/kunit_parser.py new file mode 100644 index 0000000000000..31c634e4202b1 --- /dev/null +++ b/tools/testing/kunit/kunit_parser.py @@ -0,0 +1,290 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Parses test results from a kernel dmesg log. +# +# Copyright (C) 2019, Google LLC. +# Author: Felix Guo felixguoxiuping@gmail.com +# Author: Brendan Higgins brendanhiggins@google.com + +import re + +from collections import namedtuple +from datetime import datetime +from enum import Enum, auto +from functools import reduce +from typing import List + +TestResult = namedtuple('TestResult', ['status','modules','log']) + +class TestModule(object): + def __init__(self): + self.status = None + self.name = None + self.cases = [] + + def __str__(self): + return 'TestModule(' + self.status + ',' + self.name + ',' + str(self.cases) + ')' + + def __repr__(self): + return str(self) + +class TestCase(object): + def __init__(self): + self.status = None + self.name = '' + self.log = [] + + def __str__(self): + return 'TestCase(' + self.status + ',' + self.name + ',' + str(self.log) + ')' + + def __repr__(self): + return str(self) + +class TestStatus(Enum): + SUCCESS = auto() + FAILURE = auto() + TEST_CRASHED = auto() + NO_TESTS = auto() + +kunit_start_re = re.compile(r'^TAP version [0-9]+$') +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 + yield line + elif kunit_end_re.match(line): + break + elif started: + yield line + +def raw_output(kernel_output): + for line in kernel_output: + print(line) + +DIVIDER = '=' * 60 + +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 format_module_divider(message): + return '======== ' + message + ' ========' + +def print_module_divider(message): + print_with_timestamp(DIVIDER) + print_with_timestamp(format_module_divider(message)) + +def print_log(log): + for m in log: + print_with_timestamp(m) + +TAP_ENTRIES = re.compile(r'^(TAP|\t?ok|\t?not ok|\t?[0-9]+..[0-9]+|\t?#).*$') + +def consume_non_diagnositic(lines: List[str]) -> None: + while not TAP_ENTRIES.match(lines[0]): + lines.pop(0) + +def save_non_diagnositic(lines: List[str], test_case: TestCase) -> None: + while not TAP_ENTRIES.match(lines[0]): + test_case.log.append(lines[0]) + lines.pop(0) + +OkNotOkResult = namedtuple('OkNotOkResult', ['is_ok','description', 'text']) + +OK_NOT_OK_SUBTEST = re.compile(r'^\t(ok|not ok) [0-9]+ - (.*)$') + +OK_NOT_OK_MODULE = re.compile(r'^(ok|not ok) [0-9]+ - (.*)$') + +def parse_ok_not_ok_test_case(lines: List[str], test_case: TestCase) -> bool: + save_non_diagnositic(lines, test_case) + line = lines[0] + match = OK_NOT_OK_SUBTEST.match(line) + if match: + test_case.log.append(lines.pop(0)) + test_case.name = match.group(2) + if test_case.status == TestStatus.TEST_CRASHED: + return True + if match.group(1) == 'ok': + test_case.status = TestStatus.SUCCESS + else: + test_case.status = TestStatus.FAILURE + return True + else: + return False + +SUBTEST_DIAGNOSTIC = re.compile(r'^\t# .*?: (.*)$') +DIAGNOSTIC_CRASH_MESSAGE = 'kunit test case crashed!' + +def parse_diagnostic(lines: List[str], test_case: TestCase) -> bool: + save_non_diagnositic(lines, test_case) + line = lines[0] + match = SUBTEST_DIAGNOSTIC.match(line) + if match: + test_case.log.append(lines.pop(0)) + if match.group(1) == DIAGNOSTIC_CRASH_MESSAGE: + test_case.status = TestStatus.TEST_CRASHED + return True + else: + return False + +def parse_test_case(lines: List[str]) -> TestCase: + test_case = TestCase() + save_non_diagnositic(lines, test_case) + while parse_diagnostic(lines, test_case): + pass + if parse_ok_not_ok_test_case(lines, test_case): + return test_case + else: + return None + +SUBTEST_HEADER = re.compile(r'^\t# Subtest: (.*)$') + +def parse_subtest_header(lines: List[str]) -> str: + consume_non_diagnositic(lines) + match = SUBTEST_HEADER.match(lines[0]) + if match: + lines.pop(0) + return match.group(1) + else: + return None + +SUBTEST_PLAN = re.compile(r'\t[0-9]+..([0-9]+)') + +def parse_subtest_plan(lines: List[str]) -> int: + consume_non_diagnositic(lines) + match = SUBTEST_PLAN.match(lines[0]) + if match: + lines.pop(0) + return match.group(1) + else: + return None + +def max_status(left: TestStatus, right: TestStatus) -> TestStatus: + if left == TestStatus.TEST_CRASHED or right == TestStatus.TEST_CRASHED: + return TestStatus.TEST_CRASHED + elif left == TestStatus.FAILURE or right == TestStatus.FAILURE: + return TestStatus.FAILURE + elif left != TestStatus.SUCCESS: + return left + elif right != TestStatus.SUCCESS: + return right + else: + return TestStatus.SUCCESS + +def parse_ok_not_ok_test_module(lines: List[str], test_module: TestModule) -> bool: + consume_non_diagnositic(lines) + line = lines[0] + match = OK_NOT_OK_MODULE.match(line) + if match: + lines.pop(0) + if match.group(1) == 'ok': + test_module.status = TestStatus.SUCCESS + else: + test_module.status = TestStatus.FAILURE + return True + else: + return False + +def bubble_up_errors(to_status, status_container_list) -> TestStatus: + status_list = map(to_status, status_container_list) + return reduce(max_status, status_list, TestStatus.SUCCESS) + +def bubble_up_test_case_errors(test_module: TestModule) -> TestStatus: + max_test_case_status = bubble_up_errors(lambda x: x.status, test_module.cases) + return max_status(max_test_case_status, test_module.status) + +def parse_test_module(lines: List[str]) -> TestModule: + if not lines: + return None + consume_non_diagnositic(lines) + test_module = TestModule() + test_module.status = TestStatus.SUCCESS + name = parse_subtest_header(lines) + if not name: + return None + test_module.name = name + test_case_num = parse_subtest_plan(lines) + if not test_case_num: + return None + test_case = parse_test_case(lines) + while test_case: + test_module.cases.append(test_case) + test_case = parse_test_case(lines) + if parse_ok_not_ok_test_module(lines, test_module): + test_module.status = bubble_up_test_case_errors(test_module) + return test_module + else: + print('failed to parse end of module' + lines[0]) + return None + +TAP_HEADER = re.compile(r'^TAP version 14$') + +def parse_tap_header(lines: List[str]) -> bool: + consume_non_diagnositic(lines) + if TAP_HEADER.match(lines[0]): + lines.pop(0) + return True + else: + return False + +def bubble_up_module_errors(test_module_list: List[TestModule]) -> TestStatus: + return bubble_up_errors(lambda x: x.status, test_module_list) + +def parse_test_result(lines: List[str]) -> TestResult: + if not lines: + return TestResult(TestStatus.NO_TESTS, [], lines) + consume_non_diagnositic(lines) + if not parse_tap_header(lines): + return None + test_modules = [] + test_module = parse_test_module(lines) + while test_module: + test_modules.append(test_module) + test_module = parse_test_module(lines) + return TestResult(bubble_up_module_errors(test_modules), test_modules, lines) + +def parse_run_tests(kernel_output) -> TestResult: + total_tests = 0 + failed_tests = 0 + crashed_tests = 0 + test_result = parse_test_result(list(isolate_kunit_output(kernel_output))) + for test_module in test_result.modules: + if test_module.status == TestStatus.SUCCESS: + print_module_divider(green('[PASSED] ') + test_module.name) + elif test_module.status == TestStatus.TEST_CRASHED: + print_module_divider(red('[CRASHED] ' + test_module.name)) + else: + print_module_divider(red('[FAILED] ') + test_module.name) + for test_case in test_module.cases: + total_tests += 1 + if test_case.status == TestStatus.SUCCESS: + print_with_timestamp(green('[PASSED] ') + test_case.name) + elif test_case.status == TestStatus.TEST_CRASHED: + crashed_tests += 1 + print_with_timestamp(red('[CRASHED] ' + test_case.name)) + print_log(map(yellow, test_case.log)) + print_with_timestamp('') + else: + failed_tests += 1 + print_with_timestamp(red('[FAILED] ') + test_case.name) + print_log(map(yellow, test_case.log)) + print_with_timestamp('') + print_with_timestamp(DIVIDER) + fmt = green if test_result.status == TestStatus.SUCCESS else red + print_with_timestamp( + fmt('Testing complete. %d tests run. %d failed. %d crashed.' % + (total_tests, failed_tests, crashed_tests))) + return test_result diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py new file mode 100755 index 0000000000000..50c3ed3108a6e --- /dev/null +++ b/tools/testing/kunit/kunit_tool_test.py @@ -0,0 +1,206 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 +# +# A collection of tests for tools/testing/kunit/kunit.py +# +# Copyright (C) 2019, Google LLC. +# Author: Brendan Higgins brendanhiggins@google.com + +import unittest +from unittest import mock + +import tempfile, shutil # Handling test_tmpdir + +import os + +import kunit_config +import kunit_parser +import kunit_kernel +import kunit + +test_tmpdir = '' + +def setUpModule(): + global test_tmpdir + test_tmpdir = tempfile.mkdtemp() + +def tearDownModule(): + shutil.rmtree(test_tmpdir) + +def get_absolute_path(path): + return os.path.join(os.path.dirname(__file__), path) + +class KconfigTest(unittest.TestCase): + + def test_is_subset_of(self): + kconfig0 = kunit_config.Kconfig() + self.assertTrue(kconfig0.is_subset_of(kconfig0)) + + kconfig1 = kunit_config.Kconfig() + kconfig1.add_entry(kunit_config.KconfigEntry('CONFIG_TEST=y')) + self.assertTrue(kconfig1.is_subset_of(kconfig1)) + self.assertTrue(kconfig0.is_subset_of(kconfig1)) + self.assertFalse(kconfig1.is_subset_of(kconfig0)) + + def test_read_from_file(self): + kconfig = kunit_config.Kconfig() + kconfig_path = get_absolute_path( + 'test_data/test_read_from_file.kconfig') + + kconfig.read_from_file(kconfig_path) + + expected_kconfig = kunit_config.Kconfig() + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_UML=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_MMU=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_TEST=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_EXAMPLE_TEST=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('# CONFIG_MK8 is not set')) + + self.assertEqual(kconfig.entries(), expected_kconfig.entries()) + + def test_write_to_file(self): + kconfig_path = os.path.join(test_tmpdir, '.config') + + expected_kconfig = kunit_config.Kconfig() + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_UML=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_MMU=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_TEST=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('CONFIG_EXAMPLE_TEST=y')) + expected_kconfig.add_entry( + kunit_config.KconfigEntry('# CONFIG_MK8 is not set')) + + expected_kconfig.write_to_file(kconfig_path) + + actual_kconfig = kunit_config.Kconfig() + actual_kconfig.read_from_file(kconfig_path) + + self.assertEqual(actual_kconfig.entries(), + expected_kconfig.entries()) + +class KUnitParserTest(unittest.TestCase): + + def assertContains(self, needle, haystack): + for line in haystack: + if needle in line: + return + raise AssertionError('"' + + str(needle) + '" not found in "' + str(haystack) + '"!') + + def test_output_isolated_correctly(self): + log_path = get_absolute_path( + 'test_data/test_output_isolated_correctly.log') + file = open(log_path) + result = kunit_parser.isolate_kunit_output(file.readlines()) + self.assertContains('TAP version 14\n', result) + self.assertContains(' # Subtest: example', result) + self.assertContains(' 1..2', result) + self.assertContains(' ok 1 - example_simple_test', result) + self.assertContains(' ok 2 - example_mock_test', result) + self.assertContains('ok 1 - example', result) + file.close() + + def test_parse_successful_test_log(self): + all_passed_log = get_absolute_path( + 'test_data/test_is_test_passed-all_passed.log') + file = open(all_passed_log) + result = kunit_parser.parse_run_tests(file.readlines()) + self.assertEqual( + kunit_parser.TestStatus.SUCCESS, + result.status) + file.close() + + def test_parse_failed_test_log(self): + failed_log = get_absolute_path( + 'test_data/test_is_test_passed-failure.log') + file = open(failed_log) + result = kunit_parser.parse_run_tests(file.readlines()) + self.assertEqual( + kunit_parser.TestStatus.FAILURE, + result.status) + file.close() + + def test_no_tests(self): + empty_log = get_absolute_path( + 'test_data/test_is_test_passed-no_tests_run.log') + file = open(empty_log) + result = kunit_parser.parse_run_tests( + kunit_parser.isolate_kunit_output(file.readlines())) + self.assertEqual(0, len(result.modules)) + self.assertEqual( + kunit_parser.TestStatus.NO_TESTS, + result.status) + file.close() + + def test_crashed_test(self): + crashed_log = get_absolute_path( + 'test_data/test_is_test_passed-crash.log') + file = open(crashed_log) + result = kunit_parser.parse_run_tests(file.readlines()) + self.assertEqual( + kunit_parser.TestStatus.TEST_CRASHED, + result.status) + file.close() + +class StrContains(str): + def __eq__(self, other): + return self in other + +class KUnitMainTest(unittest.TestCase): + def setUp(self): + path = get_absolute_path('test_data/test_is_test_passed-all_passed.log') + file = open(path) + all_passed_log = file.readlines() + self.print_patch = mock.patch('builtins.print') + self.print_mock = self.print_patch.start() + self.linux_source_mock = mock.Mock() + self.linux_source_mock.build_reconfig = mock.Mock(return_value=True) + self.linux_source_mock.build_um_kernel = mock.Mock(return_value=True) + self.linux_source_mock.run_kernel = mock.Mock(return_value=all_passed_log) + + def tearDown(self): + self.print_patch.stop() + pass + + def test_run_passes_args_pass(self): + kunit.main(['run'], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 1 + assert self.linux_source_mock.run_kernel.call_count == 1 + self.print_mock.assert_any_call(StrContains('Testing complete.')) + + def test_run_passes_args_fail(self): + self.linux_source_mock.run_kernel = mock.Mock(return_value=[]) + with self.assertRaises(SystemExit) as e: + kunit.main(['run'], self.linux_source_mock) + assert type(e.exception) == SystemExit + assert e.exception.code == 1 + assert self.linux_source_mock.build_reconfig.call_count == 1 + assert self.linux_source_mock.run_kernel.call_count == 1 + self.print_mock.assert_any_call(StrContains(' 0 tests run')) + + def test_run_raw_output(self): + self.linux_source_mock.run_kernel = mock.Mock(return_value=[]) + kunit.main(['run', '--raw_output'], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 1 + assert self.linux_source_mock.run_kernel.call_count == 1 + for kall in self.print_mock.call_args_list: + assert kall != mock.call(StrContains('Testing complete.')) + assert kall != mock.call(StrContains(' 0 tests run')) + + def test_run_timeout(self): + timeout = 3453 + kunit.main(['run', '--timeout', str(timeout)], self.linux_source_mock) + assert self.linux_source_mock.build_reconfig.call_count == 1 + self.linux_source_mock.run_kernel.assert_called_once_with(timeout=timeout) + self.print_mock.assert_any_call(StrContains('Testing complete.')) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/testing/kunit/test_data/test_is_test_passed-all_passed.log b/tools/testing/kunit/test_data/test_is_test_passed-all_passed.log new file mode 100644 index 0000000000000..8b5aa2f0cb498 --- /dev/null +++ b/tools/testing/kunit/test_data/test_is_test_passed-all_passed.log @@ -0,0 +1,32 @@ +TAP version 14 + # Subtest: sysctl_test + 1..8 + # sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed + ok 1 - sysctl_test_dointvec_null_tbl_data + # sysctl_test_dointvec_table_maxlen_unset: sysctl_test_dointvec_table_maxlen_unset passed + ok 2 - sysctl_test_dointvec_table_maxlen_unset + # sysctl_test_dointvec_table_len_is_zero: sysctl_test_dointvec_table_len_is_zero passed + ok 3 - sysctl_test_dointvec_table_len_is_zero + # sysctl_test_dointvec_table_read_but_position_set: sysctl_test_dointvec_table_read_but_position_set passed + ok 4 - sysctl_test_dointvec_table_read_but_position_set + # sysctl_test_dointvec_happy_single_positive: sysctl_test_dointvec_happy_single_positive passed + ok 5 - sysctl_test_dointvec_happy_single_positive + # sysctl_test_dointvec_happy_single_negative: sysctl_test_dointvec_happy_single_negative passed + ok 6 - sysctl_test_dointvec_happy_single_negative + # sysctl_test_dointvec_single_less_int_min: sysctl_test_dointvec_single_less_int_min passed + ok 7 - sysctl_test_dointvec_single_less_int_min + # sysctl_test_dointvec_single_greater_int_max: sysctl_test_dointvec_single_greater_int_max passed + ok 8 - sysctl_test_dointvec_single_greater_int_max +kunit sysctl_test: all tests passed +ok 1 - sysctl_test + # Subtest: example + 1..2 +init_module + # example_simple_test: initializing + # example_simple_test: example_simple_test passed + ok 1 - example_simple_test + # example_mock_test: initializing + # example_mock_test: example_mock_test passed + ok 2 - example_mock_test +kunit example: all tests passed +ok 2 - example diff --git a/tools/testing/kunit/test_data/test_is_test_passed-crash.log b/tools/testing/kunit/test_data/test_is_test_passed-crash.log new file mode 100644 index 0000000000000..9ea558882d524 --- /dev/null +++ b/tools/testing/kunit/test_data/test_is_test_passed-crash.log @@ -0,0 +1,69 @@ +printk: console [tty0] enabled +printk: console [mc-1] enabled +TAP version 14 + # Subtest: sysctl_test + 1..8 + # sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed + ok 1 - sysctl_test_dointvec_null_tbl_data + # sysctl_test_dointvec_table_maxlen_unset: sysctl_test_dointvec_table_maxlen_unset passed + ok 2 - sysctl_test_dointvec_table_maxlen_unset + # sysctl_test_dointvec_table_len_is_zero: sysctl_test_dointvec_table_len_is_zero passed + ok 3 - sysctl_test_dointvec_table_len_is_zero + # sysctl_test_dointvec_table_read_but_position_set: sysctl_test_dointvec_table_read_but_position_set passed + ok 4 - sysctl_test_dointvec_table_read_but_position_set + # sysctl_test_dointvec_happy_single_positive: sysctl_test_dointvec_happy_single_positive passed + ok 5 - sysctl_test_dointvec_happy_single_positive + # sysctl_test_dointvec_happy_single_negative: sysctl_test_dointvec_happy_single_negative passed + ok 6 - sysctl_test_dointvec_happy_single_negative + # sysctl_test_dointvec_single_less_int_min: sysctl_test_dointvec_single_less_int_min passed + ok 7 - sysctl_test_dointvec_single_less_int_min + # sysctl_test_dointvec_single_greater_int_max: sysctl_test_dointvec_single_greater_int_max passed + ok 8 - sysctl_test_dointvec_single_greater_int_max +kunit sysctl_test: all tests passed +ok 1 - sysctl_test + # Subtest: example + 1..2 +init_module + # example_simple_test: initializing +Stack: + 6016f7db 6f81bd30 6f81bdd0 60021450 + 6024b0e8 60021440 60018bbe 16f81bdc0 + 00000001 6f81bd30 6f81bd20 6f81bdd0 +Call Trace: + [<6016f7db>] ? kunit_try_run_case+0xab/0xf0 + [<60021450>] ? set_signals+0x0/0x60 + [<60021440>] ? get_signals+0x0/0x10 + [<60018bbe>] ? kunit_um_run_try_catch+0x5e/0xc0 + [<60021450>] ? set_signals+0x0/0x60 + [<60021440>] ? get_signals+0x0/0x10 + [<60018bb3>] ? kunit_um_run_try_catch+0x53/0xc0 + [<6016f321>] ? kunit_run_case_catch_errors+0x121/0x1a0 + [<60018b60>] ? kunit_um_run_try_catch+0x0/0xc0 + [<600189e0>] ? kunit_um_throw+0x0/0x180 + [<6016f730>] ? kunit_try_run_case+0x0/0xf0 + [<6016f600>] ? kunit_catch_run_case+0x0/0x130 + [<6016edd0>] ? kunit_vprintk+0x0/0x30 + [<6016ece0>] ? kunit_fail+0x0/0x40 + [<6016eca0>] ? kunit_abort+0x0/0x40 + [<6016ed20>] ? kunit_printk_emit+0x0/0xb0 + [<6016f200>] ? kunit_run_case_catch_errors+0x0/0x1a0 + [<6016f46e>] ? kunit_run_tests+0xce/0x260 + [<6005b390>] ? unregister_console+0x0/0x190 + [<60175b70>] ? module_kunit_initexample_test_module+0x0/0x20 + [<60001cbb>] ? do_one_initcall+0x0/0x197 + [<60001d47>] ? do_one_initcall+0x8c/0x197 + [<6005cd20>] ? irq_to_desc+0x0/0x30 + [<60002005>] ? kernel_init_freeable+0x1b3/0x272 + [<6005c5ec>] ? printk+0x0/0x9b + [<601c0086>] ? kernel_init+0x26/0x160 + [<60014442>] ? new_thread_handler+0x82/0xc0 + + # example_simple_test: kunit test case crashed! + # example_simple_test: example_simple_test failed + not ok 1 - example_simple_test + # example_mock_test: initializing + # example_mock_test: example_mock_test passed + ok 2 - example_mock_test +kunit example: one or more tests failed +not ok 2 - example +List of all partitions: diff --git a/tools/testing/kunit/test_data/test_is_test_passed-failure.log b/tools/testing/kunit/test_data/test_is_test_passed-failure.log new file mode 100644 index 0000000000000..73ed8730b76b2 --- /dev/null +++ b/tools/testing/kunit/test_data/test_is_test_passed-failure.log @@ -0,0 +1,36 @@ +TAP version 14 + # Subtest: sysctl_test + 1..8 + # sysctl_test_dointvec_null_tbl_data: sysctl_test_dointvec_null_tbl_data passed + ok 1 - sysctl_test_dointvec_null_tbl_data + # sysctl_test_dointvec_table_maxlen_unset: sysctl_test_dointvec_table_maxlen_unset passed + ok 2 - sysctl_test_dointvec_table_maxlen_unset + # sysctl_test_dointvec_table_len_is_zero: sysctl_test_dointvec_table_len_is_zero passed + ok 3 - sysctl_test_dointvec_table_len_is_zero + # sysctl_test_dointvec_table_read_but_position_set: sysctl_test_dointvec_table_read_but_position_set passed + ok 4 - sysctl_test_dointvec_table_read_but_position_set + # sysctl_test_dointvec_happy_single_positive: sysctl_test_dointvec_happy_single_positive passed + ok 5 - sysctl_test_dointvec_happy_single_positive + # sysctl_test_dointvec_happy_single_negative: sysctl_test_dointvec_happy_single_negative passed + ok 6 - sysctl_test_dointvec_happy_single_negative + # sysctl_test_dointvec_single_less_int_min: sysctl_test_dointvec_single_less_int_min passed + ok 7 - sysctl_test_dointvec_single_less_int_min + # sysctl_test_dointvec_single_greater_int_max: sysctl_test_dointvec_single_greater_int_max passed + ok 8 - sysctl_test_dointvec_single_greater_int_max +kunit sysctl_test: all tests passed +ok 1 - sysctl_test + # Subtest: example + 1..2 +init_module + # example_simple_test: initializing + # example_simple_test: EXPECTATION FAILED at kunit/example-test.c:30 + Expected 1 + 1 == 3, but + 1 + 1 == 2 + 3 == 3 + # example_simple_test: example_simple_test failed + not ok 1 - example_simple_test + # example_mock_test: initializing + # example_mock_test: example_mock_test passed + ok 2 - example_mock_test +kunit example: one or more tests failed +not ok 2 - example diff --git a/tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log b/tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log new file mode 100644 index 0000000000000..ba69f5c94b75f --- /dev/null +++ b/tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log @@ -0,0 +1,75 @@ +Core dump limits : + soft - 0 + hard - NONE +Checking environment variables for a tempdir...none found +Checking if /dev/shm is on tmpfs...OK +Checking PROT_EXEC mmap in /dev/shm...OK +Adding 24743936 bytes to physical memory to account for exec-shield gap +Linux version 4.12.0-rc3-00010-g7319eb35f493-dirty (brendanhiggins@mactruck.svl.corp.google.com) (gcc version 7.3.0 (Debian 7.3.0-5) ) #29 Thu Mar 15 14:57:19 PDT 2018 +Built 1 zonelists in Zone order, mobility grouping on. Total pages: 14038 +Kernel command line: root=98:0 +PID hash table entries: 256 (order: -1, 2048 bytes) +Dentry cache hash table entries: 8192 (order: 4, 65536 bytes) +Inode-cache hash table entries: 4096 (order: 3, 32768 bytes) +Memory: 27868K/56932K available (1681K kernel code, 480K rwdata, 400K rodata, 89K init, 205K bss, 29064K reserved, 0K cma-reserved) +SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 +NR_IRQS:15 +clocksource: timer: mask: 0xffffffffffffffff max_cycles: 0x1cd42e205, max_idle_ns: 881590404426 ns +Calibrating delay loop... 7384.26 BogoMIPS (lpj=36921344) +pid_max: default: 32768 minimum: 301 +Mount-cache hash table entries: 512 (order: 0, 4096 bytes) +Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes) +Checking that host ptys support output SIGIO...Yes +Checking that host ptys support SIGIO on close...No, enabling workaround +Using 2.6 host AIO +clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns +futex hash table entries: 256 (order: 0, 6144 bytes) +clocksource: Switched to clocksource timer +console [stderr0] disabled +mconsole (version 2) initialized on /usr/local/google/home/brendanhiggins/.uml/6Ijecl/mconsole +Checking host MADV_REMOVE support...OK +workingset: timestamp_bits=62 max_order=13 bucket_order=0 +Block layer SCSI generic (bsg) driver version 0.4 loaded (major 254) +io scheduler noop registered +io scheduler deadline registered +io scheduler cfq registered (default) +io scheduler mq-deadline registered +io scheduler kyber registered +Initialized stdio console driver +Using a channel type which is configured out of UML +setup_one_line failed for device 1 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 2 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 3 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 4 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 5 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 6 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 7 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 8 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 9 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 10 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 11 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 12 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 13 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 14 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 15 : Configuration failed +Console initialized on /dev/tty0 +console [tty0] enabled +console [mc-1] enabled +List of all partitions: +No filesystem could mount root, tried: + +Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(98,0) diff --git a/tools/testing/kunit/test_data/test_output_isolated_correctly.log b/tools/testing/kunit/test_data/test_output_isolated_correctly.log new file mode 100644 index 0000000000000..6e92bc7e28041 --- /dev/null +++ b/tools/testing/kunit/test_data/test_output_isolated_correctly.log @@ -0,0 +1,106 @@ +Linux version 5.1.0-rc7-00061-g04652f1cb4aa0 (brendanhiggins@mactruck.svl.corp.google.com) (gcc version 7.3.0 (Debian 7.3.0-18)) #163 Wed May 8 16:18:20 PDT 2019 +Built 1 zonelists, mobility grouping on. Total pages: 69906 +Kernel command line: mem=256M root=98:0 +Dentry cache hash table entries: 65536 (order: 7, 524288 bytes) +Inode-cache hash table entries: 32768 (order: 6, 262144 bytes) +Memory: 254468K/283500K available (1734K kernel code, 489K rwdata, 396K rodata, 85K init, 216K bss, 29032K reserved, 0K cma-reserved) +SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 +NR_IRQS: 15 +clocksource: timer: mask: 0xffffffffffffffff max_cycles: 0x1cd42e205, max_idle_ns: 881590404426 ns +------------[ cut here ]------------ +WARNING: CPU: 0 PID: 0 at kernel/time/clockevents.c:458 clockevents_register_device+0x143/0x160 +posix-timer cpumask == cpu_all_mask, using cpu_possible_mask instead +CPU: 0 PID: 0 Comm: swapper Not tainted 5.1.0-rc7-00061-g04652f1cb4aa0 #163 +Stack: + 6005cc00 60233e18 60233e60 60233e18 + 60233e60 00000009 00000000 6002a1b4 + 1ca00000000 60071c23 60233e78 100000000000062 +Call Trace: + [<600214c5>] ? os_is_signal_stack+0x15/0x30 + [<6005c5ec>] ? printk+0x0/0x9b + [<6001597e>] ? show_stack+0xbe/0x1c0 + [<6005cc00>] ? __printk_safe_exit+0x0/0x40 + [<6002a1b4>] ? __warn+0x144/0x170 + [<60071c23>] ? clockevents_register_device+0x143/0x160 + [<60021440>] ? get_signals+0x0/0x10 + [<6005c5ec>] ? printk+0x0/0x9b + [<6002a27b>] ? warn_slowpath_fmt+0x9b/0xb0 + [<6005c5ec>] ? printk+0x0/0x9b + [<6002a1e0>] ? warn_slowpath_fmt+0x0/0xb0 + [<6005c5ec>] ? printk+0x0/0x9b + [<60021440>] ? get_signals+0x0/0x10 + [<600213f0>] ? block_signals+0x0/0x20 + [<60071c23>] ? clockevents_register_device+0x143/0x160 + [<60021440>] ? get_signals+0x0/0x10 + [<600213f0>] ? block_signals+0x0/0x20 + [<6005c5ec>] ? printk+0x0/0x9b + [<60001bc8>] ? start_kernel+0x477/0x56a + [<600036f1>] ? start_kernel_proc+0x46/0x4d + [<60014442>] ? new_thread_handler+0x82/0xc0 + +random: get_random_bytes called from print_oops_end_marker+0x4c/0x60 with crng_init=0 +---[ end trace c83434852b3702d3 ]--- +Calibrating delay loop... 6958.28 BogoMIPS (lpj=34791424) +pid_max: default: 32768 minimum: 301 +Mount-cache hash table entries: 1024 (order: 1, 8192 bytes) +Mountpoint-cache hash table entries: 1024 (order: 1, 8192 bytes) +*** VALIDATE proc *** +Checking that host ptys support output SIGIO...Yes +Checking that host ptys support SIGIO on close...No, enabling workaround +clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns +futex hash table entries: 256 (order: 0, 6144 bytes) +clocksource: Switched to clocksource timer +printk: console [stderr0] disabled +mconsole (version 2) initialized on /usr/local/google/home/brendanhiggins/.uml/VZ2qMm/mconsole +Checking host MADV_REMOVE support...OK +workingset: timestamp_bits=62 max_order=16 bucket_order=0 +Block layer SCSI generic (bsg) driver version 0.4 loaded (major 254) +io scheduler mq-deadline registered +io scheduler kyber registered +Initialized stdio console driver +Using a channel type which is configured out of UML +setup_one_line failed for device 1 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 2 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 3 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 4 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 5 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 6 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 7 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 8 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 9 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 10 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 11 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 12 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 13 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 14 : Configuration failed +Using a channel type which is configured out of UML +setup_one_line failed for device 15 : Configuration failed +Console initialized on /dev/tty0 +printk: console [tty0] enabled +printk: console [mc-1] enabled +TAP version 14 + # Subtest: example + 1..2 +init_module + # example_simple_test: initializing + # example_simple_test: example_simple_test passed + ok 1 - example_simple_test + # example_mock_test: initializing + # example_mock_test: example_mock_test passed + ok 2 - example_mock_test +kunit example: all tests passed +ok 1 - example +List of all partitions: diff --git a/tools/testing/kunit/test_data/test_read_from_file.kconfig b/tools/testing/kunit/test_data/test_read_from_file.kconfig new file mode 100644 index 0000000000000..d2a4928ac773b --- /dev/null +++ b/tools/testing/kunit/test_data/test_read_from_file.kconfig @@ -0,0 +1,17 @@ +# +# Automatically generated file; DO NOT EDIT. +# User Mode Linux/x86 4.12.0-rc3 Kernel Configuration +# +CONFIG_UML=y +CONFIG_MMU=y + +# +# UML-specific options +# + +# +# Host processor type and features +# +# CONFIG_MK8 is not set +CONFIG_TEST=y +CONFIG_EXAMPLE_TEST=y
On Mon, Jun 17, 2019 at 01:26:08AM -0700, Brendan Higgins wrote:
create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-all_passed.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-crash.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-failure.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log create mode 100644 tools/testing/kunit/test_data/test_output_isolated_correctly.log create mode 100644 tools/testing/kunit/test_data/test_read_from_file.kconfig
Why are these being added upstream? The commit log does not explain this.
Luis
On Tue, Jun 25, 2019 at 5:01 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:08AM -0700, Brendan Higgins wrote:
create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-all_passed.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-crash.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-failure.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log create mode 100644 tools/testing/kunit/test_data/test_output_isolated_correctly.log create mode 100644 tools/testing/kunit/test_data/test_read_from_file.kconfig
Why are these being added upstream? The commit log does not explain this.
Oh sorry, those are for testing purposes. I thought that was clear from being in the test_data directory. I will reference it in the commit log in the next revision.
On Wed, Jun 26, 2019 at 01:02:55AM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 5:01 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:08AM -0700, Brendan Higgins wrote:
create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-all_passed.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-crash.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-failure.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log create mode 100644 tools/testing/kunit/test_data/test_output_isolated_correctly.log create mode 100644 tools/testing/kunit/test_data/test_read_from_file.kconfig
Why are these being added upstream? The commit log does not explain this.
Oh sorry, those are for testing purposes. I thought that was clear from being in the test_data directory. I will reference it in the commit log in the next revision.
Still, I don't get it. They seem to be results from a prior run. Why do we need them for testing purposes?
Luis
On Wed, Jun 26, 2019 at 3:03 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Wed, Jun 26, 2019 at 01:02:55AM -0700, Brendan Higgins wrote:
On Tue, Jun 25, 2019 at 5:01 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:08AM -0700, Brendan Higgins wrote:
create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-all_passed.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-crash.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-failure.log create mode 100644 tools/testing/kunit/test_data/test_is_test_passed-no_tests_run.log create mode 100644 tools/testing/kunit/test_data/test_output_isolated_correctly.log create mode 100644 tools/testing/kunit/test_data/test_read_from_file.kconfig
Why are these being added upstream? The commit log does not explain this.
Oh sorry, those are for testing purposes. I thought that was clear from being in the test_data directory. I will reference it in the commit log in the next revision.
Still, I don't get it. They seem to be results from a prior run. Why do we need them for testing purposes?
Those logs are the raw output from UML with KUnit installed. They are for testing kunit_tool, the Python scripts added in this commit. One of the things that kunit_tool does is parses the results output by UML, extracts the KUnit data, and presents it in a user friendly manner.
I added these logs so I could test that kunit_tool parses certain kinds of output correctly. For example, I want to know that it parses a test failure correctly and includes the appropriate context. So I have a log from a unit test that failed, and I have a test (a Python test that is also in this commit) that tests whether kunit_tool can parse the log correctly.
Does that make sense?
Add defconfig for UML and a fragment that can be used to configure other architectures for building KUnit tests. Add option to kunit_tool to use a defconfig to create the kunitconfig.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- arch/um/configs/kunit_defconfig | 8 ++++++++ tools/testing/kunit/configs/all_tests.config | 8 ++++++++ tools/testing/kunit/kunit.py | 18 ++++++++++++++++-- tools/testing/kunit/kunit_kernel.py | 3 ++- 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 arch/um/configs/kunit_defconfig create mode 100644 tools/testing/kunit/configs/all_tests.config
diff --git a/arch/um/configs/kunit_defconfig b/arch/um/configs/kunit_defconfig new file mode 100644 index 0000000000000..bfe49689038f1 --- /dev/null +++ b/arch/um/configs/kunit_defconfig @@ -0,0 +1,8 @@ +CONFIG_OF=y +CONFIG_OF_UNITTEST=y +CONFIG_OF_OVERLAY=y +CONFIG_I2C=y +CONFIG_I2C_MUX=y +CONFIG_KUNIT=y +CONFIG_KUNIT_TEST=y +CONFIG_KUNIT_EXAMPLE_TEST=y diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config new file mode 100644 index 0000000000000..bfe49689038f1 --- /dev/null +++ b/tools/testing/kunit/configs/all_tests.config @@ -0,0 +1,8 @@ +CONFIG_OF=y +CONFIG_OF_UNITTEST=y +CONFIG_OF_OVERLAY=y +CONFIG_I2C=y +CONFIG_I2C_MUX=y +CONFIG_KUNIT=y +CONFIG_KUNIT_TEST=y +CONFIG_KUNIT_EXAMPLE_TEST=y diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index da11bd62a4b82..3e51dc4febfdc 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -11,6 +11,7 @@ import argparse import sys import os import time +import shutil
from collections import namedtuple from enum import Enum, auto @@ -21,7 +22,7 @@ import kunit_parser
KunitResult = namedtuple('KunitResult', ['status','result'])
-KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 'build_dir']) +KunitRequest = namedtuple('KunitRequest', ['raw_output','timeout', 'jobs', 'build_dir', 'defconfig'])
class KunitStatus(Enum): SUCCESS = auto() @@ -29,8 +30,16 @@ class KunitStatus(Enum): BUILD_FAILURE = auto() TEST_FAILURE = auto()
+def create_default_kunitconfig(): + if not os.path.exists(kunit_kernel.KUNITCONFIG_PATH): + shutil.copyfile('arch/um/configs/kunit_defconfig', + kunit_kernel.KUNITCONFIG_PATH) + def run_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitRequest) -> KunitResult: + if request.defconfig: + create_default_kunitconfig() + config_start = time.time() success = linux.build_reconfig(request.build_dir) config_end = time.time() @@ -99,13 +108,18 @@ def main(argv, linux): 'directory.', type=str, default=None, metavar='build_dir')
+ run_parser.add_argument('--defconfig', + help='Uses a default kunitconfig.', + action='store_true') + cli_args = parser.parse_args(argv)
if cli_args.subcommand == 'run': request = KunitRequest(cli_args.raw_output, cli_args.timeout, cli_args.jobs, - cli_args.build_dir) + cli_args.build_dir, + cli_args.defconfig) result = run_tests(linux, request) if result.status != KunitStatus.SUCCESS: sys.exit(1) diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py index 07c0abf2f47df..bf38768353313 100644 --- a/tools/testing/kunit/kunit_kernel.py +++ b/tools/testing/kunit/kunit_kernel.py @@ -14,6 +14,7 @@ import os import kunit_config
KCONFIG_PATH = '.config' +KUNITCONFIG_PATH = 'kunitconfig'
class ConfigError(Exception): """Represents an error trying to configure the Linux kernel.""" @@ -81,7 +82,7 @@ class LinuxSourceTree(object):
def __init__(self): self._kconfig = kunit_config.Kconfig() - self._kconfig.read_from_file('kunitconfig') + self._kconfig.read_from_file(KUNITCONFIG_PATH) self._ops = LinuxSourceTreeOperations()
def clean(self):
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 Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Documentation/dev-tools/index.rst | 1 + Documentation/dev-tools/kunit/api/index.rst | 16 + Documentation/dev-tools/kunit/api/test.rst | 14 + Documentation/dev-tools/kunit/faq.rst | 62 +++ Documentation/dev-tools/kunit/index.rst | 79 +++ Documentation/dev-tools/kunit/start.rst | 180 ++++++ Documentation/dev-tools/kunit/usage.rst | 575 ++++++++++++++++++++ 7 files changed, 927 insertions(+) create mode 100644 Documentation/dev-tools/kunit/api/index.rst create mode 100644 Documentation/dev-tools/kunit/api/test.rst create mode 100644 Documentation/dev-tools/kunit/faq.rst create mode 100644 Documentation/dev-tools/kunit/index.rst create mode 100644 Documentation/dev-tools/kunit/start.rst create mode 100644 Documentation/dev-tools/kunit/usage.rst
diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst index b0522a4dd1073..09dee10d25928 100644 --- a/Documentation/dev-tools/index.rst +++ b/Documentation/dev-tools/index.rst @@ -24,6 +24,7 @@ whole; patches welcome! gdb-kernel-debugging kgdb kselftest + kunit/index
.. only:: subproject and html diff --git a/Documentation/dev-tools/kunit/api/index.rst b/Documentation/dev-tools/kunit/api/index.rst new file mode 100644 index 0000000000000..9b9bffe5d41a0 --- /dev/null +++ b/Documentation/dev-tools/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 the +following sections: + +================================= ============================================== +:doc:`test` documents all of the standard testing API + excluding mocking or mocking related features. +================================= ============================================== diff --git a/Documentation/dev-tools/kunit/api/test.rst b/Documentation/dev-tools/kunit/api/test.rst new file mode 100644 index 0000000000000..d0ce19b1e1185 --- /dev/null +++ b/Documentation/dev-tools/kunit/api/test.rst @@ -0,0 +1,14 @@ +.. 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/kunit-stream.h + :internal: diff --git a/Documentation/dev-tools/kunit/faq.rst b/Documentation/dev-tools/kunit/faq.rst new file mode 100644 index 0000000000000..bf2095112d899 --- /dev/null +++ b/Documentation/dev-tools/kunit/faq.rst @@ -0,0 +1,62 @@ +.. 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. + +Does KUnit support running on architectures other than UML? +=========================================================== + +Yes, well, mostly. + +For the most part, the KUnit core framework (what you use to write the tests) +can compile to any architecture; it compiles like just another part of the +kernel and runs when the kernel boots. However, there is some infrastructure, +like the KUnit Wrapper (``tools/testing/kunit/kunit.py``) that does not support +other architectures. + +In short, this means that, yes, you can run KUnit on other architectures, but +it might require more work than using KUnit on UML. + +For more information, see :ref:`kunit-on-non-uml`. + +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/dev-tools/kunit/index.rst b/Documentation/dev-tools/kunit/index.rst new file mode 100644 index 0000000000000..a317ab45bfe2d --- /dev/null +++ b/Documentation/dev-tools/kunit/index.rst @@ -0,0 +1,79 @@ +.. 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/dev-tools/kunit/start.rst b/Documentation/dev-tools/kunit/start.rst new file mode 100644 index 0000000000000..852c8c70ca42c --- /dev/null +++ b/Documentation/dev-tools/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 kunit *test) + { + KUNIT_EXPECT_EQ(test, 1, misc_example_add(1, 0)); + KUNIT_EXPECT_EQ(test, 2, misc_example_add(1, 1)); + KUNIT_EXPECT_EQ(test, 0, misc_example_add(-1, 1)); + KUNIT_EXPECT_EQ(test, INT_MAX, misc_example_add(0, INT_MAX)); + KUNIT_EXPECT_EQ(test, -1, misc_example_add(INT_MAX, INT_MIN)); + } + + static void misc_example_test_failure(struct kunit *test) + { + KUNIT_FAIL(test, "This test never passes."); + } + + static struct kunit_case misc_example_test_cases[] = { + KUNIT_CASE(misc_example_add_test_basic), + KUNIT_CASE(misc_example_test_failure), + {} + }; + + static struct kunit_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/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst new file mode 100644 index 0000000000000..c61b4f69d8837 --- /dev/null +++ b/Documentation/dev-tools/kunit/usage.rst @@ -0,0 +1,575 @@ +.. 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 kunit *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 kunit *test) + { + } + + void example_test_failure(struct kunit *test) + { + KUNIT_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 ``KUNIT_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 kunit *test) + { + KUNIT_EXPECT_EQ(test, 1, add(1, 0)); + KUNIT_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 kunit *``, 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 kunit *test) + { + KUNIT_EXPECT_EQ(test, 1, add(1, 0)); + KUNIT_EXPECT_EQ(test, 2, add(1, 1)); + } + + void add_test_negative(struct kunit *test) + { + KUNIT_EXPECT_EQ(test, 0, add(-1, 1)); + } + + void add_test_max(struct kunit *test) + { + KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX)); + KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN)); + } + + void add_test_overflow(struct kunit *test) + { + KUNIT_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 kunit *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)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ret); + KUNIT_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 kunit_case example_test_cases[] = { + KUNIT_CASE(example_test_foo), + KUNIT_CASE(example_test_bar), + KUNIT_CASE(example_test_baz), + {} + }; + + static struct kunit_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 kunit *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); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0); + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0); + + eeprom_buffer->flush(eeprom_buffer); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff); + } + + static void eeprom_buffer_test_flushes_after_flush_count_met(struct kunit *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); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0); + + eeprom_buffer->write(eeprom_buffer, buffer, 1); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff); + } + + static void eeprom_buffer_test_flushes_increments_of_flush_count(struct kunit *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); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0); + + eeprom_buffer->write(eeprom_buffer, buffer, 2); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff); + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff); + /* Should have only flushed the first two bytes. */ + KUNIT_EXPECT_EQ(test, fake_eeprom->contents[2], 0); + } + + static int eeprom_buffer_test_init(struct kunit *test) + { + struct eeprom_buffer_test *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + ASSERT_NOT_ERR_OR_NULL(test, ctx); + + ctx->fake_eeprom = kunit_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 kunit *test) + { + struct eeprom_buffer_test *ctx = test->priv; + + destroy_eeprom_buffer(ctx->eeprom_buffer); + } + +.. _kunit-on-non-uml: + +KUnit on non-UML architectures +============================== + +By default KUnit uses UML as a way to provide dependencies for code under test. +Under most circumstances KUnit's usage of UML should be treated as an +implementation detail of how KUnit works under the hood. Nevertheless, there +are instances where being able to run architecture specific code, or test +against real hardware is desirable. For these reasons KUnit supports running on +other architectures. + +Running existing KUnit tests on non-UML architectures +----------------------------------------------------- + +There are some special considerations when running existing KUnit tests on +non-UML architectures: + +* Hardware may not be deterministic, so a test that always passes or fails + when run under UML may not always do so on real hardware. +* Hardware and VM environments may not be hermetic. KUnit tries its best to + provide a hermetic environment to run tests; however, it cannot manage state + that it doesn't know about outside of the kernel. Consequently, tests that + may be hermetic on UML may not be hermetic on other architectures. +* Some features and tooling may not be supported outside of UML. +* Hardware and VMs are slower than UML. + +None of these are reasons not to run your KUnit tests on real hardware; they are +only things to be aware of when doing so. + +The biggest impediment will likely be that certain KUnit features and +infrastructure may not support your target environment. For example, at this +time the KUnit Wrapper (``tools/testing/kunit/kunit.py``) does not work outside +of UML. Unfortunately, there is no way around this. Using UML (or even just a +particular architecture) allows us to make a lot of assumptions that make it +possible to do things which might otherwise be impossible. + +Nevertheless, all core KUnit framework features are fully supported on all +architectures, and using them is straightforward: all you need to do is to take +your kunitconfig, your Kconfig options for the tests you would like to run, and +merge them into whatever config your are using for your platform. That's it! + +For example, let's say you have the following kunitconfig: + +.. code-block:: none + + CONFIG_KUNIT=y + CONFIG_KUNIT_EXAMPLE_TEST=y + +If you wanted to run this test on an x86 VM, you might add the following config +options to your ``.config``: + +.. code-block:: none + + CONFIG_KUNIT=y + CONFIG_KUNIT_EXAMPLE_TEST=y + CONFIG_SERIAL_8250=y + CONFIG_SERIAL_8250_CONSOLE=y + +All these new options do is enable support for a common serial console needed +for logging. + +Next, you could build a kernel with these tests as follows: + + +.. code-block:: bash + + make ARCH=x86 olddefconfig + make ARCH=x86 + +Once you have built a kernel, you could run it on QEMU as follows: + +.. code-block:: bash + + qemu-system-x86_64 -enable-kvm \ + -m 1024 \ + -kernel arch/x86_64/boot/bzImage \ + -append 'console=ttyS0' \ + --nographic + +Interspersed in the kernel logs you might see the following: + +.. code-block:: none + + TAP version 14 + # Subtest: example + 1..1 + # example_simple_test: initializing + ok 1 - example_simple_test + ok 1 - example + +Congratulations, you just ran a KUnit test on the x86 architecture! + +Writing new tests for other architectures +----------------------------------------- + +The first thing you must do is ask yourself whether it is necessary to write a +KUnit test for a specific architecture, and then whether it is necessary to +write that test for a particular piece of hardware. In general, writing a test +that depends on having access to a particular piece of hardware or software (not +included in the Linux source repo) should be avoided at all costs. + +Even if you only ever plan on running your KUnit test on your hardware +configuration, other people may want to run your tests and may not have access +to your hardware. If you write your test to run on UML, then anyone can run your +tests without knowing anything about your particular setup, and you can still +run your tests on your hardware setup just by compiling for your architecture. + +.. important:: + Always prefer tests that run on UML to tests that only run under a particular + architecture, and always prefer tests that run under QEMU or another easy + (and monitarily free) to obtain software environment to a specific piece of + hardware. + +Nevertheless, there are still valid reasons to write an architecture or hardware +specific test: for example, you might want to test some code that really belongs +in ``arch/some-arch/*``. Even so, try your best to write the test so that it +does not depend on physical hardware: if some of your test cases don't need the +hardware, only require the hardware for tests that actually need it. + +Now that you have narrowed down exactly what bits are hardware specific, the +actual procedure for writing and running the tests is pretty much the same as +writing normal KUnit tests. One special caveat is that you have to reset +hardware state in between test cases; if this is not possible, you may only be +able to run one test case per invocation. + +.. TODO(brendanhiggins@google.com): Add an actual example of an architecture + dependent KUnit test.
Add myself as maintainer of KUnit, the Linux kernel's unit testing framework.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index 57f496cff9997..f3fb3fc30853e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8590,6 +8590,17 @@ S: Maintained F: tools/testing/selftests/ F: Documentation/dev-tools/kselftest*
+KERNEL UNIT TESTING FRAMEWORK (KUnit) +M: Brendan Higgins brendanhiggins@google.com +L: linux-kselftest@vger.kernel.org +L: kunit-dev@googlegroups.com +W: https://google.github.io/kunit-docs/third_party/kernel/docs/ +S: Maintained +F: Documentation/dev-tools/kunit/ +F: include/kunit/ +F: kunit/ +F: tools/testing/kunit/ + KERNEL USERMODE HELPER M: Luis Chamberlain mcgrof@kernel.org L: linux-kernel@vger.kernel.org
From: Iurii Zaikin yzaikin@google.com
KUnit tests for initialized data behavior of proc_dointvec that is explicitly checked in the code. Includes basic parsing tests including int min/max overflow.
Signed-off-by: Iurii Zaikin yzaikin@google.com Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- Changes Since Last Revision: - Iurii did some clean up (thanks Iurii!) as suggested by Stephen Boyd. --- kernel/Makefile | 2 + kernel/sysctl-test.c | 242 +++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 10 ++ 3 files changed, 254 insertions(+) create mode 100644 kernel/sysctl-test.c
diff --git a/kernel/Makefile b/kernel/Makefile index a8d923b5481ba..50fd511cd0ee0 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -114,6 +114,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomem.o obj-$(CONFIG_ZONE_DEVICE) += memremap.o obj-$(CONFIG_RSEQ) += rseq.o
+obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o + obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o KASAN_SANITIZE_stackleak.o := n KCOV_INSTRUMENT_stackleak.o := n diff --git a/kernel/sysctl-test.c b/kernel/sysctl-test.c new file mode 100644 index 0000000000000..cb61ad3c7db63 --- /dev/null +++ b/kernel/sysctl-test.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test of proc sysctl. + */ + +#include <kunit/test.h> +#include <linux/sysctl.h> + +static int i_zero; +static int i_one_hundred = 100; + +struct test_sysctl_data { + int int_0001; + int int_0002; + int int_0003[4]; + + unsigned int uint_0001; + + char string_0001[65]; +}; + +static struct test_sysctl_data test_data = { + .int_0001 = 60, + .int_0002 = 1, + + .int_0003[0] = 0, + .int_0003[1] = 1, + .int_0003[2] = 2, + .int_0003[3] = 3, + + .uint_0001 = 314, + + .string_0001 = "(none)", +}; + +static void sysctl_test_dointvec_null_tbl_data(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = NULL, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER); + size_t len; + loff_t pos; + + len = 1234; + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); + len = 1234; + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); +} + +static void sysctl_test_dointvec_table_maxlen_unset(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = 0, + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER); + size_t len; + loff_t pos; + + len = 1234; + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); + len = 1234; + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); +} + +static void sysctl_test_dointvec_table_len_is_zero(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER); + size_t len; + loff_t pos; + + len = 0; + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); +} + +static void sysctl_test_dointvec_table_read_but_position_set(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER); + size_t len; + loff_t pos; + + len = 1234; + pos = 1; + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos)); + KUNIT_EXPECT_EQ(test, (size_t)0, len); +} + +static void sysctl_test_dointvec_happy_single_positive(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + char input[] = "9"; + size_t len = sizeof(input) - 1; + loff_t pos = 0; + + table.data = kunit_kzalloc(test, sizeof(int), GFP_USER); + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, input, &len, &pos)); + KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len); + KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos); + KUNIT_EXPECT_EQ(test, 9, ((int *)table.data)[0]); +} + +static void sysctl_test_dointvec_happy_single_negative(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + char input[] = "-9"; + size_t len = sizeof(input) - 1; + loff_t pos = 0; + + table.data = kunit_kzalloc(test, sizeof(int), GFP_USER); + KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, input, &len, &pos)); + KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len); + KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos); + KUNIT_EXPECT_EQ(test, -9, ((int *)table.data)[0]); +} + +static void sysctl_test_dointvec_single_less_int_min(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + char input[32]; + size_t len = sizeof(input) - 1; + loff_t pos = 0; + unsigned long abs_of_less_than_min = (unsigned long)INT_MAX + - (INT_MAX + INT_MIN) + 1; + + KUNIT_EXPECT_LT(test, + (size_t)snprintf(input, sizeof(input), "-%lu", + abs_of_less_than_min), + sizeof(input)); + + table.data = kunit_kzalloc(test, sizeof(int), GFP_USER); + KUNIT_EXPECT_EQ(test, -EINVAL, + proc_dointvec(&table, 1, input, &len, &pos)); + KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len); + KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]); +} + +static void sysctl_test_dointvec_single_greater_int_max(struct kunit *test) +{ + struct ctl_table table = { + .procname = "foo", + .data = &test_data.int_0001, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + .extra1 = &i_zero, + .extra2 = &i_one_hundred, + }; + char input[32]; + size_t len = sizeof(input) - 1; + loff_t pos = 0; + unsigned long greater_than_max = (unsigned long)INT_MAX + 1; + + KUNIT_EXPECT_GT(test, greater_than_max, (unsigned long)INT_MAX); + KUNIT_EXPECT_LT(test, (size_t)snprintf(input, sizeof(input), "%lu", + greater_than_max), + sizeof(input)); + table.data = kunit_kzalloc(test, sizeof(int), GFP_USER); + KUNIT_EXPECT_EQ(test, -EINVAL, + proc_dointvec(&table, 1, input, &len, &pos)); + KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len); + KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]); +} + +static struct kunit_case sysctl_test_cases[] = { + KUNIT_CASE(sysctl_test_dointvec_null_tbl_data), + KUNIT_CASE(sysctl_test_dointvec_table_maxlen_unset), + KUNIT_CASE(sysctl_test_dointvec_table_len_is_zero), + KUNIT_CASE(sysctl_test_dointvec_table_read_but_position_set), + KUNIT_CASE(sysctl_test_dointvec_happy_single_positive), + KUNIT_CASE(sysctl_test_dointvec_happy_single_negative), + KUNIT_CASE(sysctl_test_dointvec_single_less_int_min), + KUNIT_CASE(sysctl_test_dointvec_single_greater_int_max), + {} +}; + +static struct kunit_module sysctl_test_module = { + .name = "sysctl_test", + .test_cases = sysctl_test_cases, +}; + +module_test(sysctl_test_module); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index cbdfae3798965..389b8986f5b77 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1939,6 +1939,16 @@ config TEST_SYSCTL
If unsure, say N.
+config SYSCTL_KUNIT_TEST + bool "KUnit test for sysctl" + depends on KUNIT + help + This builds the proc sysctl unit test, which runs on boot. For more + information on KUnit and unit tests in general please refer to the + KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config TEST_UDELAY tristate "udelay test driver" help
On Mon, Jun 17, 2019 at 01:26:12AM -0700, Brendan Higgins wrote:
From: Iurii Zaikin yzaikin@google.com
KUnit tests for initialized data behavior of proc_dointvec that is explicitly checked in the code. Includes basic parsing tests including int min/max overflow.
First, thanks for this work! My review below.
Signed-off-by: Iurii Zaikin yzaikin@google.com Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com
Changes Since Last Revision:
- Iurii did some clean up (thanks Iurii!) as suggested by Stephen Boyd.
kernel/Makefile | 2 + kernel/sysctl-test.c | 242 +++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 10 ++ 3 files changed, 254 insertions(+) create mode 100644 kernel/sysctl-test.c
diff --git a/kernel/Makefile b/kernel/Makefile index a8d923b5481ba..50fd511cd0ee0 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -114,6 +114,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomem.o obj-$(CONFIG_ZONE_DEVICE) += memremap.o obj-$(CONFIG_RSEQ) += rseq.o +obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
And we have lib/test_sysctl.c of selftests.
I'm fine with this going in as-is to its current place, but if we have to learn from selftests I'd say we try to stick to a convention so folks know what framework a test is for, and to ensure folks can easily tell if its test code or not.
Perhaps simply a directory for kunit tests would suffice alone.
obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o KASAN_SANITIZE_stackleak.o := n KCOV_INSTRUMENT_stackleak.o := n diff --git a/kernel/sysctl-test.c b/kernel/sysctl-test.c new file mode 100644 index 0000000000000..cb61ad3c7db63 --- /dev/null +++ b/kernel/sysctl-test.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- KUnit test of proc sysctl.
- */
+#include <kunit/test.h> +#include <linux/sysctl.h>
+static int i_zero; +static int i_one_hundred = 100;
+struct test_sysctl_data {
- int int_0001;
- int int_0002;
- int int_0003[4];
- unsigned int uint_0001;
- char string_0001[65];
+};
+static struct test_sysctl_data test_data = {
- .int_0001 = 60,
- .int_0002 = 1,
- .int_0003[0] = 0,
- .int_0003[1] = 1,
- .int_0003[2] = 2,
- .int_0003[3] = 3,
- .uint_0001 = 314,
- .string_0001 = "(none)",
+};
+static void sysctl_test_dointvec_null_tbl_data(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = NULL,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
- size_t len;
- loff_t pos;
- len = 1234;
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
It is a bit odd, but it does happen, for a developer to be calling proc_dointvec() directly, instead typically folks just register a table and let it do its thing. That said, someone not too familiar with proc code would see this and really have no clue exactly what is being tested.
Even as a maintainer, I had to read the code for proc_dointvec() a bit to understand that the above is a *read* attempt to the .data field being allocated. Because its a write, the len set to a bogus does not matter as we are expecting the proc_dointvec() to update len for us.
If a test fails, it would be good to for anyone to easily grasp what is being tested. So... a few words documenting each test case would be nice.
- len = 1234;
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
And this is a write...
A nice tests given the data on the table allocated is not assigned.
I don't see any other areas in the kernel where we open code a proc_dointvec() call where the second argument is a digit, it always is with a variable. As such there would be no need for us to expose helpers to make it clear if one is a read or write. But for *this* case, I think it would be useful to add two wrappers inside this kunit test module which sprinkles the 0 or 1, this way a reader can easily know what mode is being tested.
kunit_proc_dointvec_read() kunit_proc_dointvec_write()
Or just use #define KUNIT_PROC_READ 0, #define KUNIT_PROC_WRITE 1. Whatever makes this code more legible.
+}
+static void sysctl_test_dointvec_table_maxlen_unset(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = 0,
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
- size_t len;
- loff_t pos;
- len = 1234;
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
- len = 1234;
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
In a way this is also testing for general kernel API changes. This is and the last one were good examples. So this is not just testing functionality here. There is no wrong or write answer if 0 or -EINVAL was returned other than the fact that we have been doing this for years.
Its a perhaps small but important difference for some of these tests. I *do* think its worth clarifying through documentation which ones are testing for API consistency Vs proper correctness.
+static void sysctl_test_dointvec_table_len_is_zero(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
- size_t len;
- loff_t pos;
- len = 0;
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
Likewise an API change test.
+static void sysctl_test_dointvec_table_read_but_position_set(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
- size_t len;
- loff_t pos;
- len = 1234;
- pos = 1;
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
- KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
Likewise an API test.
All the above kunit test cases are currently testing this call on __do_proc_dointvec():
if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) { *lenp = 0; return 0; }
Just an API test.
Perhaps use an api prefix or postfix for these to help distinguish which are api tests Vs correctness. We want someone who runs into a failure to *easily* determine *what* went wrong.
Right now this kunit test leaves no leashes around to help the reader.
+static void sysctl_test_dointvec_happy_single_positive(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- char input[] = "9";
- size_t len = sizeof(input) - 1;
- loff_t pos = 0;
- table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, input, &len, &pos));
- KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
- KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
- KUNIT_EXPECT_EQ(test, 9, ((int *)table.data)[0]);
+}
Yeap, running these kunit test cases will surely be faster than stupid shell :) nice!
+static void sysctl_test_dointvec_happy_single_negative(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- char input[] = "-9";
- size_t len = sizeof(input) - 1;
- loff_t pos = 0;
- table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
- KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, input, &len, &pos));
- KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
- KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
- KUNIT_EXPECT_EQ(test, -9, ((int *)table.data)[0]);
+}
+static void sysctl_test_dointvec_single_less_int_min(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- char input[32];
- size_t len = sizeof(input) - 1;
- loff_t pos = 0;
- unsigned long abs_of_less_than_min = (unsigned long)INT_MAX
- (INT_MAX + INT_MIN) + 1;
- KUNIT_EXPECT_LT(test,
(size_t)snprintf(input, sizeof(input), "-%lu",
abs_of_less_than_min),
sizeof(input));
- table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
- KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
- KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
- KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
+static void sysctl_test_dointvec_single_greater_int_max(struct kunit *test) +{
- struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
- };
- char input[32];
- size_t len = sizeof(input) - 1;
- loff_t pos = 0;
- unsigned long greater_than_max = (unsigned long)INT_MAX + 1;
- KUNIT_EXPECT_GT(test, greater_than_max, (unsigned long)INT_MAX);
- KUNIT_EXPECT_LT(test, (size_t)snprintf(input, sizeof(input), "%lu",
greater_than_max),
sizeof(input));
- table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
- KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
- KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
- KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
+static struct kunit_case sysctl_test_cases[] = {
- KUNIT_CASE(sysctl_test_dointvec_null_tbl_data),
- KUNIT_CASE(sysctl_test_dointvec_table_maxlen_unset),
- KUNIT_CASE(sysctl_test_dointvec_table_len_is_zero),
- KUNIT_CASE(sysctl_test_dointvec_table_read_but_position_set),
- KUNIT_CASE(sysctl_test_dointvec_happy_single_positive),
- KUNIT_CASE(sysctl_test_dointvec_happy_single_negative),
- KUNIT_CASE(sysctl_test_dointvec_single_less_int_min),
- KUNIT_CASE(sysctl_test_dointvec_single_greater_int_max),
- {}
+};
Oh all are API tests.. perhaps then just rename then sysctl_test_cases to sysctl_api_test_cases.
Would be good to add at least *two* other tests cases for this example, one which does a valid read and one which does a valid write.
If that is done either we add another kunit test module for correctness or just extend the above and use prefix / postfixes on the functions to distinguish between API / correctness somehow.
+static struct kunit_module sysctl_test_module = {
- .name = "sysctl_test",
- .test_cases = sysctl_test_cases,
+};
+module_test(sysctl_test_module); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index cbdfae3798965..389b8986f5b77 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1939,6 +1939,16 @@ config TEST_SYSCTL If unsure, say N. +config SYSCTL_KUNIT_TEST
- bool "KUnit test for sysctl"
- depends on KUNIT
- help
This builds the proc sysctl unit test, which runs on boot. For more
information on KUnit and unit tests in general please refer to the
KUnit documentation in Documentation/dev-tools/kunit/.
A little more description here would help. It is testing for API and hopefully also correctness (if extended with those two examples I mentioned).
If unsure, say N.
config TEST_UDELAY tristate "udelay test driver" help -- 2.22.0.410.gd8fdbe21b5-goog
Thanks for the work, it is very much appreciated and gives a clearer appreciation of value of kunit and what can be done and not. Another random test idea that comes up, would be to use different memory types for the table data. In case the kernel API users does something odd, we should be ensuring we do something proper.
Luis
On Tue, Jun 25, 2019 at 7:17 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Mon, Jun 17, 2019 at 01:26:12AM -0700, Brendan Higgins wrote:
From: Iurii Zaikin yzaikin@google.com
KUnit tests for initialized data behavior of proc_dointvec that is explicitly checked in the code. Includes basic parsing tests including int min/max overflow.
First, thanks for this work! My review below.
Signed-off-by: Iurii Zaikin yzaikin@google.com Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com
Changes Since Last Revision:
- Iurii did some clean up (thanks Iurii!) as suggested by Stephen Boyd.
kernel/Makefile | 2 + kernel/sysctl-test.c | 242 +++++++++++++++++++++++++++++++++++++++++++ lib/Kconfig.debug | 10 ++ 3 files changed, 254 insertions(+) create mode 100644 kernel/sysctl-test.c
diff --git a/kernel/Makefile b/kernel/Makefile index a8d923b5481ba..50fd511cd0ee0 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -114,6 +114,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomem.o obj-$(CONFIG_ZONE_DEVICE) += memremap.o obj-$(CONFIG_RSEQ) += rseq.o
+obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
And we have lib/test_sysctl.c of selftests.
I'm fine with this going in as-is to its current place, but if we have to learn from selftests I'd say we try to stick to a convention so folks know what framework a test is for, and to ensure folks can easily tell if its test code or not.
Perhaps simply a directory for kunit tests would suffice alone.
obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o KASAN_SANITIZE_stackleak.o := n KCOV_INSTRUMENT_stackleak.o := n diff --git a/kernel/sysctl-test.c b/kernel/sysctl-test.c new file mode 100644 index 0000000000000..cb61ad3c7db63 --- /dev/null +++ b/kernel/sysctl-test.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- KUnit test of proc sysctl.
- */
+#include <kunit/test.h> +#include <linux/sysctl.h>
+static int i_zero; +static int i_one_hundred = 100;
+struct test_sysctl_data {
int int_0001;
int int_0002;
int int_0003[4];
unsigned int uint_0001;
char string_0001[65];
+};
+static struct test_sysctl_data test_data = {
.int_0001 = 60,
.int_0002 = 1,
.int_0003[0] = 0,
.int_0003[1] = 1,
.int_0003[2] = 2,
.int_0003[3] = 3,
.uint_0001 = 314,
.string_0001 = "(none)",
+};
+static void sysctl_test_dointvec_null_tbl_data(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = NULL,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
It is a bit odd, but it does happen, for a developer to be calling proc_dointvec() directly, instead typically folks just register a table and let it do its thing. That said, someone not too familiar with proc code would see this and really have no clue exactly what is being tested.
Even as a maintainer, I had to read the code for proc_dointvec() a bit to understand that the above is a *read* attempt to the .data field being allocated. Because its a write, the len set to a bogus does not matter as we are expecting the proc_dointvec() to update len for us.
If a test fails, it would be good to for anyone to easily grasp what is being tested. So... a few words documenting each test case would be nice.
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
And this is a write...
A nice tests given the data on the table allocated is not assigned.
I don't see any other areas in the kernel where we open code a proc_dointvec() call where the second argument is a digit, it always is with a variable. As such there would be no need for us to expose helpers to make it clear if one is a read or write. But for *this* case, I think it would be useful to add two wrappers inside this kunit test module which sprinkles the 0 or 1, this way a reader can easily know what mode is being tested.
kunit_proc_dointvec_read() kunit_proc_dointvec_write()
Or just use #define KUNIT_PROC_READ 0, #define KUNIT_PROC_WRITE 1. Whatever makes this code more legible.
Went with the #define * suggestion above to keep it clear what function is being tested.
+}
+static void sysctl_test_dointvec_table_maxlen_unset(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = 0,
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
In a way this is also testing for general kernel API changes. This is and the last one were good examples. So this is not just testing functionality here. There is no wrong or write answer if 0 or -EINVAL was returned other than the fact that we have been doing this for years.
Its a perhaps small but important difference for some of these tests. I *do* think its worth clarifying through documentation which ones are testing for API consistency Vs proper correctness.
You make a good point that the test codifies the existing behavior of the function in lieu of formal documentation. However, the test cases were derived from examining the source code of the function under test and attempting to cover all branches. The assertions were added only for the values that appeared to be set deliberately in the implementation. And it makes sense to me to test that the code does exactly what the implementation author intended.
+static void sysctl_test_dointvec_table_len_is_zero(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 0;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
Likewise an API change test.
Same as the above, if the implementation author meant the function to behave deterministically with the given input, it makes sense to test the behavior. Otherwise, why not just remove the branch in the function under test and say that the given input results in undefined behavior?
+static void sysctl_test_dointvec_table_read_but_position_set(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 1234;
pos = 1;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
Likewise an API test.
All the above kunit test cases are currently testing this call on __do_proc_dointvec():
if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) { *lenp = 0; return 0; }
Just an API test.
Perhaps use an api prefix or postfix for these to help distinguish which are api tests Vs correctness. We want someone who runs into a failure to *easily* determine *what* went wrong.
Right now this kunit test leaves no leashes around to help the reader.
+static void sysctl_test_dointvec_happy_single_positive(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[] = "9";
size_t len = sizeof(input) - 1;
loff_t pos = 0;
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
KUNIT_EXPECT_EQ(test, 9, ((int *)table.data)[0]);
+}
Yeap, running these kunit test cases will surely be faster than stupid shell :) nice!
+static void sysctl_test_dointvec_happy_single_negative(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[] = "-9";
size_t len = sizeof(input) - 1;
loff_t pos = 0;
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
KUNIT_EXPECT_EQ(test, -9, ((int *)table.data)[0]);
+}
+static void sysctl_test_dointvec_single_less_int_min(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[32];
size_t len = sizeof(input) - 1;
loff_t pos = 0;
unsigned long abs_of_less_than_min = (unsigned long)INT_MAX
- (INT_MAX + INT_MIN) + 1;
KUNIT_EXPECT_LT(test,
(size_t)snprintf(input, sizeof(input), "-%lu",
abs_of_less_than_min),
sizeof(input));
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
Not sure why. I believe there has been a real bug with int overflow in proc_dointvec. Covering it with test seems like a good idea.
+static void sysctl_test_dointvec_single_greater_int_max(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[32];
size_t len = sizeof(input) - 1;
loff_t pos = 0;
unsigned long greater_than_max = (unsigned long)INT_MAX + 1;
KUNIT_EXPECT_GT(test, greater_than_max, (unsigned long)INT_MAX);
KUNIT_EXPECT_LT(test, (size_t)snprintf(input, sizeof(input), "%lu",
greater_than_max),
sizeof(input));
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
+static struct kunit_case sysctl_test_cases[] = {
KUNIT_CASE(sysctl_test_dointvec_null_tbl_data),
KUNIT_CASE(sysctl_test_dointvec_table_maxlen_unset),
KUNIT_CASE(sysctl_test_dointvec_table_len_is_zero),
KUNIT_CASE(sysctl_test_dointvec_table_read_but_position_set),
KUNIT_CASE(sysctl_test_dointvec_happy_single_positive),
KUNIT_CASE(sysctl_test_dointvec_happy_single_negative),
KUNIT_CASE(sysctl_test_dointvec_single_less_int_min),
KUNIT_CASE(sysctl_test_dointvec_single_greater_int_max),
{}
+};
Oh all are API tests.. perhaps then just rename then sysctl_test_cases to sysctl_api_test_cases.
Would be good to add at least *two* other tests cases for this example, one which does a valid read and one which does a valid write.
Added valid reads. There already are 2 valid writes.
If that is done either we add another kunit test module for correctness or just extend the above and use prefix / postfixes on the functions to distinguish between API / correctness somehow.
+static struct kunit_module sysctl_test_module = {
.name = "sysctl_test",
.test_cases = sysctl_test_cases,
+};
+module_test(sysctl_test_module); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index cbdfae3798965..389b8986f5b77 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1939,6 +1939,16 @@ config TEST_SYSCTL
If unsure, say N.
+config SYSCTL_KUNIT_TEST
bool "KUnit test for sysctl"
depends on KUNIT
help
This builds the proc sysctl unit test, which runs on boot. For more
information on KUnit and unit tests in general please refer to the
KUnit documentation in Documentation/dev-tools/kunit/.
A little more description here would help. It is testing for API and hopefully also correctness (if extended with those two examples I mentioned).
Added "Tests the API contract and implementation correctness of sysctl."
If unsure, say N.
config TEST_UDELAY tristate "udelay test driver" help -- 2.22.0.410.gd8fdbe21b5-goog
Thanks for the work, it is very much appreciated and gives a clearer appreciation of value of kunit and what can be done and not. Another random test idea that comes up, would be to use different memory types for the table data. In case the kernel API users does something odd, we should be ensuring we do something proper.
Luis
On Wed, Jun 26, 2019 at 09:07:43PM -0700, Iurii Zaikin wrote:
On Tue, Jun 25, 2019 at 7:17 PM Luis Chamberlain mcgrof@kernel.org wrote:
+static void sysctl_test_dointvec_table_maxlen_unset(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = 0,
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
In a way this is also testing for general kernel API changes. This is and the last one were good examples. So this is not just testing functionality here. There is no wrong or write answer if 0 or -EINVAL was returned other than the fact that we have been doing this for years.
Its a perhaps small but important difference for some of these tests. I *do* think its worth clarifying through documentation which ones are testing for API consistency Vs proper correctness.
You make a good point that the test codifies the existing behavior of the function in lieu of formal documentation. However, the test cases were derived from examining the source code of the function under test and attempting to cover all branches. The assertions were added only for the values that appeared to be set deliberately in the implementation. And it makes sense to me to test that the code does exactly what the implementation author intended.
I'm not arguing against adding them. I'm suggesting that it is different to test for API than for correctness of intended functionality, and it would be wise to make it clear which test cases are for API and which for correctness.
This will come up later for other kunit tests and it would be great to set precendent so that other kunit tests can follow similar practices to ensure its clear what is API realted Vs correctness of intended functionality.
In fact, I'm not yet sure if its possible to test public kernel API to userspace with kunit, but if it is possible... well, that could make linux-api folks happy as they could enable us to codify interpreation of what is expected into kunit test cases, and we'd ensure that the codified interpretation is not only documented in man pages but also through formal kunit test cases.
A regression in linux-api then could be formalized through a proper kunit tests case. And if an API evolves, it would force developers to update the respective kunit which codifies that contract.
+static void sysctl_test_dointvec_single_less_int_min(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[32];
size_t len = sizeof(input) - 1;
loff_t pos = 0;
unsigned long abs_of_less_than_min = (unsigned long)INT_MAX
- (INT_MAX + INT_MIN) + 1;
KUNIT_EXPECT_LT(test,
(size_t)snprintf(input, sizeof(input), "-%lu",
abs_of_less_than_min),
sizeof(input));
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
Not sure why.
Because you are codifying that we *definitely* return -EINVAL on overlow. Some parts of the kernel return -ERANGE for overflows for instance.
It would be a generic test for overflow if it would just test for any error.
It is a fine and good test to keep. All these tests are good to keep.
I believe there has been a real bug with int overflow in proc_dointvec. Covering it with test seems like a good idea.
Oh definitely.
+static void sysctl_test_dointvec_single_greater_int_max(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[32];
size_t len = sizeof(input) - 1;
loff_t pos = 0;
unsigned long greater_than_max = (unsigned long)INT_MAX + 1;
KUNIT_EXPECT_GT(test, greater_than_max, (unsigned long)INT_MAX);
KUNIT_EXPECT_LT(test, (size_t)snprintf(input, sizeof(input), "%lu",
greater_than_max),
sizeof(input));
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
+static struct kunit_case sysctl_test_cases[] = {
KUNIT_CASE(sysctl_test_dointvec_null_tbl_data),
KUNIT_CASE(sysctl_test_dointvec_table_maxlen_unset),
KUNIT_CASE(sysctl_test_dointvec_table_len_is_zero),
KUNIT_CASE(sysctl_test_dointvec_table_read_but_position_set),
KUNIT_CASE(sysctl_test_dointvec_happy_single_positive),
KUNIT_CASE(sysctl_test_dointvec_happy_single_negative),
KUNIT_CASE(sysctl_test_dointvec_single_less_int_min),
KUNIT_CASE(sysctl_test_dointvec_single_greater_int_max),
{}
+};
Oh all are API tests.. perhaps then just rename then sysctl_test_cases to sysctl_api_test_cases.
Would be good to add at least *two* other tests cases for this example, one which does a valid read and one which does a valid write.
Added valid reads. There already are 2 valid writes.
Thanks.
If that is done either we add another kunit test module for correctness or just extend the above and use prefix / postfixes on the functions to distinguish between API / correctness somehow.
+static struct kunit_module sysctl_test_module = {
.name = "sysctl_test",
.test_cases = sysctl_test_cases,
+};
+module_test(sysctl_test_module); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index cbdfae3798965..389b8986f5b77 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1939,6 +1939,16 @@ config TEST_SYSCTL
If unsure, say N.
+config SYSCTL_KUNIT_TEST
bool "KUnit test for sysctl"
depends on KUNIT
help
This builds the proc sysctl unit test, which runs on boot. For more
information on KUnit and unit tests in general please refer to the
KUnit documentation in Documentation/dev-tools/kunit/.
A little more description here would help. It is testing for API and hopefully also correctness (if extended with those two examples I mentioned).
Added "Tests the API contract and implementation correctness of sysctl."
Yes, much clearer, thanks!
Luis
On Wed, Jun 26, 2019 at 11:10 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Wed, Jun 26, 2019 at 09:07:43PM -0700, Iurii Zaikin wrote:
On Tue, Jun 25, 2019 at 7:17 PM Luis Chamberlain mcgrof@kernel.org wrote:
+static void sysctl_test_dointvec_table_maxlen_unset(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = 0,
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
In a way this is also testing for general kernel API changes. This is and the last one were good examples. So this is not just testing functionality here. There is no wrong or write answer if 0 or -EINVAL was returned other than the fact that we have been doing this for years.
Its a perhaps small but important difference for some of these tests. I *do* think its worth clarifying through documentation which ones are testing for API consistency Vs proper correctness.
You make a good point that the test codifies the existing behavior of the function in lieu of formal documentation. However, the test cases were derived from examining the source code of the function under test and attempting to cover all branches. The assertions were added only for the values that appeared to be set deliberately in the implementation. And it makes sense to me to test that the code does exactly what the implementation author intended.
I'm not arguing against adding them. I'm suggesting that it is different to test for API than for correctness of intended functionality, and it would be wise to make it clear which test cases are for API and which for correctness.
I see later on that some of the API stuff you are talking about is public APIs from the standpoint of user (outside of LInux) visible. To be clear, is that what you mean by public APIs throughout, or would you distinguish between correctness tests, internal API tests, and external API tests?
This will come up later for other kunit tests and it would be great to set precendent so that other kunit tests can follow similar practices to ensure its clear what is API realted Vs correctness of intended functionality.
In fact, I'm not yet sure if its possible to test public kernel API to userspace with kunit, but if it is possible... well, that could make linux-api folks happy as they could enable us to codify interpreation of what is expected into kunit test cases, and we'd ensure that the codified interpretation is not only documented in man pages but also through formal kunit test cases.
A regression in linux-api then could be formalized through a proper kunit tests case. And if an API evolves, it would force developers to update the respective kunit which codifies that contract.
Yep, I think that is long term hope. Some of the file system interface stuff that requires a filesystem to be mounted somewhere might get a little weird/difficult, but I suspect we should be able to do it eventually. I mean it's all just C code right? Should mostly boil down to someone figuring out how to do it the first time.
+static void sysctl_test_dointvec_single_less_int_min(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[32];
size_t len = sizeof(input) - 1;
loff_t pos = 0;
unsigned long abs_of_less_than_min = (unsigned long)INT_MAX
- (INT_MAX + INT_MIN) + 1;
KUNIT_EXPECT_LT(test,
(size_t)snprintf(input, sizeof(input), "-%lu",
abs_of_less_than_min),
sizeof(input));
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
Not sure why.
Because you are codifying that we *definitely* return -EINVAL on overlow. Some parts of the kernel return -ERANGE for overflows for instance.
It would be a generic test for overflow if it would just test for any error.
It is a fine and good test to keep. All these tests are good to keep.
I believe there has been a real bug with int overflow in proc_dointvec. Covering it with test seems like a good idea.
Oh definitely.
+static void sysctl_test_dointvec_single_greater_int_max(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
char input[32];
size_t len = sizeof(input) - 1;
loff_t pos = 0;
unsigned long greater_than_max = (unsigned long)INT_MAX + 1;
KUNIT_EXPECT_GT(test, greater_than_max, (unsigned long)INT_MAX);
KUNIT_EXPECT_LT(test, (size_t)snprintf(input, sizeof(input), "%lu",
greater_than_max),
sizeof(input));
table.data = kunit_kzalloc(test, sizeof(int), GFP_USER);
KUNIT_EXPECT_EQ(test, -EINVAL,
proc_dointvec(&table, 1, input, &len, &pos));
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
KUNIT_EXPECT_EQ(test, 0, ((int *)table.data)[0]);
+}
API test.
+static struct kunit_case sysctl_test_cases[] = {
KUNIT_CASE(sysctl_test_dointvec_null_tbl_data),
KUNIT_CASE(sysctl_test_dointvec_table_maxlen_unset),
KUNIT_CASE(sysctl_test_dointvec_table_len_is_zero),
KUNIT_CASE(sysctl_test_dointvec_table_read_but_position_set),
KUNIT_CASE(sysctl_test_dointvec_happy_single_positive),
KUNIT_CASE(sysctl_test_dointvec_happy_single_negative),
KUNIT_CASE(sysctl_test_dointvec_single_less_int_min),
KUNIT_CASE(sysctl_test_dointvec_single_greater_int_max),
{}
+};
Oh all are API tests.. perhaps then just rename then sysctl_test_cases to sysctl_api_test_cases.
Would be good to add at least *two* other tests cases for this example, one which does a valid read and one which does a valid write.
Added valid reads. There already are 2 valid writes.
Thanks.
If that is done either we add another kunit test module for correctness or just extend the above and use prefix / postfixes on the functions to distinguish between API / correctness somehow.
+static struct kunit_module sysctl_test_module = {
.name = "sysctl_test",
.test_cases = sysctl_test_cases,
+};
+module_test(sysctl_test_module); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index cbdfae3798965..389b8986f5b77 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1939,6 +1939,16 @@ config TEST_SYSCTL
If unsure, say N.
+config SYSCTL_KUNIT_TEST
bool "KUnit test for sysctl"
depends on KUNIT
help
This builds the proc sysctl unit test, which runs on boot. For more
information on KUnit and unit tests in general please refer to the
KUnit documentation in Documentation/dev-tools/kunit/.
A little more description here would help. It is testing for API and hopefully also correctness (if extended with those two examples I mentioned).
Added "Tests the API contract and implementation correctness of sysctl."
Yes, much clearer, thanks!
Cheers!
On Fri, Jun 28, 2019 at 01:01:54AM -0700, Brendan Higgins wrote:
On Wed, Jun 26, 2019 at 11:10 PM Luis Chamberlain mcgrof@kernel.org wrote:
On Wed, Jun 26, 2019 at 09:07:43PM -0700, Iurii Zaikin wrote:
On Tue, Jun 25, 2019 at 7:17 PM Luis Chamberlain mcgrof@kernel.org wrote:
+static void sysctl_test_dointvec_table_maxlen_unset(struct kunit *test) +{
struct ctl_table table = {
.procname = "foo",
.data = &test_data.int_0001,
.maxlen = 0,
.mode = 0644,
.proc_handler = proc_dointvec,
.extra1 = &i_zero,
.extra2 = &i_one_hundred,
};
void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
size_t len;
loff_t pos;
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 0, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
len = 1234;
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, 1, buffer, &len, &pos));
KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
In a way this is also testing for general kernel API changes. This is and the last one were good examples. So this is not just testing functionality here. There is no wrong or write answer if 0 or -EINVAL was returned other than the fact that we have been doing this for years.
Its a perhaps small but important difference for some of these tests. I *do* think its worth clarifying through documentation which ones are testing for API consistency Vs proper correctness.
You make a good point that the test codifies the existing behavior of the function in lieu of formal documentation. However, the test cases were derived from examining the source code of the function under test and attempting to cover all branches. The assertions were added only for the values that appeared to be set deliberately in the implementation. And it makes sense to me to test that the code does exactly what the implementation author intended.
I'm not arguing against adding them. I'm suggesting that it is different to test for API than for correctness of intended functionality, and it would be wise to make it clear which test cases are for API and which for correctness.
I see later on that some of the API stuff you are talking about is public APIs from the standpoint of user (outside of LInux) visible.
Right, UAPI.
To be clear, is that what you mean by public APIs throughout, or would you distinguish between correctness tests, internal API tests, and external API tests?
I would definitely recommend distingishing between all of these. Kernel API (or just call it API), UAPI, and correctness.
This will come up later for other kunit tests and it would be great to set precendent so that other kunit tests can follow similar practices to ensure its clear what is API realted Vs correctness of intended functionality.
In fact, I'm not yet sure if its possible to test public kernel API to userspace with kunit, but if it is possible... well, that could make linux-api folks happy as they could enable us to codify interpreation of what is expected into kunit test cases, and we'd ensure that the codified interpretation is not only documented in man pages but also through formal kunit test cases.
A regression in linux-api then could be formalized through a proper kunit tests case. And if an API evolves, it would force developers to update the respective kunit which codifies that contract.
Yep, I think that is long term hope. Some of the file system interface stuff that requires a filesystem to be mounted somewhere might get a little weird/difficult, but I suspect we should be able to do it eventually. I mean it's all just C code right? Should mostly boil down to someone figuring out how to do it the first time.
There used to be hacks in the kernel the call syscalls in a few places. This was cleaned up and addressed via Dominik Brodowski's series last year in March:
http://lkml.kernel.org/r/20180325162527.GA17492@light.dominikbrodowski.net
An example commit: d300b610812f3 ("kernel: use kernel_wait4() instead of sys_wait4()").
So it would seem the work is done, and you'd just have to use the respective exposed kernel_syscallname() calls, or add some if you want to test a specific syscall in kernel space.
Luis
Add entry for the new proc sysctl KUnit test to the PROC SYSCTL section.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS index f3fb3fc30853e..05cd8ffd33c8f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12718,6 +12718,7 @@ S: Maintained F: fs/proc/proc_sysctl.c F: include/linux/sysctl.h F: kernel/sysctl.c +F: kernel/sysctl-test.c F: tools/testing/selftests/sysctl/
PS3 NETWORK SUPPORT
On Mon, Jun 17, 2019 at 01:26:13AM -0700, Brendan Higgins wrote:
Add entry for the new proc sysctl KUnit test to the PROC SYSCTL section.
Signed-off-by: Brendan Higgins brendanhiggins@google.com Reviewed-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Reviewed-by: Logan Gunthorpe logang@deltatee.com
Acked-by: Luis Chamberlain mcgrof@kernel.org
Luis
Hi Brendan,
I am only responding to this because you asked me to in the v4 thread.
Thank you for evaluating my comments in the v4 thread and asking me to comment on v5
On 6/17/19 1:25 AM, Brendan Higgins wrote:
## TL;DR
A not so quick follow-up to Stephen's suggestions on PATCH v4. Nothing that really changes any functionality or usage with the minor exception of a couple public functions that Stephen asked me to rename. Nevertheless, a good deal of clean up and fixes. See changes below.
As for our current status, right now we got Reviewed-bys on all patches except:
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
However, it would probably be good to get reviews/acks from the subsystem maintainers on:
- [PATCH v5 06/18] kbuild: enable building KUnit
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
- [PATCH v5 15/18] Documentation: kunit: add documentation for KUnit
- [PATCH v5 17/18] kernel/sysctl-test: Add null pointer test for sysctl.c:proc_dointvec()
- [PATCH v5 18/18] MAINTAINERS: add proc sysctl KUnit test to PROC SYSCTL section
Other than that, I think we should be good to go.
One last thing, I updated the background to include my thoughts on KUnit vs. in kernel testing with kselftest in the background sections as suggested by Frank in the discussion on PATCH v2.
## Background
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 (however, KUnit still allows you to run tests on test machines or in VMs if you want[1]) 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.
I looked only at this section, as was specifically requested:
### But wait! Doesn't kselftest support in kernel testing?!
In a previous version of this patchset Frank pointed out that kselftest already supports writing a test that resides in the kernel using the test module feature[2]. LWN did a really great summary on this discussion here[3].
Kselftest has a feature that allows a test module to be loaded into a kernel using the kselftest framework; this does allow someone to write tests against kernel code not directly exposed to userland; however, it does not provide much of a framework around how to structure the tests. The kselftest test module feature just provides a header which has a standardized way of reporting test failures,
and then provides infrastructure to load and run the tests using the kselftest test harness.
The in-kernel tests can also be invoked at boot time if they are configured (Kconfig) as in-kernel instead of as modules. I did not check how many of the tests have tri-state configuration to allow this, but the few that I looked at did.
The kselftest test module does not seem to be opinionated at all in regards to how tests are structured, how they check for failures, how tests are organized. Even in the method it provides for reporting failures is pretty simple; it doesn't have any more advanced failure reporting or logging features. Given what's there, I think it is fair to say that it is not actually a framework, but a feature that makes it possible for someone to do some checks in kernel space.
I would call that description a little dismissive. The set of in-kernel tests that I looked like followed a common pattern and reported results in a uniform manner.
Furthermore, kselftest test module has very few users. I checked for all the tests that use it using the following grep command:
grep -Hrn -e 'kselftest_module.h'
and only got three results: lib/test_strscpy.c, lib/test_printf.c, and lib/test_bitmap.c.
You missed many tests. I listed much more than that in the v4 thread, and someone else also listed more in the v4 thread.
So despite kselftest test module's existence, there really is no feature overlap between kselftest and KUnit, save one: that you can use either to write an in-kernel test, but this is a very small feature in comparison to everything that KUnit allows you to do. KUnit is a full x-unit style unit testing framework, whereas kselftest looks a lot more like an end-to-end/functional testing framework, with a feature that makes it possible to write in-kernel tests.
The description does not give enough credit to what is in kselftest.
It does not matter whether KUnit provides additional things, relative to kselftest. The point I was making is that there appears to be _some_ overlap between kselftest and KUnit, and if there is overlap then it is worth considering whether the overlap can be unified instead of duplicated.
I don't have a dog in this fight and the discussion in the v4 thread went way off track. Thus I am not going to get sucked back into a pointless debate in this thread.
Thanks for adding this section to address the issue.
-Frank
### 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[4].
Additionally for convenience, I have applied these patches to a branch[5]. The repo may be cloned with: git clone https://kunit.googlesource.com/linux This patchset is on the kunit/rfc/v5.2-rc4/v5 branch.
## Changes Since Last Version
Aside from a couple public function renames, there isn't really anything in here that changes any functionality.
- Went through and fixed a couple of anti-patterns suggested by Stephen Boyd. Things like:
- Dropping an else clause at the end of a function.
- Dropping the comma on the closing sentinel, `{}`, of a list.
- Inlines a bunch of functions in the test case running logic in patch 01/18 to make it more readable as suggested by Stephen Boyd
- Found and fixed bug in resource deallocation logic in patch 02/18. Bug was discovered as a result of making a change suggested by Stephen Boyd. This does not substantially change how any of the code works conceptually.
- Renamed new_string_stream() to alloc_string_stream() as suggested by Stephen Boyd.
- Made string-stream a KUnit managed object - based on a suggestion made by Stephen Boyd.
- Renamed kunit_new_stream() to alloc_kunit_stream() as suggested by Stephen Boyd.
- Removed the ability to set log level after allocating a kunit_stream, as suggested by Stephen Boyd.
[1] https://google.github.io/kunit-docs/third_party/kernel/docs/usage.html#kunit... [2] https://www.kernel.org/doc/html/latest/dev-tools/kselftest.html#test-module [3] https://lwn.net/Articles/790235/ [4] https://google.github.io/kunit-docs/third_party/kernel/docs/ [5] https://kunit.googlesource.com/linux/+/kunit/rfc/v5.2-rc4/v5
Hi Brendan,
On 6/19/19 7:17 PM, Frank Rowand wrote:
Hi Brendan,
I am only responding to this because you asked me to in the v4 thread.
Thank you for evaluating my comments in the v4 thread and asking me to comment on v5
On 6/17/19 1:25 AM, Brendan Higgins wrote:
## TL;DR
A not so quick follow-up to Stephen's suggestions on PATCH v4. Nothing that really changes any functionality or usage with the minor exception of a couple public functions that Stephen asked me to rename. Nevertheless, a good deal of clean up and fixes. See changes below.
As for our current status, right now we got Reviewed-bys on all patches except:
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
However, it would probably be good to get reviews/acks from the subsystem maintainers on:
- [PATCH v5 06/18] kbuild: enable building KUnit
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
- [PATCH v5 15/18] Documentation: kunit: add documentation for KUnit
- [PATCH v5 17/18] kernel/sysctl-test: Add null pointer test for sysctl.c:proc_dointvec()
- [PATCH v5 18/18] MAINTAINERS: add proc sysctl KUnit test to PROC SYSCTL section
Other than that, I think we should be good to go.
One last thing, I updated the background to include my thoughts on KUnit vs. in kernel testing with kselftest in the background sections as suggested by Frank in the discussion on PATCH v2.
## Background
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 (however, KUnit still allows you to run tests on test machines or in VMs if you want[1]) 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.
I looked only at this section, as was specifically requested:
### But wait! Doesn't kselftest support in kernel testing?!
In a previous version of this patchset Frank pointed out that kselftest already supports writing a test that resides in the kernel using the test module feature[2]. LWN did a really great summary on this discussion here[3].
Kselftest has a feature that allows a test module to be loaded into a kernel using the kselftest framework; this does allow someone to write tests against kernel code not directly exposed to userland; however, it does not provide much of a framework around how to structure the tests. The kselftest test module feature just provides a header which has a standardized way of reporting test failures,
and then provides infrastructure to load and run the tests using the kselftest test harness.
The in-kernel tests can also be invoked at boot time if they are configured (Kconfig) as in-kernel instead of as modules. I did not check how many of the tests have tri-state configuration to allow this, but the few that I looked at did.
The kselftest test module does not seem to be opinionated at all in regards to how tests are structured, how they check for failures, how tests are organized. Even in the method it provides for reporting failures is pretty simple; it doesn't have any more advanced failure reporting or logging features. Given what's there, I think it is fair to say that it is not actually a framework, but a feature that makes it possible for someone to do some checks in kernel space.
I would call that description a little dismissive. The set of in-kernel tests that I looked like followed a common pattern and reported results in a uniform manner.
I think I commented on this before. I agree with the statement that there is no overlap between Kselftest and KUnit. I would like see this removed. Kselftest module support supports use-cases KUnit won't be able to. I can build an kernel with Kselftest test modules and use it in the filed to load and run tests if I need to debug a problem and get data from a system. I can't do that with KUnit.
In my mind, I am not viewing this as which is better. Kselftest and KUnit both have their place in the kernel development process. It isn't productive and/or necessary to comparing Kselftest and KUnit without a good understanding of the problem spaces for each of these.
I would strongly recommend not making reference to Kselftest and talk about what KUnit offers.
Furthermore, kselftest test module has very few users. I checked for all the tests that use it using the following grep command:
grep -Hrn -e 'kselftest_module.h'
and only got three results: lib/test_strscpy.c, lib/test_printf.c, and lib/test_bitmap.c.
Again, unnecessary. KUnit can't replace Kselftest module in the way Kselftest module support can be used for debugging and gathering information on system that might be in active use and not dedicated to test and development alone. I wouldn't hesitate loading a Kselftest test module on my laptop and running tests, but I wouldn't use KUnit the same way.
Again, this is not a competition between which is better. Kselftest and KUnit serve different needs and problem spaces.
Please redo this documentation to reflect that.
thanks, -- Shuah
On Fri, Jun 21, 2019 at 08:59:48AM -0600, shuah wrote:
### But wait! Doesn't kselftest support in kernel testing?!
....
I think I commented on this before. I agree with the statement that there is no overlap between Kselftest and KUnit. I would like see this removed. Kselftest module support supports use-cases KUnit won't be able to. I can build an kernel with Kselftest test modules and use it in the filed to load and run tests if I need to debug a problem and get data from a system. I can't do that with KUnit.
In my mind, I am not viewing this as which is better. Kselftest and KUnit both have their place in the kernel development process. It isn't productive and/or necessary to comparing Kselftest and KUnit without a good understanding of the problem spaces for each of these.
I would strongly recommend not making reference to Kselftest and talk about what KUnit offers.
Shuah,
Just to recall the history, this section of the FAQ was added to rebut the ***very*** strong statements that Frank made that there was overlap between Kselftest and Kunit, and that having too many ways for kernel developers to do the identical thing was harmful (he said it was too much of a burden on a kernel developer) --- and this was an argument for not including Kunit in the upstream kernel.
If we're past that objection, then perhaps this section can be dropped, but there's a very good reason why it was there. I wouldn't Brendan to be accused of ignoring feedback from those who reviewed his patches. :-)
- Ted
On 6/21/19 12:13 PM, Theodore Ts'o wrote:
On Fri, Jun 21, 2019 at 08:59:48AM -0600, shuah wrote:
### But wait! Doesn't kselftest support in kernel testing?!
....
I think I commented on this before. I agree with the statement that there is no overlap between Kselftest and KUnit. I would like see this removed. Kselftest module support supports use-cases KUnit won't be able to. I can build an kernel with Kselftest test modules and use it in the filed to load and run tests if I need to debug a problem and get data from a system. I can't do that with KUnit.
In my mind, I am not viewing this as which is better. Kselftest and KUnit both have their place in the kernel development process. It isn't productive and/or necessary to comparing Kselftest and KUnit without a good understanding of the problem spaces for each of these.
I would strongly recommend not making reference to Kselftest and talk about what KUnit offers.
Shuah,
Just to recall the history, this section of the FAQ was added to rebut the ***very*** strong statements that Frank made that there was overlap between Kselftest and Kunit, and that having too many ways for kernel developers to do the identical thing was harmful (he said it was too much of a burden on a kernel developer) --- and this was an argument for not including Kunit in the upstream kernel.
If we're past that objection, then perhaps this section can be dropped, but there's a very good reason why it was there. I wouldn't Brendan to be accused of ignoring feedback from those who reviewed his patches. :-)
Agreed. I understand that this FAQ probably was needed at one time and Brendan added it to address the concerns.
I think at some point we do need to have a document that outlines when to KUnit and when to use Kselftest modules. I think one concern people have is that if KUnit is perceived as a replacement for Ksefltest module, Kselftest module will be ignored leaving users without the ability to build and run with Kselftest modules and load them on a need basis to gather data on a systems that aren't dedicated strictly for testing.
I am trying to move the conversation forward from KUnit vs. Kselftest modules discussion to which problem areas each one addresses keeping in mind that it is not about which is better. Kselftest and KUnit both have their place in the kernel development process. We just have to be clear on usage as we write tests for each.
thanks, -- Shuah
On Fri, Jun 21, 2019 at 12:21 PM shuah shuah@kernel.org wrote:
On 6/21/19 12:13 PM, Theodore Ts'o wrote:
On Fri, Jun 21, 2019 at 08:59:48AM -0600, shuah wrote:
### But wait! Doesn't kselftest support in kernel testing?!
....
I think I commented on this before. I agree with the statement that there is no overlap between Kselftest and KUnit. I would like see this removed. Kselftest module support supports use-cases KUnit won't be able to. I can build an kernel with Kselftest test modules and use it in the filed to load and run tests if I need to debug a problem and get data from a system. I can't do that with KUnit.
Sure, I think this point has been brought up a number of times before. Maybe I didn't write this section well because, like Frank said, it comes across as being critical of the Kselftest module support; that wasn't my intention. I was speaking from the perspective that Kselftest module support is just a feature of Kselftest, and not a full framework like KUnit is (obviously Kselftest itself *is* a framework, but a very small part of it is not).
It was obvious to me what Kselftest module support was intended for, and it is not intended to cover the use case that KUnit is targeting.
In my mind, I am not viewing this as which is better. Kselftest and KUnit both have their place in the kernel development process. It isn't productive and/or necessary to comparing Kselftest and KUnit without a good understanding of the problem spaces for each of these.
Again, I didn't mean to draw a comparison of which is better than the other. I was just trying to point out that Kselftest module support doesn't make sense as a stand alone unit testing framework, or really a framework of any kind, despite how it might actually be used.
I would strongly recommend not making reference to Kselftest and talk about what KUnit offers.
I can see your point. It seems that both you and Frank seem to think that I drew a comparison between Kselftest and KUnit, which was unintended. I probably should have spent more time editing this section, but I can see the point of drawing the comparison itself might invite this confusion.
Shuah,
Just to recall the history, this section of the FAQ was added to rebut the ***very*** strong statements that Frank made that there was overlap between Kselftest and Kunit, and that having too many ways for kernel developers to do the identical thing was harmful (he said it was too much of a burden on a kernel developer) --- and this was an argument for not including Kunit in the upstream kernel.
I don't think he was actually advocating that we don't include KUnit, maybe playing devil's advocate; nevertheless, at the end, Frank seemed to agree that there were valuable things that KUnit offered. I thought he just wanted to make the point that I hadn't made the distinction sufficiently clear in the cover letter, and other reviewers might get confused in the future as well.
Additionally, it does look like people were trying to use Kselftest module support to cover some things which really were trying to be unit tests. I know this isn't really intended - everything looks like a nail when you only have a hammer, which I think Frank was pointing out furthers the above confusion.
In anycase, it sounds like I have, if anything, only made the discussion even more confusing by adding this section; sorry about that.
If we're past that objection, then perhaps this section can be dropped, but there's a very good reason why it was there. I wouldn't Brendan to be accused of ignoring feedback from those who reviewed his patches. :-)
Agreed. I understand that this FAQ probably was needed at one time and Brendan added it to address the concerns.
I don't want to speak for Frank, but I don't think it was an objection to KUnit itself, but rather an objection to not sufficiently addressing the point about how they differ.
I think at some point we do need to have a document that outlines when to KUnit and when to use Kselftest modules. I think one concern people have is that if KUnit is perceived as a replacement for Ksefltest module, Kselftest module will be ignored leaving users without the ability to build and run with Kselftest modules and load them on a need basis to gather data on a systems that aren't dedicated strictly for testing.
I absolutely agree! I posed a suggestion here[1], which after I just now searched for a link, I realize for some reason it didn't seem like it reached a number of the mailing lists that I sent it to, so I should probably resend it.
Anyway, a summary of what I suggested: We should start off by better organizing Documentation/dev-tools/ and create a landing page that groups the dev-tools by function according to what person is likely to use them and for what. Eventually and specifically for Kselftest and KUnit, I would like to have a testing guide for the kernel that explains what testing procedure should look like and what to use and when.
I am trying to move the conversation forward from KUnit vs. Kselftest modules discussion to which problem areas each one addresses keeping in mind that it is not about which is better. Kselftest and KUnit both have their place in the kernel development process. We just have to be clear on usage as we write tests for each.
I think that is the right long term approach. I think a good place to start, like I suggested above, is cleaning up Documentation/dev-tools/, but I think that belongs in a (probably several) follow-up patchset.
Frank, I believe your objection was mostly related to how KUnit is presented specifically in the cover letter, and doesn't necessarily deal with the intended use case. So I don't think that doing this, especially doing this later, really addresses your concern. I don't want to belabor the issue, but I would also rather not put words in your mouth, what are your thoughts on the above?
I think my main concern moving forward on this point is that I am not sure that I can address the debate that this section covers in a way that is both sufficiently concise for a cover letter, but also doesn't invite more potential confusion. My inclination at this point is to drop it since I think the set of reviewers for this patchset has at this point become fixed, and it seems that it will likely cause more confusion rather than reduce it; also, I don't really think this will be an issue for end users, especially once we have proper documentation in place. Alternatively, I guess I could maybe address the point elsewhere and refer to it in the cover letter? Or maybe just put it at the end of the cover letter?
[1] https://www.mail-archive.com/kgdb-bugreport@lists.sourceforge.net/msg05059.h...
On Fri, Jun 21, 2019 at 5:54 PM Brendan Higgins brendanhiggins@google.com wrote:
On Fri, Jun 21, 2019 at 12:21 PM shuah shuah@kernel.org wrote:
On 6/21/19 12:13 PM, Theodore Ts'o wrote:
On Fri, Jun 21, 2019 at 08:59:48AM -0600, shuah wrote:
### But wait! Doesn't kselftest support in kernel testing?!
....
I think I commented on this before. I agree with the statement that there is no overlap between Kselftest and KUnit. I would like see this removed. Kselftest module support supports use-cases KUnit won't be able to. I can build an kernel with Kselftest test modules and use it in the filed to load and run tests if I need to debug a problem and get data from a system. I can't do that with KUnit.
Sure, I think this point has been brought up a number of times before. Maybe I didn't write this section well because, like Frank said, it comes across as being critical of the Kselftest module support; that wasn't my intention. I was speaking from the perspective that Kselftest module support is just a feature of Kselftest, and not a full framework like KUnit is (obviously Kselftest itself *is* a framework, but a very small part of it is not).
It was obvious to me what Kselftest module support was intended for, and it is not intended to cover the use case that KUnit is targeting.
In my mind, I am not viewing this as which is better. Kselftest and KUnit both have their place in the kernel development process. It isn't productive and/or necessary to comparing Kselftest and KUnit without a good understanding of the problem spaces for each of these.
Again, I didn't mean to draw a comparison of which is better than the other. I was just trying to point out that Kselftest module support doesn't make sense as a stand alone unit testing framework, or really a framework of any kind, despite how it might actually be used.
I would strongly recommend not making reference to Kselftest and talk about what KUnit offers.
I can see your point. It seems that both you and Frank seem to think that I drew a comparison between Kselftest and KUnit, which was unintended. I probably should have spent more time editing this section, but I can see the point of drawing the comparison itself might invite this confusion.
Shuah,
Just to recall the history, this section of the FAQ was added to rebut the ***very*** strong statements that Frank made that there was overlap between Kselftest and Kunit, and that having too many ways for kernel developers to do the identical thing was harmful (he said it was too much of a burden on a kernel developer) --- and this was an argument for not including Kunit in the upstream kernel.
I don't think he was actually advocating that we don't include KUnit, maybe playing devil's advocate; nevertheless, at the end, Frank seemed to agree that there were valuable things that KUnit offered. I thought he just wanted to make the point that I hadn't made the distinction sufficiently clear in the cover letter, and other reviewers might get confused in the future as well.
Additionally, it does look like people were trying to use Kselftest module support to cover some things which really were trying to be unit tests. I know this isn't really intended - everything looks like a nail when you only have a hammer, which I think Frank was pointing out furthers the above confusion.
In anycase, it sounds like I have, if anything, only made the discussion even more confusing by adding this section; sorry about that.
If we're past that objection, then perhaps this section can be dropped, but there's a very good reason why it was there. I wouldn't Brendan to be accused of ignoring feedback from those who reviewed his patches. :-)
Agreed. I understand that this FAQ probably was needed at one time and Brendan added it to address the concerns.
I don't want to speak for Frank, but I don't think it was an objection to KUnit itself, but rather an objection to not sufficiently addressing the point about how they differ.
I think at some point we do need to have a document that outlines when to KUnit and when to use Kselftest modules. I think one concern people have is that if KUnit is perceived as a replacement for Ksefltest module, Kselftest module will be ignored leaving users without the ability to build and run with Kselftest modules and load them on a need basis to gather data on a systems that aren't dedicated strictly for testing.
I absolutely agree! I posed a suggestion here[1], which after I just now searched for a link, I realize for some reason it didn't seem like it reached a number of the mailing lists that I sent it to, so I should probably resend it.
Anyway, a summary of what I suggested: We should start off by better organizing Documentation/dev-tools/ and create a landing page that groups the dev-tools by function according to what person is likely to use them and for what. Eventually and specifically for Kselftest and KUnit, I would like to have a testing guide for the kernel that explains what testing procedure should look like and what to use and when.
I am trying to move the conversation forward from KUnit vs. Kselftest modules discussion to which problem areas each one addresses keeping in mind that it is not about which is better. Kselftest and KUnit both have their place in the kernel development process. We just have to be clear on usage as we write tests for each.
I think that is the right long term approach. I think a good place to start, like I suggested above, is cleaning up Documentation/dev-tools/, but I think that belongs in a (probably several) follow-up patchset.
Frank, I believe your objection was mostly related to how KUnit is presented specifically in the cover letter, and doesn't necessarily deal with the intended use case. So I don't think that doing this, especially doing this later, really addresses your concern. I don't want to belabor the issue, but I would also rather not put words in your mouth, what are your thoughts on the above?
I think my main concern moving forward on this point is that I am not sure that I can address the debate that this section covers in a way that is both sufficiently concise for a cover letter, but also doesn't invite more potential confusion. My inclination at this point is to drop it since I think the set of reviewers for this patchset has at this point become fixed, and it seems that it will likely cause more confusion rather than reduce it; also, I don't really think this will
Since no one has objected to dropping the "### But wait! Doesn't kselftest support in kernel testing?!" section in the past almost two weeks, I am just going to continue on without it.
Cheers
be an issue for end users, especially once we have proper documentation in place. Alternatively, I guess I could maybe address the point elsewhere and refer to it in the cover letter? Or maybe just put it at the end of the cover letter?
[1] https://www.mail-archive.com/kgdb-bugreport@lists.sourceforge.net/msg05059.h...
On Wed, Jun 19, 2019 at 6:17 PM Frank Rowand frowand.list@gmail.com wrote:
Hi Brendan,
I am only responding to this because you asked me to in the v4 thread.
Yes, I did! Thank you, I appreciate it!
Thank you for evaluating my comments in the v4 thread and asking me to comment on v5
Of course, I feel as though I ought to address any and all valid comments.
On 6/17/19 1:25 AM, Brendan Higgins wrote:
## TL;DR
A not so quick follow-up to Stephen's suggestions on PATCH v4. Nothing that really changes any functionality or usage with the minor exception of a couple public functions that Stephen asked me to rename. Nevertheless, a good deal of clean up and fixes. See changes below.
As for our current status, right now we got Reviewed-bys on all patches except:
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
However, it would probably be good to get reviews/acks from the subsystem maintainers on:
- [PATCH v5 06/18] kbuild: enable building KUnit
- [PATCH v5 08/18] objtool: add kunit_try_catch_throw to the noreturn list
- [PATCH v5 15/18] Documentation: kunit: add documentation for KUnit
- [PATCH v5 17/18] kernel/sysctl-test: Add null pointer test for sysctl.c:proc_dointvec()
- [PATCH v5 18/18] MAINTAINERS: add proc sysctl KUnit test to PROC SYSCTL section
Other than that, I think we should be good to go.
One last thing, I updated the background to include my thoughts on KUnit vs. in kernel testing with kselftest in the background sections as suggested by Frank in the discussion on PATCH v2.
## Background
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 (however, KUnit still allows you to run tests on test machines or in VMs if you want[1]) 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.
I looked only at this section, as was specifically requested:
### But wait! Doesn't kselftest support in kernel testing?!
In a previous version of this patchset Frank pointed out that kselftest already supports writing a test that resides in the kernel using the test module feature[2]. LWN did a really great summary on this discussion here[3].
Kselftest has a feature that allows a test module to be loaded into a kernel using the kselftest framework; this does allow someone to write tests against kernel code not directly exposed to userland; however, it does not provide much of a framework around how to structure the tests. The kselftest test module feature just provides a header which has a standardized way of reporting test failures,
and then provides infrastructure to load and run the tests using the kselftest test harness.
The in-kernel tests can also be invoked at boot time if they are configured (Kconfig) as in-kernel instead of as modules. I did not check how many of the tests have tri-state configuration to allow this, but the few that I looked at did.
The kselftest test module does not seem to be opinionated at all in regards to how tests are structured, how they check for failures, how tests are organized. Even in the method it provides for reporting failures is pretty simple; it doesn't have any more advanced failure reporting or logging features. Given what's there, I think it is fair to say that it is not actually a framework, but a feature that makes it possible for someone to do some checks in kernel space.
I would call that description a little dismissive. The set of in-kernel tests that I looked like followed a common pattern and reported results in a uniform manner.
I didn't mean to sound dismissive. I was only referring to what was present in the actual header itself. There really isn't much there; it provides a function which takes an expression, evaluates it, increments a counter of all tests, and if false, prints out a warning; also, it provides a module init which runs the user defined test function called selftest(), and then prints the number of tests that passed and the number of tests that failed; that's it. I was just trying to make the point that it is pretty bare bones, and isn't really much of a framework.
Furthermore, kselftest test module has very few users. I checked for all the tests that use it using the following grep command:
grep -Hrn -e 'kselftest_module.h'
and only got three results: lib/test_strscpy.c, lib/test_printf.c, and lib/test_bitmap.c.
You missed many tests. I listed much more than that in the v4 thread, and someone else also listed more in the v4 thread.
Oh, sorry, I forgot that more were listed in the thread.
So despite kselftest test module's existence, there really is no feature overlap between kselftest and KUnit, save one: that you can use either to write an in-kernel test, but this is a very small feature in comparison to everything that KUnit allows you to do. KUnit is a full x-unit style unit testing framework, whereas kselftest looks a lot more like an end-to-end/functional testing framework, with a feature that makes it possible to write in-kernel tests.
The description does not give enough credit to what is in kselftest.
You are right about me missing a number of the tests, but there really is not much infrastructure in kselftest for this at all. It really doesn't impose any structure of any kind other than that there must be exactly one static function named selftest() that takes no arguments; and then you use KSTM_CHECK_ZERO() to do some checks; that's it. It doesn't have anything else in the actual kselftest module stuff.
It does not matter whether KUnit provides additional things, relative to kselftest. The point I was making is that there appears to be _some_ overlap between kselftest and KUnit, and if there is overlap then it is worth considering whether the overlap can be unified instead of duplicated.
I think I understand what you are saying, but the point I was trying to make here is that it is so simplistic, it doesn't really conceptually overlap since it is so limited in what structure and features it provides. It's kind of like what Ted said previously about how you have C so you can technically do whatever you want, but there is nothing inherent to the kselftest test module that does any of those things (other than what I mentioned above).
I don't have a dog in this fight and the discussion in the v4 thread went way off track. Thus I am not going to get sucked back into a pointless debate in this thread.
Sure, I don't want to debate the point further either (I had a hard time understanding what the point was at the end myself).
Nevertheless, I do want to make sure that I addressed this because I think you did indeed have a point that was worth addressing, and I don't want to waste anyone's time in the future by not addressing it.
Nevertheless, like I said, I don't want to get too detailed on the topic otherwise like Shuah suggests later, it might end up just leading people to draw a comparison that doesn't need to be made, but I also don't want to misrepresent anything. In anycase, I will follow up on this point directly with Shuah.
Thanks for adding this section to address the issue.
No need to thank me; that is what I felt is the correct thing to do. I didn't address the point before and it caused you and other to spend a lot of time debating the point.
Also, it looks like Shuah is asking me to drop the section. I will discuss this point further there.
Thanks!
-Frank
### 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[4].
Additionally for convenience, I have applied these patches to a branch[5]. The repo may be cloned with: git clone https://kunit.googlesource.com/linux This patchset is on the kunit/rfc/v5.2-rc4/v5 branch.
## Changes Since Last Version
Aside from a couple public function renames, there isn't really anything in here that changes any functionality.
- Went through and fixed a couple of anti-patterns suggested by Stephen Boyd. Things like:
- Dropping an else clause at the end of a function.
- Dropping the comma on the closing sentinel, `{}`, of a list.
- Inlines a bunch of functions in the test case running logic in patch 01/18 to make it more readable as suggested by Stephen Boyd
- Found and fixed bug in resource deallocation logic in patch 02/18. Bug was discovered as a result of making a change suggested by Stephen Boyd. This does not substantially change how any of the code works conceptually.
- Renamed new_string_stream() to alloc_string_stream() as suggested by Stephen Boyd.
- Made string-stream a KUnit managed object - based on a suggestion made by Stephen Boyd.
- Renamed kunit_new_stream() to alloc_kunit_stream() as suggested by Stephen Boyd.
- Removed the ability to set log level after allocating a kunit_stream, as suggested by Stephen Boyd.
[1] https://google.github.io/kunit-docs/third_party/kernel/docs/usage.html#kunit... [2] https://www.kernel.org/doc/html/latest/dev-tools/kselftest.html#test-module [3] https://lwn.net/Articles/790235/ [4] https://google.github.io/kunit-docs/third_party/kernel/docs/ [5] https://kunit.googlesource.com/linux/+/kunit/rfc/v5.2-rc4/v5
On Wed, Jun 19, 2019 at 06:17:51PM -0700, Frank Rowand wrote:
It does not matter whether KUnit provides additional things, relative to kselftest. The point I was making is that there appears to be _some_ overlap between kselftest and KUnit, and if there is overlap then it is worth considering whether the overlap can be unified instead of duplicated.
From my experience as an author of several kselftests drivers, one
faily complex, and after reviewing the sysctl kunit test module, I disagree with this.
Even if there were an overlap, I'd say let the best test harness win. Just as we have different LSMs that do something similar.
But this is not about that though. Although both are testing code, they do so in *very* different ways.
The design philosophy and architecture are fundamentally different. The *only* thing I can think of where there is overlap is that both can test similar code paths. Beyond that, the layout of how one itemizes tests could be borrowed, but that would be up to each kselftest author to decide, in fact some ksefltests do exist which follow similar pattern of itemizing test cases and running them. Kunit just provides a proper framework to do this easily but also with a focus on UML. This last aspect makes kselftests fundamentally orthogonal from an architecture / design perspective.
After careful review, I cannot personally identify what could be shared at this point. Can you? If you did identify one part, how do you recommend to share it?
Luis
linux-kselftest-mirror@lists.linaro.org