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 36 static void sigint_handler(int simple) 37 { 38 exit_req = 1; 39 } 40 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 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 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 74 static bool should_skip_test(const struct scx_test *test, const char * filter) 75 { 76 return !strstr(test->name, filter); 77 } 78 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 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 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 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