Merge tag 'linux-kselftest-kunit-6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull kunit updates from Shuah Khan:

 - add support for running Rust documentation tests as KUnit tests

 - make init, str, sync, types doctests compilable/testable

 - add support for attributes API which include speed, modules
   attributes, ability to filter and report attributes

 - add support for marking tests slow using attributes API

 - add attributes API documentation

 - fix a wild-memory-access bug in kunit_filter_suites() and a possible
   memory leak in kunit_filter_suites()

 - add support for counting number of test suites in a module, list
   action to kunit test modules, and test filtering on module tests

* tag 'linux-kselftest-kunit-6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: (25 commits)
  kunit: fix struct kunit_attr header
  kunit: replace KUNIT_TRIGGER_STATIC_STUB maro with KUNIT_STATIC_STUB_REDIRECT
  kunit: Allow kunit test modules to use test filtering
  kunit: Make 'list' action available to kunit test modules
  kunit: Report the count of test suites in a module
  kunit: fix uninitialized variables bug in attributes filtering
  kunit: fix possible memory leak in kunit_filter_suites()
  kunit: fix wild-memory-access bug in kunit_filter_suites()
  kunit: Add documentation of KUnit test attributes
  kunit: add tests for filtering attributes
  kunit: time: Mark test as slow using test attributes
  kunit: memcpy: Mark tests as slow using test attributes
  kunit: tool: Add command line interface to filter and report attributes
  kunit: Add ability to filter attributes
  kunit: Add module attribute
  kunit: Add speed attribute
  kunit: Add test attributes API structure
  MAINTAINERS: add Rust KUnit files to the KUnit entry
  rust: support running Rust documentation tests as KUnit ones
  rust: types: make doctests compilable/testable
  ...
This commit is contained in:
Linus Torvalds
2023-08-28 18:56:38 -07:00
38 changed files with 1808 additions and 151 deletions

View File

@@ -321,3 +321,15 @@ command line arguments:
- ``--json``: If set, stores the test results in a JSON format and prints to `stdout` or
saves to a file if a filename is specified.
- ``--filter``: Specifies filters on test attributes, for example, ``speed!=slow``.
Multiple filters can be used by wrapping input in quotes and separating filters
by commas. Example: ``--filter "speed>slow, module=example"``.
- ``--filter_action``: If set to ``skip``, filtered tests will be shown as skipped
in the output rather than showing no output.
- ``--list_tests``: If set, lists all tests that will be run.
- ``--list_tests_attr``: If set, lists all tests that will be run and all of their
attributes.

View File

@@ -262,3 +262,169 @@ other code executed during boot, e.g.
# Reset coverage counters before running the test.
$ echo 0 > /sys/kernel/debug/gcov/reset
$ modprobe kunit-example-test
Test Attributes and Filtering
=============================
Test suites and cases can be marked with test attributes, such as speed of
test. These attributes will later be printed in test output and can be used to
filter test execution.
Marking Test Attributes
-----------------------
Tests are marked with an attribute by including a ``kunit_attributes`` object
in the test definition.
Test cases can be marked using the ``KUNIT_CASE_ATTR(test_name, attributes)``
macro to define the test case instead of ``KUNIT_CASE(test_name)``.
.. code-block:: c
static const struct kunit_attributes example_attr = {
.speed = KUNIT_VERY_SLOW,
};
static struct kunit_case example_test_cases[] = {
KUNIT_CASE_ATTR(example_test, example_attr),
};
.. note::
To mark a test case as slow, you can also use ``KUNIT_CASE_SLOW(test_name)``.
This is a helpful macro as the slow attribute is the most commonly used.
Test suites can be marked with an attribute by setting the "attr" field in the
suite definition.
.. code-block:: c
static const struct kunit_attributes example_attr = {
.speed = KUNIT_VERY_SLOW,
};
static struct kunit_suite example_test_suite = {
...,
.attr = example_attr,
};
.. note::
Not all attributes need to be set in a ``kunit_attributes`` object. Unset
attributes will remain uninitialized and act as though the attribute is set
to 0 or NULL. Thus, if an attribute is set to 0, it is treated as unset.
These unset attributes will not be reported and may act as a default value
for filtering purposes.
Reporting Attributes
--------------------
When a user runs tests, attributes will be present in the raw kernel output (in
KTAP format). Note that attributes will be hidden by default in kunit.py output
for all passing tests but the raw kernel output can be accessed using the
``--raw_output`` flag. This is an example of how test attributes for test cases
will be formatted in kernel output:
.. code-block:: none
# example_test.speed: slow
ok 1 example_test
This is an example of how test attributes for test suites will be formatted in
kernel output:
.. code-block:: none
KTAP version 2
# Subtest: example_suite
# module: kunit_example_test
1..3
...
ok 1 example_suite
Additionally, users can output a full attribute report of tests with their
attributes, using the command line flag ``--list_tests_attr``:
.. code-block:: bash
kunit.py run "example" --list_tests_attr
.. note::
This report can be accessed when running KUnit manually by passing in the
module_param ``kunit.action=list_attr``.
Filtering
---------
Users can filter tests using the ``--filter`` command line flag when running
tests. As an example:
.. code-block:: bash
kunit.py run --filter speed=slow
You can also use the following operations on filters: "<", ">", "<=", ">=",
"!=", and "=". Example:
.. code-block:: bash
kunit.py run --filter "speed>slow"
This example will run all tests with speeds faster than slow. Note that the
characters < and > are often interpreted by the shell, so they may need to be
quoted or escaped, as above.
Additionally, you can use multiple filters at once. Simply separate filters
using commas. Example:
.. code-block:: bash
kunit.py run --filter "speed>slow, module=kunit_example_test"
.. note::
You can use this filtering feature when running KUnit manually by passing
the filter as a module param: ``kunit.filter="speed>slow, speed<=normal"``.
Filtered tests will not run or show up in the test output. You can use the
``--filter_action=skip`` flag to skip filtered tests instead. These tests will be
shown in the test output in the test but will not run. To use this feature when
running KUnit manually, use the module param ``kunit.filter_action=skip``.
Rules of Filtering Procedure
----------------------------
Since both suites and test cases can have attributes, there may be conflicts
between attributes during filtering. The process of filtering follows these
rules:
- Filtering always operates at a per-test level.
- If a test has an attribute set, then the test's value is filtered on.
- Otherwise, the value falls back to the suite's value.
- If neither are set, the attribute has a global "default" value, which is used.
List of Current Attributes
--------------------------
``speed``
This attribute indicates the speed of a test's execution (how slow or fast the
test is).
This attribute is saved as an enum with the following categories: "normal",
"slow", or "very_slow". The assumed default speed for tests is "normal". This
indicates that the test takes a relatively trivial amount of time (less than
1 second), regardless of the machine it is running on. Any test slower than
this could be marked as "slow" or "very_slow".
The macro ``KUNIT_CASE_SLOW(test_name)`` can be easily used to set the speed
of a test case to "slow".
``module``
This attribute indicates the name of the module associated with the test.
This attribute is automatically saved as a string and is printed for each suite.
Tests can also be filtered using this attribute.

View File

@@ -11394,6 +11394,8 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git k
F: Documentation/dev-tools/kunit/
F: include/kunit/
F: lib/kunit/
F: rust/kernel/kunit.rs
F: scripts/rustdoc_test_*
F: tools/testing/kunit/
KERNEL USERMODE HELPER

View File

@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* KUnit API to save and access test attributes
*
* Copyright (C) 2023, Google LLC.
* Author: Rae Moar <rmoar@google.com>
*/
#ifndef _KUNIT_ATTRIBUTES_H
#define _KUNIT_ATTRIBUTES_H
/*
* struct kunit_attr_filter - representation of attributes filter with the
* attribute object and string input
*/
struct kunit_attr_filter {
struct kunit_attr *attr;
char *input;
};
/*
* Returns the name of the filter's attribute.
*/
const char *kunit_attr_filter_name(struct kunit_attr_filter filter);
/*
* Print all test attributes for a test case or suite.
* Output format for test cases: "# <test_name>.<attribute>: <value>"
* Output format for test suites: "# <attribute>: <value>"
*/
void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level);
/*
* Returns the number of fitlers in input.
*/
int kunit_get_filter_count(char *input);
/*
* Parse attributes filter input and return an objects containing the
* attribute object and the string input of the next filter.
*/
struct kunit_attr_filter kunit_next_attr_filter(char **filters, int *err);
/*
* Returns a copy of the suite containing only tests that pass the filter.
*/
struct kunit_suite *kunit_filter_attr_tests(const struct kunit_suite *const suite,
struct kunit_attr_filter filter, char *action, int *err);
#endif /* _KUNIT_ATTRIBUTES_H */

View File

@@ -11,7 +11,7 @@
#if !IS_ENABLED(CONFIG_KUNIT)
/* If CONFIG_KUNIT is not enabled, these stubs quietly disappear. */
#define KUNIT_TRIGGER_STATIC_STUB(real_fn_name, args...) do {} while (0)
#define KUNIT_STATIC_STUB_REDIRECT(real_fn_name, args...) do {} while (0)
#else
@@ -30,7 +30,7 @@
* This is a function prologue which is used to allow calls to the current
* function to be redirected by a KUnit test. KUnit tests can call
* kunit_activate_static_stub() to pass a replacement function in. The
* replacement function will be called by KUNIT_TRIGGER_STATIC_STUB(), which
* replacement function will be called by KUNIT_STATIC_STUB_REDIRECT(), which
* will then return from the function. If the caller is not in a KUnit context,
* the function will continue execution as normal.
*
@@ -87,7 +87,7 @@ void __kunit_activate_static_stub(struct kunit *test,
* When activated, calls to real_fn_addr from within this test (even if called
* indirectly) will instead call replacement_addr. The function pointed to by
* real_fn_addr must begin with the static stub prologue in
* KUNIT_TRIGGER_STATIC_STUB() for this to work. real_fn_addr and
* KUNIT_STATIC_STUB_REDIRECT() for this to work. real_fn_addr and
* replacement_addr must have the same type.
*
* The redirection can be disabled again with kunit_deactivate_static_stub().

View File

@@ -9,6 +9,8 @@
#ifndef _KUNIT_TEST_BUG_H
#define _KUNIT_TEST_BUG_H
#include <linux/stddef.h> /* for NULL */
#if IS_ENABLED(CONFIG_KUNIT)
#include <linux/jump_label.h> /* For static branch */

View File

@@ -63,12 +63,35 @@ enum kunit_status {
KUNIT_SKIPPED,
};
/* Attribute struct/enum definitions */
/*
* Speed Attribute is stored as an enum and separated into categories of
* speed: very_slowm, slow, and normal. These speeds are relative to
* other KUnit tests.
*
* Note: unset speed attribute acts as default of KUNIT_SPEED_NORMAL.
*/
enum kunit_speed {
KUNIT_SPEED_UNSET,
KUNIT_SPEED_VERY_SLOW,
KUNIT_SPEED_SLOW,
KUNIT_SPEED_NORMAL,
KUNIT_SPEED_MAX = KUNIT_SPEED_NORMAL,
};
/* Holds attributes for each test case and suite */
struct kunit_attributes {
enum kunit_speed speed;
};
/**
* struct kunit_case - represents an individual test case.
*
* @run_case: the function representing the actual test case.
* @name: the name of the test case.
* @generate_params: the generator function for parameterized tests.
* @attr: the attributes associated with the test
*
* A test case is a function with the signature,
* ``void (*)(struct kunit *)``
@@ -104,9 +127,11 @@ struct kunit_case {
void (*run_case)(struct kunit *test);
const char *name;
const void* (*generate_params)(const void *prev, char *desc);
struct kunit_attributes attr;
/* private: internal use only. */
enum kunit_status status;
char *module_name;
char *log;
};
@@ -131,7 +156,32 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
* &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 }
#define KUNIT_CASE(test_name) \
{ .run_case = test_name, .name = #test_name, \
.module_name = KBUILD_MODNAME}
/**
* KUNIT_CASE_ATTR - A helper for creating a &struct kunit_case
* with attributes
*
* @test_name: a reference to a test case function.
* @attributes: a reference to a struct kunit_attributes object containing
* test attributes
*/
#define KUNIT_CASE_ATTR(test_name, attributes) \
{ .run_case = test_name, .name = #test_name, \
.attr = attributes, .module_name = KBUILD_MODNAME}
/**
* KUNIT_CASE_SLOW - A helper for creating a &struct kunit_case
* with the slow attribute
*
* @test_name: a reference to a test case function.
*/
#define KUNIT_CASE_SLOW(test_name) \
{ .run_case = test_name, .name = #test_name, \
.attr.speed = KUNIT_SPEED_SLOW, .module_name = KBUILD_MODNAME}
/**
* KUNIT_CASE_PARAM - A helper for creation a parameterized &struct kunit_case
@@ -152,7 +202,21 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
*/
#define KUNIT_CASE_PARAM(test_name, gen_params) \
{ .run_case = test_name, .name = #test_name, \
.generate_params = gen_params }
.generate_params = gen_params, .module_name = KBUILD_MODNAME}
/**
* KUNIT_CASE_PARAM_ATTR - A helper for creating a parameterized &struct
* kunit_case with attributes
*
* @test_name: a reference to a test case function.
* @gen_params: a reference to a parameter generator function.
* @attributes: a reference to a struct kunit_attributes object containing
* test attributes
*/
#define KUNIT_CASE_PARAM_ATTR(test_name, gen_params, attributes) \
{ .run_case = test_name, .name = #test_name, \
.generate_params = gen_params, \
.attr = attributes, .module_name = KBUILD_MODNAME}
/**
* struct kunit_suite - describes a related collection of &struct kunit_case
@@ -163,6 +227,7 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
* @init: called before every test case.
* @exit: called after every test case.
* @test_cases: a null terminated array of test cases.
* @attr: the attributes associated with the test suite
*
* 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
@@ -182,6 +247,7 @@ struct kunit_suite {
int (*init)(struct kunit *test);
void (*exit)(struct kunit *test);
struct kunit_case *test_cases;
struct kunit_attributes attr;
/* private: internal use only */
char status_comment[KUNIT_STATUS_COMMENT_SIZE];
@@ -190,6 +256,12 @@ struct kunit_suite {
int suite_init_err;
};
/* Stores an array of suites, end points one past the end */
struct kunit_suite_set {
struct kunit_suite * const *start;
struct kunit_suite * const *end;
};
/**
* struct kunit - represents a running instance of a test.
*
@@ -237,6 +309,10 @@ static inline void kunit_set_failure(struct kunit *test)
}
bool kunit_enabled(void);
const char *kunit_action(void);
const char *kunit_filter_glob(void);
char *kunit_filter(void);
char *kunit_filter_action(void);
void kunit_init_test(struct kunit *test, const char *name, char *log);
@@ -247,10 +323,21 @@ size_t kunit_suite_num_test_cases(struct kunit_suite *suite);
unsigned int kunit_test_case_num(struct kunit_suite *suite,
struct kunit_case *test_case);
struct kunit_suite_set
kunit_filter_suites(const struct kunit_suite_set *suite_set,
const char *filter_glob,
char *filters,
char *filter_action,
int *err);
void kunit_free_suite_set(struct kunit_suite_set suite_set);
int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_suites);
void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites);
void kunit_exec_run_tests(struct kunit_suite_set *suite_set, bool builtin);
void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr);
#if IS_BUILTIN(CONFIG_KUNIT)
int kunit_run_all_tests(void);
#else

View File

@@ -86,7 +86,7 @@ static void time64_to_tm_test_date_range(struct kunit *test)
}
static struct kunit_case time_test_cases[] = {
KUNIT_CASE(time64_to_tm_test_date_range),
KUNIT_CASE_SLOW(time64_to_tm_test_date_range),
{}
};

View File

@@ -2696,6 +2696,9 @@ config MEMCPY_SLOW_KUNIT_TEST
and bit ranges. These can be very slow, so they are split out
as a separate config, in case they need to be disabled.
Note this config option will be replaced by the use of KUnit test
attributes.
config IS_SIGNED_TYPE_KUNIT_TEST
tristate "Test is_signed_type() macro" if !KUNIT_ALL_TESTS
depends on KUNIT
@@ -3005,6 +3008,19 @@ config RUST_BUILD_ASSERT_ALLOW
If unsure, say N.
config RUST_KERNEL_DOCTESTS
bool "Doctests for the `kernel` crate" if !KUNIT_ALL_TESTS
depends on RUST && KUNIT=y
default KUNIT_ALL_TESTS
help
This builds the documentation tests of the `kernel` crate
as KUnit tests.
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.
endmenu # "Rust"
endmenu # Kernel hacking

View File

@@ -4,7 +4,7 @@
menuconfig KUNIT
tristate "KUnit - Enable support for unit tests"
select GLOB if KUNIT=y
select GLOB
help
Enables support for kernel unit tests (KUnit), a lightweight unit
testing and mocking framework for the Linux kernel. These tests are

View File

@@ -6,7 +6,8 @@ kunit-objs += test.o \
string-stream.o \
assert.o \
try-catch.o \
executor.o
executor.o \
attributes.o
ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o

414
lib/kunit/attributes.c Normal file
View File

@@ -0,0 +1,414 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit API to save and access test attributes
*
* Copyright (C) 2023, Google LLC.
* Author: Rae Moar <rmoar@google.com>
*/
#include <kunit/test.h>
#include <kunit/attributes.h>
/* Options for printing attributes:
* PRINT_ALWAYS - attribute is printed for every test case and suite if set
* PRINT_SUITE - attribute is printed for every suite if set but not for test cases
* PRINT_NEVER - attribute is never printed
*/
enum print_ops {
PRINT_ALWAYS,
PRINT_SUITE,
PRINT_NEVER,
};
/**
* struct kunit_attr - represents a test attribute and holds flexible
* helper functions to interact with attribute.
*
* @name: name of test attribute, eg. speed
* @get_attr: function to return attribute value given a test
* @to_string: function to return string representation of given
* attribute value
* @filter: function to indicate whether a given attribute value passes a
* filter
* @attr_default: default attribute value used during filtering
* @print: value of enum print_ops to indicate when to print attribute
*/
struct kunit_attr {
const char *name;
void *(*get_attr)(void *test_or_suite, bool is_test);
const char *(*to_string)(void *attr, bool *to_free);
int (*filter)(void *attr, const char *input, int *err);
void *attr_default;
enum print_ops print;
};
/* String Lists for enum Attributes */
static const char * const speed_str_list[] = {"unset", "very_slow", "slow", "normal"};
/* To String Methods */
static const char *attr_enum_to_string(void *attr, const char * const str_list[], bool *to_free)
{
long val = (long)attr;
*to_free = false;
if (!val)
return NULL;
return str_list[val];
}
static const char *attr_speed_to_string(void *attr, bool *to_free)
{
return attr_enum_to_string(attr, speed_str_list, to_free);
}
static const char *attr_string_to_string(void *attr, bool *to_free)
{
*to_free = false;
return (char *) attr;
}
/* Filter Methods */
static const char op_list[] = "<>!=";
/*
* Returns whether the inputted integer value matches the filter given
* by the operation string and inputted integer.
*/
static int int_filter(long val, const char *op, int input, int *err)
{
if (!strncmp(op, "<=", 2))
return (val <= input);
else if (!strncmp(op, ">=", 2))
return (val >= input);
else if (!strncmp(op, "!=", 2))
return (val != input);
else if (!strncmp(op, ">", 1))
return (val > input);
else if (!strncmp(op, "<", 1))
return (val < input);
else if (!strncmp(op, "=", 1))
return (val == input);
*err = -EINVAL;
pr_err("kunit executor: invalid filter operation: %s\n", op);
return false;
}
/*
* Returns whether the inputted enum value "attr" matches the filter given
* by the input string. Note: the str_list includes the corresponding string
* list to the enum values.
*/
static int attr_enum_filter(void *attr, const char *input, int *err,
const char * const str_list[], int max)
{
int i, j, input_int = -1;
long test_val = (long)attr;
const char *input_val = NULL;
for (i = 0; input[i]; i++) {
if (!strchr(op_list, input[i])) {
input_val = input + i;
break;
}
}
if (!input_val) {
*err = -EINVAL;
pr_err("kunit executor: filter value not found: %s\n", input);
return false;
}
for (j = 0; j <= max; j++) {
if (!strcmp(input_val, str_list[j]))
input_int = j;
}
if (input_int < 0) {
*err = -EINVAL;
pr_err("kunit executor: invalid filter input: %s\n", input);
return false;
}
return int_filter(test_val, input, input_int, err);
}
static int attr_speed_filter(void *attr, const char *input, int *err)
{
return attr_enum_filter(attr, input, err, speed_str_list, KUNIT_SPEED_MAX);
}
/*
* Returns whether the inputted string value (attr) matches the filter given
* by the input string.
*/
static int attr_string_filter(void *attr, const char *input, int *err)
{
char *str = attr;
if (!strncmp(input, "<", 1)) {
*err = -EINVAL;
pr_err("kunit executor: invalid filter input: %s\n", input);
return false;
} else if (!strncmp(input, ">", 1)) {
*err = -EINVAL;
pr_err("kunit executor: invalid filter input: %s\n", input);
return false;
} else if (!strncmp(input, "!=", 2)) {
return (strcmp(input + 2, str) != 0);
} else if (!strncmp(input, "=", 1)) {
return (strcmp(input + 1, str) == 0);
}
*err = -EINVAL;
pr_err("kunit executor: invalid filter operation: %s\n", input);
return false;
}
/* Get Attribute Methods */
static void *attr_speed_get(void *test_or_suite, bool is_test)
{
struct kunit_suite *suite = is_test ? NULL : test_or_suite;
struct kunit_case *test = is_test ? test_or_suite : NULL;
if (test)
return ((void *) test->attr.speed);
else
return ((void *) suite->attr.speed);
}
static void *attr_module_get(void *test_or_suite, bool is_test)
{
struct kunit_suite *suite = is_test ? NULL : test_or_suite;
struct kunit_case *test = is_test ? test_or_suite : NULL;
// Suites get their module attribute from their first test_case
if (test)
return ((void *) test->module_name);
else if (kunit_suite_num_test_cases(suite) > 0)
return ((void *) suite->test_cases[0].module_name);
else
return (void *) "";
}
/* List of all Test Attributes */
static struct kunit_attr kunit_attr_list[] = {
{
.name = "speed",
.get_attr = attr_speed_get,
.to_string = attr_speed_to_string,
.filter = attr_speed_filter,
.attr_default = (void *)KUNIT_SPEED_NORMAL,
.print = PRINT_ALWAYS,
},
{
.name = "module",
.get_attr = attr_module_get,
.to_string = attr_string_to_string,
.filter = attr_string_filter,
.attr_default = (void *)"",
.print = PRINT_SUITE,
}
};
/* Helper Functions to Access Attributes */
const char *kunit_attr_filter_name(struct kunit_attr_filter filter)
{
return filter.attr->name;
}
void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level)
{
int i;
bool to_free = false;
void *attr;
const char *attr_name, *attr_str;
struct kunit_suite *suite = is_test ? NULL : test_or_suite;
struct kunit_case *test = is_test ? test_or_suite : NULL;
for (i = 0; i < ARRAY_SIZE(kunit_attr_list); i++) {
if (kunit_attr_list[i].print == PRINT_NEVER ||
(test && kunit_attr_list[i].print == PRINT_SUITE))
continue;
attr = kunit_attr_list[i].get_attr(test_or_suite, is_test);
if (attr) {
attr_name = kunit_attr_list[i].name;
attr_str = kunit_attr_list[i].to_string(attr, &to_free);
if (test) {
kunit_log(KERN_INFO, test, "%*s# %s.%s: %s",
KUNIT_INDENT_LEN * test_level, "", test->name,
attr_name, attr_str);
} else {
kunit_log(KERN_INFO, suite, "%*s# %s: %s",
KUNIT_INDENT_LEN * test_level, "", attr_name, attr_str);
}
/* Free to_string of attribute if needed */
if (to_free)
kfree(attr_str);
}
}
}
/* Helper Functions to Filter Attributes */
int kunit_get_filter_count(char *input)
{
int i, comma_index = 0, count = 0;
for (i = 0; input[i]; i++) {
if (input[i] == ',') {
if ((i - comma_index) > 1)
count++;
comma_index = i;
}
}
if ((i - comma_index) > 0)
count++;
return count;
}
struct kunit_attr_filter kunit_next_attr_filter(char **filters, int *err)
{
struct kunit_attr_filter filter = {};
int i, j, comma_index = 0, new_start_index = 0;
int op_index = -1, attr_index = -1;
char op;
char *input = *filters;
/* Parse input until operation */
for (i = 0; input[i]; i++) {
if (op_index < 0 && strchr(op_list, input[i])) {
op_index = i;
} else if (!comma_index && input[i] == ',') {
comma_index = i;
} else if (comma_index && input[i] != ' ') {
new_start_index = i;
break;
}
}
if (op_index <= 0) {
*err = -EINVAL;
pr_err("kunit executor: filter operation not found: %s\n", input);
return filter;
}
/* Temporarily set operator to \0 character. */
op = input[op_index];
input[op_index] = '\0';
/* Find associated kunit_attr object */
for (j = 0; j < ARRAY_SIZE(kunit_attr_list); j++) {
if (!strcmp(input, kunit_attr_list[j].name)) {
attr_index = j;
break;
}
}
input[op_index] = op;
if (attr_index < 0) {
*err = -EINVAL;
pr_err("kunit executor: attribute not found: %s\n", input);
} else {
filter.attr = &kunit_attr_list[attr_index];
}
if (comma_index > 0) {
input[comma_index] = '\0';
filter.input = input + op_index;
input = input + new_start_index;
} else {
filter.input = input + op_index;
input = NULL;
}
*filters = input;
return filter;
}
struct kunit_suite *kunit_filter_attr_tests(const struct kunit_suite *const suite,
struct kunit_attr_filter filter, char *action, int *err)
{
int n = 0;
struct kunit_case *filtered, *test_case;
struct kunit_suite *copy;
void *suite_val, *test_val;
bool suite_result, test_result, default_result, result;
/* Allocate memory for new copy of suite and list of test cases */
copy = kmemdup(suite, sizeof(*copy), GFP_KERNEL);
if (!copy)
return ERR_PTR(-ENOMEM);
kunit_suite_for_each_test_case(suite, test_case) { n++; }
filtered = kcalloc(n + 1, sizeof(*filtered), GFP_KERNEL);
if (!filtered) {
kfree(copy);
return ERR_PTR(-ENOMEM);
}
n = 0;
/* Save filtering result on default value */
default_result = filter.attr->filter(filter.attr->attr_default, filter.input, err);
if (*err)
goto err;
/* Save suite attribute value and filtering result on that value */
suite_val = filter.attr->get_attr((void *)suite, false);
suite_result = filter.attr->filter(suite_val, filter.input, err);
if (*err)
goto err;
/* For each test case, save test case if passes filtering. */
kunit_suite_for_each_test_case(suite, test_case) {
test_val = filter.attr->get_attr((void *) test_case, true);
test_result = filter.attr->filter(filter.attr->get_attr(test_case, true),
filter.input, err);
if (*err)
goto err;
/*
* If attribute value of test case is set, filter on that value.
* If not, filter on suite value if set. If not, filter on
* default value.
*/
result = false;
if (test_val) {
if (test_result)
result = true;
} else if (suite_val) {
if (suite_result)
result = true;
} else if (default_result) {
result = true;
}
if (result) {
filtered[n++] = *test_case;
} else if (action && strcmp(action, "skip") == 0) {
test_case->status = KUNIT_SKIPPED;
filtered[n++] = *test_case;
}
}
err:
if (n == 0 || *err) {
kfree(copy);
kfree(filtered);
return NULL;
}
copy->test_cases = filtered;
return copy;
}

View File

@@ -2,6 +2,7 @@
#include <linux/reboot.h>
#include <kunit/test.h>
#include <kunit/attributes.h>
#include <linux/glob.h>
#include <linux/moduleparam.h>
@@ -12,28 +13,59 @@
extern struct kunit_suite * const __kunit_suites_start[];
extern struct kunit_suite * const __kunit_suites_end[];
#if IS_BUILTIN(CONFIG_KUNIT)
static char *filter_glob_param;
static char *action_param;
module_param_named(filter_glob, filter_glob_param, charp, 0);
MODULE_PARM_DESC(filter_glob,
"Filter which KUnit test suites/tests run at boot-time, e.g. list* or list*.*del_test");
module_param_named(action, action_param, charp, 0);
module_param_named(action, action_param, charp, 0400);
MODULE_PARM_DESC(action,
"Changes KUnit executor behavior, valid values are:\n"
"<none>: run the tests like normal\n"
"'list' to list test names instead of running them.\n");
"'list' to list test names instead of running them.\n"
"'list_attr' to list test names and attributes instead of running them.\n");
const char *kunit_action(void)
{
return action_param;
}
static char *filter_glob_param;
static char *filter_param;
static char *filter_action_param;
module_param_named(filter_glob, filter_glob_param, charp, 0400);
MODULE_PARM_DESC(filter_glob,
"Filter which KUnit test suites/tests run at boot-time, e.g. list* or list*.*del_test");
module_param_named(filter, filter_param, charp, 0400);
MODULE_PARM_DESC(filter,
"Filter which KUnit test suites/tests run at boot-time using attributes, e.g. speed>slow");
module_param_named(filter_action, filter_action_param, charp, 0400);
MODULE_PARM_DESC(filter_action,
"Changes behavior of filtered tests using attributes, valid values are:\n"
"<none>: do not run filtered tests as normal\n"
"'skip': skip all filtered tests instead so tests will appear in output\n");
const char *kunit_filter_glob(void)
{
return filter_glob_param;
}
char *kunit_filter(void)
{
return filter_param;
}
char *kunit_filter_action(void)
{
return filter_action_param;
}
/* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */
struct kunit_test_filter {
struct kunit_glob_filter {
char *suite_glob;
char *test_glob;
};
/* Split "suite_glob.test_glob" into two. Assumes filter_glob is not empty. */
static void kunit_parse_filter_glob(struct kunit_test_filter *parsed,
static void kunit_parse_glob_filter(struct kunit_glob_filter *parsed,
const char *filter_glob)
{
const int len = strlen(filter_glob);
@@ -55,7 +87,7 @@ static void kunit_parse_filter_glob(struct kunit_test_filter *parsed,
/* Create a copy of suite with only tests that match test_glob. */
static struct kunit_suite *
kunit_filter_tests(const struct kunit_suite *const suite, const char *test_glob)
kunit_filter_glob_tests(const struct kunit_suite *const suite, const char *test_glob)
{
int n = 0;
struct kunit_case *filtered, *test_case;
@@ -89,16 +121,7 @@ kunit_filter_tests(const struct kunit_suite *const suite, const char *test_glob)
return copy;
}
static char *kunit_shutdown;
core_param(kunit_shutdown, kunit_shutdown, charp, 0644);
/* Stores an array of suites, end points one past the end */
struct suite_set {
struct kunit_suite * const *start;
struct kunit_suite * const *end;
};
static void kunit_free_suite_set(struct suite_set suite_set)
void kunit_free_suite_set(struct kunit_suite_set suite_set)
{
struct kunit_suite * const *suites;
@@ -107,47 +130,144 @@ static void kunit_free_suite_set(struct suite_set suite_set)
kfree(suite_set.start);
}
static struct suite_set kunit_filter_suites(const struct suite_set *suite_set,
const char *filter_glob,
int *err)
struct kunit_suite_set
kunit_filter_suites(const struct kunit_suite_set *suite_set,
const char *filter_glob,
char *filters,
char *filter_action,
int *err)
{
int i;
struct kunit_suite **copy, *filtered_suite;
struct suite_set filtered;
struct kunit_test_filter filter;
int i, j, k;
int filter_count = 0;
struct kunit_suite **copy, **copy_start, *filtered_suite, *new_filtered_suite;
struct kunit_suite_set filtered = {NULL, NULL};
struct kunit_glob_filter parsed_glob;
struct kunit_attr_filter *parsed_filters = NULL;
const size_t max = suite_set->end - suite_set->start;
copy = kmalloc_array(max, sizeof(*filtered.start), GFP_KERNEL);
filtered.start = copy;
if (!copy) { /* won't be able to run anything, return an empty set */
filtered.end = copy;
return filtered;
}
copy_start = copy;
kunit_parse_filter_glob(&filter, filter_glob);
if (filter_glob)
kunit_parse_glob_filter(&parsed_glob, filter_glob);
for (i = 0; &suite_set->start[i] != suite_set->end; i++) {
if (!glob_match(filter.suite_glob, suite_set->start[i]->name))
continue;
filtered_suite = kunit_filter_tests(suite_set->start[i], filter.test_glob);
if (IS_ERR(filtered_suite)) {
*err = PTR_ERR(filtered_suite);
/* Parse attribute filters */
if (filters) {
filter_count = kunit_get_filter_count(filters);
parsed_filters = kcalloc(filter_count, sizeof(*parsed_filters), GFP_KERNEL);
if (!parsed_filters) {
kfree(copy);
return filtered;
}
for (j = 0; j < filter_count; j++)
parsed_filters[j] = kunit_next_attr_filter(&filters, err);
if (*err)
goto err;
}
for (i = 0; &suite_set->start[i] != suite_set->end; i++) {
filtered_suite = suite_set->start[i];
if (filter_glob) {
if (!glob_match(parsed_glob.suite_glob, filtered_suite->name))
continue;
filtered_suite = kunit_filter_glob_tests(filtered_suite,
parsed_glob.test_glob);
if (IS_ERR(filtered_suite)) {
*err = PTR_ERR(filtered_suite);
goto err;
}
}
if (filter_count > 0 && parsed_filters != NULL) {
for (k = 0; k < filter_count; k++) {
new_filtered_suite = kunit_filter_attr_tests(filtered_suite,
parsed_filters[k], filter_action, err);
/* Free previous copy of suite */
if (k > 0 || filter_glob) {
kfree(filtered_suite->test_cases);
kfree(filtered_suite);
}
filtered_suite = new_filtered_suite;
if (*err)
goto err;
if (IS_ERR(filtered_suite)) {
*err = PTR_ERR(filtered_suite);
goto err;
}
if (!filtered_suite)
break;
}
}
if (!filtered_suite)
continue;
*copy++ = filtered_suite;
}
filtered.start = copy_start;
filtered.end = copy;
kfree(filter.suite_glob);
kfree(filter.test_glob);
err:
if (*err)
kfree(copy);
if (filter_glob) {
kfree(parsed_glob.suite_glob);
kfree(parsed_glob.test_glob);
}
if (filter_count)
kfree(parsed_filters);
return filtered;
}
void kunit_exec_run_tests(struct kunit_suite_set *suite_set, bool builtin)
{
size_t num_suites = suite_set->end - suite_set->start;
if (builtin || num_suites) {
pr_info("KTAP version 1\n");
pr_info("1..%zu\n", num_suites);
}
__kunit_test_suites_init(suite_set->start, num_suites);
}
void kunit_exec_list_tests(struct kunit_suite_set *suite_set, bool include_attr)
{
struct kunit_suite * const *suites;
struct kunit_case *test_case;
/* Hack: print a ktap header so kunit.py can find the start of KUnit output. */
pr_info("KTAP version 1\n");
for (suites = suite_set->start; suites < suite_set->end; suites++) {
/* Print suite name and suite attributes */
pr_info("%s\n", (*suites)->name);
if (include_attr)
kunit_print_attr((void *)(*suites), false, 0);
/* Print test case name and attributes in suite */
kunit_suite_for_each_test_case((*suites), test_case) {
pr_info("%s.%s\n", (*suites)->name, test_case->name);
if (include_attr)
kunit_print_attr((void *)test_case, true, 0);
}
}
}
#if IS_BUILTIN(CONFIG_KUNIT)
static char *kunit_shutdown;
core_param(kunit_shutdown, kunit_shutdown, charp, 0644);
static void kunit_handle_shutdown(void)
{
if (!kunit_shutdown)
@@ -162,41 +282,20 @@ static void kunit_handle_shutdown(void)
}
static void kunit_exec_run_tests(struct suite_set *suite_set)
{
size_t num_suites = suite_set->end - suite_set->start;
pr_info("KTAP version 1\n");
pr_info("1..%zu\n", num_suites);
__kunit_test_suites_init(suite_set->start, num_suites);
}
static void kunit_exec_list_tests(struct suite_set *suite_set)
{
struct kunit_suite * const *suites;
struct kunit_case *test_case;
/* Hack: print a ktap header so kunit.py can find the start of KUnit output. */
pr_info("KTAP version 1\n");
for (suites = suite_set->start; suites < suite_set->end; suites++)
kunit_suite_for_each_test_case((*suites), test_case) {
pr_info("%s.%s\n", (*suites)->name, test_case->name);
}
}
int kunit_run_all_tests(void)
{
struct suite_set suite_set = {__kunit_suites_start, __kunit_suites_end};
struct kunit_suite_set suite_set = {
__kunit_suites_start, __kunit_suites_end,
};
int err = 0;
if (!kunit_enabled()) {
pr_info("kunit: disabled\n");
goto out;
}
if (filter_glob_param) {
suite_set = kunit_filter_suites(&suite_set, filter_glob_param, &err);
if (filter_glob_param || filter_param) {
suite_set = kunit_filter_suites(&suite_set, filter_glob_param,
filter_param, filter_action_param, &err);
if (err) {
pr_err("kunit executor: error filtering suites: %d\n", err);
goto out;
@@ -204,13 +303,15 @@ int kunit_run_all_tests(void)
}
if (!action_param)
kunit_exec_run_tests(&suite_set);
kunit_exec_run_tests(&suite_set, true);
else if (strcmp(action_param, "list") == 0)
kunit_exec_list_tests(&suite_set);
kunit_exec_list_tests(&suite_set, false);
else if (strcmp(action_param, "list_attr") == 0)
kunit_exec_list_tests(&suite_set, true);
else
pr_err("kunit executor: unknown action '%s'\n", action_param);
if (filter_glob_param) { /* a copy was made of each suite */
if (filter_glob_param || filter_param) { /* a copy was made of each suite */
kunit_free_suite_set(suite_set);
}

View File

@@ -7,6 +7,7 @@
*/
#include <kunit/test.h>
#include <kunit/attributes.h>
static void kfree_at_end(struct kunit *test, const void *to_free);
static struct kunit_suite *alloc_fake_suite(struct kunit *test,
@@ -24,15 +25,15 @@ static struct kunit_case dummy_test_cases[] = {
static void parse_filter_test(struct kunit *test)
{
struct kunit_test_filter filter = {NULL, NULL};
struct kunit_glob_filter filter = {NULL, NULL};
kunit_parse_filter_glob(&filter, "suite");
kunit_parse_glob_filter(&filter, "suite");
KUNIT_EXPECT_STREQ(test, filter.suite_glob, "suite");
KUNIT_EXPECT_FALSE(test, filter.test_glob);
kfree(filter.suite_glob);
kfree(filter.test_glob);
kunit_parse_filter_glob(&filter, "suite.test");
kunit_parse_glob_filter(&filter, "suite.test");
KUNIT_EXPECT_STREQ(test, filter.suite_glob, "suite");
KUNIT_EXPECT_STREQ(test, filter.test_glob, "test");
kfree(filter.suite_glob);
@@ -42,15 +43,17 @@ static void parse_filter_test(struct kunit *test)
static void filter_suites_test(struct kunit *test)
{
struct kunit_suite *subsuite[3] = {NULL, NULL};
struct suite_set suite_set = {.start = subsuite, .end = &subsuite[2]};
struct suite_set got;
struct kunit_suite_set suite_set = {
.start = subsuite, .end = &subsuite[2],
};
struct kunit_suite_set got;
int err = 0;
subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases);
/* Want: suite1, suite2, NULL -> suite2, NULL */
got = kunit_filter_suites(&suite_set, "suite2", &err);
got = kunit_filter_suites(&suite_set, "suite2", NULL, NULL, &err);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start);
KUNIT_ASSERT_EQ(test, err, 0);
kfree_at_end(test, got.start);
@@ -66,15 +69,17 @@ static void filter_suites_test(struct kunit *test)
static void filter_suites_test_glob_test(struct kunit *test)
{
struct kunit_suite *subsuite[3] = {NULL, NULL};
struct suite_set suite_set = {.start = subsuite, .end = &subsuite[2]};
struct suite_set got;
struct kunit_suite_set suite_set = {
.start = subsuite, .end = &subsuite[2],
};
struct kunit_suite_set got;
int err = 0;
subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases);
/* Want: suite1, suite2, NULL -> suite2 (just test1), NULL */
got = kunit_filter_suites(&suite_set, "suite2.test2", &err);
got = kunit_filter_suites(&suite_set, "suite2.test2", NULL, NULL, &err);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start);
KUNIT_ASSERT_EQ(test, err, 0);
kfree_at_end(test, got.start);
@@ -93,14 +98,16 @@ static void filter_suites_test_glob_test(struct kunit *test)
static void filter_suites_to_empty_test(struct kunit *test)
{
struct kunit_suite *subsuite[3] = {NULL, NULL};
struct suite_set suite_set = {.start = subsuite, .end = &subsuite[2]};
struct suite_set got;
struct kunit_suite_set suite_set = {
.start = subsuite, .end = &subsuite[2],
};
struct kunit_suite_set got;
int err = 0;
subsuite[0] = alloc_fake_suite(test, "suite1", dummy_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2", dummy_test_cases);
got = kunit_filter_suites(&suite_set, "not_found", &err);
got = kunit_filter_suites(&suite_set, "not_found", NULL, NULL, &err);
KUNIT_ASSERT_EQ(test, err, 0);
kfree_at_end(test, got.start); /* just in case */
@@ -108,11 +115,132 @@ static void filter_suites_to_empty_test(struct kunit *test)
"should be empty to indicate no match");
}
static void parse_filter_attr_test(struct kunit *test)
{
int j, filter_count;
struct kunit_attr_filter *parsed_filters;
char *filters = "speed>slow, module!=example";
int err = 0;
filter_count = kunit_get_filter_count(filters);
KUNIT_EXPECT_EQ(test, filter_count, 2);
parsed_filters = kunit_kcalloc(test, filter_count, sizeof(*parsed_filters),
GFP_KERNEL);
for (j = 0; j < filter_count; j++) {
parsed_filters[j] = kunit_next_attr_filter(&filters, &err);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "failed to parse filter '%s'", filters[j]);
}
KUNIT_EXPECT_STREQ(test, kunit_attr_filter_name(parsed_filters[0]), "speed");
KUNIT_EXPECT_STREQ(test, parsed_filters[0].input, ">slow");
KUNIT_EXPECT_STREQ(test, kunit_attr_filter_name(parsed_filters[1]), "module");
KUNIT_EXPECT_STREQ(test, parsed_filters[1].input, "!=example");
}
static struct kunit_case dummy_attr_test_cases[] = {
/* .run_case is not important, just needs to be non-NULL */
{ .name = "slow", .run_case = dummy_test, .module_name = "dummy",
.attr.speed = KUNIT_SPEED_SLOW },
{ .name = "normal", .run_case = dummy_test, .module_name = "dummy" },
{},
};
static void filter_attr_test(struct kunit *test)
{
struct kunit_suite *subsuite[3] = {NULL, NULL};
struct kunit_suite_set suite_set = {
.start = subsuite, .end = &subsuite[2],
};
struct kunit_suite_set got;
int err = 0;
subsuite[0] = alloc_fake_suite(test, "normal_suite", dummy_attr_test_cases);
subsuite[1] = alloc_fake_suite(test, "slow_suite", dummy_attr_test_cases);
subsuite[1]->attr.speed = KUNIT_SPEED_SLOW; // Set suite attribute
/*
* Want: normal_suite(slow, normal), slow_suite(slow, normal),
* NULL -> normal_suite(normal), NULL
*
* The normal test in slow_suite is filtered out because the speed
* attribute is unset and thus, the filtering is based on the parent attribute
* of slow.
*/
got = kunit_filter_suites(&suite_set, NULL, "speed>slow", NULL, &err);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start);
KUNIT_ASSERT_EQ(test, err, 0);
kfree_at_end(test, got.start);
/* Validate we just have normal_suite */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]);
KUNIT_EXPECT_STREQ(test, got.start[0]->name, "normal_suite");
KUNIT_ASSERT_EQ(test, got.end - got.start, 1);
/* Now validate we just have normal test case */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
KUNIT_EXPECT_STREQ(test, got.start[0]->test_cases[0].name, "normal");
KUNIT_EXPECT_FALSE(test, got.start[0]->test_cases[1].name);
}
static void filter_attr_empty_test(struct kunit *test)
{
struct kunit_suite *subsuite[3] = {NULL, NULL};
struct kunit_suite_set suite_set = {
.start = subsuite, .end = &subsuite[2],
};
struct kunit_suite_set got;
int err = 0;
subsuite[0] = alloc_fake_suite(test, "suite1", dummy_attr_test_cases);
subsuite[1] = alloc_fake_suite(test, "suite2", dummy_attr_test_cases);
got = kunit_filter_suites(&suite_set, NULL, "module!=dummy", NULL, &err);
KUNIT_ASSERT_EQ(test, err, 0);
kfree_at_end(test, got.start); /* just in case */
KUNIT_EXPECT_PTR_EQ_MSG(test, got.start, got.end,
"should be empty to indicate no match");
}
static void filter_attr_skip_test(struct kunit *test)
{
struct kunit_suite *subsuite[2] = {NULL};
struct kunit_suite_set suite_set = {
.start = subsuite, .end = &subsuite[1],
};
struct kunit_suite_set got;
int err = 0;
subsuite[0] = alloc_fake_suite(test, "suite", dummy_attr_test_cases);
/* Want: suite(slow, normal), NULL -> suite(slow with SKIP, normal), NULL */
got = kunit_filter_suites(&suite_set, NULL, "speed>slow", "skip", &err);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start);
KUNIT_ASSERT_EQ(test, err, 0);
kfree_at_end(test, got.start);
/* Validate we have both the slow and normal test */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
KUNIT_ASSERT_EQ(test, kunit_suite_num_test_cases(got.start[0]), 2);
KUNIT_EXPECT_STREQ(test, got.start[0]->test_cases[0].name, "slow");
KUNIT_EXPECT_STREQ(test, got.start[0]->test_cases[1].name, "normal");
/* Now ensure slow is skipped and normal is not */
KUNIT_EXPECT_EQ(test, got.start[0]->test_cases[0].status, KUNIT_SKIPPED);
KUNIT_EXPECT_FALSE(test, got.start[0]->test_cases[1].status);
}
static struct kunit_case executor_test_cases[] = {
KUNIT_CASE(parse_filter_test),
KUNIT_CASE(filter_suites_test),
KUNIT_CASE(filter_suites_test_glob_test),
KUNIT_CASE(filter_suites_to_empty_test),
KUNIT_CASE(parse_filter_attr_test),
KUNIT_CASE(filter_attr_test),
KUNIT_CASE(filter_attr_empty_test),
KUNIT_CASE(filter_attr_skip_test),
{}
};

View File

@@ -220,6 +220,14 @@ static void example_params_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, param->value % param->value, 0);
}
/*
* This test should always pass. Can be used to practice filtering attributes.
*/
static void example_slow_test(struct kunit *test)
{
KUNIT_EXPECT_EQ(test, 1 + 1, 2);
}
/*
* Here we make a list of all the test cases we want to add to the test suite
* below.
@@ -237,6 +245,7 @@ static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_all_expect_macros_test),
KUNIT_CASE(example_static_stub_test),
KUNIT_CASE_PARAM(example_params_test, example_gen_params),
KUNIT_CASE_SLOW(example_slow_test),
{}
};

View File

@@ -9,6 +9,7 @@
#include <kunit/resource.h>
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include <kunit/attributes.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -168,6 +169,13 @@ size_t kunit_suite_num_test_cases(struct kunit_suite *suite)
}
EXPORT_SYMBOL_GPL(kunit_suite_num_test_cases);
/* Currently supported test levels */
enum {
KUNIT_LEVEL_SUITE = 0,
KUNIT_LEVEL_CASE,
KUNIT_LEVEL_CASE_PARAM,
};
static void kunit_print_suite_start(struct kunit_suite *suite)
{
/*
@@ -181,17 +189,11 @@ static void kunit_print_suite_start(struct kunit_suite *suite)
pr_info(KUNIT_SUBTEST_INDENT "KTAP version 1\n");
pr_info(KUNIT_SUBTEST_INDENT "# Subtest: %s\n",
suite->name);
kunit_print_attr((void *)suite, false, KUNIT_LEVEL_CASE);
pr_info(KUNIT_SUBTEST_INDENT "1..%zd\n",
kunit_suite_num_test_cases(suite));
}
/* Currently supported test levels */
enum {
KUNIT_LEVEL_SUITE = 0,
KUNIT_LEVEL_CASE,
KUNIT_LEVEL_CASE_PARAM,
};
static void kunit_print_ok_not_ok(struct kunit *test,
unsigned int test_level,
enum kunit_status status,
@@ -611,18 +613,22 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_suite_for_each_test_case(suite, test_case) {
struct kunit test = { .param_value = NULL, .param_index = 0 };
struct kunit_result_stats param_stats = { 0 };
test_case->status = KUNIT_SKIPPED;
kunit_init_test(&test, test_case->name, test_case->log);
if (!test_case->generate_params) {
if (test_case->status == KUNIT_SKIPPED) {
/* Test marked as skip */
test.status = KUNIT_SKIPPED;
kunit_update_stats(&param_stats, test.status);
} else if (!test_case->generate_params) {
/* Non-parameterised test. */
test_case->status = KUNIT_SKIPPED;
kunit_run_case_catch_errors(suite, test_case, &test);
kunit_update_stats(&param_stats, test.status);
} else {
/* Get initial param. */
param_desc[0] = '\0';
test.param_value = test_case->generate_params(NULL, param_desc);
test_case->status = KUNIT_SKIPPED;
kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT
"KTAP version 1\n");
kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT
@@ -651,6 +657,7 @@ int kunit_run_tests(struct kunit_suite *suite)
}
}
kunit_print_attr((void *)test_case, true, KUNIT_LEVEL_CASE);
kunit_print_test_stats(&test, param_stats);
@@ -729,12 +736,45 @@ EXPORT_SYMBOL_GPL(__kunit_test_suites_exit);
#ifdef CONFIG_MODULES
static void kunit_module_init(struct module *mod)
{
__kunit_test_suites_init(mod->kunit_suites, mod->num_kunit_suites);
struct kunit_suite_set suite_set = {
mod->kunit_suites, mod->kunit_suites + mod->num_kunit_suites,
};
const char *action = kunit_action();
int err = 0;
suite_set = kunit_filter_suites(&suite_set,
kunit_filter_glob() ?: "*.*",
kunit_filter(), kunit_filter_action(),
&err);
if (err)
pr_err("kunit module: error filtering suites: %d\n", err);
mod->kunit_suites = (struct kunit_suite **)suite_set.start;
mod->num_kunit_suites = suite_set.end - suite_set.start;
if (!action)
kunit_exec_run_tests(&suite_set, false);
else if (!strcmp(action, "list"))
kunit_exec_list_tests(&suite_set, false);
else if (!strcmp(action, "list_attr"))
kunit_exec_list_tests(&suite_set, true);
else
pr_err("kunit: unknown action '%s'\n", action);
}
static void kunit_module_exit(struct module *mod)
{
__kunit_test_suites_exit(mod->kunit_suites, mod->num_kunit_suites);
struct kunit_suite_set suite_set = {
mod->kunit_suites, mod->kunit_suites + mod->num_kunit_suites,
};
const char *action = kunit_action();
if (!action)
__kunit_test_suites_exit(mod->kunit_suites,
mod->num_kunit_suites);
if (suite_set.start)
kunit_free_suite_set(suite_set);
}
static int kunit_module_notify(struct notifier_block *nb, unsigned long val,

View File

@@ -551,10 +551,10 @@ static void strtomem_test(struct kunit *test)
static struct kunit_case memcpy_test_cases[] = {
KUNIT_CASE(memset_test),
KUNIT_CASE(memcpy_test),
KUNIT_CASE(memcpy_large_test),
KUNIT_CASE(memmove_test),
KUNIT_CASE(memmove_large_test),
KUNIT_CASE(memmove_overlap_test),
KUNIT_CASE_SLOW(memcpy_large_test),
KUNIT_CASE_SLOW(memmove_test),
KUNIT_CASE_SLOW(memmove_large_test),
KUNIT_CASE_SLOW(memmove_overlap_test),
KUNIT_CASE(strtomem_test),
{}
};

2
rust/.gitignore vendored
View File

@@ -2,6 +2,8 @@
bindings_generated.rs
bindings_helpers_generated.rs
doctests_kernel_generated.rs
doctests_kernel_generated_kunit.c
uapi_generated.rs
exports_*_generated.h
doc/

View File

@@ -27,6 +27,12 @@ endif
obj-$(CONFIG_RUST) += exports.o
always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.rs
always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.c
obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.o
obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.o
# Avoids running `$(RUSTC)` for the sysroot when it may not be available.
ifdef CONFIG_RUST
@@ -39,9 +45,11 @@ ifeq ($(quiet),silent_)
cargo_quiet=-q
rust_test_quiet=-q
rustdoc_test_quiet=--test-args -q
rustdoc_test_kernel_quiet=>/dev/null
else ifeq ($(quiet),quiet_)
rust_test_quiet=-q
rustdoc_test_quiet=--test-args -q
rustdoc_test_kernel_quiet=>/dev/null
else
cargo_quiet=--verbose
endif
@@ -157,6 +165,27 @@ quiet_cmd_rustdoc_test = RUSTDOC T $<
-L$(objtree)/$(obj)/test --output $(objtree)/$(obj)/doc \
--crate-name $(subst rusttest-,,$@) $<
quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $<
cmd_rustdoc_test_kernel = \
rm -rf $(objtree)/$(obj)/test/doctests/kernel; \
mkdir -p $(objtree)/$(obj)/test/doctests/kernel; \
OBJTREE=$(abspath $(objtree)) \
$(RUSTDOC) --test $(rust_flags) \
@$(objtree)/include/generated/rustc_cfg \
-L$(objtree)/$(obj) --extern alloc --extern kernel \
--extern build_error --extern macros \
--extern bindings --extern uapi \
--no-run --crate-name kernel -Zunstable-options \
--test-builder $(objtree)/scripts/rustdoc_test_builder \
$< $(rustdoc_test_kernel_quiet); \
$(objtree)/scripts/rustdoc_test_gen
%/doctests_kernel_generated.rs %/doctests_kernel_generated_kunit.c: \
$(src)/kernel/lib.rs $(obj)/kernel.o \
$(objtree)/scripts/rustdoc_test_builder \
$(objtree)/scripts/rustdoc_test_gen FORCE
$(call if_changed,rustdoc_test_kernel)
# We cannot use `-Zpanic-abort-tests` because some tests are dynamic,
# so for the moment we skip `-Cpanic=abort`.
quiet_cmd_rustc_test = RUSTC T $<

View File

@@ -6,6 +6,7 @@
* Sorted alphabetically.
*/
#include <kunit/test.h>
#include <linux/errname.h>
#include <linux/slab.h>
#include <linux/refcount.h>

Some files were not shown because too many files have changed in this diff Show More