all repos — mgba @ 4f671290989321fc98ec15e0ec307eaafc07d031

mGBA Game Boy Advance Emulator

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}