aco: add framework for unit testing
[mesa.git] / src / amd / compiler / tests / main.cpp
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 static 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
52 std::map<std::string, TestDef> tests;
53 FILE *output = NULL;
54
55 static TestDef current_test;
56 static unsigned tests_written = 0;
57 static FILE *checker_stdin = NULL;
58 static char *checker_stdin_data = NULL;
59 static size_t checker_stdin_size = 0;
60
61 static char *output_data = NULL;
62 static size_t output_size = 0;
63 static size_t output_offset = 0;
64
65 static char current_variant[64] = {0};
66 static std::set<std::string> *variant_filter = NULL;
67
68 bool test_failed = false;
69 bool test_skipped = false;
70 static char fail_message[256] = {0};
71
72 void 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
106 bool 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
121 void 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
132 void 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
143 void 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
166 int 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 write(stdin_pipe[1], checker_stdin_data, checker_stdin_size);
177 close(stdin_pipe[1]);
178 dup2(stdin_pipe[0], STDIN_FILENO);
179
180 execlp(ACO_TEST_PYTHON_BIN, ACO_TEST_PYTHON_BIN, ACO_TEST_SOURCE_DIR "/check_output.py", NULL);
181
182 fprintf(stderr, "%s: execl() failed: %s\n", argv[0], strerror(errno));
183 return 99;
184 }
185
186 bool match_test(std::string name, std::string pattern)
187 {
188 if (name.length() < pattern.length())
189 return false;
190 if (pattern.back() == '.')
191 name.resize(pattern.length());
192 return name == pattern;
193 }
194
195 int main(int argc, char **argv)
196 {
197 int print_help = 0;
198 int do_list = 0;
199 int do_check = 1;
200 const struct option opts[] = {
201 { "help", no_argument, &print_help, 1 },
202 { "list", no_argument, &do_list, 1 },
203 { "no-check", no_argument, &do_check, 0 },
204 { NULL, 0, NULL, 0 }
205 };
206
207 int c;
208 while ((c = getopt_long(argc, argv, "hl", opts, NULL)) != -1) {
209 switch (c) {
210 case 'h':
211 print_help = 1;
212 break;
213 case 'l':
214 do_list = 1;
215 break;
216 case 0:
217 break;
218 case '?':
219 default:
220 fprintf(stderr, "%s: Invalid argument\n", argv[0]);
221 return 99;
222 }
223 }
224
225 if (print_help) {
226 fprintf(stderr, help_message, argv[0]);
227 return 99;
228 }
229
230 if (do_list) {
231 for (auto test : tests)
232 printf("%s\n", test.first.c_str());
233 return 99;
234 }
235
236 std::vector<std::pair<std::string, std::string>> names;
237 for (int i = optind; i < argc; i++) {
238 std::string name = argv[i];
239 std::string variant;
240 size_t pos = name.find('/');
241 if (pos != std::string::npos) {
242 variant = name.substr(pos + 1);
243 name = name.substr(0, pos);
244 }
245 names.emplace_back(std::pair<std::string, std::string>(name, variant));
246 }
247
248 if (do_check)
249 checker_stdin = open_memstream(&checker_stdin_data, &checker_stdin_size);
250
251 LLVMInitializeAMDGPUTargetInfo();
252 LLVMInitializeAMDGPUTarget();
253 LLVMInitializeAMDGPUTargetMC();
254 LLVMInitializeAMDGPUDisassembler();
255
256 aco::init();
257
258 for (auto pair : tests) {
259 bool found = names.empty();
260 bool all_variants = names.empty();
261 std::set<std::string> variants;
262 for (const std::pair<std::string, std::string>& name : names) {
263 if (match_test(pair.first, name.first)) {
264 found = true;
265 if (name.second.empty())
266 all_variants = true;
267 else
268 variants.insert(name.second);
269 }
270 }
271
272 if (found) {
273 variant_filter = all_variants ? NULL : &variants;
274 printf("Running '%s'\n", pair.first.c_str());
275 run_test(pair.second);
276 }
277 }
278 if (!tests_written) {
279 fprintf(stderr, "%s: No matching tests\n", argv[0]);
280 return 99;
281 }
282
283 if (checker_stdin) {
284 printf("\n");
285 return check_output(argv);
286 } else {
287 printf("Tests ran\n");
288 return 99;
289 }
290 }