main.cpp revision 7ec681f3
1/* 2 * Copyright © 2020 Valve Corporation 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 * IN THE SOFTWARE. 22 * 23 */ 24#include <map> 25#include <set> 26#include <string> 27#include <vector> 28#include <stdio.h> 29#include <string.h> 30#include <getopt.h> 31#include <unistd.h> 32#include <stdarg.h> 33#include <llvm-c/Target.h> 34#include "aco_ir.h" 35#include "framework.h" 36 37#include "util/u_cpu_detect.h" 38 39static const char *help_message = 40 "Usage: %s [-h] [-l --list] [--no-check] [TEST [TEST ...]]\n" 41 "\n" 42 "Run ACO unit test(s). If TEST is not provided, all tests are run.\n" 43 "\n" 44 "positional arguments:\n" 45 " TEST Run TEST. If TEST ends with a '.', run tests with names\n" 46 " starting with TEST. The test variant (after the '/') can\n" 47 " be omitted to run all variants\n" 48 "\n" 49 "optional arguments:\n" 50 " -h, --help Show this help message and exit.\n" 51 " -l --list List unit tests.\n" 52 " --no-check Print test output instead of checking it.\n"; 53 54std::map<std::string, TestDef> tests; 55FILE *output = NULL; 56 57static TestDef current_test; 58static unsigned tests_written = 0; 59static FILE *checker_stdin = NULL; 60static char *checker_stdin_data = NULL; 61static size_t checker_stdin_size = 0; 62 63static char *output_data = NULL; 64static size_t output_size = 0; 65static size_t output_offset = 0; 66 67static char current_variant[64] = {0}; 68static std::set<std::string> *variant_filter = NULL; 69 70bool test_failed = false; 71bool test_skipped = false; 72static char fail_message[256] = {0}; 73 74void write_test() 75{ 76 if (!checker_stdin) { 77 /* not entirely correct, but shouldn't matter */ 78 tests_written++; 79 return; 80 } 81 82 fflush(output); 83 if (output_offset == output_size && !test_skipped && !test_failed) 84 return; 85 86 char *data = output_data + output_offset; 87 uint32_t size = output_size - output_offset; 88 89 fwrite("test", 1, 4, checker_stdin); 90 fwrite(current_test.name, 1, strlen(current_test.name)+1, checker_stdin); 91 fwrite(current_variant, 1, strlen(current_variant)+1, checker_stdin); 92 fwrite(current_test.source_file, 1, strlen(current_test.source_file)+1, checker_stdin); 93 if (test_failed || test_skipped) { 94 const char *res = test_failed ? "failed" : "skipped"; 95 fwrite("\x01", 1, 1, checker_stdin); 96 fwrite(res, 1, strlen(res)+1, checker_stdin); 97 fwrite(fail_message, 1, strlen(fail_message)+1, checker_stdin); 98 } else { 99 fwrite("\x00", 1, 1, checker_stdin); 100 } 101 fwrite(&size, 4, 1, checker_stdin); 102 fwrite(data, 1, size, checker_stdin); 103 104 tests_written++; 105 output_offset += size; 106} 107 108bool set_variant(const char *name) 109{ 110 if (variant_filter && !variant_filter->count(name)) 111 return false; 112 113 write_test(); 114 test_failed = false; 115 test_skipped = false; 116 strncpy(current_variant, name, sizeof(current_variant) - 1); 117 118 printf("Running '%s/%s'\n", current_test.name, name); 119 120 return true; 121} 122 123void fail_test(const char *fmt, ...) 124{ 125 va_list args; 126 va_start(args, fmt); 127 128 test_failed = true; 129 vsnprintf(fail_message, sizeof(fail_message), fmt, args); 130 131 va_end(args); 132} 133 134void skip_test(const char *fmt, ...) 135{ 136 va_list args; 137 va_start(args, fmt); 138 139 test_skipped = true; 140 vsnprintf(fail_message, sizeof(fail_message), fmt, args); 141 142 va_end(args); 143} 144 145void run_test(TestDef def) 146{ 147 current_test = def; 148 output_data = NULL; 149 output_size = 0; 150 output_offset = 0; 151 test_failed = false; 152 test_skipped = false; 153 memset(current_variant, 0, sizeof(current_variant)); 154 155 if (checker_stdin) 156 output = open_memstream(&output_data, &output_size); 157 else 158 output = stdout; 159 160 current_test.func(); 161 write_test(); 162 163 if (checker_stdin) 164 fclose(output); 165 free(output_data); 166} 167 168int check_output(char **argv) 169{ 170 fflush(stdout); 171 fflush(stderr); 172 173 fclose(checker_stdin); 174 175 int stdin_pipe[2]; 176 pipe(stdin_pipe); 177 178 pid_t child_pid = fork(); 179 if (child_pid == -1) { 180 fprintf(stderr, "%s: fork() failed: %s\n", argv[0], strerror(errno)); 181 return 99; 182 } else if (child_pid != 0) { 183 /* Evaluate test output externally using Python */ 184 dup2(stdin_pipe[0], STDIN_FILENO); 185 close(stdin_pipe[0]); 186 close(stdin_pipe[1]); 187 188 execlp(ACO_TEST_PYTHON_BIN, ACO_TEST_PYTHON_BIN, ACO_TEST_SOURCE_DIR "/check_output.py", NULL); 189 fprintf(stderr, "%s: execlp() failed: %s\n", argv[0], strerror(errno)); 190 return 99; 191 } else { 192 /* Feed input data to the Python process. Writing large streams to 193 * stdin will block eventually, so this is done in a forked process 194 * to let the test checker process chunks of data as they arrive */ 195 write(stdin_pipe[1], checker_stdin_data, checker_stdin_size); 196 close(stdin_pipe[0]); 197 close(stdin_pipe[1]); 198 _exit(0); 199 } 200} 201 202bool match_test(std::string name, std::string pattern) 203{ 204 if (name.length() < pattern.length()) 205 return false; 206 if (pattern.back() == '.') 207 name.resize(pattern.length()); 208 return name == pattern; 209} 210 211int main(int argc, char **argv) 212{ 213 int print_help = 0; 214 int do_list = 0; 215 int do_check = 1; 216 const struct option opts[] = { 217 { "help", no_argument, &print_help, 1 }, 218 { "list", no_argument, &do_list, 1 }, 219 { "no-check", no_argument, &do_check, 0 }, 220 { NULL, 0, NULL, 0 } 221 }; 222 223 int c; 224 while ((c = getopt_long(argc, argv, "hl", opts, NULL)) != -1) { 225 switch (c) { 226 case 'h': 227 print_help = 1; 228 break; 229 case 'l': 230 do_list = 1; 231 break; 232 case 0: 233 break; 234 case '?': 235 default: 236 fprintf(stderr, "%s: Invalid argument\n", argv[0]); 237 return 99; 238 } 239 } 240 241 if (print_help) { 242 fprintf(stderr, help_message, argv[0]); 243 return 99; 244 } 245 246 util_cpu_detect(); 247 248 if (do_list) { 249 for (auto test : tests) 250 printf("%s\n", test.first.c_str()); 251 return 99; 252 } 253 254 std::vector<std::pair<std::string, std::string>> names; 255 for (int i = optind; i < argc; i++) { 256 std::string name = argv[i]; 257 std::string variant; 258 size_t pos = name.find('/'); 259 if (pos != std::string::npos) { 260 variant = name.substr(pos + 1); 261 name = name.substr(0, pos); 262 } 263 names.emplace_back(std::pair<std::string, std::string>(name, variant)); 264 } 265 266 if (do_check) 267 checker_stdin = open_memstream(&checker_stdin_data, &checker_stdin_size); 268 269 LLVMInitializeAMDGPUTargetInfo(); 270 LLVMInitializeAMDGPUTarget(); 271 LLVMInitializeAMDGPUTargetMC(); 272 LLVMInitializeAMDGPUDisassembler(); 273 274 aco::init(); 275 276 for (auto pair : tests) { 277 bool found = names.empty(); 278 bool all_variants = names.empty(); 279 std::set<std::string> variants; 280 for (const std::pair<std::string, std::string>& name : names) { 281 if (match_test(pair.first, name.first)) { 282 found = true; 283 if (name.second.empty()) 284 all_variants = true; 285 else 286 variants.insert(name.second); 287 } 288 } 289 290 if (found) { 291 variant_filter = all_variants ? NULL : &variants; 292 printf("Running '%s'\n", pair.first.c_str()); 293 run_test(pair.second); 294 } 295 } 296 if (!tests_written) { 297 fprintf(stderr, "%s: No matching tests\n", argv[0]); 298 return 99; 299 } 300 301 if (checker_stdin) { 302 printf("\n"); 303 return check_output(argv); 304 } else { 305 printf("Tests ran\n"); 306 return 99; 307 } 308} 309