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 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 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