xref: /linux/tools/testing/selftests/sched_ext/runner.c (revision f03ffe53ab6ffc798ed8291090cebf19c6e5fa3b)
1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3  * Copyright (c) 2024 Meta Platforms, Inc. and affiliates.
4  * Copyright (c) 2024 David Vernet <dvernet@meta.com>
5  * Copyright (c) 2024 Tejun Heo <tj@kernel.org>
6  */
7 #include <stdio.h>
8 #include <unistd.h>
9 #include <signal.h>
10 #include <libgen.h>
11 #include <bpf/bpf.h>
12 #include "scx_test.h"
13 
14 const char help_fmt[] =
15 "The runner for sched_ext tests.\n"
16 "\n"
17 "The runner is statically linked against all testcases, and runs them all serially.\n"
18 "It's required for the testcases to be serial, as only a single host-wide sched_ext\n"
19 "scheduler may be loaded at any given time."
20 "\n"
21 "Usage: %s [-t TEST] [-s] [-l] [-q]\n"
22 "\n"
23 "  -t TEST       Only run tests whose name includes this string\n"
24 "  -s            Include print output for skipped tests\n"
25 "  -l            List all available tests\n"
26 "  -q            Don't print the test descriptions during run\n"
27 "  -h            Display this help and exit\n";
28 
29 static volatile int exit_req;
30 static bool quiet, print_skipped, list;
31 
32 #define MAX_SCX_TESTS 2048
33 
34 static struct scx_test __scx_tests[MAX_SCX_TESTS];
35 static unsigned __scx_num_tests = 0;
36 
37 static void sigint_handler(int simple)
38 {
39 	exit_req = 1;
40 }
41 
42 static void print_test_preamble(const struct scx_test *test, bool quiet)
43 {
44 	printf("===== START =====\n");
45 	printf("TEST: %s\n", test->name);
46 	if (!quiet)
47 		printf("DESCRIPTION: %s\n", test->description);
48 	printf("OUTPUT:\n");
49 
50 	/*
51 	 * The tests may fork with the preamble buffered
52 	 * in the children's stdout. Flush before the test
53 	 * to avoid printing the message multiple times.
54 	 */
55 	fflush(stdout);
56 	fflush(stderr);
57 }
58 
59 static const char *status_to_result(enum scx_test_status status)
60 {
61 	switch (status) {
62 	case SCX_TEST_PASS:
63 	case SCX_TEST_SKIP:
64 		return "ok";
65 	case SCX_TEST_FAIL:
66 		return "not ok";
67 	default:
68 		return "<UNKNOWN>";
69 	}
70 }
71 
72 static void print_test_result(const struct scx_test *test,
73 			      enum scx_test_status status,
74 			      unsigned int testnum)
75 {
76 	const char *result = status_to_result(status);
77 	const char *directive = status == SCX_TEST_SKIP ? "SKIP " : "";
78 
79 	printf("%s %u %s # %s\n", result, testnum, test->name, directive);
80 	printf("=====  END  =====\n");
81 }
82 
83 static bool should_skip_test(const struct scx_test *test, const char * filter)
84 {
85 	return !strstr(test->name, filter);
86 }
87 
88 static enum scx_test_status run_test(const struct scx_test *test)
89 {
90 	enum scx_test_status status;
91 	void *context = NULL;
92 
93 	if (test->setup) {
94 		status = test->setup(&context);
95 		if (status != SCX_TEST_PASS)
96 			return status;
97 	}
98 
99 	status = test->run(context);
100 
101 	if (test->cleanup)
102 		test->cleanup(context);
103 
104 	return status;
105 }
106 
107 static bool test_valid(const struct scx_test *test)
108 {
109 	if (!test) {
110 		fprintf(stderr, "NULL test detected\n");
111 		return false;
112 	}
113 
114 	if (!test->name) {
115 		fprintf(stderr,
116 			"Test with no name found. Must specify test name.\n");
117 		return false;
118 	}
119 
120 	if (!test->description) {
121 		fprintf(stderr, "Test %s requires description.\n", test->name);
122 		return false;
123 	}
124 
125 	if (!test->run) {
126 		fprintf(stderr, "Test %s has no run() callback\n", test->name);
127 		return false;
128 	}
129 
130 	return true;
131 }
132 
133 int main(int argc, char **argv)
134 {
135 	const char *filter = NULL;
136 	const char *failed_tests[MAX_SCX_TESTS];
137 	unsigned testnum = 0, i;
138 	unsigned passed = 0, skipped = 0, failed = 0;
139 	int opt;
140 
141 	signal(SIGINT, sigint_handler);
142 	signal(SIGTERM, sigint_handler);
143 
144 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
145 
146 	while ((opt = getopt(argc, argv, "qslt:h")) != -1) {
147 		switch (opt) {
148 		case 'q':
149 			quiet = true;
150 			break;
151 		case 's':
152 			print_skipped = true;
153 			break;
154 		case 'l':
155 			list = true;
156 			break;
157 		case 't':
158 			filter = optarg;
159 			break;
160 		default:
161 			fprintf(stderr, help_fmt, basename(argv[0]));
162 			return opt != 'h';
163 		}
164 	}
165 
166 	for (i = 0; i < __scx_num_tests; i++) {
167 		enum scx_test_status status;
168 		struct scx_test *test = &__scx_tests[i];
169 
170 		if (exit_req)
171 			break;
172 
173 		if (list) {
174 			printf("%s\n", test->name);
175 			if (i == (__scx_num_tests - 1))
176 				return 0;
177 			continue;
178 		}
179 
180 		if (filter && should_skip_test(test, filter)) {
181 			/*
182 			 * Printing the skipped tests and their preambles can
183 			 * add a lot of noise to the runner output. Printing
184 			 * this is only really useful for CI, so let's skip it
185 			 * by default.
186 			 */
187 			if (print_skipped) {
188 				print_test_preamble(test, quiet);
189 				print_test_result(test, SCX_TEST_SKIP, ++testnum);
190 			}
191 			continue;
192 		}
193 
194 		print_test_preamble(test, quiet);
195 		status = run_test(test);
196 		print_test_result(test, status, ++testnum);
197 		switch (status) {
198 		case SCX_TEST_PASS:
199 			passed++;
200 			break;
201 		case SCX_TEST_SKIP:
202 			skipped++;
203 			break;
204 		case SCX_TEST_FAIL:
205 			failed_tests[failed++] = test->name;
206 			break;
207 		}
208 	}
209 	printf("\n\n=============================\n\n");
210 	printf("RESULTS:\n\n");
211 	printf("PASSED:  %u\n", passed);
212 	printf("SKIPPED: %u\n", skipped);
213 	printf("FAILED:  %u\n", failed);
214 	if (failed > 0) {
215 		printf("\nFailed tests:\n");
216 		for (i = 0; i < failed; i++)
217 			printf("  - %s\n", failed_tests[i]);
218 	}
219 
220 	return failed > 0 ? 1 : 0;
221 }
222 
223 void scx_test_register(struct scx_test *test)
224 {
225 	SCX_BUG_ON(!test_valid(test), "Invalid test found");
226 	SCX_BUG_ON(__scx_num_tests >= MAX_SCX_TESTS, "Maximum tests exceeded");
227 
228 	__scx_tests[__scx_num_tests++] = *test;
229 }
230