1 // SPDX-License-Identifier: GPL-2.0
3 * KUnit API to save and access test attributes
5 * Copyright (C) 2023, Google LLC.
6 * Author: Rae Moar <rmoar@google.com>
9 #include <kunit/test.h>
10 #include <kunit/attributes.h>
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
24 * struct kunit_attr - represents a test attribute and holds flexible
25 * helper functions to interact with attribute.
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
31 * @filter: function to indicate whether a given attribute value passes a
33 * @attr_default: default attribute value used during filtering
34 * @print: value of enum print_ops to indicate when to print attribute
38 void *(*get_attr
)(void *test_or_suite
, bool is_test
);
39 const char *(*to_string
)(void *attr
, bool *to_free
);
40 int (*filter
)(void *attr
, const char *input
, int *err
);
45 /* String Lists for enum Attributes */
47 static const char * const speed_str_list
[] = {"unset", "very_slow", "slow", "normal"};
49 /* To String Methods */
51 static const char *attr_enum_to_string(void *attr
, const char * const str_list
[], bool *to_free
)
53 long val
= (long)attr
;
61 static const char *attr_bool_to_string(void *attr
, bool *to_free
)
63 bool val
= (bool)attr
;
71 static const char *attr_speed_to_string(void *attr
, bool *to_free
)
73 return attr_enum_to_string(attr
, speed_str_list
, to_free
);
76 static const char *attr_string_to_string(void *attr
, bool *to_free
)
84 static const char op_list
[] = "<>!=";
87 * Returns whether the inputted integer value matches the filter given
88 * by the operation string and inputted integer.
90 static int int_filter(long val
, const char *op
, int input
, int *err
)
92 if (!strncmp(op
, "<=", 2))
93 return (val
<= input
);
94 else if (!strncmp(op
, ">=", 2))
95 return (val
>= input
);
96 else if (!strncmp(op
, "!=", 2))
97 return (val
!= input
);
98 else if (!strncmp(op
, ">", 1))
100 else if (!strncmp(op
, "<", 1))
101 return (val
< input
);
102 else if (!strncmp(op
, "=", 1))
103 return (val
== input
);
105 pr_err("kunit executor: invalid filter operation: %s\n", op
);
110 * Returns whether the inputted enum value "attr" matches the filter given
111 * by the input string. Note: the str_list includes the corresponding string
112 * list to the enum values.
114 static int attr_enum_filter(void *attr
, const char *input
, int *err
,
115 const char * const str_list
[], int max
)
117 int i
, j
, input_int
= -1;
118 long test_val
= (long)attr
;
119 const char *input_val
= NULL
;
121 for (i
= 0; input
[i
]; i
++) {
122 if (!strchr(op_list
, input
[i
])) {
123 input_val
= input
+ i
;
130 pr_err("kunit executor: filter value not found: %s\n", input
);
134 for (j
= 0; j
<= max
; j
++) {
135 if (!strcmp(input_val
, str_list
[j
]))
141 pr_err("kunit executor: invalid filter input: %s\n", input
);
145 return int_filter(test_val
, input
, input_int
, err
);
148 static int attr_speed_filter(void *attr
, const char *input
, int *err
)
150 return attr_enum_filter(attr
, input
, err
, speed_str_list
, KUNIT_SPEED_MAX
);
154 * Returns whether the inputted string value (attr) matches the filter given
155 * by the input string.
157 static int attr_string_filter(void *attr
, const char *input
, int *err
)
161 if (!strncmp(input
, "<", 1)) {
163 pr_err("kunit executor: invalid filter input: %s\n", input
);
165 } else if (!strncmp(input
, ">", 1)) {
167 pr_err("kunit executor: invalid filter input: %s\n", input
);
169 } else if (!strncmp(input
, "!=", 2)) {
170 return (strcmp(input
+ 2, str
) != 0);
171 } else if (!strncmp(input
, "=", 1)) {
172 return (strcmp(input
+ 1, str
) == 0);
175 pr_err("kunit executor: invalid filter operation: %s\n", input
);
179 static int attr_bool_filter(void *attr
, const char *input
, int *err
)
181 int i
, input_int
= -1;
182 long val
= (long)attr
;
183 const char *input_str
= NULL
;
185 for (i
= 0; input
[i
]; i
++) {
186 if (!strchr(op_list
, input
[i
])) {
187 input_str
= input
+ i
;
194 pr_err("kunit executor: filter value not found: %s\n", input
);
198 if (!strcmp(input_str
, "true"))
199 input_int
= (int)true;
200 else if (!strcmp(input_str
, "false"))
201 input_int
= (int)false;
204 pr_err("kunit executor: invalid filter input: %s\n", input
);
208 return int_filter(val
, input
, input_int
, err
);
211 /* Get Attribute Methods */
213 static void *attr_speed_get(void *test_or_suite
, bool is_test
)
215 struct kunit_suite
*suite
= is_test
? NULL
: test_or_suite
;
216 struct kunit_case
*test
= is_test
? test_or_suite
: NULL
;
219 return ((void *) test
->attr
.speed
);
221 return ((void *) suite
->attr
.speed
);
224 static void *attr_module_get(void *test_or_suite
, bool is_test
)
226 struct kunit_suite
*suite
= is_test
? NULL
: test_or_suite
;
227 struct kunit_case
*test
= is_test
? test_or_suite
: NULL
;
229 // Suites get their module attribute from their first test_case
231 return ((void *) test
->module_name
);
232 else if (kunit_suite_num_test_cases(suite
) > 0)
233 return ((void *) suite
->test_cases
[0].module_name
);
238 static void *attr_is_init_get(void *test_or_suite
, bool is_test
)
240 struct kunit_suite
*suite
= is_test
? NULL
: test_or_suite
;
241 struct kunit_case
*test
= is_test
? test_or_suite
: NULL
;
244 return ((void *) NULL
);
246 return ((void *) suite
->is_init
);
249 /* List of all Test Attributes */
251 static struct kunit_attr kunit_attr_list
[] = {
254 .get_attr
= attr_speed_get
,
255 .to_string
= attr_speed_to_string
,
256 .filter
= attr_speed_filter
,
257 .attr_default
= (void *)KUNIT_SPEED_NORMAL
,
258 .print
= PRINT_ALWAYS
,
262 .get_attr
= attr_module_get
,
263 .to_string
= attr_string_to_string
,
264 .filter
= attr_string_filter
,
265 .attr_default
= (void *)"",
266 .print
= PRINT_SUITE
,
270 .get_attr
= attr_is_init_get
,
271 .to_string
= attr_bool_to_string
,
272 .filter
= attr_bool_filter
,
273 .attr_default
= (void *)false,
274 .print
= PRINT_SUITE
,
278 /* Helper Functions to Access Attributes */
280 const char *kunit_attr_filter_name(struct kunit_attr_filter filter
)
282 return filter
.attr
->name
;
285 void kunit_print_attr(void *test_or_suite
, bool is_test
, unsigned int test_level
)
288 bool to_free
= false;
290 const char *attr_name
, *attr_str
;
291 struct kunit_suite
*suite
= is_test
? NULL
: test_or_suite
;
292 struct kunit_case
*test
= is_test
? test_or_suite
: NULL
;
294 for (i
= 0; i
< ARRAY_SIZE(kunit_attr_list
); i
++) {
295 if (kunit_attr_list
[i
].print
== PRINT_NEVER
||
296 (test
&& kunit_attr_list
[i
].print
== PRINT_SUITE
))
298 attr
= kunit_attr_list
[i
].get_attr(test_or_suite
, is_test
);
300 attr_name
= kunit_attr_list
[i
].name
;
301 attr_str
= kunit_attr_list
[i
].to_string(attr
, &to_free
);
303 kunit_log(KERN_INFO
, test
, "%*s# %s.%s: %s",
304 KUNIT_INDENT_LEN
* test_level
, "", test
->name
,
305 attr_name
, attr_str
);
307 kunit_log(KERN_INFO
, suite
, "%*s# %s: %s",
308 KUNIT_INDENT_LEN
* test_level
, "", attr_name
, attr_str
);
311 /* Free to_string of attribute if needed */
318 /* Helper Functions to Filter Attributes */
320 int kunit_get_filter_count(char *input
)
322 int i
, comma_index
= 0, count
= 0;
324 for (i
= 0; input
[i
]; i
++) {
325 if (input
[i
] == ',') {
326 if ((i
- comma_index
) > 1)
331 if ((i
- comma_index
) > 0)
336 struct kunit_attr_filter
kunit_next_attr_filter(char **filters
, int *err
)
338 struct kunit_attr_filter filter
= {};
339 int i
, j
, comma_index
= 0, new_start_index
= 0;
340 int op_index
= -1, attr_index
= -1;
342 char *input
= *filters
;
344 /* Parse input until operation */
345 for (i
= 0; input
[i
]; i
++) {
346 if (op_index
< 0 && strchr(op_list
, input
[i
])) {
348 } else if (!comma_index
&& input
[i
] == ',') {
350 } else if (comma_index
&& input
[i
] != ' ') {
358 pr_err("kunit executor: filter operation not found: %s\n", input
);
362 /* Temporarily set operator to \0 character. */
363 op
= input
[op_index
];
364 input
[op_index
] = '\0';
366 /* Find associated kunit_attr object */
367 for (j
= 0; j
< ARRAY_SIZE(kunit_attr_list
); j
++) {
368 if (!strcmp(input
, kunit_attr_list
[j
].name
)) {
374 input
[op_index
] = op
;
376 if (attr_index
< 0) {
378 pr_err("kunit executor: attribute not found: %s\n", input
);
380 filter
.attr
= &kunit_attr_list
[attr_index
];
383 if (comma_index
> 0) {
384 input
[comma_index
] = '\0';
385 filter
.input
= input
+ op_index
;
386 input
= input
+ new_start_index
;
388 filter
.input
= input
+ op_index
;
397 struct kunit_suite
*kunit_filter_attr_tests(const struct kunit_suite
*const suite
,
398 struct kunit_attr_filter filter
, char *action
, int *err
)
401 struct kunit_case
*filtered
, *test_case
;
402 struct kunit_suite
*copy
;
403 void *suite_val
, *test_val
;
404 bool suite_result
, test_result
, default_result
, result
;
406 /* Allocate memory for new copy of suite and list of test cases */
407 copy
= kmemdup(suite
, sizeof(*copy
), GFP_KERNEL
);
409 return ERR_PTR(-ENOMEM
);
411 kunit_suite_for_each_test_case(suite
, test_case
) { n
++; }
413 filtered
= kcalloc(n
+ 1, sizeof(*filtered
), GFP_KERNEL
);
416 return ERR_PTR(-ENOMEM
);
421 /* Save filtering result on default value */
422 default_result
= filter
.attr
->filter(filter
.attr
->attr_default
, filter
.input
, err
);
426 /* Save suite attribute value and filtering result on that value */
427 suite_val
= filter
.attr
->get_attr((void *)suite
, false);
428 suite_result
= filter
.attr
->filter(suite_val
, filter
.input
, err
);
432 /* For each test case, save test case if passes filtering. */
433 kunit_suite_for_each_test_case(suite
, test_case
) {
434 test_val
= filter
.attr
->get_attr((void *) test_case
, true);
435 test_result
= filter
.attr
->filter(filter
.attr
->get_attr(test_case
, true),
441 * If attribute value of test case is set, filter on that value.
442 * If not, filter on suite value if set. If not, filter on
449 } else if (suite_val
) {
452 } else if (default_result
) {
457 filtered
[n
++] = *test_case
;
458 } else if (action
&& strcmp(action
, "skip") == 0) {
459 test_case
->status
= KUNIT_SKIPPED
;
460 filtered
[n
++] = *test_case
;
465 if (n
== 0 || *err
) {
471 copy
->test_cases
= filtered
;