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 " -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 unsigned testnum = 0, i; 137 unsigned passed = 0, skipped = 0, failed = 0; 138 int opt; 139 140 signal(SIGINT, sigint_handler); 141 signal(SIGTERM, sigint_handler); 142 143 libbpf_set_strict_mode(LIBBPF_STRICT_ALL); 144 145 while ((opt = getopt(argc, argv, "qslt:h")) != -1) { 146 switch (opt) { 147 case 'q': 148 quiet = true; 149 break; 150 case 's': 151 print_skipped = true; 152 break; 153 case 'l': 154 list = true; 155 break; 156 case 't': 157 filter = optarg; 158 break; 159 default: 160 fprintf(stderr, help_fmt, basename(argv[0])); 161 return opt != 'h'; 162 } 163 } 164 165 for (i = 0; i < __scx_num_tests; i++) { 166 enum scx_test_status status; 167 struct scx_test *test = &__scx_tests[i]; 168 169 if (exit_req) 170 break; 171 172 if (list) { 173 printf("%s\n", test->name); 174 if (i == (__scx_num_tests - 1)) 175 return 0; 176 continue; 177 } 178 179 if (filter && should_skip_test(test, filter)) { 180 /* 181 * Printing the skipped tests and their preambles can 182 * add a lot of noise to the runner output. Printing 183 * this is only really useful for CI, so let's skip it 184 * by default. 185 */ 186 if (print_skipped) { 187 print_test_preamble(test, quiet); 188 print_test_result(test, SCX_TEST_SKIP, ++testnum); 189 } 190 continue; 191 } 192 193 print_test_preamble(test, quiet); 194 status = run_test(test); 195 print_test_result(test, status, ++testnum); 196 switch (status) { 197 case SCX_TEST_PASS: 198 passed++; 199 break; 200 case SCX_TEST_SKIP: 201 skipped++; 202 break; 203 case SCX_TEST_FAIL: 204 failed++; 205 break; 206 } 207 } 208 printf("\n\n=============================\n\n"); 209 printf("RESULTS:\n\n"); 210 printf("PASSED: %u\n", passed); 211 printf("SKIPPED: %u\n", skipped); 212 printf("FAILED: %u\n", failed); 213 214 return 0; 215 } 216 217 void scx_test_register(struct scx_test *test) 218 { 219 SCX_BUG_ON(!test_valid(test), "Invalid test found"); 220 SCX_BUG_ON(__scx_num_tests >= MAX_SCX_TESTS, "Maximum tests exceeded"); 221 222 __scx_tests[__scx_num_tests++] = *test; 223 } 224