src/platform/test/cinema-main.c (view raw)
1/* Copyright (c) 2013-2020 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include <mgba/core/config.h>
7#include <mgba/core/core.h>
8#include <mgba/core/log.h>
9#include <mgba/core/version.h>
10#include <mgba/feature/commandline.h>
11#include <mgba/feature/video-logger.h>
12
13#include <mgba-util/vector.h>
14#include <mgba-util/vfs.h>
15
16#ifdef _MSC_VER
17#include <mgba-util/platform/windows/getopt.h>
18#else
19#include <getopt.h>
20#endif
21
22#include <stdlib.h>
23
24#define MAX_TEST 200
25
26static const struct option longOpts[] = {
27 { "base", required_argument, 0, 'b' },
28 { "help", no_argument, 0, 'h' },
29 { "quiet", no_argument, 0, 'q' },
30 { "dry-run", no_argument, 0, 'n' },
31 { "verbose", no_argument, 0, 'v' },
32 { "version", no_argument, 0, '\0' },
33 { 0, 0, 0, 0 }
34};
35
36static const char shortOpts[] = "b:hnqv";
37
38enum CInemaStatus {
39 CI_PASS,
40 CI_FAIL,
41 CI_XPASS,
42 CI_XFAIL,
43 CI_SKIP
44};
45
46struct CInemaTest {
47 char directory[MAX_TEST];
48 char filename[MAX_TEST];
49 char name[MAX_TEST];
50 struct mCoreConfig config;
51 enum CInemaStatus status;
52 int failedFrames;
53};
54
55DECLARE_VECTOR(CInemaTestList, struct CInemaTest)
56DEFINE_VECTOR(CInemaTestList, struct CInemaTest)
57
58static bool showVersion = false;
59static bool showUsage = false;
60static char base[PATH_MAX] = {0};
61static bool dryRun = false;
62static int verbosity = 0;
63
64ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) {
65 if (verbosity < minlevel) {
66 return;
67 }
68 va_list args;
69 va_start(args, format);
70 vprintf(format, args);
71 va_end(args);
72}
73
74ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) {
75 if (verbosity < minlevel) {
76 return;
77 }
78 va_list args;
79 va_start(args, format);
80 vfprintf(stderr, format, args);
81 va_end(args);
82}
83
84bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename);
85void CInemaTestDeinit(struct CInemaTest*);
86
87static bool parseCInemaArgs(int argc, char* const* argv) {
88 int ch;
89 int index = 0;
90 while ((ch = getopt_long(argc, argv, shortOpts, longOpts, &index)) != -1) {
91 const struct option* opt = &longOpts[index];
92 switch (ch) {
93 case '\0':
94 if (strcmp(opt->name, "version") == 0) {
95 showVersion = true;
96 } else {
97 return false;
98 }
99 break;
100 case 'b':
101 strncpy(base, optarg, sizeof(base));
102 // TODO: Verify path exists
103 break;
104 case 'h':
105 showUsage = true;
106 break;
107 case 'n':
108 dryRun = true;
109 break;
110 case 'q':
111 --verbosity;
112 break;
113 case 'v':
114 ++verbosity;
115 break;
116 default:
117 return false;
118 }
119 }
120
121 return true;
122}
123
124static void usageCInema(const char* arg0) {
125 printf("usage: %s [-h] [-b BASE] [--version] [test...]\n", arg0);
126 puts(" -b, --base Path to the CInema base directory");
127 puts(" -h, --help Print this usage and exit");
128 puts(" -n, --dry-run List all collected tests instead of running them");
129 puts(" -q, --quiet Decrease log verbosity (can be repeated)");
130 puts(" -v, --verbose Increase log verbosity (can be repeated)");
131 puts(" --version Print version and exit");
132}
133
134static bool determineBase(int argc, char* const* argv) {
135 // TODO: Better dynamic detection
136 separatePath(__FILE__, base, NULL, NULL);
137 strncat(base, PATH_SEP ".." PATH_SEP ".." PATH_SEP ".." PATH_SEP "cinema", sizeof(base) - strlen(base) - 1);
138 return true;
139}
140
141static bool collectTests(struct CInemaTestList* tests, const char* path) {
142 CIerr(1, "Considering path %s\n", path);
143 struct VDir* dir = VDirOpen(path);
144 if (!dir) {
145 return false;
146 }
147 struct VDirEntry* entry = dir->listNext(dir);
148 while (entry) {
149 char subpath[PATH_MAX];
150 snprintf(subpath, sizeof(subpath), "%s" PATH_SEP "%s", path, entry->name(entry));
151 if (entry->type(entry) == VFS_DIRECTORY && strncmp(entry->name(entry), ".", 2) != 0 && strncmp(entry->name(entry), "..", 3) != 0) {
152 if (!collectTests(tests, subpath)) {
153 dir->close(dir);
154 return false;
155 }
156 } else if (entry->type(entry) == VFS_FILE && strncmp(entry->name(entry), "test.", 5) == 0) {
157 CIerr(2, "Found potential test %s\n", subpath);
158 struct VFile* vf = dir->openFile(dir, entry->name(entry), O_RDONLY);
159 if (vf) {
160 if (mCoreIsCompatible(vf) != PLATFORM_NONE || mVideoLogIsCompatible(vf) != PLATFORM_NONE) {
161 struct CInemaTest* test = CInemaTestListAppend(tests);
162 if (!CInemaTestInit(test, path, entry->name(entry))) {
163 CIerr(2, "Failed to create test\n");
164 CInemaTestListResize(tests, -1);
165 } else {
166 CIerr(1, "Found test %s\n", test->name);
167 }
168 } else {
169 CIerr(2, "Not a compatible file\n");
170 }
171 vf->close(vf);
172 } else {
173 CIerr(2, "Failed to open file\n");
174 }
175 }
176 entry = dir->listNext(dir);
177 }
178 dir->close(dir);
179 return true;
180}
181
182static int _compareNames(const void* a, const void* b) {
183 const struct CInemaTest* ta = a;
184 const struct CInemaTest* tb = b;
185
186 return strncmp(ta->name, tb->name, sizeof(ta->name));
187}
188
189static void reduceTestList(struct CInemaTestList* tests) {
190 qsort(CInemaTestListGetPointer(tests, 0), CInemaTestListSize(tests), sizeof(struct CInemaTest), _compareNames);
191
192 size_t i;
193 for (i = 1; i < CInemaTestListSize(tests);) {
194 struct CInemaTest* cur = CInemaTestListGetPointer(tests, i);
195 struct CInemaTest* prev = CInemaTestListGetPointer(tests, i - 1);
196 if (strncmp(cur->name, prev->name, sizeof(cur->name)) != 0) {
197 ++i;
198 continue;
199 }
200 CInemaTestDeinit(cur);
201 CInemaTestListShift(tests, i, 1);
202 }
203}
204
205bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char* filename) {
206 if (strncmp(base, directory, strlen(base)) != 0) {
207 return false;
208 }
209 strncpy(test->directory, directory, sizeof(test->directory));
210 strncpy(test->filename, filename, sizeof(test->filename));
211 directory += strlen(base) + 1;
212 strncpy(test->name, directory, sizeof(test->name));
213 char* str = strstr(test->name, PATH_SEP);
214 while (str) {
215 str[0] = '.';
216 str = strstr(str, PATH_SEP);
217 }
218 return true;
219}
220
221void CInemaTestDeinit(struct CInemaTest* test) {
222 // TODO: Write
223}
224
225int main(int argc, char** argv) {
226 int status = 0;
227 if (!parseCInemaArgs(argc, argv)) {
228 status = 1;
229 goto cleanup;
230 }
231
232 if (showVersion) {
233 version(argv[0]);
234 goto cleanup;
235 }
236
237 if (showUsage) {
238 usageCInema(argv[0]);
239 goto cleanup;
240 }
241
242 argc -= optind;
243 argv += optind;
244
245 if (!base[0] && !determineBase(argc, argv)) {
246 CIerr(0, "Could not determine CInema test base. Please specify manually.");
247 status = 1;
248 goto cleanup;
249 }
250#ifndef _WIN32
251 char* rbase = realpath(base, NULL);
252 strncpy(base, rbase, PATH_MAX);
253 free(rbase);
254#endif
255
256 struct CInemaTestList tests;
257 CInemaTestListInit(&tests, 0);
258
259 if (argc > 0) {
260 size_t i;
261 for (i = 0; i < (size_t) argc; ++i) {
262 char path[PATH_MAX + 1] = {0};
263 char* arg = argv[i];
264 strncpy(path, base, sizeof(path));
265
266 bool dotSeen = true;
267 size_t j;
268 for (arg = argv[i], j = strlen(path); arg[0] && j < sizeof(path); ++arg) {
269 if (arg[0] == '.') {
270 dotSeen = true;
271 } else {
272 if (dotSeen) {
273 strncpy(&path[j], PATH_SEP, sizeof(path) - j);
274 j += strlen(PATH_SEP);
275 dotSeen = false;
276 if (!j) {
277 break;
278 }
279 }
280 path[j] = arg[0];
281 ++j;
282 }
283 }
284
285 if (!collectTests(&tests, path)) {
286 status = 1;
287 break;
288 }
289 }
290 } else if (!collectTests(&tests, base)) {
291 status = 1;
292 }
293
294 if (CInemaTestListSize(&tests) == 0) {
295 CIerr(1, "No tests found.");
296 status = 1;
297 } else {
298 reduceTestList(&tests);
299 }
300
301 if (dryRun) {
302 size_t i;
303 for (i = 0; i < CInemaTestListSize(&tests); ++i) {
304 struct CInemaTest* test = CInemaTestListGetPointer(&tests, i);
305 CIlog(-1, "%s\n", test->name);
306 }
307 } else {
308 // TODO: Write
309 }
310
311 size_t i;
312 for (i = 0; i < CInemaTestListSize(&tests); ++i) {
313 struct CInemaTest* test = CInemaTestListGetPointer(&tests, i);
314 CInemaTestDeinit(test);
315 }
316
317 CInemaTestListDeinit(&tests);
318
319cleanup:
320 return status;
321}