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