forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
kunit: test: add KUnit test runner core
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 <[email protected]> Reviewed-by: Greg Kroah-Hartman <[email protected]> Reviewed-by: Logan Gunthorpe <[email protected]> Reviewed-by: Luis Chamberlain <[email protected]> Reviewed-by: Stephen Boyd <[email protected]> Signed-off-by: Shuah Khan <[email protected]>
- Loading branch information
Showing
4 changed files
with
393 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* SPDX-License-Identifier: GPL-2.0 */ | ||
/* | ||
* Base unit test (KUnit) API. | ||
* | ||
* Copyright (C) 2019, Google LLC. | ||
* Author: Brendan Higgins <[email protected]> | ||
*/ | ||
|
||
#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_suite and will be run after the suite's init | ||
* function and followed by the suite'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_suite - describes a related collection of &struct kunit_case | ||
* | ||
* @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_suite 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_suite for KUnit | ||
* to run it. | ||
*/ | ||
struct kunit_suite { | ||
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_suite). | ||
* | ||
* 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! */ | ||
/* | ||
* success starts as true, and may only be set to false during a | ||
* test case; thus, it is safe to update this across multiple | ||
* threads using WRITE_ONCE; however, as a consequence, it may only | ||
* be read after the test case finishes once all threads associated | ||
* with the test case have terminated. | ||
*/ | ||
bool success; /* Read only after test_case finishes! */ | ||
}; | ||
|
||
void kunit_init_test(struct kunit *test, const char *name); | ||
|
||
int kunit_run_tests(struct kunit_suite *suite); | ||
|
||
/** | ||
* kunit_test_suite() - used to register a &struct kunit_suite with KUnit. | ||
* | ||
* @suite: a statically allocated &struct kunit_suite. | ||
* | ||
* Registers @suite with the test framework. See &struct kunit_suite for | ||
* more information. | ||
* | ||
* NOTE: Currently KUnit tests are all run as late_initcalls; this means | ||
* that they cannot test anything where tests must run at a different init | ||
* phase. One significant restriction resulting from this is that KUnit | ||
* cannot reliably test anything that is initialize in the late_init phase; | ||
* another is that KUnit is useless to test things that need to be run in | ||
* an earlier init phase. | ||
* | ||
* TODO([email protected]): Don't run all KUnit tests as | ||
* late_initcalls. I have some future work planned to dispatch all KUnit | ||
* tests from the same place, and at the very least to do so after | ||
* everything else is definitely initialized. | ||
*/ | ||
#define kunit_test_suite(suite) \ | ||
static int kunit_suite_init##suite(void) \ | ||
{ \ | ||
return kunit_run_tests(&suite); \ | ||
} \ | ||
late_initcall(kunit_suite_init##suite) | ||
|
||
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 @test. | ||
* | ||
* @test: The test context object. | ||
* @fmt: A printk() style format string. | ||
* | ||
* Prints an info level message associated with the test suite 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 @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 @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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# | ||
# KUnit base configuration | ||
# | ||
|
||
menuconfig KUNIT | ||
bool "KUnit - Enable support for unit tests" | ||
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/. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
obj-$(CONFIG_KUNIT) += test.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Base unit test (KUnit) API. | ||
* | ||
* Copyright (C) 2019, Google LLC. | ||
* Author: Brendan Higgins <[email protected]> | ||
*/ | ||
|
||
#include <kunit/test.h> | ||
#include <linux/kernel.h> | ||
|
||
static void kunit_set_failure(struct kunit *test) | ||
{ | ||
WRITE_ONCE(test->success, false); | ||
} | ||
|
||
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 void kunit_print_tap_version(void) | ||
{ | ||
static bool kunit_has_printed_tap_version; | ||
|
||
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_suite *suite) | ||
{ | ||
kunit_print_tap_version(); | ||
kunit_printk_emit(LOGLEVEL_INFO, "\t# Subtest: %s\n", suite->name); | ||
kunit_printk_emit(LOGLEVEL_INFO, | ||
"\t1..%zd\n", | ||
kunit_test_cases_len(suite->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_suite_has_succeeded(struct kunit_suite *suite) | ||
{ | ||
const struct kunit_case *test_case; | ||
|
||
for (test_case = suite->test_cases; test_case->run_case; test_case++) | ||
if (!test_case->success) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
static void kunit_print_subtest_end(struct kunit_suite *suite) | ||
{ | ||
static size_t kunit_suite_counter = 1; | ||
|
||
kunit_print_ok_not_ok(false, | ||
kunit_suite_has_succeeded(suite), | ||
kunit_suite_counter++, | ||
suite->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) | ||
{ | ||
test->name = name; | ||
test->success = true; | ||
} | ||
|
||
/* | ||
* Performs all logic to run a test case. | ||
*/ | ||
static void kunit_run_case(struct kunit_suite *suite, | ||
struct kunit_case *test_case) | ||
{ | ||
struct kunit test; | ||
|
||
kunit_init_test(&test, test_case->name); | ||
|
||
if (suite->init) { | ||
int ret; | ||
|
||
ret = suite->init(&test); | ||
if (ret) { | ||
kunit_err(&test, "failed to initialize: %d\n", ret); | ||
kunit_set_failure(&test); | ||
test_case->success = test.success; | ||
return; | ||
} | ||
} | ||
|
||
test_case->run_case(&test); | ||
|
||
if (suite->exit) | ||
suite->exit(&test); | ||
|
||
test_case->success = test.success; | ||
} | ||
|
||
int kunit_run_tests(struct kunit_suite *suite) | ||
{ | ||
struct kunit_case *test_case; | ||
size_t test_case_count = 1; | ||
|
||
kunit_print_subtest_start(suite); | ||
|
||
for (test_case = suite->test_cases; test_case->run_case; test_case++) { | ||
kunit_run_case(suite, test_case); | ||
kunit_print_test_case_ok_not_ok(test_case, test_case_count++); | ||
} | ||
|
||
kunit_print_subtest_end(suite); | ||
|
||
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); | ||
} |