Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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.

Reviewed-by: David Gow <davidgow@google.com>
Signed-off-by: Rae Moar <rmoar@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>

authored by

Rae Moar and committed by
Shuah Khan
39e92cb1 64bd4641

+161 -12
+19
include/kunit/attributes.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * KUnit API to save and access test attributes 4 + * 5 + * Copyright (C) 2023, Google LLC. 6 + * Author: Rae Moar <rmoar@google.com> 7 + */ 8 + 9 + #ifndef _KUNIT_ATTRIBUTES_H 10 + #define _KUNIT_ATTRIBUTES_H 11 + 12 + /* 13 + * Print all test attributes for a test case or suite. 14 + * Output format for test cases: "# <test_name>.<attribute>: <value>" 15 + * Output format for test suites: "# <attribute>: <value>" 16 + */ 17 + void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level); 18 + 19 + #endif /* _KUNIT_ATTRIBUTES_H */
+33
include/kunit/test.h
··· 63 63 KUNIT_SKIPPED, 64 64 }; 65 65 66 + /* Holds attributes for each test case and suite */ 67 + struct kunit_attributes {}; 68 + 66 69 /** 67 70 * struct kunit_case - represents an individual test case. 68 71 * 69 72 * @run_case: the function representing the actual test case. 70 73 * @name: the name of the test case. 71 74 * @generate_params: the generator function for parameterized tests. 75 + * @attr: the attributes associated with the test 72 76 * 73 77 * A test case is a function with the signature, 74 78 * ``void (*)(struct kunit *)`` ··· 108 104 void (*run_case)(struct kunit *test); 109 105 const char *name; 110 106 const void* (*generate_params)(const void *prev, char *desc); 107 + struct kunit_attributes attr; 111 108 112 109 /* private: internal use only. */ 113 110 enum kunit_status status; ··· 139 134 #define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name } 140 135 141 136 /** 137 + * KUNIT_CASE_ATTR - A helper for creating a &struct kunit_case 138 + * with attributes 139 + * 140 + * @test_name: a reference to a test case function. 141 + * @attributes: a reference to a struct kunit_attributes object containing 142 + * test attributes 143 + */ 144 + #define KUNIT_CASE_ATTR(test_name, attributes) \ 145 + { .run_case = test_name, .name = #test_name, \ 146 + .attr = attributes } 147 + 148 + /** 142 149 * KUNIT_CASE_PARAM - A helper for creation a parameterized &struct kunit_case 143 150 * 144 151 * @test_name: a reference to a test case function. ··· 172 155 .generate_params = gen_params } 173 156 174 157 /** 158 + * KUNIT_CASE_PARAM_ATTR - A helper for creating a parameterized &struct 159 + * kunit_case with attributes 160 + * 161 + * @test_name: a reference to a test case function. 162 + * @gen_params: a reference to a parameter generator function. 163 + * @attributes: a reference to a struct kunit_attributes object containing 164 + * test attributes 165 + */ 166 + #define KUNIT_CASE_PARAM_ATTR(test_name, gen_params, attributes) \ 167 + { .run_case = test_name, .name = #test_name, \ 168 + .generate_params = gen_params, \ 169 + .attr = attributes } 170 + 171 + /** 175 172 * struct kunit_suite - describes a related collection of &struct kunit_case 176 173 * 177 174 * @name: the name of the test. Purely informational. ··· 194 163 * @init: called before every test case. 195 164 * @exit: called after every test case. 196 165 * @test_cases: a null terminated array of test cases. 166 + * @attr: the attributes associated with the test suite 197 167 * 198 168 * A kunit_suite is a collection of related &struct kunit_case s, such that 199 169 * @init is called before every test case and @exit is called after every ··· 214 182 int (*init)(struct kunit *test); 215 183 void (*exit)(struct kunit *test); 216 184 struct kunit_case *test_cases; 185 + struct kunit_attributes attr; 217 186 218 187 /* private: internal use only */ 219 188 char status_comment[KUNIT_STATUS_COMMENT_SIZE];
+2 -1
lib/kunit/Makefile
··· 6 6 string-stream.o \ 7 7 assert.o \ 8 8 try-catch.o \ 9 - executor.o 9 + executor.o \ 10 + attributes.o 10 11 11 12 ifeq ($(CONFIG_KUNIT_DEBUGFS),y) 12 13 kunit-objs += debugfs.o
+80
lib/kunit/attributes.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * KUnit API to save and access test attributes 4 + * 5 + * Copyright (C) 2023, Google LLC. 6 + * Author: Rae Moar <rmoar@google.com> 7 + */ 8 + 9 + #include <kunit/test.h> 10 + #include <kunit/attributes.h> 11 + 12 + /* Options for printing attributes: 13 + * PRINT_ALWAYS - attribute is printed for every test case and suite if set 14 + * PRINT_SUITE - attribute is printed for every suite if set but not for test cases 15 + * PRINT_NEVER - attribute is never printed 16 + */ 17 + enum print_ops { 18 + PRINT_ALWAYS, 19 + PRINT_SUITE, 20 + PRINT_NEVER, 21 + }; 22 + 23 + /** 24 + * struct kunit_attr - represents a test attribute and holds flexible 25 + * helper functions to interact with attribute. 26 + * 27 + * @name: name of test attribute, eg. speed 28 + * @get_attr: function to return attribute value given a test 29 + * @to_string: function to return string representation of given 30 + * attribute value 31 + * @filter: function to indicate whether a given attribute value passes a 32 + * filter 33 + */ 34 + struct kunit_attr { 35 + const char *name; 36 + void *(*get_attr)(void *test_or_suite, bool is_test); 37 + const char *(*to_string)(void *attr, bool *to_free); 38 + int (*filter)(void *attr, const char *input, int *err); 39 + void *attr_default; 40 + enum print_ops print; 41 + }; 42 + 43 + /* List of all Test Attributes */ 44 + 45 + static struct kunit_attr kunit_attr_list[] = {}; 46 + 47 + /* Helper Functions to Access Attributes */ 48 + 49 + void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level) 50 + { 51 + int i; 52 + bool to_free; 53 + void *attr; 54 + const char *attr_name, *attr_str; 55 + struct kunit_suite *suite = is_test ? NULL : test_or_suite; 56 + struct kunit_case *test = is_test ? test_or_suite : NULL; 57 + 58 + for (i = 0; i < ARRAY_SIZE(kunit_attr_list); i++) { 59 + if (kunit_attr_list[i].print == PRINT_NEVER || 60 + (test && kunit_attr_list[i].print == PRINT_SUITE)) 61 + continue; 62 + attr = kunit_attr_list[i].get_attr(test_or_suite, is_test); 63 + if (attr) { 64 + attr_name = kunit_attr_list[i].name; 65 + attr_str = kunit_attr_list[i].to_string(attr, &to_free); 66 + if (test) { 67 + kunit_log(KERN_INFO, test, "%*s# %s.%s: %s", 68 + KUNIT_INDENT_LEN * test_level, "", test->name, 69 + attr_name, attr_str); 70 + } else { 71 + kunit_log(KERN_INFO, suite, "%*s# %s: %s", 72 + KUNIT_INDENT_LEN * test_level, "", attr_name, attr_str); 73 + } 74 + 75 + /* Free to_string of attribute if needed */ 76 + if (to_free) 77 + kfree(attr_str); 78 + } 79 + } 80 + }
+17 -4
lib/kunit/executor.c
··· 2 2 3 3 #include <linux/reboot.h> 4 4 #include <kunit/test.h> 5 + #include <kunit/attributes.h> 5 6 #include <linux/glob.h> 6 7 #include <linux/moduleparam.h> 7 8 ··· 25 24 MODULE_PARM_DESC(action, 26 25 "Changes KUnit executor behavior, valid values are:\n" 27 26 "<none>: run the tests like normal\n" 28 - "'list' to list test names instead of running them.\n"); 27 + "'list' to list test names instead of running them.\n" 28 + "'list_attr' to list test names and attributes instead of running them.\n"); 29 29 30 30 /* glob_match() needs NULL terminated strings, so we need a copy of filter_glob_param. */ 31 31 struct kunit_test_filter { ··· 174 172 __kunit_test_suites_init(suite_set->start, num_suites); 175 173 } 176 174 177 - static void kunit_exec_list_tests(struct suite_set *suite_set) 175 + static void kunit_exec_list_tests(struct suite_set *suite_set, bool include_attr) 178 176 { 179 177 struct kunit_suite * const *suites; 180 178 struct kunit_case *test_case; ··· 182 180 /* Hack: print a ktap header so kunit.py can find the start of KUnit output. */ 183 181 pr_info("KTAP version 1\n"); 184 182 185 - for (suites = suite_set->start; suites < suite_set->end; suites++) 183 + for (suites = suite_set->start; suites < suite_set->end; suites++) { 184 + /* Print suite name and suite attributes */ 185 + pr_info("%s\n", (*suites)->name); 186 + if (include_attr) 187 + kunit_print_attr((void *)(*suites), false, 0); 188 + 189 + /* Print test case name and attributes in suite */ 186 190 kunit_suite_for_each_test_case((*suites), test_case) { 187 191 pr_info("%s.%s\n", (*suites)->name, test_case->name); 192 + if (include_attr) 193 + kunit_print_attr((void *)test_case, true, 0); 188 194 } 195 + } 189 196 } 190 197 191 198 int kunit_run_all_tests(void) ··· 217 206 if (!action_param) 218 207 kunit_exec_run_tests(&suite_set); 219 208 else if (strcmp(action_param, "list") == 0) 220 - kunit_exec_list_tests(&suite_set); 209 + kunit_exec_list_tests(&suite_set, false); 210 + else if (strcmp(action_param, "list_attr") == 0) 211 + kunit_exec_list_tests(&suite_set, true); 221 212 else 222 213 pr_err("kunit executor: unknown action '%s'\n", action_param); 223 214
+10 -7
lib/kunit/test.c
··· 9 9 #include <kunit/resource.h> 10 10 #include <kunit/test.h> 11 11 #include <kunit/test-bug.h> 12 + #include <kunit/attributes.h> 12 13 #include <linux/kernel.h> 13 14 #include <linux/module.h> 14 15 #include <linux/moduleparam.h> ··· 169 168 } 170 169 EXPORT_SYMBOL_GPL(kunit_suite_num_test_cases); 171 170 171 + /* Currently supported test levels */ 172 + enum { 173 + KUNIT_LEVEL_SUITE = 0, 174 + KUNIT_LEVEL_CASE, 175 + KUNIT_LEVEL_CASE_PARAM, 176 + }; 177 + 172 178 static void kunit_print_suite_start(struct kunit_suite *suite) 173 179 { 174 180 /* ··· 189 181 pr_info(KUNIT_SUBTEST_INDENT "KTAP version 1\n"); 190 182 pr_info(KUNIT_SUBTEST_INDENT "# Subtest: %s\n", 191 183 suite->name); 184 + kunit_print_attr((void *)suite, false, KUNIT_LEVEL_CASE); 192 185 pr_info(KUNIT_SUBTEST_INDENT "1..%zd\n", 193 186 kunit_suite_num_test_cases(suite)); 194 187 } 195 - 196 - /* Currently supported test levels */ 197 - enum { 198 - KUNIT_LEVEL_SUITE = 0, 199 - KUNIT_LEVEL_CASE, 200 - KUNIT_LEVEL_CASE_PARAM, 201 - }; 202 188 203 189 static void kunit_print_ok_not_ok(struct kunit *test, 204 190 unsigned int test_level, ··· 653 651 } 654 652 } 655 653 654 + kunit_print_attr((void *)test_case, true, KUNIT_LEVEL_CASE); 656 655 657 656 kunit_print_test_stats(&test, param_stats); 658 657