2023-07-07 21:42:27

by Rae Moar

[permalink] [raw]
Subject: [RFC v2 0/9] kunit: Add test attributes API

Hello everyone,

This is an RFC patch series to propose the addition of a test attributes
framework to KUnit.

There has been interest in filtering out "slow" KUnit tests. Most notably,
a new config, CONFIG_MEMCPY_SLOW_KUNIT_TEST, has been added to exclude a
particularly slow memcpy test
(https://lore.kernel.org/all/[email protected]/).

This proposed attributes framework would be used to save and access test
associated data, including whether a test is slow. These attributes would
be reportable (via KTAP and command line output) and some will be
filterable.

This framework is designed to allow for the addition of other attributes in
the future. These attributes could include whether the test is flaky,
associated test files, etc.

This is the second version of the RFC I have added a few big changes:
- Change method for inputting filters to allow for spaces in filtering
values
- Add option to skip filtered tests instead of not run or show them with
the --filter_skip flag
- Separate the new feature to list tests and their attributes into both
--list_tests (lists just tests) and --list_tests_attr (lists all)
- Add new attribute to store module name associated with test
- Add Tests to executor_test.c
- Add Documentation
- A few small changes to code commented on previously

I would love to hear about the new features. If the series seems overall
good I will send out the next version as an official patch series.

Thanks!
Rae

Rae Moar (9):
kunit: Add test attributes API structure
kunit: Add speed attribute
kunit: Add module attribute
kunit: Add ability to filter attributes
kunit: tool: Add command line interface to filter and report
attributes
kunit: memcpy: Mark tests as slow using test attributes
kunit: time: Mark test as slow using test attributes
kunit: add tests for filtering attributes
kunit: Add documentation of KUnit test attributes

.../dev-tools/kunit/running_tips.rst | 163 +++++++
include/kunit/attributes.h | 50 +++
include/kunit/test.h | 68 ++-
kernel/time/time_test.c | 2 +-
lib/Kconfig.debug | 3 +
lib/kunit/Makefile | 3 +-
lib/kunit/attributes.c | 406 ++++++++++++++++++
lib/kunit/executor.c | 115 ++++-
lib/kunit/executor_test.c | 119 ++++-
lib/kunit/kunit-example-test.c | 9 +
lib/kunit/test.c | 27 +-
lib/memcpy_kunit.c | 8 +-
tools/testing/kunit/kunit.py | 80 +++-
tools/testing/kunit/kunit_kernel.py | 6 +-
tools/testing/kunit/kunit_tool_test.py | 39 +-
15 files changed, 1022 insertions(+), 76 deletions(-)
create mode 100644 include/kunit/attributes.h
create mode 100644 lib/kunit/attributes.c


base-commit: 2e66833579ed759d7b7da1a8f07eb727ec6e80db
--
2.41.0.255.g8b1d071c50-goog



2023-07-07 21:42:59

by Rae Moar

[permalink] [raw]
Subject: [RFC v2 9/9] kunit: Add documentation of KUnit test attributes

Add documentation on the use of test attributes under the section "Tips for
Running KUnit Tests" in the KUnit docs.

Documentation includes three sections on how to mark tests with attributes,
how attributes are reported, and how the user can filter tests using test
attributes.

Signed-off-by: Rae Moar <[email protected]>
---

Changes since v1:
- This is a new patch

.../dev-tools/kunit/running_tips.rst | 163 ++++++++++++++++++
1 file changed, 163 insertions(+)

diff --git a/Documentation/dev-tools/kunit/running_tips.rst b/Documentation/dev-tools/kunit/running_tips.rst
index 8e8c493f17d1..c9bc5a6595d3 100644
--- a/Documentation/dev-tools/kunit/running_tips.rst
+++ b/Documentation/dev-tools/kunit/running_tips.rst
@@ -262,3 +262,166 @@ 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 kernel output (in KTAP
+format). 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_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 ``kunit.filter`` module param with
+``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".
+
+``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.
+
--
2.41.0.255.g8b1d071c50-goog


2023-07-07 21:45:10

by Rae Moar

[permalink] [raw]
Subject: [RFC v2 1/9] kunit: Add test attributes API structure

Add the basic structure of the test attribute API to KUnit, which can be
used to save and access test associated data.

Add attributes.c and attributes.h to hold associated structs and functions
for the API.

Create a struct that holds a variety of associated helper functions for
each test attribute. These helper functions will be used to get the
attribute value, convert the value to a string, and filter based on the
value. This struct is flexible by design to allow for attributes of
numerous types and contexts.

Add a method to print test attributes in the format of "# [<test_name if
not suite>.]<attribute_name>: <attribute_value>".

Example for a suite: "# speed: slow"

Example for a test case: "# test_case.speed: very_slow"

Use this method to report attributes in the KTAP output (KTAP spec:
https://docs.kernel.org/dev-tools/ktap.html) and _list_tests output when
kernel's new kunit.action=list_attr option is used. Note this is derivative
of the kunit.action=list option.

In test.h, add fields and associated helper functions to test cases and
suites to hold user-inputted test attributes.

Signed-off-by: Rae Moar <[email protected]>
---

Changes since v1:
- Add list_attr option to only include attribute in the _list_tests output
when this module param is set
- Add printing options for attributes to print always, print only for
suites, or print never.

include/kunit/attributes.h | 19 +++++++++
include/kunit/test.h | 33 ++++++++++++++++
lib/kunit/Makefile | 3 +-
lib/kunit/attributes.c | 80 ++++++++++++++++++++++++++++++++++++++
lib/kunit/executor.c | 21 ++++++++--
lib/kunit/test.c | 17 ++++----
6 files changed, 161 insertions(+), 12 deletions(-)
create mode 100644 include/kunit/attributes.h
create mode 100644 lib/kunit/attributes.c

diff --git a/include/kunit/attributes.h b/include/kunit/attributes.h
new file mode 100644
index 000000000000..9fcd184cce36
--- /dev/null
+++ b/include/kunit/attributes.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * KUnit API to save and access test attributes
+ *
+ * Copyright (C) 2023, Google LLC.
+ * Author: Rae Moar <[email protected]>
+ */
+
+#ifndef _KUNIT_ATTRIBUTES_H
+#define _KUNIT_ATTRIBUTES_H
+
+/*
+ * 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);
+
+#endif /* _KUNIT_ATTRIBUTES_H */
diff --git a/include/kunit/test.h b/include/kunit/test.h
index 23120d50499e..1fc9155988e9 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -63,12 +63,16 @@ enum kunit_status {
KUNIT_SKIPPED,
};

+/* Holds attributes for each test case and suite */
+struct kunit_attributes {};
+
/**
* 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,6 +108,7 @@ 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;
@@ -133,6 +138,18 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
*/
#define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }

+/**
+ * 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 }
+
/**
* KUNIT_CASE_PARAM - A helper for creation a parameterized &struct kunit_case
*
@@ -154,6 +171,20 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
{ .run_case = test_name, .name = #test_name, \
.generate_params = gen_params }

+/**
+ * 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 }
+
/**
* struct kunit_suite - describes a related collection of &struct kunit_case
*
@@ -163,6 +194,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 +214,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];
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index cb417f504996..46f75f23dfe4 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -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
diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c
new file mode 100644
index 000000000000..9bda5a5f4030
--- /dev/null
+++ b/lib/kunit/attributes.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit API to save and access test attributes
+ *
+ * Copyright (C) 2023, Google LLC.
+ * Author: Rae Moar <[email protected]>
+ */
+
+#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
+ */
+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;
+};
+
+/* List of all Test Attributes */
+
+static struct kunit_attr kunit_attr_list[] = {};
+
+/* Helper Functions to Access Attributes */
+
+void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level)
+{
+ int i;
+ bool to_free;
+ 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);
+ }
+ }
+}
diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c
index 74982b83707c..12e38a48a5cc 100644
--- a/lib/kunit/executor.c
+++ b/lib/kunit/executor.c
@@ -2,6 +2,7 @@

#include <linux/reboot.h>
#include <kunit/test.h>
+#include <kunit/attributes.h>
#include <linux/glob.h>
#include <linux/moduleparam.h>

@@ -24,7 +25,8 @@ module_param_named(action, action_param, charp, 0);
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");

/* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */
struct kunit_test_filter {
@@ -172,7 +174,7 @@ static void kunit_exec_run_tests(struct suite_set *suite_set)
__kunit_test_suites_init(suite_set->start, num_suites);
}

-static void kunit_exec_list_tests(struct suite_set *suite_set)
+static void kunit_exec_list_tests(struct suite_set *suite_set, bool include_attr)
{
struct kunit_suite * const *suites;
struct kunit_case *test_case;
@@ -180,10 +182,19 @@ static void kunit_exec_list_tests(struct suite_set *suite_set)
/* 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++)
+ 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);
}
+ }
}

int kunit_run_all_tests(void)
@@ -206,7 +217,9 @@ int kunit_run_all_tests(void)
if (!action_param)
kunit_exec_run_tests(&suite_set);
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);

diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index 84e4666555c9..9ee55139ecd1 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -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,
@@ -651,6 +653,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);

--
2.41.0.255.g8b1d071c50-goog


2023-07-07 21:46:40

by Rae Moar

[permalink] [raw]
Subject: [RFC v2 7/9] kunit: time: Mark test as slow using test attributes

Mark the time KUnit test, time64_to_tm_test_date_range, as slow using test
attributes.

This test ran relatively much slower than most other KUnit tests.

By marking this test as slow, the test can now be filtered using the KUnit
test attribute filtering feature. Example: --filter "speed>slow". This will
run only the tests that have speeds faster than slow. The slow attribute
will also be outputted in KTAP.

Signed-off-by: Rae Moar <[email protected]>
---

Changes since v1:
- No changes.

kernel/time/time_test.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c
index 831e8e779ace..ca058c8af6ba 100644
--- a/kernel/time/time_test.c
+++ b/kernel/time/time_test.c
@@ -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),
{}
};

--
2.41.0.255.g8b1d071c50-goog


2023-07-07 21:50:54

by Rae Moar

[permalink] [raw]
Subject: [RFC v2 8/9] kunit: add tests for filtering attributes

Add four tests to executor_test.c to test behavior of filtering attributes.

- parse_filter_attr_test - to test the parsing of inputted filters

- filter_attr_test - to test the filtering procedure on attributes

- filter_attr_empty_test - to test the behavior when all tests are filtered
out

- filter_attr_skip_test - to test the configurable filter_skip option

Signed-off-by: Rae Moar <[email protected]>
---

Changes since v1:
- This is a new patch

lib/kunit/executor_test.c | 107 ++++++++++++++++++++++++++++++++++++++
1 file changed, 107 insertions(+)

diff --git a/lib/kunit/executor_test.c b/lib/kunit/executor_test.c
index d7ab069324b5..145a78ade33d 100644
--- a/lib/kunit/executor_test.c
+++ b/lib/kunit/executor_test.c
@@ -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,
@@ -22,6 +23,14 @@ static struct kunit_case dummy_test_cases[] = {
{},
};

+static struct kunit_case dummy_attr_test_cases[] = {
+ /* .run_case is not important, just needs to be non-NULL */
+ { .name = "test1", .run_case = dummy_test, .module_name = "dummy",
+ .attr.speed = KUNIT_SPEED_SLOW },
+ { .name = "test2", .run_case = dummy_test, .module_name = "dummy" },
+ {},
+};
+
static void parse_filter_test(struct kunit *test)
{
struct kunit_glob_filter filter = {NULL, NULL};
@@ -108,11 +117,109 @@ 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 = kcalloc(filter_count + 1, sizeof(*parsed_filters), GFP_KERNEL);
+ for (j = 0; j < filter_count; j++)
+ parsed_filters[j] = kunit_next_attr_filter(&filters, &err);
+
+ 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");
+
+ kfree(parsed_filters);
+}
+
+static void filter_attr_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;
+ 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);
+ subsuite[1]->attr.speed = KUNIT_SPEED_SLOW; // Set suite attribute
+
+ /* Want: suite1(test1, test2), suite2(test1, test2), NULL -> suite1(test2), NULL */
+ 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 suite1 */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]);
+ KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->name, "suite1");
+ KUNIT_ASSERT_EQ(test, got.end - got.start, 1);
+
+ /* Now validate we just have test2 */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
+ KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test2");
+ 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 suite_set suite_set = {.start = subsuite, .end = &subsuite[2]};
+ struct 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 suite_set suite_set = {.start = subsuite, .end = &subsuite[1]};
+ struct suite_set got;
+ int err = 0;
+
+ subsuite[0] = alloc_fake_suite(test, "suite1", dummy_attr_test_cases);
+
+ /* Want: suite1(test1, test2), NULL -> suite1(test1 with SKIP, test2), 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 test1 and test2 */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
+ KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test1");
+ KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[1].name, "test2");
+
+ /* Now ensure test1 is skipped and test2 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),
{}
};

--
2.41.0.255.g8b1d071c50-goog


2023-07-10 19:07:58

by Daniel Latypov

[permalink] [raw]
Subject: Re: [RFC v2 8/9] kunit: add tests for filtering attributes

On Fri, Jul 7, 2023 at 2:10 PM Rae Moar <[email protected]> wrote:
>
> Add four tests to executor_test.c to test behavior of filtering attributes.
>
> - parse_filter_attr_test - to test the parsing of inputted filters
>
> - filter_attr_test - to test the filtering procedure on attributes
>
> - filter_attr_empty_test - to test the behavior when all tests are filtered
> out
>
> - filter_attr_skip_test - to test the configurable filter_skip option
>
> Signed-off-by: Rae Moar <[email protected]>

I love that I'm able to read this patch first and get a feel for what
exactly the patch series is doing overall.

Some nits and suggestions below.

> ---
>
> Changes since v1:
> - This is a new patch
>
> lib/kunit/executor_test.c | 107 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 107 insertions(+)
>
> diff --git a/lib/kunit/executor_test.c b/lib/kunit/executor_test.c
> index d7ab069324b5..145a78ade33d 100644
> --- a/lib/kunit/executor_test.c
> +++ b/lib/kunit/executor_test.c
> @@ -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,
> @@ -22,6 +23,14 @@ static struct kunit_case dummy_test_cases[] = {
> {},
> };
>
> +static struct kunit_case dummy_attr_test_cases[] = {
> + /* .run_case is not important, just needs to be non-NULL */
> + { .name = "test1", .run_case = dummy_test, .module_name = "dummy",
> + .attr.speed = KUNIT_SPEED_SLOW },
> + { .name = "test2", .run_case = dummy_test, .module_name = "dummy" },
> + {},
> +};

1) can we move this array to be just above parse_filter_attr_test so
it's next to where it's used?

2) How about renaming "test1" to "slow" to make the assertions in the
test case a bit easier to follow?
Right now readers need to remember which test case was supposed to be
filtered out.


> +
> static void parse_filter_test(struct kunit *test)
> {
> struct kunit_glob_filter filter = {NULL, NULL};
> @@ -108,11 +117,109 @@ 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 = kcalloc(filter_count + 1, sizeof(*parsed_filters), GFP_KERNEL);

nit: kunit_kcalloc() instead?

> + for (j = 0; j < filter_count; j++)
> + parsed_filters[j] = kunit_next_attr_filter(&filters, &err);

then here we probably want to check err, i.e.
KUNIT_ASSERT_EQ_MSG(test, err, 0, "failed to parse filter '%s'", filters[i]);


> +
> + 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");
> +
> + kfree(parsed_filters);
> +}
> +
> +static void filter_attr_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;
> + 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);
> + subsuite[1]->attr.speed = KUNIT_SPEED_SLOW; // Set suite attribute

Similarly, perhaps we can rename suite2 to "slow_suite"?
That would cause this line to go over 80 characters wide, but since
that's no longer a hard limit, I think this would be a decent place to
go past it.

> +
> + /* Want: suite1(test1, test2), suite2(test1, test2), NULL -> suite1(test2), NULL */
> + 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 suite1 */
> + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]);
> + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->name, "suite1");
> + KUNIT_ASSERT_EQ(test, got.end - got.start, 1);
> +
> + /* Now validate we just have test2 */
> + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
> + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test2");
> + 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 suite_set suite_set = {.start = subsuite, .end = &subsuite[2]};
> + struct 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 suite_set suite_set = {.start = subsuite, .end = &subsuite[1]};
> + struct suite_set got;
> + int err = 0;
> +
> + subsuite[0] = alloc_fake_suite(test, "suite1", dummy_attr_test_cases);
> +
> + /* Want: suite1(test1, test2), NULL -> suite1(test1 with SKIP, test2), 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 test1 and test2 */
> + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);

Should we assert that we have 2 test cases before we dereference the second one?
The other code in this file (that I wrote) is being a bit sloppy and
deref'ing test_cases[0] without checking. It's doing that since I was
relying on the fact that the filtering code drops suites with no test
cases, so we don't necessarily need to check len(test_cases) >= 1.
(In terms of best practices, we should be defensive and checking that, though).

But in this case, we have no such guarantee about the second element.

> + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test1");
> + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[1].name, "test2");

Trying to remember, I think the cast to `const char *` is no longer
necessary after one of David's changes...
I think we might just never have gotten around to cleaning that up due
to the ordering in which the patches went in...

> +
> + /* Now ensure test1 is skipped and test2 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);

Should we check that it's equal to KUNIT_SUCCESS instead?


> +}
> +
> 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),
> {}
> };
>
> --
> 2.41.0.255.g8b1d071c50-goog
>

2023-07-12 22:06:48

by Rae Moar

[permalink] [raw]
Subject: Re: [RFC v2 8/9] kunit: add tests for filtering attributes

On Mon, Jul 10, 2023 at 2:07 PM Daniel Latypov <[email protected]> wrote:
>
> On Fri, Jul 7, 2023 at 2:10 PM Rae Moar <[email protected]> wrote:
> >
> > Add four tests to executor_test.c to test behavior of filtering attributes.
> >
> > - parse_filter_attr_test - to test the parsing of inputted filters
> >
> > - filter_attr_test - to test the filtering procedure on attributes
> >
> > - filter_attr_empty_test - to test the behavior when all tests are filtered
> > out
> >
> > - filter_attr_skip_test - to test the configurable filter_skip option
> >
> > Signed-off-by: Rae Moar <[email protected]>
>
> I love that I'm able to read this patch first and get a feel for what
> exactly the patch series is doing overall.


Thanks!

>
>
> Some nits and suggestions below.
>
> > ---
> >
> > Changes since v1:
> > - This is a new patch
> >
> > lib/kunit/executor_test.c | 107 ++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 107 insertions(+)
> >
> > diff --git a/lib/kunit/executor_test.c b/lib/kunit/executor_test.c
> > index d7ab069324b5..145a78ade33d 100644
> > --- a/lib/kunit/executor_test.c
> > +++ b/lib/kunit/executor_test.c
> > @@ -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,
> > @@ -22,6 +23,14 @@ static struct kunit_case dummy_test_cases[] = {
> > {},
> > };
> >
> > +static struct kunit_case dummy_attr_test_cases[] = {
> > + /* .run_case is not important, just needs to be non-NULL */
> > + { .name = "test1", .run_case = dummy_test, .module_name = "dummy",
> > + .attr.speed = KUNIT_SPEED_SLOW },
> > + { .name = "test2", .run_case = dummy_test, .module_name = "dummy" },
> > + {},
> > +};
>
> 1) can we move this array to be just above parse_filter_attr_test so
> it's next to where it's used?

This seems like a great idea. I will move it down.

>
>
> 2) How about renaming "test1" to "slow" to make the assertions in the
> test case a bit easier to follow?
> Right now readers need to remember which test case was supposed to be
> filtered out.

Yes this sounds good. I think including "slow" would be helpful
although I might also consider the name "slow_test".

>
>
> > +
> > static void parse_filter_test(struct kunit *test)
> > {
> > struct kunit_glob_filter filter = {NULL, NULL};
> > @@ -108,11 +117,109 @@ 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 = kcalloc(filter_count + 1, sizeof(*parsed_filters), GFP_KERNEL);
>
> nit: kunit_kcalloc() instead?

Right, that makes sense. Thanks!

>
> > + for (j = 0; j < filter_count; j++)
> > + parsed_filters[j] = kunit_next_attr_filter(&filters, &err);
>
> then here we probably want to check err, i.e.
> KUNIT_ASSERT_EQ_MSG(test, err, 0, "failed to parse filter '%s'", filters[i]);
>

Sounds good. I will add this.

>
> > +
> > + 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");
> > +
> > + kfree(parsed_filters);
> > +}
> > +
> > +static void filter_attr_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;
> > + 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);
> > + subsuite[1]->attr.speed = KUNIT_SPEED_SLOW; // Set suite attribute
>
> Similarly, perhaps we can rename suite2 to "slow_suite"?
> That would cause this line to go over 80 characters wide, but since
> that's no longer a hard limit, I think this would be a decent place to
> go past it.

Like above, I like this idea. I'll change the name. Interesting idea
about the 80 character limit also.

>
>
> > +
> > + /* Want: suite1(test1, test2), suite2(test1, test2), NULL -> suite1(test2), NULL */
> > + 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 suite1 */
> > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]);
> > + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->name, "suite1");
> > + KUNIT_ASSERT_EQ(test, got.end - got.start, 1);
> > +
> > + /* Now validate we just have test2 */
> > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
> > + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test2");
> > + 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 suite_set suite_set = {.start = subsuite, .end = &subsuite[2]};
> > + struct 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 suite_set suite_set = {.start = subsuite, .end = &subsuite[1]};
> > + struct suite_set got;
> > + int err = 0;
> > +
> > + subsuite[0] = alloc_fake_suite(test, "suite1", dummy_attr_test_cases);
> > +
> > + /* Want: suite1(test1, test2), NULL -> suite1(test1 with SKIP, test2), 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 test1 and test2 */
> > + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, got.start[0]->test_cases);
>
> Should we assert that we have 2 test cases before we dereference the second one?
> The other code in this file (that I wrote) is being a bit sloppy and
> deref'ing test_cases[0] without checking. It's doing that since I was
> relying on the fact that the filtering code drops suites with no test
> cases, so we don't necessarily need to check len(test_cases) >= 1.
> (In terms of best practices, we should be defensive and checking that, though).
>
> But in this case, we have no such guarantee about the second element.

Good point. I'll add an assert statement here about the length of test_cases.

>
>
> > + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[0].name, "test1");
> > + KUNIT_EXPECT_STREQ(test, (const char *)got.start[0]->test_cases[1].name, "test2");
>
> Trying to remember, I think the cast to `const char *` is no longer
> necessary after one of David's changes...
> I think we might just never have gotten around to cleaning that up due
> to the ordering in which the patches went in...

Ahh got it. That is my bad. I'll double check if these are necessary.

>
>
> > +
> > + /* Now ensure test1 is skipped and test2 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);
>
> Should we check that it's equal to KUNIT_SUCCESS instead?
>

I wouldn't expect the status to be set in this case. But the status is
returning as 0 so it would pass for both the assert statement above
and if it's equal to KUNIT_SUCCESS. But since it is not supposed to be
set to KUNIT_SUCCESS, I'm inclined to keep it this way.

Thanks for all the comments Daniel!
-Rae

>
>
> > +}
> > +
> > 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),
> > {}
> > };
> >
> > --
> > 2.41.0.255.g8b1d071c50-goog
> >

2023-07-18 07:44:34

by David Gow

[permalink] [raw]
Subject: Re: [RFC v2 1/9] kunit: Add test attributes API structure

On Sat, 8 Jul 2023 at 05:09, Rae Moar <[email protected]> wrote:
>
> Add the basic structure of the test attribute API to KUnit, which can be
> used to save and access test associated data.
>
> Add attributes.c and attributes.h to hold associated structs and functions
> for the API.
>
> Create a struct that holds a variety of associated helper functions for
> each test attribute. These helper functions will be used to get the
> attribute value, convert the value to a string, and filter based on the
> value. This struct is flexible by design to allow for attributes of
> numerous types and contexts.
>
> Add a method to print test attributes in the format of "# [<test_name if
> not suite>.]<attribute_name>: <attribute_value>".
>
> Example for a suite: "# speed: slow"
>
> Example for a test case: "# test_case.speed: very_slow"

So, this is the one thing I'm a little unsure about here, and it's
really more of a problem with test names overall.

As noted in the KTAPv2 attributes and test name proposals, the names
and attributes are only really defined for "suites", hence the need to
have a different output format for test cases.

Personally, I'd prefer to keep the formats the same if we can (at
least for the actual KTAP output; I'm less concerned with the
list_attr option). That might make things a bit more difficult to
parse, though.

One possibility would be to combine the KTAP attributes and test name
specs and suggest that every test has a "test name" attribute, which
must be the first attribute output.

The output would then look something like:
KTAP version 2
# Name: my_suite
# Other-Attr: value
1..2
KTAP version 2
# Name: test_1
# Other-Attr: value
ok 1 test_1
# Name: test_2
# Other-Attr: value
not ok 2 test_2
ok 1 my_suite

Would there be any problems with something like that?

I'm less concerned with the list_attr option, as that's not something
totally standardised in the way KTAP is.

>
> Use this method to report attributes in the KTAP output (KTAP spec:
> https://docs.kernel.org/dev-tools/ktap.html) and _list_tests output when
> kernel's new kunit.action=list_attr option is used. Note this is derivative
> of the kunit.action=list option.
>
> In test.h, add fields and associated helper functions to test cases and
> suites to hold user-inputted test attributes.
>
> Signed-off-by: Rae Moar <[email protected]>
> ---

The only other thing I'd really like to support one day is having
attributes for individual parameters in parameterised tests. I think
it makes sense as a follow-up, though.

>
> Changes since v1:
> - Add list_attr option to only include attribute in the _list_tests output
> when this module param is set
> - Add printing options for attributes to print always, print only for
> suites, or print never.
>
> include/kunit/attributes.h | 19 +++++++++
> include/kunit/test.h | 33 ++++++++++++++++
> lib/kunit/Makefile | 3 +-
> lib/kunit/attributes.c | 80 ++++++++++++++++++++++++++++++++++++++
> lib/kunit/executor.c | 21 ++++++++--
> lib/kunit/test.c | 17 ++++----
> 6 files changed, 161 insertions(+), 12 deletions(-)
> create mode 100644 include/kunit/attributes.h
> create mode 100644 lib/kunit/attributes.c
>
> diff --git a/include/kunit/attributes.h b/include/kunit/attributes.h
> new file mode 100644
> index 000000000000..9fcd184cce36
> --- /dev/null
> +++ b/include/kunit/attributes.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KUnit API to save and access test attributes
> + *
> + * Copyright (C) 2023, Google LLC.
> + * Author: Rae Moar <[email protected]>
> + */
> +
> +#ifndef _KUNIT_ATTRIBUTES_H
> +#define _KUNIT_ATTRIBUTES_H
> +
> +/*
> + * 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);
> +
> +#endif /* _KUNIT_ATTRIBUTES_H */
> diff --git a/include/kunit/test.h b/include/kunit/test.h
> index 23120d50499e..1fc9155988e9 100644
> --- a/include/kunit/test.h
> +++ b/include/kunit/test.h
> @@ -63,12 +63,16 @@ enum kunit_status {
> KUNIT_SKIPPED,
> };
>
> +/* Holds attributes for each test case and suite */
> +struct kunit_attributes {};

Do we want a separate set of attributes for test cases and suites?
(I think probably not, but it's worth making sure.)

> +
> /**
> * 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,6 +108,7 @@ 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;
> @@ -133,6 +138,18 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
> */
> #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
>
> +/**
> + * 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 }
> +
> /**
> * KUNIT_CASE_PARAM - A helper for creation a parameterized &struct kunit_case
> *
> @@ -154,6 +171,20 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
> { .run_case = test_name, .name = #test_name, \
> .generate_params = gen_params }
>
> +/**
> + * 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 }
> +

I do worry a bit that we'll end up with a huge list of variants of the
KUNIT_CASE_* macros if we start adding more things here. I can't think
of a better way to handle it at the moment, though.



> /**
> * struct kunit_suite - describes a related collection of &struct kunit_case
> *
> @@ -163,6 +194,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 +214,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];
> diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> index cb417f504996..46f75f23dfe4 100644
> --- a/lib/kunit/Makefile
> +++ b/lib/kunit/Makefile
> @@ -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
> diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c
> new file mode 100644
> index 000000000000..9bda5a5f4030
> --- /dev/null
> +++ b/lib/kunit/attributes.c
> @@ -0,0 +1,80 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KUnit API to save and access test attributes
> + *
> + * Copyright (C) 2023, Google LLC.
> + * Author: Rae Moar <[email protected]>
> + */
> +
> +#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
> + */
> +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;
> +};
> +
> +/* List of all Test Attributes */
> +
> +static struct kunit_attr kunit_attr_list[] = {};
> +
> +/* Helper Functions to Access Attributes */
> +
> +void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level)
> +{
> + int i;
> + bool to_free;
> + 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);
> + }
> + }
> +}
> diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c
> index 74982b83707c..12e38a48a5cc 100644
> --- a/lib/kunit/executor.c
> +++ b/lib/kunit/executor.c
> @@ -2,6 +2,7 @@
>
> #include <linux/reboot.h>
> #include <kunit/test.h>
> +#include <kunit/attributes.h>
> #include <linux/glob.h>
> #include <linux/moduleparam.h>
>
> @@ -24,7 +25,8 @@ module_param_named(action, action_param, charp, 0);
> 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");
>
> /* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */
> struct kunit_test_filter {
> @@ -172,7 +174,7 @@ static void kunit_exec_run_tests(struct suite_set *suite_set)
> __kunit_test_suites_init(suite_set->start, num_suites);
> }
>
> -static void kunit_exec_list_tests(struct suite_set *suite_set)
> +static void kunit_exec_list_tests(struct suite_set *suite_set, bool include_attr)
> {
> struct kunit_suite * const *suites;
> struct kunit_case *test_case;
> @@ -180,10 +182,19 @@ static void kunit_exec_list_tests(struct suite_set *suite_set)
> /* 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++)
> + 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);
> }
> + }
> }
>
> int kunit_run_all_tests(void)
> @@ -206,7 +217,9 @@ int kunit_run_all_tests(void)
> if (!action_param)
> kunit_exec_run_tests(&suite_set);
> 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);
>
> diff --git a/lib/kunit/test.c b/lib/kunit/test.c
> index 84e4666555c9..9ee55139ecd1 100644
> --- a/lib/kunit/test.c
> +++ b/lib/kunit/test.c
> @@ -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,
> @@ -651,6 +653,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);
>
> --
> 2.41.0.255.g8b1d071c50-goog
>


Attachments:
smime.p7s (3.91 kB)
S/MIME Cryptographic Signature

2023-07-18 07:50:39

by David Gow

[permalink] [raw]
Subject: Re: [RFC v2 7/9] kunit: time: Mark test as slow using test attributes

On Sat, 8 Jul 2023 at 05:10, Rae Moar <[email protected]> wrote:
>
> Mark the time KUnit test, time64_to_tm_test_date_range, as slow using test
> attributes.
>
> This test ran relatively much slower than most other KUnit tests.
>
> By marking this test as slow, the test can now be filtered using the KUnit
> test attribute filtering feature. Example: --filter "speed>slow". This will
> run only the tests that have speeds faster than slow. The slow attribute
> will also be outputted in KTAP.
>
> Signed-off-by: Rae Moar <[email protected]>
> ---

Thanks: this test is slow enough to really annoy me on some machines.
(It's possibly even on the edge of "very_slow" territory, though I
suspect "slow" is better.)

Reviewed-by: David Gow <[email protected]>

Cheers,
-- David


>
> Changes since v1:
> - No changes.
>
> kernel/time/time_test.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c
> index 831e8e779ace..ca058c8af6ba 100644
> --- a/kernel/time/time_test.c
> +++ b/kernel/time/time_test.c
> @@ -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),
> {}
> };
>
> --
> 2.41.0.255.g8b1d071c50-goog
>


Attachments:
smime.p7s (3.91 kB)
S/MIME Cryptographic Signature

2023-07-18 07:56:27

by David Gow

[permalink] [raw]
Subject: Re: [RFC v2 9/9] kunit: Add documentation of KUnit test attributes

On Sat, 8 Jul 2023 at 05:10, Rae Moar <[email protected]> wrote:
>
> Add documentation on the use of test attributes under the section "Tips for
> Running KUnit Tests" in the KUnit docs.
>
> Documentation includes three sections on how to mark tests with attributes,
> how attributes are reported, and how the user can filter tests using test
> attributes.
>
> Signed-off-by: Rae Moar <[email protected]>
> ---

Looks good overall. Some nitpicks below.

Reviewed-by: David Gow <[email protected]>

>
> Changes since v1:
> - This is a new patch
>
> .../dev-tools/kunit/running_tips.rst | 163 ++++++++++++++++++
> 1 file changed, 163 insertions(+)
>
> diff --git a/Documentation/dev-tools/kunit/running_tips.rst b/Documentation/dev-tools/kunit/running_tips.rst
> index 8e8c493f17d1..c9bc5a6595d3 100644
> --- a/Documentation/dev-tools/kunit/running_tips.rst
> +++ b/Documentation/dev-tools/kunit/running_tips.rst
> @@ -262,3 +262,166 @@ 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 kernel output (in KTAP
> +format). 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
> +

Maybe worth noting that kunit.py will hide these for passing tests by
default, and --raw_output is needed to see them?

> +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_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 ``kunit.filter`` module param with
> +``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
> +--------------------------

I wonder whether this should end up part of the KTAP spec (or as an
appendix/supplement to it). Or even as a separate page within the
KUnit documentation to avoid running_tips.rst from getting too huge.

> +
> +``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".

Is it worth noting that "KUNIT_CASE_SLOW()" can be used to easily set
this 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.
> +
> --
> 2.41.0.255.g8b1d071c50-goog

>

Error: new blank line at EOF.


Attachments:
smime.p7s (3.91 kB)
S/MIME Cryptographic Signature

2023-07-18 21:07:15

by Rae Moar

[permalink] [raw]
Subject: Re: [RFC v2 9/9] kunit: Add documentation of KUnit test attributes

On Tue, Jul 18, 2023 at 3:39 AM David Gow <[email protected]> wrote:
>
> On Sat, 8 Jul 2023 at 05:10, Rae Moar <[email protected]> wrote:
> >
> > Add documentation on the use of test attributes under the section "Tips for
> > Running KUnit Tests" in the KUnit docs.
> >
> > Documentation includes three sections on how to mark tests with attributes,
> > how attributes are reported, and how the user can filter tests using test
> > attributes.
> >
> > Signed-off-by: Rae Moar <[email protected]>
> > ---
>
> Looks good overall. Some nitpicks below.
>
> Reviewed-by: David Gow <[email protected]>
>
> >
> > Changes since v1:
> > - This is a new patch
> >
> > .../dev-tools/kunit/running_tips.rst | 163 ++++++++++++++++++
> > 1 file changed, 163 insertions(+)
> >
> > diff --git a/Documentation/dev-tools/kunit/running_tips.rst b/Documentation/dev-tools/kunit/running_tips.rst
> > index 8e8c493f17d1..c9bc5a6595d3 100644
> > --- a/Documentation/dev-tools/kunit/running_tips.rst
> > +++ b/Documentation/dev-tools/kunit/running_tips.rst
> > @@ -262,3 +262,166 @@ 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 kernel output (in KTAP
> > +format). 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
> > +
>
> Maybe worth noting that kunit.py will hide these for passing tests by
> default, and --raw_output is needed to see them?
>

I will definitely add this in. If attributes are popular in the
future, I could create a future patch to show attributes in the parser
output as well.

> > +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_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 ``kunit.filter`` module param with
> > +``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
> > +--------------------------
>
> I wonder whether this should end up part of the KTAP spec (or as an
> appendix/supplement to it). Or even as a separate page within the
> KUnit documentation to avoid running_tips.rst from getting too huge.

I am a bit hesitant to move this as part of the KTAP spec in case
there will exist KTAP attributes/data that are not supported by the
KUnit test attributes framework (could be runtime specific attributes
that use a different framework?).

However, I do worry about the size of this page. Do you think that I
should move all of the attributes to a new documentation page?

>
> > +
> > +``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".
>
> Is it worth noting that "KUNIT_CASE_SLOW()" can be used to easily set
> this to slow?

This definitely seems important to add. I will add this to the documentation.

>
>
> > +
> > +``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.
> > +
> > --
> > 2.41.0.255.g8b1d071c50-goog
>
> >
>
> Error: new blank line at EOF.

Oops. I will change this.

2023-07-18 21:32:50

by Rae Moar

[permalink] [raw]
Subject: Re: [RFC v2 1/9] kunit: Add test attributes API structure

On Tue, Jul 18, 2023 at 3:39 AM David Gow <[email protected]> wrote:
>
> On Sat, 8 Jul 2023 at 05:09, Rae Moar <[email protected]> wrote:
> >
> > Add the basic structure of the test attribute API to KUnit, which can be
> > used to save and access test associated data.
> >
> > Add attributes.c and attributes.h to hold associated structs and functions
> > for the API.
> >
> > Create a struct that holds a variety of associated helper functions for
> > each test attribute. These helper functions will be used to get the
> > attribute value, convert the value to a string, and filter based on the
> > value. This struct is flexible by design to allow for attributes of
> > numerous types and contexts.
> >
> > Add a method to print test attributes in the format of "# [<test_name if
> > not suite>.]<attribute_name>: <attribute_value>".
> >
> > Example for a suite: "# speed: slow"
> >
> > Example for a test case: "# test_case.speed: very_slow"
>
> So, this is the one thing I'm a little unsure about here, and it's
> really more of a problem with test names overall.
>
> As noted in the KTAPv2 attributes and test name proposals, the names
> and attributes are only really defined for "suites", hence the need to
> have a different output format for test cases.
>
> Personally, I'd prefer to keep the formats the same if we can (at
> least for the actual KTAP output; I'm less concerned with the
> list_attr option). That might make things a bit more difficult to
> parse, though.
>
> One possibility would be to combine the KTAP attributes and test name
> specs and suggest that every test has a "test name" attribute, which
> must be the first attribute output.
>
> The output would then look something like:
> KTAP version 2
> # Name: my_suite
> # Other-Attr: value
> 1..2
> KTAP version 2
> # Name: test_1
> # Other-Attr: value
> ok 1 test_1
> # Name: test_2
> # Other-Attr: value
> not ok 2 test_2
> ok 1 my_suite
>
> Would there be any problems with something like that?
>
> I'm less concerned with the list_attr option, as that's not something
> totally standardised in the way KTAP is.
>

This is a really interesting idea. I like that this standardizes the
concept of KTAP test metadata for both test suites and test cases. I
would love to discuss this concept further as KTAP v2 is developed.

My main concern would be that there is push back on stating the test
name when it is already present in the result line. This adds
potentially unnecessary lines to the output. However, one positive to
this is that diagnostic data could be printed under this header which
would reduce any confusion for which test the diagnostic data refers
to.

I would be interested if anyone else has any opinions on this.

> >
> > Use this method to report attributes in the KTAP output (KTAP spec:
> > https://docs.kernel.org/dev-tools/ktap.html) and _list_tests output when
> > kernel's new kunit.action=list_attr option is used. Note this is derivative
> > of the kunit.action=list option.
> >
> > In test.h, add fields and associated helper functions to test cases and
> > suites to hold user-inputted test attributes.
> >
> > Signed-off-by: Rae Moar <[email protected]>
> > ---
>
> The only other thing I'd really like to support one day is having
> attributes for individual parameters in parameterised tests. I think
> it makes sense as a follow-up, though.
>

That is an exciting idea! I think that would be ideal as a follow-up.

> >
> > Changes since v1:
> > - Add list_attr option to only include attribute in the _list_tests output
> > when this module param is set
> > - Add printing options for attributes to print always, print only for
> > suites, or print never.
> >
> > include/kunit/attributes.h | 19 +++++++++
> > include/kunit/test.h | 33 ++++++++++++++++
> > lib/kunit/Makefile | 3 +-
> > lib/kunit/attributes.c | 80 ++++++++++++++++++++++++++++++++++++++
> > lib/kunit/executor.c | 21 ++++++++--
> > lib/kunit/test.c | 17 ++++----
> > 6 files changed, 161 insertions(+), 12 deletions(-)
> > create mode 100644 include/kunit/attributes.h
> > create mode 100644 lib/kunit/attributes.c
> >
> > diff --git a/include/kunit/attributes.h b/include/kunit/attributes.h
> > new file mode 100644
> > index 000000000000..9fcd184cce36
> > --- /dev/null
> > +++ b/include/kunit/attributes.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * KUnit API to save and access test attributes
> > + *
> > + * Copyright (C) 2023, Google LLC.
> > + * Author: Rae Moar <[email protected]>
> > + */
> > +
> > +#ifndef _KUNIT_ATTRIBUTES_H
> > +#define _KUNIT_ATTRIBUTES_H
> > +
> > +/*
> > + * 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);
> > +
> > +#endif /* _KUNIT_ATTRIBUTES_H */
> > diff --git a/include/kunit/test.h b/include/kunit/test.h
> > index 23120d50499e..1fc9155988e9 100644
> > --- a/include/kunit/test.h
> > +++ b/include/kunit/test.h
> > @@ -63,12 +63,16 @@ enum kunit_status {
> > KUNIT_SKIPPED,
> > };
> >
> > +/* Holds attributes for each test case and suite */
> > +struct kunit_attributes {};
>
> Do we want a separate set of attributes for test cases and suites?
> (I think probably not, but it's worth making sure.)
>

I'm thinking if our goal is to eventually move to arbitrary nesting
for tests it would be easiest to try to keep this list the same. But I
agree. There may definitely be attributes that are more applicable for
test cases or suites. I'm inclined to keep it this way.

> > +
> > /**
> > * 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,6 +108,7 @@ 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;
> > @@ -133,6 +138,18 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
> > */
> > #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
> >
> > +/**
> > + * 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 }
> > +
> > /**
> > * KUNIT_CASE_PARAM - A helper for creation a parameterized &struct kunit_case
> > *
> > @@ -154,6 +171,20 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
> > { .run_case = test_name, .name = #test_name, \
> > .generate_params = gen_params }
> >
> > +/**
> > + * 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 }
> > +
>
> I do worry a bit that we'll end up with a huge list of variants of the
> KUNIT_CASE_* macros if we start adding more things here. I can't think
> of a better way to handle it at the moment, though.
>
>

I agree. If this becomes an issue, this could be a follow up change?

>
> > /**
> > * struct kunit_suite - describes a related collection of &struct kunit_case
> > *
> > @@ -163,6 +194,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 +214,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];
> > diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> > index cb417f504996..46f75f23dfe4 100644
> > --- a/lib/kunit/Makefile
> > +++ b/lib/kunit/Makefile
> > @@ -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
> > diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c
> > new file mode 100644
> > index 000000000000..9bda5a5f4030
> > --- /dev/null
> > +++ b/lib/kunit/attributes.c
> > @@ -0,0 +1,80 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * KUnit API to save and access test attributes
> > + *
> > + * Copyright (C) 2023, Google LLC.
> > + * Author: Rae Moar <[email protected]>
> > + */
> > +
> > +#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
> > + */
> > +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;
> > +};
> > +
> > +/* List of all Test Attributes */
> > +
> > +static struct kunit_attr kunit_attr_list[] = {};
> > +
> > +/* Helper Functions to Access Attributes */
> > +
> > +void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level)
> > +{
> > + int i;
> > + bool to_free;
> > + 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);
> > + }
> > + }
> > +}
> > diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c
> > index 74982b83707c..12e38a48a5cc 100644
> > --- a/lib/kunit/executor.c
> > +++ b/lib/kunit/executor.c
> > @@ -2,6 +2,7 @@
> >
> > #include <linux/reboot.h>
> > #include <kunit/test.h>
> > +#include <kunit/attributes.h>
> > #include <linux/glob.h>
> > #include <linux/moduleparam.h>
> >
> > @@ -24,7 +25,8 @@ module_param_named(action, action_param, charp, 0);
> > 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");
> >
> > /* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */
> > struct kunit_test_filter {
> > @@ -172,7 +174,7 @@ static void kunit_exec_run_tests(struct suite_set *suite_set)
> > __kunit_test_suites_init(suite_set->start, num_suites);
> > }
> >
> > -static void kunit_exec_list_tests(struct suite_set *suite_set)
> > +static void kunit_exec_list_tests(struct suite_set *suite_set, bool include_attr)
> > {
> > struct kunit_suite * const *suites;
> > struct kunit_case *test_case;
> > @@ -180,10 +182,19 @@ static void kunit_exec_list_tests(struct suite_set *suite_set)
> > /* 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++)
> > + 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);
> > }
> > + }
> > }
> >
> > int kunit_run_all_tests(void)
> > @@ -206,7 +217,9 @@ int kunit_run_all_tests(void)
> > if (!action_param)
> > kunit_exec_run_tests(&suite_set);
> > 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);
> >
> > diff --git a/lib/kunit/test.c b/lib/kunit/test.c
> > index 84e4666555c9..9ee55139ecd1 100644
> > --- a/lib/kunit/test.c
> > +++ b/lib/kunit/test.c
> > @@ -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,
> > @@ -651,6 +653,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);
> >
> > --
> > 2.41.0.255.g8b1d071c50-goog
> >

2023-07-19 09:01:51

by David Gow

[permalink] [raw]
Subject: Re: [RFC v2 9/9] kunit: Add documentation of KUnit test attributes

On Wed, 19 Jul 2023 at 04:49, Rae Moar <[email protected]> wrote:
>
> On Tue, Jul 18, 2023 at 3:39 AM David Gow <[email protected]> wrote:
> >
> > On Sat, 8 Jul 2023 at 05:10, Rae Moar <[email protected]> wrote:
> > >
> > > Add documentation on the use of test attributes under the section "Tips for
> > > Running KUnit Tests" in the KUnit docs.
> > >
> > > Documentation includes three sections on how to mark tests with attributes,
> > > how attributes are reported, and how the user can filter tests using test
> > > attributes.
> > >
> > > Signed-off-by: Rae Moar <[email protected]>
> > > ---
> >
> > Looks good overall. Some nitpicks below.
> >
> > Reviewed-by: David Gow <[email protected]>
> >
> > >
> > > Changes since v1:
> > > - This is a new patch
> > >
> > > .../dev-tools/kunit/running_tips.rst | 163 ++++++++++++++++++
> > > 1 file changed, 163 insertions(+)
> > >
> > > diff --git a/Documentation/dev-tools/kunit/running_tips.rst b/Documentation/dev-tools/kunit/running_tips.rst
> > > index 8e8c493f17d1..c9bc5a6595d3 100644
> > > --- a/Documentation/dev-tools/kunit/running_tips.rst
> > > +++ b/Documentation/dev-tools/kunit/running_tips.rst
> > > @@ -262,3 +262,166 @@ 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 kernel output (in KTAP
> > > +format). 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
> > > +
> >
> > Maybe worth noting that kunit.py will hide these for passing tests by
> > default, and --raw_output is needed to see them?
> >
>
> I will definitely add this in. If attributes are popular in the
> future, I could create a future patch to show attributes in the parser
> output as well.

Yeah, that could definitely be useful as a follow-up patch.

> > > +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_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 ``kunit.filter`` module param with
> > > +``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
> > > +--------------------------
> >
> > I wonder whether this should end up part of the KTAP spec (or as an
> > appendix/supplement to it). Or even as a separate page within the
> > KUnit documentation to avoid running_tips.rst from getting too huge.
>
> I am a bit hesitant to move this as part of the KTAP spec in case
> there will exist KTAP attributes/data that are not supported by the
> KUnit test attributes framework (could be runtime specific attributes
> that use a different framework?).

This is probably something best worked out as part of the KTAP spec
process. Either attribute names are a free-for-all (albeit hopefully
one where there are some documented 'common' attributes), or we need
some sort of namespacing between "General KTAP attributes",
"KUnit-specific attributes", "subsystem-specific attributes",
"totally-made-up-on-the-spot attributes", etc.

e.g., email headers have a list of 'standard' ones, but anyone can add
their own as long as it starts with 'X-'. Or OpenGL extensions are
always of the form GL_blah_blah_blah_<vendor> (where vendor is the
code for the company that proposed it, or EXT or ARB for those which
have been agreed upon by everyone).

> However, I do worry about the size of this page. Do you think that I
> should move all of the attributes to a new documentation page?

While I don't think it's a problem with only two attributes, it'd
probably be the more futureproof thing to do.

That being said, maybe we wait until there's a decision on the KTAP
side? Up to you.



> >
> > > +
> > > +``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".
> >
> > Is it worth noting that "KUNIT_CASE_SLOW()" can be used to easily set
> > this to slow?
>
> This definitely seems important to add. I will add this to the documentation.
>
> >
> >
> > > +
> > > +``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.
> > > +
> > > --
> > > 2.41.0.255.g8b1d071c50-goog
> >
> > >
> >
> > Error: new blank line at EOF.
>
> Oops. I will change this.


Attachments:
smime.p7s (3.91 kB)
S/MIME Cryptographic Signature

2023-07-19 09:03:27

by David Gow

[permalink] [raw]
Subject: Re: [RFC v2 1/9] kunit: Add test attributes API structure

On Wed, 19 Jul 2023 at 05:01, Rae Moar <[email protected]> wrote:
>
> On Tue, Jul 18, 2023 at 3:39 AM David Gow <[email protected]> wrote:
> >
> > On Sat, 8 Jul 2023 at 05:09, Rae Moar <[email protected]> wrote:
> > >
> > > Add the basic structure of the test attribute API to KUnit, which can be
> > > used to save and access test associated data.
> > >
> > > Add attributes.c and attributes.h to hold associated structs and functions
> > > for the API.
> > >
> > > Create a struct that holds a variety of associated helper functions for
> > > each test attribute. These helper functions will be used to get the
> > > attribute value, convert the value to a string, and filter based on the
> > > value. This struct is flexible by design to allow for attributes of
> > > numerous types and contexts.
> > >
> > > Add a method to print test attributes in the format of "# [<test_name if
> > > not suite>.]<attribute_name>: <attribute_value>".
> > >
> > > Example for a suite: "# speed: slow"
> > >
> > > Example for a test case: "# test_case.speed: very_slow"
> >
> > So, this is the one thing I'm a little unsure about here, and it's
> > really more of a problem with test names overall.
> >
> > As noted in the KTAPv2 attributes and test name proposals, the names
> > and attributes are only really defined for "suites", hence the need to
> > have a different output format for test cases.
> >
> > Personally, I'd prefer to keep the formats the same if we can (at
> > least for the actual KTAP output; I'm less concerned with the
> > list_attr option). That might make things a bit more difficult to
> > parse, though.
> >
> > One possibility would be to combine the KTAP attributes and test name
> > specs and suggest that every test has a "test name" attribute, which
> > must be the first attribute output.
> >
> > The output would then look something like:
> > KTAP version 2
> > # Name: my_suite
> > # Other-Attr: value
> > 1..2
> > KTAP version 2
> > # Name: test_1
> > # Other-Attr: value
> > ok 1 test_1
> > # Name: test_2
> > # Other-Attr: value
> > not ok 2 test_2
> > ok 1 my_suite
> >
> > Would there be any problems with something like that?
> >
> > I'm less concerned with the list_attr option, as that's not something
> > totally standardised in the way KTAP is.
> >
>
> This is a really interesting idea. I like that this standardizes the
> concept of KTAP test metadata for both test suites and test cases. I
> would love to discuss this concept further as KTAP v2 is developed.
>
> My main concern would be that there is push back on stating the test
> name when it is already present in the result line. This adds
> potentially unnecessary lines to the output. However, one positive to
> this is that diagnostic data could be printed under this header which
> would reduce any confusion for which test the diagnostic data refers
> to.

Yeah, I think the main advantage is that the test name is known when
any diagnostic/other lines are read, and the main disadvantage is that
for trivial cases, it becomes pretty redundant.

>
> I would be interested if anyone else has any opinions on this.
>

Let's stick with what we're doing for now, and take this to the KTAP thread.

> > >
> > > Use this method to report attributes in the KTAP output (KTAP spec:
> > > https://docs.kernel.org/dev-tools/ktap.html) and _list_tests output when
> > > kernel's new kunit.action=list_attr option is used. Note this is derivative
> > > of the kunit.action=list option.
> > >
> > > In test.h, add fields and associated helper functions to test cases and
> > > suites to hold user-inputted test attributes.
> > >
> > > Signed-off-by: Rae Moar <[email protected]>
> > > ---
> >
> > The only other thing I'd really like to support one day is having
> > attributes for individual parameters in parameterised tests. I think
> > it makes sense as a follow-up, though.
> >
>
> That is an exciting idea! I think that would be ideal as a follow-up.
>

Yeah, definitely no pressure to have that in the next version.

> > >
> > > Changes since v1:
> > > - Add list_attr option to only include attribute in the _list_tests output
> > > when this module param is set
> > > - Add printing options for attributes to print always, print only for
> > > suites, or print never.
> > >
> > > include/kunit/attributes.h | 19 +++++++++
> > > include/kunit/test.h | 33 ++++++++++++++++
> > > lib/kunit/Makefile | 3 +-
> > > lib/kunit/attributes.c | 80 ++++++++++++++++++++++++++++++++++++++
> > > lib/kunit/executor.c | 21 ++++++++--
> > > lib/kunit/test.c | 17 ++++----
> > > 6 files changed, 161 insertions(+), 12 deletions(-)
> > > create mode 100644 include/kunit/attributes.h
> > > create mode 100644 lib/kunit/attributes.c
> > >
> > > diff --git a/include/kunit/attributes.h b/include/kunit/attributes.h
> > > new file mode 100644
> > > index 000000000000..9fcd184cce36
> > > --- /dev/null
> > > +++ b/include/kunit/attributes.h
> > > @@ -0,0 +1,19 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * KUnit API to save and access test attributes
> > > + *
> > > + * Copyright (C) 2023, Google LLC.
> > > + * Author: Rae Moar <[email protected]>
> > > + */
> > > +
> > > +#ifndef _KUNIT_ATTRIBUTES_H
> > > +#define _KUNIT_ATTRIBUTES_H
> > > +
> > > +/*
> > > + * 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);
> > > +
> > > +#endif /* _KUNIT_ATTRIBUTES_H */
> > > diff --git a/include/kunit/test.h b/include/kunit/test.h
> > > index 23120d50499e..1fc9155988e9 100644
> > > --- a/include/kunit/test.h
> > > +++ b/include/kunit/test.h
> > > @@ -63,12 +63,16 @@ enum kunit_status {
> > > KUNIT_SKIPPED,
> > > };
> > >
> > > +/* Holds attributes for each test case and suite */
> > > +struct kunit_attributes {};
> >
> > Do we want a separate set of attributes for test cases and suites?
> > (I think probably not, but it's worth making sure.)
> >
>
> I'm thinking if our goal is to eventually move to arbitrary nesting
> for tests it would be easiest to try to keep this list the same. But I
> agree. There may definitely be attributes that are more applicable for
> test cases or suites. I'm inclined to keep it this way.
>

Agreed: having them share the same thing is definitely more future proof.

> > > +
> > > /**
> > > * 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,6 +108,7 @@ 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;
> > > @@ -133,6 +138,18 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
> > > */
> > > #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
> > >
> > > +/**
> > > + * 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 }
> > > +
> > > /**
> > > * KUNIT_CASE_PARAM - A helper for creation a parameterized &struct kunit_case
> > > *
> > > @@ -154,6 +171,20 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
> > > { .run_case = test_name, .name = #test_name, \
> > > .generate_params = gen_params }
> > >
> > > +/**
> > > + * 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 }
> > > +
> >
> > I do worry a bit that we'll end up with a huge list of variants of the
> > KUNIT_CASE_* macros if we start adding more things here. I can't think
> > of a better way to handle it at the moment, though.
> >
> >
>
> I agree. If this becomes an issue, this could be a follow up change?
>
Yeah, sounds good.


> >
> > > /**
> > > * struct kunit_suite - describes a related collection of &struct kunit_case
> > > *
> > > @@ -163,6 +194,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 +214,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];
> > > diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> > > index cb417f504996..46f75f23dfe4 100644
> > > --- a/lib/kunit/Makefile
> > > +++ b/lib/kunit/Makefile
> > > @@ -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
> > > diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c
> > > new file mode 100644
> > > index 000000000000..9bda5a5f4030
> > > --- /dev/null
> > > +++ b/lib/kunit/attributes.c
> > > @@ -0,0 +1,80 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * KUnit API to save and access test attributes
> > > + *
> > > + * Copyright (C) 2023, Google LLC.
> > > + * Author: Rae Moar <[email protected]>
> > > + */
> > > +
> > > +#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
> > > + */
> > > +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;
> > > +};
> > > +
> > > +/* List of all Test Attributes */
> > > +
> > > +static struct kunit_attr kunit_attr_list[] = {};
> > > +
> > > +/* Helper Functions to Access Attributes */
> > > +
> > > +void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level)
> > > +{
> > > + int i;
> > > + bool to_free;
> > > + 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);
> > > + }
> > > + }
> > > +}
> > > diff --git a/lib/kunit/executor.c b/lib/kunit/executor.c
> > > index 74982b83707c..12e38a48a5cc 100644
> > > --- a/lib/kunit/executor.c
> > > +++ b/lib/kunit/executor.c
> > > @@ -2,6 +2,7 @@
> > >
> > > #include <linux/reboot.h>
> > > #include <kunit/test.h>
> > > +#include <kunit/attributes.h>
> > > #include <linux/glob.h>
> > > #include <linux/moduleparam.h>
> > >
> > > @@ -24,7 +25,8 @@ module_param_named(action, action_param, charp, 0);
> > > 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");
> > >
> > > /* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */
> > > struct kunit_test_filter {
> > > @@ -172,7 +174,7 @@ static void kunit_exec_run_tests(struct suite_set *suite_set)
> > > __kunit_test_suites_init(suite_set->start, num_suites);
> > > }
> > >
> > > -static void kunit_exec_list_tests(struct suite_set *suite_set)
> > > +static void kunit_exec_list_tests(struct suite_set *suite_set, bool include_attr)
> > > {
> > > struct kunit_suite * const *suites;
> > > struct kunit_case *test_case;
> > > @@ -180,10 +182,19 @@ static void kunit_exec_list_tests(struct suite_set *suite_set)
> > > /* 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++)
> > > + 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);
> > > }
> > > + }
> > > }
> > >
> > > int kunit_run_all_tests(void)
> > > @@ -206,7 +217,9 @@ int kunit_run_all_tests(void)
> > > if (!action_param)
> > > kunit_exec_run_tests(&suite_set);
> > > 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);
> > >
> > > diff --git a/lib/kunit/test.c b/lib/kunit/test.c
> > > index 84e4666555c9..9ee55139ecd1 100644
> > > --- a/lib/kunit/test.c
> > > +++ b/lib/kunit/test.c
> > > @@ -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,
> > > @@ -651,6 +653,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);
> > >
> > > --
> > > 2.41.0.255.g8b1d071c50-goog
> > >


Attachments:
smime.p7s (3.91 kB)
S/MIME Cryptographic Signature