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