xref: /kernel/linux/linux-6.6/lib/kunit/attributes.c (revision 62306a36)
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 */
17enum 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 */
36struct 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
47static const char * const speed_str_list[] = {"unset", "very_slow", "slow", "normal"};
48
49/* To String Methods */
50
51static 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
61static const char *attr_speed_to_string(void *attr, bool *to_free)
62{
63	return attr_enum_to_string(attr, speed_str_list, to_free);
64}
65
66static const char *attr_string_to_string(void *attr, bool *to_free)
67{
68	*to_free = false;
69	return (char *) attr;
70}
71
72/* Filter Methods */
73
74static const char op_list[] = "<>!=";
75
76/*
77 * Returns whether the inputted integer value matches the filter given
78 * by the operation string and inputted integer.
79 */
80static int int_filter(long val, const char *op, int input, int *err)
81{
82	if (!strncmp(op, "<=", 2))
83		return (val <= input);
84	else if (!strncmp(op, ">=", 2))
85		return (val >= input);
86	else if (!strncmp(op, "!=", 2))
87		return (val != input);
88	else if (!strncmp(op, ">", 1))
89		return (val > input);
90	else if (!strncmp(op, "<", 1))
91		return (val < input);
92	else if (!strncmp(op, "=", 1))
93		return (val == input);
94	*err = -EINVAL;
95	pr_err("kunit executor: invalid filter operation: %s\n", op);
96	return false;
97}
98
99/*
100 * Returns whether the inputted enum value "attr" matches the filter given
101 * by the input string. Note: the str_list includes the corresponding string
102 * list to the enum values.
103 */
104static int attr_enum_filter(void *attr, const char *input, int *err,
105		const char * const str_list[], int max)
106{
107	int i, j, input_int = -1;
108	long test_val = (long)attr;
109	const char *input_val = NULL;
110
111	for (i = 0; input[i]; i++) {
112		if (!strchr(op_list, input[i])) {
113			input_val = input + i;
114			break;
115		}
116	}
117
118	if (!input_val) {
119		*err = -EINVAL;
120		pr_err("kunit executor: filter value not found: %s\n", input);
121		return false;
122	}
123
124	for (j = 0; j <= max; j++) {
125		if (!strcmp(input_val, str_list[j]))
126			input_int = j;
127	}
128
129	if (input_int < 0) {
130		*err = -EINVAL;
131		pr_err("kunit executor: invalid filter input: %s\n", input);
132		return false;
133	}
134
135	return int_filter(test_val, input, input_int, err);
136}
137
138static int attr_speed_filter(void *attr, const char *input, int *err)
139{
140	return attr_enum_filter(attr, input, err, speed_str_list, KUNIT_SPEED_MAX);
141}
142
143/*
144 * Returns whether the inputted string value (attr) matches the filter given
145 * by the input string.
146 */
147static int attr_string_filter(void *attr, const char *input, int *err)
148{
149	char *str = attr;
150
151	if (!strncmp(input, "<", 1)) {
152		*err = -EINVAL;
153		pr_err("kunit executor: invalid filter input: %s\n", input);
154		return false;
155	} else if (!strncmp(input, ">", 1)) {
156		*err = -EINVAL;
157		pr_err("kunit executor: invalid filter input: %s\n", input);
158		return false;
159	} else if (!strncmp(input, "!=", 2)) {
160		return (strcmp(input + 2, str) != 0);
161	} else if (!strncmp(input, "=", 1)) {
162		return (strcmp(input + 1, str) == 0);
163	}
164	*err = -EINVAL;
165	pr_err("kunit executor: invalid filter operation: %s\n", input);
166	return false;
167}
168
169
170/* Get Attribute Methods */
171
172static void *attr_speed_get(void *test_or_suite, bool is_test)
173{
174	struct kunit_suite *suite = is_test ? NULL : test_or_suite;
175	struct kunit_case *test = is_test ? test_or_suite : NULL;
176
177	if (test)
178		return ((void *) test->attr.speed);
179	else
180		return ((void *) suite->attr.speed);
181}
182
183static void *attr_module_get(void *test_or_suite, bool is_test)
184{
185	struct kunit_suite *suite = is_test ? NULL : test_or_suite;
186	struct kunit_case *test = is_test ? test_or_suite : NULL;
187
188	// Suites get their module attribute from their first test_case
189	if (test)
190		return ((void *) test->module_name);
191	else if (kunit_suite_num_test_cases(suite) > 0)
192		return ((void *) suite->test_cases[0].module_name);
193	else
194		return (void *) "";
195}
196
197/* List of all Test Attributes */
198
199static struct kunit_attr kunit_attr_list[] = {
200	{
201		.name = "speed",
202		.get_attr = attr_speed_get,
203		.to_string = attr_speed_to_string,
204		.filter = attr_speed_filter,
205		.attr_default = (void *)KUNIT_SPEED_NORMAL,
206		.print = PRINT_ALWAYS,
207	},
208	{
209		.name = "module",
210		.get_attr = attr_module_get,
211		.to_string = attr_string_to_string,
212		.filter = attr_string_filter,
213		.attr_default = (void *)"",
214		.print = PRINT_SUITE,
215	}
216};
217
218/* Helper Functions to Access Attributes */
219
220const char *kunit_attr_filter_name(struct kunit_attr_filter filter)
221{
222	return filter.attr->name;
223}
224
225void kunit_print_attr(void *test_or_suite, bool is_test, unsigned int test_level)
226{
227	int i;
228	bool to_free = false;
229	void *attr;
230	const char *attr_name, *attr_str;
231	struct kunit_suite *suite = is_test ? NULL : test_or_suite;
232	struct kunit_case *test = is_test ? test_or_suite : NULL;
233
234	for (i = 0; i < ARRAY_SIZE(kunit_attr_list); i++) {
235		if (kunit_attr_list[i].print == PRINT_NEVER ||
236				(test && kunit_attr_list[i].print == PRINT_SUITE))
237			continue;
238		attr = kunit_attr_list[i].get_attr(test_or_suite, is_test);
239		if (attr) {
240			attr_name = kunit_attr_list[i].name;
241			attr_str = kunit_attr_list[i].to_string(attr, &to_free);
242			if (test) {
243				kunit_log(KERN_INFO, test, "%*s# %s.%s: %s",
244					KUNIT_INDENT_LEN * test_level, "", test->name,
245					attr_name, attr_str);
246			} else {
247				kunit_log(KERN_INFO, suite, "%*s# %s: %s",
248					KUNIT_INDENT_LEN * test_level, "", attr_name, attr_str);
249			}
250
251			/* Free to_string of attribute if needed */
252			if (to_free)
253				kfree(attr_str);
254		}
255	}
256}
257
258/* Helper Functions to Filter Attributes */
259
260int kunit_get_filter_count(char *input)
261{
262	int i, comma_index = 0, count = 0;
263
264	for (i = 0; input[i]; i++) {
265		if (input[i] == ',') {
266			if ((i - comma_index) > 1)
267				count++;
268			comma_index = i;
269		}
270	}
271	if ((i - comma_index) > 0)
272		count++;
273	return count;
274}
275
276struct kunit_attr_filter kunit_next_attr_filter(char **filters, int *err)
277{
278	struct kunit_attr_filter filter = {};
279	int i, j, comma_index = 0, new_start_index = 0;
280	int op_index = -1, attr_index = -1;
281	char op;
282	char *input = *filters;
283
284	/* Parse input until operation */
285	for (i = 0; input[i]; i++) {
286		if (op_index < 0 && strchr(op_list, input[i])) {
287			op_index = i;
288		} else if (!comma_index && input[i] == ',') {
289			comma_index = i;
290		} else if (comma_index && input[i] != ' ') {
291			new_start_index = i;
292			break;
293		}
294	}
295
296	if (op_index <= 0) {
297		*err = -EINVAL;
298		pr_err("kunit executor: filter operation not found: %s\n", input);
299		return filter;
300	}
301
302	/* Temporarily set operator to \0 character. */
303	op = input[op_index];
304	input[op_index] = '\0';
305
306	/* Find associated kunit_attr object */
307	for (j = 0; j < ARRAY_SIZE(kunit_attr_list); j++) {
308		if (!strcmp(input, kunit_attr_list[j].name)) {
309			attr_index = j;
310			break;
311		}
312	}
313
314	input[op_index] = op;
315
316	if (attr_index < 0) {
317		*err = -EINVAL;
318		pr_err("kunit executor: attribute not found: %s\n", input);
319	} else {
320		filter.attr = &kunit_attr_list[attr_index];
321	}
322
323	if (comma_index > 0) {
324		input[comma_index] = '\0';
325		filter.input = input + op_index;
326		input = input + new_start_index;
327	} else {
328		filter.input = input + op_index;
329		input = NULL;
330	}
331
332	*filters = input;
333
334	return filter;
335}
336
337struct kunit_suite *kunit_filter_attr_tests(const struct kunit_suite *const suite,
338		struct kunit_attr_filter filter, char *action, int *err)
339{
340	int n = 0;
341	struct kunit_case *filtered, *test_case;
342	struct kunit_suite *copy;
343	void *suite_val, *test_val;
344	bool suite_result, test_result, default_result, result;
345
346	/* Allocate memory for new copy of suite and list of test cases */
347	copy = kmemdup(suite, sizeof(*copy), GFP_KERNEL);
348	if (!copy)
349		return ERR_PTR(-ENOMEM);
350
351	kunit_suite_for_each_test_case(suite, test_case) { n++; }
352
353	filtered = kcalloc(n + 1, sizeof(*filtered), GFP_KERNEL);
354	if (!filtered) {
355		kfree(copy);
356		return ERR_PTR(-ENOMEM);
357	}
358
359	n = 0;
360
361	/* Save filtering result on default value */
362	default_result = filter.attr->filter(filter.attr->attr_default, filter.input, err);
363	if (*err)
364		goto err;
365
366	/* Save suite attribute value and filtering result on that value */
367	suite_val = filter.attr->get_attr((void *)suite, false);
368	suite_result = filter.attr->filter(suite_val, filter.input, err);
369	if (*err)
370		goto err;
371
372	/* For each test case, save test case if passes filtering. */
373	kunit_suite_for_each_test_case(suite, test_case) {
374		test_val = filter.attr->get_attr((void *) test_case, true);
375		test_result = filter.attr->filter(filter.attr->get_attr(test_case, true),
376				filter.input, err);
377		if (*err)
378			goto err;
379
380		/*
381		 * If attribute value of test case is set, filter on that value.
382		 * If not, filter on suite value if set. If not, filter on
383		 * default value.
384		 */
385		result = false;
386		if (test_val) {
387			if (test_result)
388				result = true;
389		} else if (suite_val) {
390			if (suite_result)
391				result = true;
392		} else if (default_result) {
393			result = true;
394		}
395
396		if (result) {
397			filtered[n++] = *test_case;
398		} else if (action && strcmp(action, "skip") == 0) {
399			test_case->status = KUNIT_SKIPPED;
400			filtered[n++] = *test_case;
401		}
402	}
403
404err:
405	if (n == 0 || *err) {
406		kfree(copy);
407		kfree(filtered);
408		return NULL;
409	}
410
411	copy->test_cases = filtered;
412
413	return copy;
414}
415