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 | * @attr_default: default attribute value used during filtering |
34 | * @print: value of enum print_ops to indicate when to print attribute |
35 | */ |
36 | struct kunit_attr { |
37 | const char *name; |
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); |
41 | void *attr_default; |
42 | enum print_ops print; |
43 | }; |
44 | |
45 | /* String Lists for enum Attributes */ |
46 | |
47 | static const char * const speed_str_list[] = {"unset" , "very_slow" , "slow" , "normal" }; |
48 | |
49 | /* To String Methods */ |
50 | |
51 | static const char *attr_enum_to_string(void *attr, const char * const str_list[], bool *to_free) |
52 | { |
53 | long val = (long)attr; |
54 | |
55 | *to_free = false; |
56 | if (!val) |
57 | return NULL; |
58 | return str_list[val]; |
59 | } |
60 | |
61 | static const char *attr_bool_to_string(void *attr, bool *to_free) |
62 | { |
63 | bool val = (bool)attr; |
64 | |
65 | *to_free = false; |
66 | if (val) |
67 | return "true" ; |
68 | return "false" ; |
69 | } |
70 | |
71 | static const char *attr_speed_to_string(void *attr, bool *to_free) |
72 | { |
73 | return attr_enum_to_string(attr, str_list: speed_str_list, to_free); |
74 | } |
75 | |
76 | static const char *attr_string_to_string(void *attr, bool *to_free) |
77 | { |
78 | *to_free = false; |
79 | return (char *) attr; |
80 | } |
81 | |
82 | /* Filter Methods */ |
83 | |
84 | static const char op_list[] = "<>!=" ; |
85 | |
86 | /* |
87 | * Returns whether the inputted integer value matches the filter given |
88 | * by the operation string and inputted integer. |
89 | */ |
90 | static int int_filter(long val, const char *op, int input, int *err) |
91 | { |
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)) |
99 | return (val > input); |
100 | else if (!strncmp(op, "<" , 1)) |
101 | return (val < input); |
102 | else if (!strncmp(op, "=" , 1)) |
103 | return (val == input); |
104 | *err = -EINVAL; |
105 | pr_err("kunit executor: invalid filter operation: %s\n" , op); |
106 | return false; |
107 | } |
108 | |
109 | /* |
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. |
113 | */ |
114 | static int attr_enum_filter(void *attr, const char *input, int *err, |
115 | const char * const str_list[], int max) |
116 | { |
117 | int i, j, input_int = -1; |
118 | long test_val = (long)attr; |
119 | const char *input_val = NULL; |
120 | |
121 | for (i = 0; input[i]; i++) { |
122 | if (!strchr(op_list, input[i])) { |
123 | input_val = input + i; |
124 | break; |
125 | } |
126 | } |
127 | |
128 | if (!input_val) { |
129 | *err = -EINVAL; |
130 | pr_err("kunit executor: filter value not found: %s\n" , input); |
131 | return false; |
132 | } |
133 | |
134 | for (j = 0; j <= max; j++) { |
135 | if (!strcmp(input_val, str_list[j])) |
136 | input_int = j; |
137 | } |
138 | |
139 | if (input_int < 0) { |
140 | *err = -EINVAL; |
141 | pr_err("kunit executor: invalid filter input: %s\n" , input); |
142 | return false; |
143 | } |
144 | |
145 | return int_filter(val: test_val, op: input, input: input_int, err); |
146 | } |
147 | |
148 | static int attr_speed_filter(void *attr, const char *input, int *err) |
149 | { |
150 | return attr_enum_filter(attr, input, err, str_list: speed_str_list, max: KUNIT_SPEED_MAX); |
151 | } |
152 | |
153 | /* |
154 | * Returns whether the inputted string value (attr) matches the filter given |
155 | * by the input string. |
156 | */ |
157 | static int attr_string_filter(void *attr, const char *input, int *err) |
158 | { |
159 | char *str = attr; |
160 | |
161 | if (!strncmp(input, "<" , 1)) { |
162 | *err = -EINVAL; |
163 | pr_err("kunit executor: invalid filter input: %s\n" , input); |
164 | return false; |
165 | } else if (!strncmp(input, ">" , 1)) { |
166 | *err = -EINVAL; |
167 | pr_err("kunit executor: invalid filter input: %s\n" , input); |
168 | return false; |
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); |
173 | } |
174 | *err = -EINVAL; |
175 | pr_err("kunit executor: invalid filter operation: %s\n" , input); |
176 | return false; |
177 | } |
178 | |
179 | static int attr_bool_filter(void *attr, const char *input, int *err) |
180 | { |
181 | int i, input_int = -1; |
182 | long val = (long)attr; |
183 | const char *input_str = NULL; |
184 | |
185 | for (i = 0; input[i]; i++) { |
186 | if (!strchr(op_list, input[i])) { |
187 | input_str = input + i; |
188 | break; |
189 | } |
190 | } |
191 | |
192 | if (!input_str) { |
193 | *err = -EINVAL; |
194 | pr_err("kunit executor: filter value not found: %s\n" , input); |
195 | return false; |
196 | } |
197 | |
198 | if (!strcmp(input_str, "true" )) |
199 | input_int = (int)true; |
200 | else if (!strcmp(input_str, "false" )) |
201 | input_int = (int)false; |
202 | else { |
203 | *err = -EINVAL; |
204 | pr_err("kunit executor: invalid filter input: %s\n" , input); |
205 | return false; |
206 | } |
207 | |
208 | return int_filter(val, op: input, input: input_int, err); |
209 | } |
210 | |
211 | /* Get Attribute Methods */ |
212 | |
213 | static void *attr_speed_get(void *test_or_suite, bool is_test) |
214 | { |
215 | struct kunit_suite *suite = is_test ? NULL : test_or_suite; |
216 | struct kunit_case *test = is_test ? test_or_suite : NULL; |
217 | |
218 | if (test) |
219 | return ((void *) test->attr.speed); |
220 | else |
221 | return ((void *) suite->attr.speed); |
222 | } |
223 | |
224 | static void *attr_module_get(void *test_or_suite, bool is_test) |
225 | { |
226 | struct kunit_suite *suite = is_test ? NULL : test_or_suite; |
227 | struct kunit_case *test = is_test ? test_or_suite : NULL; |
228 | |
229 | // Suites get their module attribute from their first test_case |
230 | if (test) |
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); |
234 | else |
235 | return (void *) "" ; |
236 | } |
237 | |
238 | static void *attr_is_init_get(void *test_or_suite, bool is_test) |
239 | { |
240 | struct kunit_suite *suite = is_test ? NULL : test_or_suite; |
241 | struct kunit_case *test = is_test ? test_or_suite : NULL; |
242 | |
243 | if (test) |
244 | return ((void *) NULL); |
245 | else |
246 | return ((void *) suite->is_init); |
247 | } |
248 | |
249 | /* List of all Test Attributes */ |
250 | |
251 | static struct kunit_attr kunit_attr_list[] = { |
252 | { |
253 | .name = "speed" , |
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, |
259 | }, |
260 | { |
261 | .name = "module" , |
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, |
267 | }, |
268 | { |
269 | .name = "is_init" , |
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, |
275 | } |
276 | }; |
277 | |
278 | /* Helper Functions to Access Attributes */ |
279 | |
280 | const char *kunit_attr_filter_name(struct kunit_attr_filter filter) |
281 | { |
282 | return filter.attr->name; |
283 | } |
284 | |
285 | void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level) |
286 | { |
287 | int i; |
288 | bool to_free = false; |
289 | void *attr; |
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; |
293 | |
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)) |
297 | continue; |
298 | attr = kunit_attr_list[i].get_attr(test_or_suite, is_test); |
299 | if (attr) { |
300 | attr_name = kunit_attr_list[i].name; |
301 | attr_str = kunit_attr_list[i].to_string(attr, &to_free); |
302 | if (test) { |
303 | kunit_log(KERN_INFO, test, "%*s# %s.%s: %s" , |
304 | KUNIT_INDENT_LEN * test_level, "" , test->name, |
305 | attr_name, attr_str); |
306 | } else { |
307 | kunit_log(KERN_INFO, suite, "%*s# %s: %s" , |
308 | KUNIT_INDENT_LEN * test_level, "" , attr_name, attr_str); |
309 | } |
310 | |
311 | /* Free to_string of attribute if needed */ |
312 | if (to_free) |
313 | kfree(objp: attr_str); |
314 | } |
315 | } |
316 | } |
317 | |
318 | /* Helper Functions to Filter Attributes */ |
319 | |
320 | int kunit_get_filter_count(char *input) |
321 | { |
322 | int i, comma_index = 0, count = 0; |
323 | |
324 | for (i = 0; input[i]; i++) { |
325 | if (input[i] == ',') { |
326 | if ((i - comma_index) > 1) |
327 | count++; |
328 | comma_index = i; |
329 | } |
330 | } |
331 | if ((i - comma_index) > 0) |
332 | count++; |
333 | return count; |
334 | } |
335 | |
336 | struct kunit_attr_filter kunit_next_attr_filter(char **filters, int *err) |
337 | { |
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; |
341 | char op; |
342 | char *input = *filters; |
343 | |
344 | /* Parse input until operation */ |
345 | for (i = 0; input[i]; i++) { |
346 | if (op_index < 0 && strchr(op_list, input[i])) { |
347 | op_index = i; |
348 | } else if (!comma_index && input[i] == ',') { |
349 | comma_index = i; |
350 | } else if (comma_index && input[i] != ' ') { |
351 | new_start_index = i; |
352 | break; |
353 | } |
354 | } |
355 | |
356 | if (op_index <= 0) { |
357 | *err = -EINVAL; |
358 | pr_err("kunit executor: filter operation not found: %s\n" , input); |
359 | return filter; |
360 | } |
361 | |
362 | /* Temporarily set operator to \0 character. */ |
363 | op = input[op_index]; |
364 | input[op_index] = '\0'; |
365 | |
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)) { |
369 | attr_index = j; |
370 | break; |
371 | } |
372 | } |
373 | |
374 | input[op_index] = op; |
375 | |
376 | if (attr_index < 0) { |
377 | *err = -EINVAL; |
378 | pr_err("kunit executor: attribute not found: %s\n" , input); |
379 | } else { |
380 | filter.attr = &kunit_attr_list[attr_index]; |
381 | } |
382 | |
383 | if (comma_index > 0) { |
384 | input[comma_index] = '\0'; |
385 | filter.input = input + op_index; |
386 | input = input + new_start_index; |
387 | } else { |
388 | filter.input = input + op_index; |
389 | input = NULL; |
390 | } |
391 | |
392 | *filters = input; |
393 | |
394 | return filter; |
395 | } |
396 | |
397 | struct kunit_suite *kunit_filter_attr_tests(const struct kunit_suite *const suite, |
398 | struct kunit_attr_filter filter, char *action, int *err) |
399 | { |
400 | int n = 0; |
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; |
405 | |
406 | /* Allocate memory for new copy of suite and list of test cases */ |
407 | copy = kmemdup(p: suite, size: sizeof(*copy), GFP_KERNEL); |
408 | if (!copy) |
409 | return ERR_PTR(error: -ENOMEM); |
410 | |
411 | kunit_suite_for_each_test_case(suite, test_case) { n++; } |
412 | |
413 | filtered = kcalloc(n: n + 1, size: sizeof(*filtered), GFP_KERNEL); |
414 | if (!filtered) { |
415 | kfree(objp: copy); |
416 | return ERR_PTR(error: -ENOMEM); |
417 | } |
418 | |
419 | n = 0; |
420 | |
421 | /* Save filtering result on default value */ |
422 | default_result = filter.attr->filter(filter.attr->attr_default, filter.input, err); |
423 | if (*err) |
424 | goto err; |
425 | |
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); |
429 | if (*err) |
430 | goto err; |
431 | |
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), |
436 | filter.input, err); |
437 | if (*err) |
438 | goto err; |
439 | |
440 | /* |
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 |
443 | * default value. |
444 | */ |
445 | result = false; |
446 | if (test_val) { |
447 | if (test_result) |
448 | result = true; |
449 | } else if (suite_val) { |
450 | if (suite_result) |
451 | result = true; |
452 | } else if (default_result) { |
453 | result = true; |
454 | } |
455 | |
456 | if (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; |
461 | } |
462 | } |
463 | |
464 | err: |
465 | if (n == 0 || *err) { |
466 | kfree(objp: copy); |
467 | kfree(objp: filtered); |
468 | return NULL; |
469 | } |
470 | |
471 | copy->test_cases = filtered; |
472 | |
473 | return copy; |
474 | } |
475 | |