mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:41:42 +00:00
linux-kselftest-kunit-6.6-rc1
This kunit update for Linux 6.6.rc1 consists of: -- Adds support for running Rust documentation tests as KUnit tests -- Makes init, str, sync, types doctests compilable/testable -- Adds support for attributes API which include speed, modules attributes, ability to filter and report attributes. -- Adds support for marking tests slow using attributes API. -- Adds attributes API documentation -- Fixes to wild-memory-access bug in kunit_filter_suites() and a possible memory leak in kunit_filter_suites() -- Adds support for counting number of test suites in a module, list action to kunit test modules, and test filtering on module tests. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEPZKym/RZuOCGeA/kCwJExA0NQxwFAmTsxL8ACgkQCwJExA0N Qxwt6BAA5FgF7nUeGRZCnot4MQCNGRThxsns2k3CKjM1Iokp8tstTDoNHXzk2veS WlRYOHFQqQOVTVRP+laXyjjMMHnlnhFxqbv93UKsen4JIUJDLFLq9x+0i+0bZh97 N1rE5cKUnqjAOL6MIJuomW9IzEIrbMcqdljm6SOCZp90NLvq1+I4pDGLgx2bxcow Y/7dkx+dnlEsoACZ19CL1L2TaR21GpKdpOudpHNCShsbE0aOAlyHAVcmH64FTqCy Z1LtrA0odS71q0yxDVCk5X3cIkeVfGBMz6aMZBRzS9k5jU4H1EN1eG1rGdGErIe5 YduwX3KMikYJB2stT64T1vgldIpT/emxqkBigmxQ37g3Flgopz4bI1snMBry+nKb ViD/WQNjsf2iL8MooCgYBzH7yjmX6lXXQTZXROogBj4lP2/0gHiQVZyXZEAjtoO3 uNzUbfHQGnvtTphBHV4nNGaO+7kU9Y/oX8TYFcSYJQzcH5UVx16uBwevZjT1bii/ q89bRAQLnJpzkR93SGpnmsRgoDcYJSYsEA1o/f9Eqq8j3guOS2idpJvkheXq8+A2 MqTSOCJHENKZ3v0UGKlvZUPStaMaqN58z/VjlWug5EaB83LLfPcXJrGjz/EHk967 hYDHcwPoamTegr1zlg3ckOLiWEhga2tv6aHPkshkcFphpnhRU/c= =Nsb8 -----END PGP SIGNATURE----- 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:
commit
815c24a085
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
50
include/kunit/attributes.h
Normal file
50
include/kunit/attributes.h
Normal 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 */
|
@ -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().
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
414
lib/kunit/attributes.c
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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(¶m_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(¶m_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,
|
||||
|
@ -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
2
rust/.gitignore
vendored
@ -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/
|
||||
|
@ -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 $<
|
||||
|
@ -6,6 +6,7 @@
|
||||
* Sorted alphabetically.
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
#include <linux/errname.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/refcount.h>
|
||||
|
@ -18,6 +18,7 @@
|
||||
* accidentally exposed.
|
||||
*/
|
||||
|
||||
#include <kunit/test-bug.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/err.h>
|
||||
@ -135,6 +136,12 @@ void rust_helper_put_task_struct(struct task_struct *t)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rust_helper_put_task_struct);
|
||||
|
||||
struct kunit *rust_helper_kunit_get_current_test(void)
|
||||
{
|
||||
return kunit_get_current_test();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test);
|
||||
|
||||
/*
|
||||
* We use `bindgen`'s `--size_t-is-usize` option to bind the C `size_t` type
|
||||
* as the Rust `usize` type, so we can use it in contexts where Rust
|
||||
|
@ -120,14 +120,24 @@
|
||||
//! `slot` gets called.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use kernel::{prelude::*, init};
|
||||
//! # #![allow(unreachable_pub, clippy::disallowed_names)]
|
||||
//! use kernel::{prelude::*, init, types::Opaque};
|
||||
//! use core::{ptr::addr_of_mut, marker::PhantomPinned, pin::Pin};
|
||||
//! # mod bindings {
|
||||
//! # #![allow(non_camel_case_types)]
|
||||
//! # pub struct foo;
|
||||
//! # pub unsafe fn init_foo(_ptr: *mut foo) {}
|
||||
//! # pub unsafe fn destroy_foo(_ptr: *mut foo) {}
|
||||
//! # pub unsafe fn enable_foo(_ptr: *mut foo, _flags: u32) -> i32 { 0 }
|
||||
//! # }
|
||||
//! # // `Error::from_errno` is `pub(crate)` in the `kernel` crate, thus provide a workaround.
|
||||
//! # trait FromErrno {
|
||||
//! # fn from_errno(errno: core::ffi::c_int) -> Error {
|
||||
//! # // Dummy error that can be constructed outside the `kernel` crate.
|
||||
//! # Error::from(core::fmt::Error)
|
||||
//! # }
|
||||
//! # }
|
||||
//! # impl FromErrno for Error {}
|
||||
//! /// # Invariants
|
||||
//! ///
|
||||
//! /// `foo` is always initialized
|
||||
@ -158,7 +168,7 @@
|
||||
//! if err != 0 {
|
||||
//! // Enabling has failed, first clean up the foo and then return the error.
|
||||
//! bindings::destroy_foo(Opaque::raw_get(foo));
|
||||
//! return Err(Error::from_kernel_errno(err));
|
||||
//! return Err(Error::from_errno(err));
|
||||
//! }
|
||||
//!
|
||||
//! // All fields of `RawFoo` have been initialized, since `_p` is a ZST.
|
||||
@ -226,8 +236,7 @@ pub mod macros;
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![allow(clippy::disallowed_names, clippy::new_ret_no_self)]
|
||||
/// # use kernel::{init, pin_init, stack_pin_init, init::*, sync::Mutex, new_mutex};
|
||||
/// # use macros::pin_data;
|
||||
/// # use kernel::{init, macros::pin_data, pin_init, stack_pin_init, init::*, sync::Mutex, new_mutex};
|
||||
/// # use core::pin::Pin;
|
||||
/// #[pin_data]
|
||||
/// struct Foo {
|
||||
@ -277,7 +286,7 @@ macro_rules! stack_pin_init {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # #![allow(clippy::disallowed_names, clippy::new_ret_no_self)]
|
||||
/// # use kernel::{init, pin_init, stack_try_pin_init, init::*, sync::Mutex, new_mutex};
|
||||
/// # use macros::pin_data;
|
||||
@ -303,7 +312,7 @@ macro_rules! stack_pin_init {
|
||||
/// pr_info!("a: {}", &*foo.a.lock());
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// ```rust,ignore
|
||||
/// # #![allow(clippy::disallowed_names, clippy::new_ret_no_self)]
|
||||
/// # use kernel::{init, pin_init, stack_try_pin_init, init::*, sync::Mutex, new_mutex};
|
||||
/// # use macros::pin_data;
|
||||
@ -513,8 +522,7 @@ macro_rules! stack_try_pin_init {
|
||||
/// For instance:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use kernel::pin_init;
|
||||
/// # use macros::pin_data;
|
||||
/// # use kernel::{macros::pin_data, pin_init};
|
||||
/// # use core::{ptr::addr_of_mut, marker::PhantomPinned};
|
||||
/// #[pin_data]
|
||||
/// struct Buf {
|
||||
@ -841,7 +849,7 @@ macro_rules! init {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use kernel::{init::PinInit, error::Error, InPlaceInit};
|
||||
/// use kernel::{init::{PinInit, zeroed}, error::Error};
|
||||
/// struct BigBuf {
|
||||
/// big: Box<[u8; 1024 * 1024 * 1024]>,
|
||||
/// small: [u8; 1024 * 1024],
|
||||
|
163
rust/kernel/kunit.rs
Normal file
163
rust/kernel/kunit.rs
Normal file
@ -0,0 +1,163 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! KUnit-based macros for Rust unit tests.
|
||||
//!
|
||||
//! C header: [`include/kunit/test.h`](../../../../../include/kunit/test.h)
|
||||
//!
|
||||
//! Reference: <https://docs.kernel.org/dev-tools/kunit/index.html>
|
||||
|
||||
use core::{ffi::c_void, fmt};
|
||||
|
||||
/// Prints a KUnit error-level message.
|
||||
///
|
||||
/// Public but hidden since it should only be used from KUnit generated code.
|
||||
#[doc(hidden)]
|
||||
pub fn err(args: fmt::Arguments<'_>) {
|
||||
// SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
|
||||
// are passing.
|
||||
#[cfg(CONFIG_PRINTK)]
|
||||
unsafe {
|
||||
bindings::_printk(
|
||||
b"\x013%pA\0".as_ptr() as _,
|
||||
&args as *const _ as *const c_void,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a KUnit info-level message.
|
||||
///
|
||||
/// Public but hidden since it should only be used from KUnit generated code.
|
||||
#[doc(hidden)]
|
||||
pub fn info(args: fmt::Arguments<'_>) {
|
||||
// SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we
|
||||
// are passing.
|
||||
#[cfg(CONFIG_PRINTK)]
|
||||
unsafe {
|
||||
bindings::_printk(
|
||||
b"\x016%pA\0".as_ptr() as _,
|
||||
&args as *const _ as *const c_void,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that a boolean expression is `true` at runtime.
|
||||
///
|
||||
/// Public but hidden since it should only be used from generated tests.
|
||||
///
|
||||
/// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit
|
||||
/// facilities. See [`assert!`] for more details.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! kunit_assert {
|
||||
($name:literal, $file:literal, $diff:expr, $condition:expr $(,)?) => {
|
||||
'out: {
|
||||
// Do nothing if the condition is `true`.
|
||||
if $condition {
|
||||
break 'out;
|
||||
}
|
||||
|
||||
static FILE: &'static $crate::str::CStr = $crate::c_str!($file);
|
||||
static LINE: i32 = core::line!() as i32 - $diff;
|
||||
static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition));
|
||||
|
||||
// SAFETY: FFI call without safety requirements.
|
||||
let kunit_test = unsafe { $crate::bindings::kunit_get_current_test() };
|
||||
if kunit_test.is_null() {
|
||||
// The assertion failed but this task is not running a KUnit test, so we cannot call
|
||||
// KUnit, but at least print an error to the kernel log. This may happen if this
|
||||
// macro is called from an spawned thread in a test (see
|
||||
// `scripts/rustdoc_test_gen.rs`) or if some non-test code calls this macro by
|
||||
// mistake (it is hidden to prevent that).
|
||||
//
|
||||
// This mimics KUnit's failed assertion format.
|
||||
$crate::kunit::err(format_args!(
|
||||
" # {}: ASSERTION FAILED at {FILE}:{LINE}\n",
|
||||
$name
|
||||
));
|
||||
$crate::kunit::err(format_args!(
|
||||
" Expected {CONDITION} to be true, but is false\n"
|
||||
));
|
||||
$crate::kunit::err(format_args!(
|
||||
" Failure not reported to KUnit since this is a non-KUnit task\n"
|
||||
));
|
||||
break 'out;
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct Location($crate::bindings::kunit_loc);
|
||||
|
||||
#[repr(transparent)]
|
||||
struct UnaryAssert($crate::bindings::kunit_unary_assert);
|
||||
|
||||
// SAFETY: There is only a static instance and in that one the pointer field points to
|
||||
// an immutable C string.
|
||||
unsafe impl Sync for Location {}
|
||||
|
||||
// SAFETY: There is only a static instance and in that one the pointer field points to
|
||||
// an immutable C string.
|
||||
unsafe impl Sync for UnaryAssert {}
|
||||
|
||||
static LOCATION: Location = Location($crate::bindings::kunit_loc {
|
||||
file: FILE.as_char_ptr(),
|
||||
line: LINE,
|
||||
});
|
||||
static ASSERTION: UnaryAssert = UnaryAssert($crate::bindings::kunit_unary_assert {
|
||||
assert: $crate::bindings::kunit_assert {},
|
||||
condition: CONDITION.as_char_ptr(),
|
||||
expected_true: true,
|
||||
});
|
||||
|
||||
// SAFETY:
|
||||
// - FFI call.
|
||||
// - The `kunit_test` pointer is valid because we got it from
|
||||
// `kunit_get_current_test()` and it was not null. This means we are in a KUnit
|
||||
// test, and that the pointer can be passed to KUnit functions and assertions.
|
||||
// - The string pointers (`file` and `condition` above) point to null-terminated
|
||||
// strings since they are `CStr`s.
|
||||
// - The function pointer (`format`) points to the proper function.
|
||||
// - The pointers passed will remain valid since they point to `static`s.
|
||||
// - The format string is allowed to be null.
|
||||
// - There are, however, problems with this: first of all, this will end up stopping
|
||||
// the thread, without running destructors. While that is problematic in itself,
|
||||
// it is considered UB to have what is effectively a forced foreign unwind
|
||||
// with `extern "C"` ABI. One could observe the stack that is now gone from
|
||||
// another thread. We should avoid pinning stack variables to prevent library UB,
|
||||
// too. For the moment, given that test failures are reported immediately before the
|
||||
// next test runs, that test failures should be fixed and that KUnit is explicitly
|
||||
// documented as not suitable for production environments, we feel it is reasonable.
|
||||
unsafe {
|
||||
$crate::bindings::__kunit_do_failed_assertion(
|
||||
kunit_test,
|
||||
core::ptr::addr_of!(LOCATION.0),
|
||||
$crate::bindings::kunit_assert_type_KUNIT_ASSERTION,
|
||||
core::ptr::addr_of!(ASSERTION.0.assert),
|
||||
Some($crate::bindings::kunit_unary_assert_format),
|
||||
core::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
// SAFETY: FFI call; the `test` pointer is valid because this hidden macro should only
|
||||
// be called by the generated documentation tests which forward the test pointer given
|
||||
// by KUnit.
|
||||
unsafe {
|
||||
$crate::bindings::__kunit_abort(kunit_test);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Asserts that two expressions are equal to each other (using [`PartialEq`]).
|
||||
///
|
||||
/// Public but hidden since it should only be used from generated tests.
|
||||
///
|
||||
/// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit
|
||||
/// facilities. See [`assert!`] for more details.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! kunit_assert_eq {
|
||||
($name:literal, $file:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{
|
||||
// For the moment, we just forward to the expression assert because, for binary asserts,
|
||||
// KUnit supports only a few types (e.g. integers).
|
||||
$crate::kunit_assert!($name, $file, $diff, $left == $right);
|
||||
}};
|
||||
}
|
@ -34,6 +34,8 @@ mod build_assert;
|
||||
pub mod error;
|
||||
pub mod init;
|
||||
pub mod ioctl;
|
||||
#[cfg(CONFIG_KUNIT)]
|
||||
pub mod kunit;
|
||||
pub mod prelude;
|
||||
pub mod print;
|
||||
mod static_assert;
|
||||
|
@ -213,6 +213,7 @@ impl fmt::Display for CStr {
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::c_str;
|
||||
/// # use kernel::fmt;
|
||||
/// # use kernel::str::CStr;
|
||||
/// # use kernel::str::CString;
|
||||
/// let penguin = c_str!("🐧");
|
||||
@ -241,6 +242,7 @@ impl fmt::Debug for CStr {
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::c_str;
|
||||
/// # use kernel::fmt;
|
||||
/// # use kernel::str::CStr;
|
||||
/// # use kernel::str::CString;
|
||||
/// let penguin = c_str!("🐧");
|
||||
@ -529,7 +531,7 @@ impl fmt::Write for Formatter {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kernel::str::CString;
|
||||
/// use kernel::{str::CString, fmt};
|
||||
///
|
||||
/// let s = CString::try_from_fmt(fmt!("{}{}{}", "abc", 10, 20)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "abc1020\0".as_bytes());
|
||||
|
@ -73,6 +73,7 @@ mod std_vendor;
|
||||
/// assert_eq!(cloned.b, 20);
|
||||
///
|
||||
/// // The refcount drops to zero when `cloned` goes out of scope, and the memory is freed.
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// Using `Arc<T>` as the type of `self`:
|
||||
@ -98,6 +99,7 @@ mod std_vendor;
|
||||
/// let obj = Arc::try_new(Example { a: 10, b: 20 })?;
|
||||
/// obj.use_reference();
|
||||
/// obj.take_over();
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// Coercion from `Arc<Example>` to `Arc<dyn MyTrait>`:
|
||||
@ -121,6 +123,7 @@ mod std_vendor;
|
||||
///
|
||||
/// // `coerced` has type `Arc<dyn MyTrait>`.
|
||||
/// let coerced: Arc<dyn MyTrait> = obj;
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
pub struct Arc<T: ?Sized> {
|
||||
ptr: NonNull<ArcInner<T>>,
|
||||
@ -336,7 +339,7 @@ impl<T: ?Sized> From<Pin<UniqueArc<T>>> for Arc<T> {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use crate::sync::{Arc, ArcBorrow};
|
||||
/// use kernel::sync::{Arc, ArcBorrow};
|
||||
///
|
||||
/// struct Example;
|
||||
///
|
||||
@ -349,12 +352,13 @@ impl<T: ?Sized> From<Pin<UniqueArc<T>>> for Arc<T> {
|
||||
///
|
||||
/// // Assert that both `obj` and `cloned` point to the same underlying object.
|
||||
/// assert!(core::ptr::eq(&*obj, &*cloned));
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// Using `ArcBorrow<T>` as the type of `self`:
|
||||
///
|
||||
/// ```
|
||||
/// use crate::sync::{Arc, ArcBorrow};
|
||||
/// use kernel::sync::{Arc, ArcBorrow};
|
||||
///
|
||||
/// struct Example {
|
||||
/// a: u32,
|
||||
@ -369,6 +373,7 @@ impl<T: ?Sized> From<Pin<UniqueArc<T>>> for Arc<T> {
|
||||
///
|
||||
/// let obj = Arc::try_new(Example { a: 10, b: 20 })?;
|
||||
/// obj.as_arc_borrow().use_reference();
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
pub struct ArcBorrow<'a, T: ?Sized + 'a> {
|
||||
inner: NonNull<ArcInner<T>>,
|
||||
|
@ -63,6 +63,7 @@ macro_rules! new_mutex {
|
||||
/// assert_eq!(e.c, 10);
|
||||
/// assert_eq!(e.d.lock().a, 20);
|
||||
/// assert_eq!(e.d.lock().b, 30);
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// The following example shows how to use interior mutability to modify the contents of a struct
|
||||
|
@ -61,6 +61,7 @@ macro_rules! new_spinlock {
|
||||
/// assert_eq!(e.c, 10);
|
||||
/// assert_eq!(e.d.lock().a, 20);
|
||||
/// assert_eq!(e.d.lock().b, 30);
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// The following example shows how to use interior mutability to modify the contents of a struct
|
||||
|
@ -91,7 +91,7 @@ impl ForeignOwnable for () {
|
||||
/// In the example below, we have multiple exit paths and we want to log regardless of which one is
|
||||
/// taken:
|
||||
/// ```
|
||||
/// # use kernel::ScopeGuard;
|
||||
/// # use kernel::types::ScopeGuard;
|
||||
/// fn example1(arg: bool) {
|
||||
/// let _log = ScopeGuard::new(|| pr_info!("example1 completed\n"));
|
||||
///
|
||||
@ -109,7 +109,7 @@ impl ForeignOwnable for () {
|
||||
/// In the example below, we want to log the same message on all early exits but a different one on
|
||||
/// the main exit path:
|
||||
/// ```
|
||||
/// # use kernel::ScopeGuard;
|
||||
/// # use kernel::types::ScopeGuard;
|
||||
/// fn example2(arg: bool) {
|
||||
/// let log = ScopeGuard::new(|| pr_info!("example2 returned early\n"));
|
||||
///
|
||||
@ -130,7 +130,7 @@ impl ForeignOwnable for () {
|
||||
/// In the example below, we need a mutable object (the vector) to be accessible within the log
|
||||
/// function, so we wrap it in the [`ScopeGuard`]:
|
||||
/// ```
|
||||
/// # use kernel::ScopeGuard;
|
||||
/// # use kernel::types::ScopeGuard;
|
||||
/// fn example3(arg: bool) -> Result {
|
||||
/// let mut vec =
|
||||
/// ScopeGuard::new_with_data(Vec::new(), |v| pr_info!("vec had {} elements\n", v.len()));
|
||||
|
2
scripts/.gitignore
vendored
2
scripts/.gitignore
vendored
@ -5,6 +5,8 @@
|
||||
/kallsyms
|
||||
/module.lds
|
||||
/recordmcount
|
||||
/rustdoc_test_builder
|
||||
/rustdoc_test_gen
|
||||
/sign-file
|
||||
/sorttable
|
||||
/target.json
|
||||
|
@ -9,6 +9,8 @@ hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable
|
||||
hostprogs-always-$(CONFIG_ASN1) += asn1_compiler
|
||||
hostprogs-always-$(CONFIG_MODULE_SIG_FORMAT) += sign-file
|
||||
hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert
|
||||
hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder
|
||||
hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen
|
||||
always-$(CONFIG_RUST) += target.json
|
||||
|
||||
filechk_rust_target = $< < include/config/auto.conf
|
||||
@ -18,6 +20,8 @@ $(obj)/target.json: scripts/generate_rust_target include/config/auto.conf FORCE
|
||||
|
||||
hostprogs += generate_rust_target
|
||||
generate_rust_target-rust := y
|
||||
rustdoc_test_builder-rust := y
|
||||
rustdoc_test_gen-rust := y
|
||||
|
||||
HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include
|
||||
HOSTLDLIBS_sorttable = -lpthread
|
||||
|
72
scripts/rustdoc_test_builder.rs
Normal file
72
scripts/rustdoc_test_builder.rs
Normal file
@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Test builder for `rustdoc`-generated tests.
|
||||
//!
|
||||
//! This script is a hack to extract the test from `rustdoc`'s output. Ideally, `rustdoc` would
|
||||
//! have an option to generate this information instead, e.g. as JSON output.
|
||||
//!
|
||||
//! The `rustdoc`-generated test names look like `{file}_{line}_{number}`, e.g.
|
||||
//! `...path_rust_kernel_sync_arc_rs_42_0`. `number` is the "test number", needed in cases like
|
||||
//! a macro that expands into items with doctests is invoked several times within the same line.
|
||||
//!
|
||||
//! However, since these names are used for bisection in CI, the line number makes it not stable
|
||||
//! at all. In the future, we would like `rustdoc` to give us the Rust item path associated with
|
||||
//! the test, plus a "test number" (for cases with several examples per item) and generate a name
|
||||
//! from that. For the moment, we generate ourselves a new name, `{file}_{number}` instead, in
|
||||
//! the `gen` script (done there since we need to be aware of all the tests in a given file).
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
let mut body = String::new();
|
||||
stdin.read_to_string(&mut body).unwrap();
|
||||
|
||||
// Find the generated function name looking for the inner function inside `main()`.
|
||||
//
|
||||
// The line we are looking for looks like one of the following:
|
||||
//
|
||||
// ```
|
||||
// fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_28_0() {
|
||||
// fn main() { #[allow(non_snake_case)] fn _doctest_main_rust_kernel_file_rs_37_0() -> Result<(), impl core::fmt::Debug> {
|
||||
// ```
|
||||
//
|
||||
// It should be unlikely that doctest code matches such lines (when code is formatted properly).
|
||||
let rustdoc_function_name = body
|
||||
.lines()
|
||||
.find_map(|line| {
|
||||
Some(
|
||||
line.split_once("fn main() {")?
|
||||
.1
|
||||
.split_once("fn ")?
|
||||
.1
|
||||
.split_once("()")?
|
||||
.0,
|
||||
)
|
||||
.filter(|x| x.chars().all(|c| c.is_alphanumeric() || c == '_'))
|
||||
})
|
||||
.expect("No test function found in `rustdoc`'s output.");
|
||||
|
||||
// Qualify `Result` to avoid the collision with our own `Result` coming from the prelude.
|
||||
let body = body.replace(
|
||||
&format!("{rustdoc_function_name}() -> Result<(), impl core::fmt::Debug> {{"),
|
||||
&format!("{rustdoc_function_name}() -> core::result::Result<(), impl core::fmt::Debug> {{"),
|
||||
);
|
||||
|
||||
// For tests that get generated with `Result`, like above, `rustdoc` generates an `unwrap()` on
|
||||
// the return value to check there were no returned errors. Instead, we use our assert macro
|
||||
// since we want to just fail the test, not panic the kernel.
|
||||
//
|
||||
// We save the result in a variable so that the failed assertion message looks nicer.
|
||||
let body = body.replace(
|
||||
&format!("}} {rustdoc_function_name}().unwrap() }}"),
|
||||
&format!("}} let test_return_value = {rustdoc_function_name}(); assert!(test_return_value.is_ok()); }}"),
|
||||
);
|
||||
|
||||
// Figure out a smaller test name based on the generated function name.
|
||||
let name = rustdoc_function_name.split_once("_rust_kernel_").unwrap().1;
|
||||
|
||||
let path = format!("rust/test/doctests/kernel/{name}");
|
||||
|
||||
std::fs::write(path, body.as_bytes()).unwrap();
|
||||
}
|
260
scripts/rustdoc_test_gen.rs
Normal file
260
scripts/rustdoc_test_gen.rs
Normal file
@ -0,0 +1,260 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Generates KUnit tests from saved `rustdoc`-generated tests.
|
||||
//!
|
||||
//! KUnit passes a context (`struct kunit *`) to each test, which should be forwarded to the other
|
||||
//! KUnit functions and macros.
|
||||
//!
|
||||
//! However, we want to keep this as an implementation detail because:
|
||||
//!
|
||||
//! - Test code should not care about the implementation.
|
||||
//!
|
||||
//! - Documentation looks worse if it needs to carry extra details unrelated to the piece
|
||||
//! being described.
|
||||
//!
|
||||
//! - Test code should be able to define functions and call them, without having to carry
|
||||
//! the context.
|
||||
//!
|
||||
//! - Later on, we may want to be able to test non-kernel code (e.g. `core`, `alloc` or
|
||||
//! third-party crates) which likely use the standard library `assert*!` macros.
|
||||
//!
|
||||
//! For this reason, instead of the passed context, `kunit_get_current_test()` is used instead
|
||||
//! (i.e. `current->kunit_test`).
|
||||
//!
|
||||
//! Note that this means other threads/tasks potentially spawned by a given test, if failing, will
|
||||
//! report the failure in the kernel log but will not fail the actual test. Saving the pointer in
|
||||
//! e.g. a `static` per test does not fully solve the issue either, because currently KUnit does
|
||||
//! not support assertions (only expectations) from other tasks. Thus leave that feature for
|
||||
//! the future, which simplifies the code here too. We could also simply not allow `assert`s in
|
||||
//! other tasks, but that seems overly constraining, and we do want to support them, eventually.
|
||||
|
||||
use std::{
|
||||
fs,
|
||||
fs::File,
|
||||
io::{BufWriter, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Find the real path to the original file based on the `file` portion of the test name.
|
||||
///
|
||||
/// `rustdoc` generated `file`s look like `sync_locked_by_rs`. Underscores (except the last one)
|
||||
/// may represent an actual underscore in a directory/file, or a path separator. Thus the actual
|
||||
/// file might be `sync_locked_by.rs`, `sync/locked_by.rs`, `sync_locked/by.rs` or
|
||||
/// `sync/locked/by.rs`. This function walks the file system to determine which is the real one.
|
||||
///
|
||||
/// This does require that ambiguities do not exist, but that seems fair, especially since this is
|
||||
/// all supposed to be temporary until `rustdoc` gives us proper metadata to build this. If such
|
||||
/// ambiguities are detected, they are diagnosed and the script panics.
|
||||
fn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: &str) -> &'a str {
|
||||
valid_paths.clear();
|
||||
|
||||
let potential_components: Vec<&str> = file.strip_suffix("_rs").unwrap().split('_').collect();
|
||||
|
||||
find_candidates(srctree, valid_paths, Path::new(""), &potential_components);
|
||||
fn find_candidates(
|
||||
srctree: &Path,
|
||||
valid_paths: &mut Vec<PathBuf>,
|
||||
prefix: &Path,
|
||||
potential_components: &[&str],
|
||||
) {
|
||||
// The base case: check whether all the potential components left, joined by underscores,
|
||||
// is a file.
|
||||
let joined_potential_components = potential_components.join("_") + ".rs";
|
||||
if srctree
|
||||
.join("rust/kernel")
|
||||
.join(prefix)
|
||||
.join(&joined_potential_components)
|
||||
.is_file()
|
||||
{
|
||||
// Avoid `srctree` here in order to keep paths relative to it in the KTAP output.
|
||||
valid_paths.push(
|
||||
Path::new("rust/kernel")
|
||||
.join(prefix)
|
||||
.join(joined_potential_components),
|
||||
);
|
||||
}
|
||||
|
||||
// In addition, check whether each component prefix, joined by underscores, is a directory.
|
||||
// If not, there is no need to check for combinations with that prefix.
|
||||
for i in 1..potential_components.len() {
|
||||
let (components_prefix, components_rest) = potential_components.split_at(i);
|
||||
let prefix = prefix.join(components_prefix.join("_"));
|
||||
if srctree.join("rust/kernel").join(&prefix).is_dir() {
|
||||
find_candidates(srctree, valid_paths, &prefix, components_rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
valid_paths.len() > 0,
|
||||
"No path candidates found. This is likely a bug in the build system, or some files went \
|
||||
away while compiling."
|
||||
);
|
||||
|
||||
if valid_paths.len() > 1 {
|
||||
eprintln!("Several path candidates found:");
|
||||
for path in valid_paths {
|
||||
eprintln!(" {path:?}");
|
||||
}
|
||||
panic!(
|
||||
"Several path candidates found, please resolve the ambiguity by renaming a file or \
|
||||
folder."
|
||||
);
|
||||
}
|
||||
|
||||
valid_paths[0].to_str().unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let srctree = std::env::var("srctree").unwrap();
|
||||
let srctree = Path::new(&srctree);
|
||||
|
||||
let mut paths = fs::read_dir("rust/test/doctests/kernel")
|
||||
.unwrap()
|
||||
.map(|entry| entry.unwrap().path())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort paths.
|
||||
paths.sort();
|
||||
|
||||
let mut rust_tests = String::new();
|
||||
let mut c_test_declarations = String::new();
|
||||
let mut c_test_cases = String::new();
|
||||
let mut body = String::new();
|
||||
let mut last_file = String::new();
|
||||
let mut number = 0;
|
||||
let mut valid_paths: Vec<PathBuf> = Vec::new();
|
||||
let mut real_path: &str = "";
|
||||
for path in paths {
|
||||
// The `name` follows the `{file}_{line}_{number}` pattern (see description in
|
||||
// `scripts/rustdoc_test_builder.rs`). Discard the `number`.
|
||||
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
|
||||
// Extract the `file` and the `line`, discarding the `number`.
|
||||
let (file, line) = name.rsplit_once('_').unwrap().0.rsplit_once('_').unwrap();
|
||||
|
||||
// Generate an ID sequence ("test number") for each one in the file.
|
||||
if file == last_file {
|
||||
number += 1;
|
||||
} else {
|
||||
number = 0;
|
||||
last_file = file.to_string();
|
||||
|
||||
// Figure out the real path, only once per file.
|
||||
real_path = find_real_path(srctree, &mut valid_paths, file);
|
||||
}
|
||||
|
||||
// Generate a KUnit name (i.e. test name and C symbol) for this test.
|
||||
//
|
||||
// We avoid the line number, like `rustdoc` does, to make things slightly more stable for
|
||||
// bisection purposes. However, to aid developers in mapping back what test failed, we will
|
||||
// print a diagnostics line in the KTAP report.
|
||||
let kunit_name = format!("rust_doctest_kernel_{file}_{number}");
|
||||
|
||||
// Read the test's text contents to dump it below.
|
||||
body.clear();
|
||||
File::open(path).unwrap().read_to_string(&mut body).unwrap();
|
||||
|
||||
// Calculate how many lines before `main` function (including the `main` function line).
|
||||
let body_offset = body
|
||||
.lines()
|
||||
.take_while(|line| !line.contains("fn main() {"))
|
||||
.count()
|
||||
+ 1;
|
||||
|
||||
use std::fmt::Write;
|
||||
write!(
|
||||
rust_tests,
|
||||
r#"/// Generated `{name}` KUnit test case from a Rust documentation test.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn {kunit_name}(__kunit_test: *mut kernel::bindings::kunit) {{
|
||||
/// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
|
||||
#[allow(unused)]
|
||||
macro_rules! assert {{
|
||||
($cond:expr $(,)?) => {{{{
|
||||
kernel::kunit_assert!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $cond);
|
||||
}}}}
|
||||
}}
|
||||
|
||||
/// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
|
||||
#[allow(unused)]
|
||||
macro_rules! assert_eq {{
|
||||
($left:expr, $right:expr $(,)?) => {{{{
|
||||
kernel::kunit_assert_eq!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right);
|
||||
}}}}
|
||||
}}
|
||||
|
||||
// Many tests need the prelude, so provide it by default.
|
||||
#[allow(unused)]
|
||||
use kernel::prelude::*;
|
||||
|
||||
// Unconditionally print the location of the original doctest (i.e. rather than the location in
|
||||
// the generated file) so that developers can easily map the test back to the source code.
|
||||
//
|
||||
// This information is also printed when assertions fail, but this helps in the successful cases
|
||||
// when the user is running KUnit manually, or when passing `--raw_output` to `kunit.py`.
|
||||
//
|
||||
// This follows the syntax for declaring test metadata in the proposed KTAP v2 spec, which may
|
||||
// be used for the proposed KUnit test attributes API. Thus hopefully this will make migration
|
||||
// easier later on.
|
||||
kernel::kunit::info(format_args!(" # {kunit_name}.location: {real_path}:{line}\n"));
|
||||
|
||||
/// The anchor where the test code body starts.
|
||||
#[allow(unused)]
|
||||
static __DOCTEST_ANCHOR: i32 = core::line!() as i32 + {body_offset} + 1;
|
||||
{{
|
||||
{body}
|
||||
main();
|
||||
}}
|
||||
}}
|
||||
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap();
|
||||
write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap();
|
||||
}
|
||||
|
||||
let rust_tests = rust_tests.trim();
|
||||
let c_test_declarations = c_test_declarations.trim();
|
||||
let c_test_cases = c_test_cases.trim();
|
||||
|
||||
write!(
|
||||
BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()),
|
||||
r#"//! `kernel` crate documentation tests.
|
||||
|
||||
const __LOG_PREFIX: &[u8] = b"rust_doctests_kernel\0";
|
||||
|
||||
{rust_tests}
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
write!(
|
||||
BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()),
|
||||
r#"/*
|
||||
* `kernel` crate documentation tests.
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
|
||||
{c_test_declarations}
|
||||
|
||||
static struct kunit_case test_cases[] = {{
|
||||
{c_test_cases}
|
||||
{{ }}
|
||||
}};
|
||||
|
||||
static struct kunit_suite test_suite = {{
|
||||
.name = "rust_doctests_kernel",
|
||||
.test_cases = test_cases,
|
||||
}};
|
||||
|
||||
kunit_test_suite(test_suite);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
}
|
@ -55,8 +55,12 @@ class KunitExecRequest(KunitParseRequest):
|
||||
build_dir: str
|
||||
timeout: int
|
||||
filter_glob: str
|
||||
filter: str
|
||||
filter_action: Optional[str]
|
||||
kernel_args: Optional[List[str]]
|
||||
run_isolated: Optional[str]
|
||||
list_tests: bool
|
||||
list_tests_attr: bool
|
||||
|
||||
@dataclass
|
||||
class KunitRequest(KunitExecRequest, KunitBuildRequest):
|
||||
@ -102,19 +106,41 @@ def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
|
||||
def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> List[str]:
|
||||
args = ['kunit.action=list']
|
||||
|
||||
if request.kernel_args:
|
||||
args.extend(request.kernel_args)
|
||||
|
||||
output = linux.run_kernel(args=args,
|
||||
timeout=request.timeout,
|
||||
filter_glob=request.filter_glob,
|
||||
filter=request.filter,
|
||||
filter_action=request.filter_action,
|
||||
build_dir=request.build_dir)
|
||||
lines = kunit_parser.extract_tap_lines(output)
|
||||
# Hack! Drop the dummy TAP version header that the executor prints out.
|
||||
lines.pop()
|
||||
|
||||
# Filter out any extraneous non-test output that might have gotten mixed in.
|
||||
return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
|
||||
return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)]
|
||||
|
||||
def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]:
|
||||
args = ['kunit.action=list_attr']
|
||||
|
||||
if request.kernel_args:
|
||||
args.extend(request.kernel_args)
|
||||
|
||||
output = linux.run_kernel(args=args,
|
||||
timeout=request.timeout,
|
||||
filter_glob=request.filter_glob,
|
||||
filter=request.filter,
|
||||
filter_action=request.filter_action,
|
||||
build_dir=request.build_dir)
|
||||
lines = kunit_parser.extract_tap_lines(output)
|
||||
# Hack! Drop the dummy TAP version header that the executor prints out.
|
||||
lines.pop()
|
||||
|
||||
# Filter out any extraneous non-test output that might have gotten mixed in.
|
||||
return lines
|
||||
|
||||
def _suites_from_test_list(tests: List[str]) -> List[str]:
|
||||
"""Extracts all the suites from an ordered list of tests."""
|
||||
@ -128,10 +154,18 @@ def _suites_from_test_list(tests: List[str]) -> List[str]:
|
||||
suites.append(suite)
|
||||
return suites
|
||||
|
||||
|
||||
|
||||
def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> KunitResult:
|
||||
filter_globs = [request.filter_glob]
|
||||
if request.list_tests:
|
||||
output = _list_tests(linux, request)
|
||||
for line in output:
|
||||
print(line.rstrip())
|
||||
return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
|
||||
if request.list_tests_attr:
|
||||
attr_output = _list_tests_attr(linux, request)
|
||||
for line in attr_output:
|
||||
print(line.rstrip())
|
||||
return KunitResult(status=KunitStatus.SUCCESS, elapsed_time=0.0)
|
||||
if request.run_isolated:
|
||||
tests = _list_tests(linux, request)
|
||||
if request.run_isolated == 'test':
|
||||
@ -155,6 +189,8 @@ def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -
|
||||
args=request.kernel_args,
|
||||
timeout=request.timeout,
|
||||
filter_glob=filter_glob,
|
||||
filter=request.filter,
|
||||
filter_action=request.filter_action,
|
||||
build_dir=request.build_dir)
|
||||
|
||||
_, test_result = parse_tests(request, metadata, run_result)
|
||||
@ -341,6 +377,16 @@ def add_exec_opts(parser: argparse.ArgumentParser) -> None:
|
||||
nargs='?',
|
||||
default='',
|
||||
metavar='filter_glob')
|
||||
parser.add_argument('--filter',
|
||||
help='Filter KUnit tests with attributes, '
|
||||
'e.g. module=example or speed>slow',
|
||||
type=str,
|
||||
default='')
|
||||
parser.add_argument('--filter_action',
|
||||
help='If set to skip, filtered tests will be skipped, '
|
||||
'e.g. --filter_action=skip. Otherwise they will not run.',
|
||||
type=str,
|
||||
choices=['skip'])
|
||||
parser.add_argument('--kernel_args',
|
||||
help='Kernel command-line parameters. Maybe be repeated',
|
||||
action='append', metavar='')
|
||||
@ -350,6 +396,12 @@ def add_exec_opts(parser: argparse.ArgumentParser) -> None:
|
||||
'what ran before it.',
|
||||
type=str,
|
||||
choices=['suite', 'test'])
|
||||
parser.add_argument('--list_tests', help='If set, list all tests that will be '
|
||||
'run.',
|
||||
action='store_true')
|
||||
parser.add_argument('--list_tests_attr', help='If set, list all tests and test '
|
||||
'attributes.',
|
||||
action='store_true')
|
||||
|
||||
def add_parse_opts(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
|
||||
@ -398,8 +450,12 @@ def run_handler(cli_args: argparse.Namespace) -> None:
|
||||
json=cli_args.json,
|
||||
timeout=cli_args.timeout,
|
||||
filter_glob=cli_args.filter_glob,
|
||||
filter=cli_args.filter,
|
||||
filter_action=cli_args.filter_action,
|
||||
kernel_args=cli_args.kernel_args,
|
||||
run_isolated=cli_args.run_isolated)
|
||||
run_isolated=cli_args.run_isolated,
|
||||
list_tests=cli_args.list_tests,
|
||||
list_tests_attr=cli_args.list_tests_attr)
|
||||
result = run_tests(linux, request)
|
||||
if result.status != KunitStatus.SUCCESS:
|
||||
sys.exit(1)
|
||||
@ -441,8 +497,12 @@ def exec_handler(cli_args: argparse.Namespace) -> None:
|
||||
json=cli_args.json,
|
||||
timeout=cli_args.timeout,
|
||||
filter_glob=cli_args.filter_glob,
|
||||
filter=cli_args.filter,
|
||||
filter_action=cli_args.filter_action,
|
||||
kernel_args=cli_args.kernel_args,
|
||||
run_isolated=cli_args.run_isolated)
|
||||
run_isolated=cli_args.run_isolated,
|
||||
list_tests=cli_args.list_tests,
|
||||
list_tests_attr=cli_args.list_tests_attr)
|
||||
result = exec_tests(linux, exec_request)
|
||||
stdout.print_with_timestamp((
|
||||
'Elapsed time: %.3fs\n') % (result.elapsed_time))
|
||||
|
@ -330,11 +330,15 @@ class LinuxSourceTree:
|
||||
return False
|
||||
return self.validate_config(build_dir)
|
||||
|
||||
def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', timeout: Optional[int]=None) -> Iterator[str]:
|
||||
def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', filter: str='', filter_action: Optional[str]=None, timeout: Optional[int]=None) -> Iterator[str]:
|
||||
if not args:
|
||||
args = []
|
||||
if filter_glob:
|
||||
args.append('kunit.filter_glob='+filter_glob)
|
||||
args.append('kunit.filter_glob=' + filter_glob)
|
||||
if filter:
|
||||
args.append('kunit.filter="' + filter + '"')
|
||||
if filter_action:
|
||||
args.append('kunit.filter_action=' + filter_action)
|
||||
args.append('kunit.enable=1')
|
||||
|
||||
process = self._ops.start(args, build_dir)
|
||||
|
@ -212,6 +212,7 @@ KTAP_START = re.compile(r'\s*KTAP version ([0-9]+)$')
|
||||
TAP_START = re.compile(r'\s*TAP version ([0-9]+)$')
|
||||
KTAP_END = re.compile(r'\s*(List of all partitions:|'
|
||||
'Kernel panic - not syncing: VFS:|reboot: System halted)')
|
||||
EXECUTOR_ERROR = re.compile(r'\s*kunit executor: (.*)$')
|
||||
|
||||
def extract_tap_lines(kernel_output: Iterable[str]) -> LineStream:
|
||||
"""Extracts KTAP lines from the kernel output."""
|
||||
@ -242,6 +243,8 @@ def extract_tap_lines(kernel_output: Iterable[str]) -> LineStream:
|
||||
# remove the prefix, if any.
|
||||
line = line[prefix_len:]
|
||||
yield line_num, line
|
||||
elif EXECUTOR_ERROR.search(line):
|
||||
yield line_num, line
|
||||
return LineStream(lines=isolate_ktap_output(kernel_output))
|
||||
|
||||
KTAP_VERSIONS = [1]
|
||||
@ -447,7 +450,7 @@ def parse_diagnostic(lines: LineStream) -> List[str]:
|
||||
Log of diagnostic lines
|
||||
"""
|
||||
log = [] # type: List[str]
|
||||
non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START]
|
||||
non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START, TAP_START]
|
||||
while lines and not any(re.match(lines.peek())
|
||||
for re in non_diagnostic_lines):
|
||||
log.append(lines.pop())
|
||||
@ -713,6 +716,11 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest:
|
||||
"""
|
||||
test = Test()
|
||||
test.log.extend(log)
|
||||
|
||||
# Parse any errors prior to parsing tests
|
||||
err_log = parse_diagnostic(lines)
|
||||
test.log.extend(err_log)
|
||||
|
||||
if not is_subtest:
|
||||
# If parsing the main/top-level test, parse KTAP version line and
|
||||
# test plan
|
||||
@ -774,6 +782,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest:
|
||||
# Don't override a bad status if this test had one reported.
|
||||
# Assumption: no subtests means CRASHED is from Test.__init__()
|
||||
if test.status in (TestStatus.TEST_CRASHED, TestStatus.SUCCESS):
|
||||
print_log(test.log)
|
||||
test.status = TestStatus.NO_TESTS
|
||||
test.add_error('0 tests run!')
|
||||
|
||||
|
@ -597,7 +597,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 0)
|
||||
self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir='.kunit', filter_glob='', timeout=300)
|
||||
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_run_passes_args_pass(self):
|
||||
@ -605,7 +605,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
|
||||
self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir='.kunit', filter_glob='', timeout=300)
|
||||
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_exec_passes_args_fail(self):
|
||||
@ -629,7 +629,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
kunit.main(['run'])
|
||||
self.assertEqual(e.exception.code, 1)
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir='.kunit', filter_glob='', timeout=300)
|
||||
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
|
||||
self.print_mock.assert_any_call(StrContains(' 0 tests run!'))
|
||||
|
||||
def test_exec_raw_output(self):
|
||||
@ -670,13 +670,13 @@ class KUnitMainTest(unittest.TestCase):
|
||||
self.linux_source_mock.run_kernel = mock.Mock(return_value=[])
|
||||
kunit.main(['run', '--raw_output', 'filter_glob'])
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir='.kunit', filter_glob='filter_glob', timeout=300)
|
||||
args=None, build_dir='.kunit', filter_glob='filter_glob', filter='', filter_action=None, timeout=300)
|
||||
|
||||
def test_exec_timeout(self):
|
||||
timeout = 3453
|
||||
kunit.main(['exec', '--timeout', str(timeout)])
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
|
||||
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_run_timeout(self):
|
||||
@ -684,7 +684,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
kunit.main(['run', '--timeout', str(timeout)])
|
||||
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir='.kunit', filter_glob='', timeout=timeout)
|
||||
args=None, build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=timeout)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_run_builddir(self):
|
||||
@ -692,7 +692,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
kunit.main(['run', '--build_dir=.kunit'])
|
||||
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir=build_dir, filter_glob='', timeout=300)
|
||||
args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_config_builddir(self):
|
||||
@ -710,7 +710,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
build_dir = '.kunit'
|
||||
kunit.main(['exec', '--build_dir', build_dir])
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=None, build_dir=build_dir, filter_glob='', timeout=300)
|
||||
args=None, build_dir=build_dir, filter_glob='', filter='', filter_action=None, timeout=300)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_run_kunitconfig(self):
|
||||
@ -786,7 +786,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
kunit.main(['run', '--kernel_args=a=1', '--kernel_args=b=2'])
|
||||
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=['a=1','b=2'], build_dir='.kunit', filter_glob='', timeout=300)
|
||||
args=['a=1','b=2'], build_dir='.kunit', filter_glob='', filter='', filter_action=None, timeout=300)
|
||||
self.print_mock.assert_any_call(StrContains('Testing complete.'))
|
||||
|
||||
def test_list_tests(self):
|
||||
@ -794,13 +794,11 @@ class KUnitMainTest(unittest.TestCase):
|
||||
self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
|
||||
|
||||
got = kunit._list_tests(self.linux_source_mock,
|
||||
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', None, 'suite'))
|
||||
|
||||
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False))
|
||||
self.assertEqual(got, want)
|
||||
# Should respect the user's filter glob when listing tests.
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', timeout=300)
|
||||
|
||||
args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', filter='', filter_action=None, timeout=300)
|
||||
|
||||
@mock.patch.object(kunit, '_list_tests')
|
||||
def test_run_isolated_by_suite(self, mock_tests):
|
||||
@ -809,10 +807,10 @@ class KUnitMainTest(unittest.TestCase):
|
||||
|
||||
# Should respect the user's filter glob when listing tests.
|
||||
mock_tests.assert_called_once_with(mock.ANY,
|
||||
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*.test*', None, 'suite'))
|
||||
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False))
|
||||
self.linux_source_mock.run_kernel.assert_has_calls([
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
|
||||
])
|
||||
|
||||
@mock.patch.object(kunit, '_list_tests')
|
||||
@ -822,13 +820,12 @@ class KUnitMainTest(unittest.TestCase):
|
||||
|
||||
# Should respect the user's filter glob when listing tests.
|
||||
mock_tests.assert_called_once_with(mock.ANY,
|
||||
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', None, 'test'))
|
||||
kunit.KunitExecRequest(None, None, '.kunit', 300, 'suite*', '', None, None, 'test', False, False))
|
||||
self.linux_source_mock.run_kernel.assert_has_calls([
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test1', filter='', filter_action=None, timeout=300),
|
||||
])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -9,4 +9,4 @@ CONFIG_SERIAL_AMBA_PL011_CONSOLE=y''',
|
||||
qemu_arch='aarch64',
|
||||
kernel_path='arch/arm64/boot/Image.gz',
|
||||
kernel_command_line='console=ttyAMA0',
|
||||
extra_qemu_params=['-machine', 'virt', '-cpu', 'cortex-a57'])
|
||||
extra_qemu_params=['-machine', 'virt', '-cpu', 'max,pauth-impdef=on'])
|
||||
|
Loading…
Reference in New Issue
Block a user