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
sigint_handler(int simple)37 static void sigint_handler(int simple)
38 {
39 exit_req = 1;
40 }
41
print_test_preamble(const struct scx_test * test,bool quiet)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
status_to_result(enum scx_test_status status)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
print_test_result(const struct scx_test * test,enum scx_test_status status,unsigned int testnum)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
should_skip_test(const struct scx_test * test,const char * filter)83 static bool should_skip_test(const struct scx_test *test, const char * filter)
84 {
85 return !strstr(test->name, filter);
86 }
87
run_test(const struct scx_test * test)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
test_valid(const struct scx_test * test)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
main(int argc,char ** argv)133 int main(int argc, char **argv)
134 {
135 const char *filter = NULL;
136 const char *failed_tests[MAX_SCX_TESTS];
137 const char *skipped_tests[MAX_SCX_TESTS];
138 unsigned testnum = 0, i;
139 unsigned passed = 0, skipped = 0, failed = 0;
140 int opt;
141
142 signal(SIGINT, sigint_handler);
143 signal(SIGTERM, sigint_handler);
144
145 libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
146
147 while ((opt = getopt(argc, argv, "qslt:h")) != -1) {
148 switch (opt) {
149 case 'q':
150 quiet = true;
151 break;
152 case 's':
153 print_skipped = true;
154 break;
155 case 'l':
156 list = true;
157 break;
158 case 't':
159 filter = optarg;
160 break;
161 default:
162 fprintf(stderr, help_fmt, basename(argv[0]));
163 return opt != 'h';
164 }
165 }
166
167 if (optind < argc) {
168 fprintf(stderr, "Unexpected argument '%s'. Use -t to filter tests.\n",
169 argv[optind]);
170 return 1;
171 }
172
173 if (filter) {
174 for (i = 0; i < __scx_num_tests; i++) {
175 if (!should_skip_test(&__scx_tests[i], filter))
176 break;
177 }
178 if (i == __scx_num_tests) {
179 fprintf(stderr, "No tests matched filter '%s'\n", filter);
180 fprintf(stderr, "Available tests (use -l to list):\n");
181 for (i = 0; i < __scx_num_tests; i++)
182 fprintf(stderr, " %s\n", __scx_tests[i].name);
183 return 1;
184 }
185 }
186
187 for (i = 0; i < __scx_num_tests; i++) {
188 enum scx_test_status status;
189 struct scx_test *test = &__scx_tests[i];
190
191 if (exit_req)
192 break;
193
194 if (list) {
195 printf("%s\n", test->name);
196 if (i == (__scx_num_tests - 1))
197 return 0;
198 continue;
199 }
200
201 if (filter && should_skip_test(test, filter)) {
202 /*
203 * Printing the skipped tests and their preambles can
204 * add a lot of noise to the runner output. Printing
205 * this is only really useful for CI, so let's skip it
206 * by default.
207 */
208 if (print_skipped) {
209 print_test_preamble(test, quiet);
210 print_test_result(test, SCX_TEST_SKIP, ++testnum);
211 }
212 continue;
213 }
214
215 print_test_preamble(test, quiet);
216 status = run_test(test);
217 print_test_result(test, status, ++testnum);
218 switch (status) {
219 case SCX_TEST_PASS:
220 passed++;
221 break;
222 case SCX_TEST_SKIP:
223 skipped_tests[skipped++] = test->name;
224 break;
225 case SCX_TEST_FAIL:
226 failed_tests[failed++] = test->name;
227 break;
228 }
229 }
230 printf("\n\n=============================\n\n");
231 printf("RESULTS:\n\n");
232 printf("PASSED: %u\n", passed);
233 printf("SKIPPED: %u\n", skipped);
234 printf("FAILED: %u\n", failed);
235 if (skipped > 0) {
236 printf("\nSkipped tests:\n");
237 for (i = 0; i < skipped; i++)
238 printf(" - %s\n", skipped_tests[i]);
239 }
240 if (failed > 0) {
241 printf("\nFailed tests:\n");
242 for (i = 0; i < failed; i++)
243 printf(" - %s\n", failed_tests[i]);
244 }
245
246 return failed > 0 ? 1 : 0;
247 }
248
scx_test_register(struct scx_test * test)249 void scx_test_register(struct scx_test *test)
250 {
251 SCX_BUG_ON(!test_valid(test), "Invalid test found");
252 SCX_BUG_ON(__scx_num_tests >= MAX_SCX_TESTS, "Maximum tests exceeded");
253
254 __scx_tests[__scx_num_tests++] = *test;
255 }
256