• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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