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 (list) { 170 printf("%s\n", test->name); 171 if (i == (__scx_num_tests - 1)) 172 return 0; 173 continue; 174 } 175 176 if (filter && should_skip_test(test, filter)) { 177 /* 178 * Printing the skipped tests and their preambles can 179 * add a lot of noise to the runner output. Printing 180 * this is only really useful for CI, so let's skip it 181 * by default. 182 */ 183 if (print_skipped) { 184 print_test_preamble(test, quiet); 185 print_test_result(test, SCX_TEST_SKIP, ++testnum); 186 } 187 continue; 188 } 189 190 print_test_preamble(test, quiet); 191 status = run_test(test); 192 print_test_result(test, status, ++testnum); 193 switch (status) { 194 case SCX_TEST_PASS: 195 passed++; 196 break; 197 case SCX_TEST_SKIP: 198 skipped++; 199 break; 200 case SCX_TEST_FAIL: 201 failed++; 202 break; 203 } 204 } 205 printf("\n\n=============================\n\n"); 206 printf("RESULTS:\n\n"); 207 printf("PASSED: %u\n", passed); 208 printf("SKIPPED: %u\n", skipped); 209 printf("FAILED: %u\n", failed); 210 211 return 0; 212 } 213 214 void scx_test_register(struct scx_test *test) 215 { 216 SCX_BUG_ON(!test_valid(test), "Invalid test found"); 217 SCX_BUG_ON(__scx_num_tests >= MAX_SCX_TESTS, "Maximum tests exceeded"); 218 219 __scx_tests[__scx_num_tests++] = *test; 220 } 221