1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3 * Copyright (c) 2024 Meta Platforms, Inc. and affiliates.
4 * Copyright (c) 2024 David Vernet <[email protected]>
5 * Copyright (c) 2024 Tejun Heo <[email protected]>
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] [-h]\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
status_to_result(enum scx_test_status status)51 static const char *status_to_result(enum scx_test_status status)
52 {
53 switch (status) {
54 case SCX_TEST_PASS:
55 case SCX_TEST_SKIP:
56 return "ok";
57 case SCX_TEST_FAIL:
58 return "not ok";
59 default:
60 return "<UNKNOWN>";
61 }
62 }
63
print_test_result(const struct scx_test * test,enum scx_test_status status,unsigned int testnum)64 static void print_test_result(const struct scx_test *test,
65 enum scx_test_status status,
66 unsigned int testnum)
67 {
68 const char *result = status_to_result(status);
69 const char *directive = status == SCX_TEST_SKIP ? "SKIP " : "";
70
71 printf("%s %u %s # %s\n", result, testnum, test->name, directive);
72 printf("===== END =====\n");
73 }
74
should_skip_test(const struct scx_test * test,const char * filter)75 static bool should_skip_test(const struct scx_test *test, const char * filter)
76 {
77 return !strstr(test->name, filter);
78 }
79
run_test(const struct scx_test * test)80 static enum scx_test_status run_test(const struct scx_test *test)
81 {
82 enum scx_test_status status;
83 void *context = NULL;
84
85 if (test->setup) {
86 status = test->setup(&context);
87 if (status != SCX_TEST_PASS)
88 return status;
89 }
90
91 status = test->run(context);
92
93 if (test->cleanup)
94 test->cleanup(context);
95
96 return status;
97 }
98
test_valid(const struct scx_test * test)99 static bool test_valid(const struct scx_test *test)
100 {
101 if (!test) {
102 fprintf(stderr, "NULL test detected\n");
103 return false;
104 }
105
106 if (!test->name) {
107 fprintf(stderr,
108 "Test with no name found. Must specify test name.\n");
109 return false;
110 }
111
112 if (!test->description) {
113 fprintf(stderr, "Test %s requires description.\n", test->name);
114 return false;
115 }
116
117 if (!test->run) {
118 fprintf(stderr, "Test %s has no run() callback\n", test->name);
119 return false;
120 }
121
122 return true;
123 }
124
main(int argc,char ** argv)125 int main(int argc, char **argv)
126 {
127 const char *filter = NULL;
128 unsigned testnum = 0, i;
129 unsigned passed = 0, skipped = 0, failed = 0;
130 int opt;
131
132 signal(SIGINT, sigint_handler);
133 signal(SIGTERM, sigint_handler);
134
135 libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
136
137 while ((opt = getopt(argc, argv, "qslt:h")) != -1) {
138 switch (opt) {
139 case 'q':
140 quiet = true;
141 break;
142 case 's':
143 print_skipped = true;
144 break;
145 case 'l':
146 list = true;
147 break;
148 case 't':
149 filter = optarg;
150 break;
151 default:
152 fprintf(stderr, help_fmt, basename(argv[0]));
153 return opt != 'h';
154 }
155 }
156
157 for (i = 0; i < __scx_num_tests; i++) {
158 enum scx_test_status status;
159 struct scx_test *test = &__scx_tests[i];
160
161 if (list) {
162 printf("%s\n", test->name);
163 if (i == (__scx_num_tests - 1))
164 return 0;
165 continue;
166 }
167
168 if (filter && should_skip_test(test, filter)) {
169 /*
170 * Printing the skipped tests and their preambles can
171 * add a lot of noise to the runner output. Printing
172 * this is only really useful for CI, so let's skip it
173 * by default.
174 */
175 if (print_skipped) {
176 print_test_preamble(test, quiet);
177 print_test_result(test, SCX_TEST_SKIP, ++testnum);
178 }
179 continue;
180 }
181
182 print_test_preamble(test, quiet);
183 status = run_test(test);
184 print_test_result(test, status, ++testnum);
185 switch (status) {
186 case SCX_TEST_PASS:
187 passed++;
188 break;
189 case SCX_TEST_SKIP:
190 skipped++;
191 break;
192 case SCX_TEST_FAIL:
193 failed++;
194 break;
195 }
196 }
197 printf("\n\n=============================\n\n");
198 printf("RESULTS:\n\n");
199 printf("PASSED: %u\n", passed);
200 printf("SKIPPED: %u\n", skipped);
201 printf("FAILED: %u\n", failed);
202
203 return 0;
204 }
205
scx_test_register(struct scx_test * test)206 void scx_test_register(struct scx_test *test)
207 {
208 SCX_BUG_ON(!test_valid(test), "Invalid test found");
209 SCX_BUG_ON(__scx_num_tests >= MAX_SCX_TESTS, "Maximum tests exceeded");
210
211 __scx_tests[__scx_num_tests++] = *test;
212 }
213