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