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