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] [-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 "  -q            Don't print the test descriptions during run\n"
26 "  -h            Display this help and exit\n";
27 
28 static volatile int exit_req;
29 static bool quiet, print_skipped;
30 
31 #define MAX_SCX_TESTS 2048
32 
33 static struct scx_test __scx_tests[MAX_SCX_TESTS];
34 static unsigned __scx_num_tests = 0;
35 
sigint_handler(int simple)36 static void sigint_handler(int simple)
37 {
38 	exit_req = 1;
39 }
40 
print_test_preamble(const struct scx_test * test,bool quiet)41 static void print_test_preamble(const struct scx_test *test, bool quiet)
42 {
43 	printf("===== START =====\n");
44 	printf("TEST: %s\n", test->name);
45 	if (!quiet)
46 		printf("DESCRIPTION: %s\n", test->description);
47 	printf("OUTPUT:\n");
48 }
49 
status_to_result(enum scx_test_status status)50 static const char *status_to_result(enum scx_test_status status)
51 {
52 	switch (status) {
53 	case SCX_TEST_PASS:
54 	case SCX_TEST_SKIP:
55 		return "ok";
56 	case SCX_TEST_FAIL:
57 		return "not ok";
58 	default:
59 		return "<UNKNOWN>";
60 	}
61 }
62 
print_test_result(const struct scx_test * test,enum scx_test_status status,unsigned int testnum)63 static void print_test_result(const struct scx_test *test,
64 			      enum scx_test_status status,
65 			      unsigned int testnum)
66 {
67 	const char *result = status_to_result(status);
68 	const char *directive = status == SCX_TEST_SKIP ? "SKIP " : "";
69 
70 	printf("%s %u %s # %s\n", result, testnum, test->name, directive);
71 	printf("=====  END  =====\n");
72 }
73 
should_skip_test(const struct scx_test * test,const char * filter)74 static bool should_skip_test(const struct scx_test *test, const char * filter)
75 {
76 	return !strstr(test->name, filter);
77 }
78 
run_test(const struct scx_test * test)79 static enum scx_test_status run_test(const struct scx_test *test)
80 {
81 	enum scx_test_status status;
82 	void *context = NULL;
83 
84 	if (test->setup) {
85 		status = test->setup(&context);
86 		if (status != SCX_TEST_PASS)
87 			return status;
88 	}
89 
90 	status = test->run(context);
91 
92 	if (test->cleanup)
93 		test->cleanup(context);
94 
95 	return status;
96 }
97 
test_valid(const struct scx_test * test)98 static bool test_valid(const struct scx_test *test)
99 {
100 	if (!test) {
101 		fprintf(stderr, "NULL test detected\n");
102 		return false;
103 	}
104 
105 	if (!test->name) {
106 		fprintf(stderr,
107 			"Test with no name found. Must specify test name.\n");
108 		return false;
109 	}
110 
111 	if (!test->description) {
112 		fprintf(stderr, "Test %s requires description.\n", test->name);
113 		return false;
114 	}
115 
116 	if (!test->run) {
117 		fprintf(stderr, "Test %s has no run() callback\n", test->name);
118 		return false;
119 	}
120 
121 	return true;
122 }
123 
main(int argc,char ** argv)124 int main(int argc, char **argv)
125 {
126 	const char *filter = NULL;
127 	unsigned testnum = 0, i;
128 	unsigned passed = 0, skipped = 0, failed = 0;
129 	int opt;
130 
131 	signal(SIGINT, sigint_handler);
132 	signal(SIGTERM, sigint_handler);
133 
134 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
135 
136 	while ((opt = getopt(argc, argv, "qst:h")) != -1) {
137 		switch (opt) {
138 		case 'q':
139 			quiet = true;
140 			break;
141 		case 's':
142 			print_skipped = true;
143 			break;
144 		case 't':
145 			filter = optarg;
146 			break;
147 		default:
148 			fprintf(stderr, help_fmt, basename(argv[0]));
149 			return opt != 'h';
150 		}
151 	}
152 
153 	for (i = 0; i < __scx_num_tests; i++) {
154 		enum scx_test_status status;
155 		struct scx_test *test = &__scx_tests[i];
156 
157 		if (filter && should_skip_test(test, filter)) {
158 			/*
159 			 * Printing the skipped tests and their preambles can
160 			 * add a lot of noise to the runner output. Printing
161 			 * this is only really useful for CI, so let's skip it
162 			 * by default.
163 			 */
164 			if (print_skipped) {
165 				print_test_preamble(test, quiet);
166 				print_test_result(test, SCX_TEST_SKIP, ++testnum);
167 			}
168 			continue;
169 		}
170 
171 		print_test_preamble(test, quiet);
172 		status = run_test(test);
173 		print_test_result(test, status, ++testnum);
174 		switch (status) {
175 		case SCX_TEST_PASS:
176 			passed++;
177 			break;
178 		case SCX_TEST_SKIP:
179 			skipped++;
180 			break;
181 		case SCX_TEST_FAIL:
182 			failed++;
183 			break;
184 		}
185 	}
186 	printf("\n\n=============================\n\n");
187 	printf("RESULTS:\n\n");
188 	printf("PASSED:  %u\n", passed);
189 	printf("SKIPPED: %u\n", skipped);
190 	printf("FAILED:  %u\n", failed);
191 
192 	return 0;
193 }
194 
scx_test_register(struct scx_test * test)195 void scx_test_register(struct scx_test *test)
196 {
197 	SCX_BUG_ON(!test_valid(test), "Invalid test found");
198 	SCX_BUG_ON(__scx_num_tests >= MAX_SCX_TESTS, "Maximum tests exceeded");
199 
200 	__scx_tests[__scx_num_tests++] = *test;
201 }
202