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 37static const char *help_message = 38 "Usage: %s [-h] [-l --list] [--no-check] [TEST [TEST ...]]\n" 39 "\n" 40 "Run ACO unit test(s). If TEST is not provided, all tests are run.\n" 41 "\n" 42 "positional arguments:\n" 43 " TEST Run TEST. If TEST ends with a '.', run tests with names\n" 44 " starting with TEST. The test variant (after the '/') can\n" 45 " be omitted to run all variants\n" 46 "\n" 47 "optional arguments:\n" 48 " -h, --help Show this help message and exit.\n" 49 " -l --list List unit tests.\n" 50 " --no-check Print test output instead of checking it.\n"; 51 52std::map<std::string, TestDef> tests; 53FILE *output = NULL; 54 55static TestDef current_test; 56static unsigned tests_written = 0; 57static FILE *checker_stdin = NULL; 58static char *checker_stdin_data = NULL; 59static size_t checker_stdin_size = 0; 60 61static char *output_data = NULL; 62static size_t output_size = 0; 63static size_t output_offset = 0; 64 65static char current_variant[64] = {0}; 66static std::set<std::string> *variant_filter = NULL; 67 68bool test_failed = false; 69bool test_skipped = false; 70static char fail_message[256] = {0}; 71 72void write_test() 73{ 74 if (!checker_stdin) { 75 /* not entirely correct, but shouldn't matter */ 76 tests_written++; 77 return; 78 } 79 80 fflush(output); 81 if (output_offset == output_size && !test_skipped && !test_failed) 82 return; 83 84 char *data = output_data + output_offset; 85 uint32_t size = output_size - output_offset; 86 87 fwrite("test", 1, 4, checker_stdin); 88 fwrite(current_test.name, 1, strlen(current_test.name)+1, checker_stdin); 89 fwrite(current_variant, 1, strlen(current_variant)+1, checker_stdin); 90 fwrite(current_test.source_file, 1, strlen(current_test.source_file)+1, checker_stdin); 91 if (test_failed || test_skipped) { 92 const char *res = test_failed ? "failed" : "skipped"; 93 fwrite("\x01", 1, 1, checker_stdin); 94 fwrite(res, 1, strlen(res)+1, checker_stdin); 95 fwrite(fail_message, 1, strlen(fail_message)+1, checker_stdin); 96 } else { 97 fwrite("\x00", 1, 1, checker_stdin); 98 } 99 fwrite(&size, 4, 1, checker_stdin); 100 fwrite(data, 1, size, checker_stdin); 101 102 tests_written++; 103 output_offset += size; 104} 105 106bool set_variant(const char *name) 107{ 108 if (variant_filter && !variant_filter->count(name)) 109 return false; 110 111 write_test(); 112 test_failed = false; 113 test_skipped = false; 114 strncpy(current_variant, name, sizeof(current_variant) - 1); 115 116 printf("Running '%s/%s'\n", current_test.name, name); 117 118 return true; 119} 120 121void fail_test(const char *fmt, ...) 122{ 123 va_list args; 124 va_start(args, fmt); 125 126 test_failed = true; 127 vsnprintf(fail_message, sizeof(fail_message), fmt, args); 128 129 va_end(args); 130} 131 132void skip_test(const char *fmt, ...) 133{ 134 va_list args; 135 va_start(args, fmt); 136 137 test_skipped = true; 138 vsnprintf(fail_message, sizeof(fail_message), fmt, args); 139 140 va_end(args); 141} 142 143void run_test(TestDef def) 144{ 145 current_test = def; 146 output_data = NULL; 147 output_size = 0; 148 output_offset = 0; 149 test_failed = false; 150 test_skipped = false; 151 memset(current_variant, 0, sizeof(current_variant)); 152 153 if (checker_stdin) 154 output = open_memstream(&output_data, &output_size); 155 else 156 output = stdout; 157 158 current_test.func(); 159 write_test(); 160 161 if (checker_stdin) 162 fclose(output); 163 free(output_data); 164} 165 166int check_output(char **argv) 167{ 168 fflush(stdout); 169 fflush(stderr); 170 171 fclose(checker_stdin); 172 173 int stdin_pipe[2]; 174 pipe(stdin_pipe); 175 176 pid_t child_pid = fork(); 177 if (child_pid == -1) { 178 fprintf(stderr, "%s: fork() failed: %s\n", argv[0], strerror(errno)); 179 return 99; 180 } else if (child_pid != 0) { 181 /* Evaluate test output externally using Python */ 182 dup2(stdin_pipe[0], STDIN_FILENO); 183 close(stdin_pipe[0]); 184 close(stdin_pipe[1]); 185 186 execlp(ACO_TEST_PYTHON_BIN, ACO_TEST_PYTHON_BIN, ACO_TEST_SOURCE_DIR "/check_output.py", NULL); 187 fprintf(stderr, "%s: execlp() failed: %s\n", argv[0], strerror(errno)); 188 return 99; 189 } else { 190 /* Feed input data to the Python process. Writing large streams to 191 * stdin will block eventually, so this is done in a forked process 192 * to let the test checker process chunks of data as they arrive */ 193 write(stdin_pipe[1], checker_stdin_data, checker_stdin_size); 194 close(stdin_pipe[0]); 195 close(stdin_pipe[1]); 196 _exit(0); 197 } 198} 199 200bool match_test(std::string name, std::string pattern) 201{ 202 if (name.length() < pattern.length()) 203 return false; 204 if (pattern.back() == '.') 205 name.resize(pattern.length()); 206 return name == pattern; 207} 208 209int main(int argc, char **argv) 210{ 211 int print_help = 0; 212 int do_list = 0; 213 int do_check = 1; 214 const struct option opts[] = { 215 { "help", no_argument, &print_help, 1 }, 216 { "list", no_argument, &do_list, 1 }, 217 { "no-check", no_argument, &do_check, 0 }, 218 { NULL, 0, NULL, 0 } 219 }; 220 221 int c; 222 while ((c = getopt_long(argc, argv, "hl", opts, NULL)) != -1) { 223 switch (c) { 224 case 'h': 225 print_help = 1; 226 break; 227 case 'l': 228 do_list = 1; 229 break; 230 case 0: 231 break; 232 case '?': 233 default: 234 fprintf(stderr, "%s: Invalid argument\n", argv[0]); 235 return 99; 236 } 237 } 238 239 if (print_help) { 240 fprintf(stderr, help_message, argv[0]); 241 return 99; 242 } 243 244 if (do_list) { 245 for (auto test : tests) 246 printf("%s\n", test.first.c_str()); 247 return 99; 248 } 249 250 std::vector<std::pair<std::string, std::string>> names; 251 for (int i = optind; i < argc; i++) { 252 std::string name = argv[i]; 253 std::string variant; 254 size_t pos = name.find('/'); 255 if (pos != std::string::npos) { 256 variant = name.substr(pos + 1); 257 name = name.substr(0, pos); 258 } 259 names.emplace_back(std::pair<std::string, std::string>(name, variant)); 260 } 261 262 if (do_check) 263 checker_stdin = open_memstream(&checker_stdin_data, &checker_stdin_size); 264 265 LLVMInitializeAMDGPUTargetInfo(); 266 LLVMInitializeAMDGPUTarget(); 267 LLVMInitializeAMDGPUTargetMC(); 268 LLVMInitializeAMDGPUDisassembler(); 269 270 aco::init(); 271 272 for (auto pair : tests) { 273 bool found = names.empty(); 274 bool all_variants = names.empty(); 275 std::set<std::string> variants; 276 for (const std::pair<std::string, std::string>& name : names) { 277 if (match_test(pair.first, name.first)) { 278 found = true; 279 if (name.second.empty()) 280 all_variants = true; 281 else 282 variants.insert(name.second); 283 } 284 } 285 286 if (found) { 287 variant_filter = all_variants ? NULL : &variants; 288 printf("Running '%s'\n", pair.first.c_str()); 289 run_test(pair.second); 290 } 291 } 292 if (!tests_written) { 293 fprintf(stderr, "%s: No matching tests\n", argv[0]); 294 return 99; 295 } 296 297 if (checker_stdin) { 298 printf("\n"); 299 return check_output(argv); 300 } else { 301 printf("Tests ran\n"); 302 return 99; 303 } 304} 305