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/png-io.h>
14#include <mgba-util/table.h>
15#include <mgba-util/vector.h>
16#include <mgba-util/vfs.h>
17
18#ifdef _MSC_VER
19#include <mgba-util/platform/windows/getopt.h>
20#else
21#include <getopt.h>
22#endif
23
24#include <stdlib.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27
28#define MAX_TEST 200
29
30static const struct option longOpts[] = {
31 { "base", required_argument, 0, 'b' },
32 { "diffs", no_argument, 0, 'd' },
33 { "help", no_argument, 0, 'h' },
34 { "dry-run", no_argument, 0, 'n' },
35 { "outdir", required_argument, 0, 'o' },
36 { "quiet", no_argument, 0, 'q' },
37 { "rebaseline", no_argument, 0, 'r' },
38 { "verbose", no_argument, 0, 'v' },
39 { "version", no_argument, 0, '\0' },
40 { 0, 0, 0, 0 }
41};
42
43static const char shortOpts[] = "b:dhno:qrv";
44
45enum CInemaStatus {
46 CI_PASS,
47 CI_FAIL,
48 CI_XPASS,
49 CI_XFAIL,
50 CI_ERROR,
51 CI_SKIP
52};
53
54struct CInemaTest {
55 char directory[MAX_TEST];
56 char filename[MAX_TEST];
57 char name[MAX_TEST];
58 enum CInemaStatus status;
59 unsigned failedFrames;
60 uint64_t failedPixels;
61 unsigned totalFrames;
62 uint64_t totalDistance;
63 uint64_t totalPixels;
64};
65
66struct CInemaImage {
67 void* data;
68 unsigned width;
69 unsigned height;
70 unsigned stride;
71};
72
73DECLARE_VECTOR(CInemaTestList, struct CInemaTest)
74DEFINE_VECTOR(CInemaTestList, struct CInemaTest)
75
76DECLARE_VECTOR(ImageList, void*)
77DEFINE_VECTOR(ImageList, void*)
78
79static bool showVersion = false;
80static bool showUsage = false;
81static char base[PATH_MAX] = {0};
82static char outdir[PATH_MAX] = {'.'};
83static bool dryRun = false;
84static bool diffs = false;
85static bool rebaseline = false;
86static int verbosity = 0;
87
88bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename);
89void CInemaTestRun(struct CInemaTest*, struct Table* configTree);
90
91bool CInemaConfigGetUInt(struct Table* configTree, const char* testName, const char* key, unsigned* value);
92void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCore* core);
93
94static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args);
95
96ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) {
97 if (verbosity < minlevel) {
98 return;
99 }
100 va_list args;
101 va_start(args, format);
102 vprintf(format, args);
103 va_end(args);
104}
105
106ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) {
107 if (verbosity < minlevel) {
108 return;
109 }
110 va_list args;
111 va_start(args, format);
112 vfprintf(stderr, format, args);
113 va_end(args);
114}
115
116static bool parseCInemaArgs(int argc, char* const* argv) {
117 int ch;
118 int index = 0;
119 while ((ch = getopt_long(argc, argv, shortOpts, longOpts, &index)) != -1) {
120 const struct option* opt = &longOpts[index];
121 switch (ch) {
122 case '\0':
123 if (strcmp(opt->name, "version") == 0) {
124 showVersion = true;
125 } else {
126 return false;
127 }
128 break;
129 case 'b':
130 strncpy(base, optarg, sizeof(base));
131 // TODO: Verify path exists
132 break;
133 case 'd':
134 diffs = true;
135 break;
136 case 'h':
137 showUsage = true;
138 break;
139 case 'n':
140 dryRun = true;
141 break;
142 case 'o':
143 strncpy(outdir, optarg, sizeof(outdir));
144 // TODO: Make directory
145 break;
146 case 'q':
147 --verbosity;
148 break;
149 case 'r':
150 rebaseline = true;
151 break;
152 case 'v':
153 ++verbosity;
154 break;
155 default:
156 return false;
157 }
158 }
159
160 return true;
161}
162
163static void usageCInema(const char* arg0) {
164 printf("usage: %s [-dhnqv] [-b BASE] [-o DIR] [--version] [test...]\n", arg0);
165 puts(" -b, --base [BASE] Path to the CInema base directory");
166 puts(" -d, --diffs Output image diffs from failures");
167 puts(" -h, --help Print this usage and exit");
168 puts(" -n, --dry-run List all collected tests instead of running them");
169 puts(" -o, --output [DIR] Path to output applicable results");
170 puts(" -q, --quiet Decrease log verbosity (can be repeated)");
171 puts(" -r, --rebaseline Rewrite the baseline for failing tests");
172 puts(" -v, --verbose Increase log verbosity (can be repeated)");
173 puts(" --version Print version and exit");
174}
175
176static bool determineBase(int argc, char* const* argv) {
177 // TODO: Better dynamic detection
178 separatePath(__FILE__, base, NULL, NULL);
179 strncat(base, PATH_SEP ".." PATH_SEP ".." PATH_SEP ".." PATH_SEP "cinema", sizeof(base) - strlen(base) - 1);
180 return true;
181}
182
183static bool collectTests(struct CInemaTestList* tests, const char* path) {
184 CIerr(2, "Considering path %s\n", path);
185 struct VDir* dir = VDirOpen(path);
186 if (!dir) {
187 return false;
188 }
189 struct VDirEntry* entry = dir->listNext(dir);
190 while (entry) {
191 char subpath[PATH_MAX];
192 snprintf(subpath, sizeof(subpath), "%s" PATH_SEP "%s", path, entry->name(entry));
193 if (entry->type(entry) == VFS_DIRECTORY && strncmp(entry->name(entry), ".", 2) != 0 && strncmp(entry->name(entry), "..", 3) != 0) {
194 if (!collectTests(tests, subpath)) {
195 dir->close(dir);
196 return false;
197 }
198 } else if (entry->type(entry) == VFS_FILE && strncmp(entry->name(entry), "test.", 5) == 0) {
199 CIerr(3, "Found potential test %s\n", subpath);
200 struct VFile* vf = dir->openFile(dir, entry->name(entry), O_RDONLY);
201 if (vf) {
202 if (mCoreIsCompatible(vf) != PLATFORM_NONE || mVideoLogIsCompatible(vf) != PLATFORM_NONE) {
203 struct CInemaTest* test = CInemaTestListAppend(tests);
204 if (!CInemaTestInit(test, path, entry->name(entry))) {
205 CIerr(3, "Failed to create test\n");
206 CInemaTestListResize(tests, -1);
207 } else {
208 CIerr(2, "Found test %s\n", test->name);
209 }
210 } else {
211 CIerr(3, "Not a compatible file\n");
212 }
213 vf->close(vf);
214 } else {
215 CIerr(3, "Failed to open file\n");
216 }
217 }
218 entry = dir->listNext(dir);
219 }
220 dir->close(dir);
221 return true;
222}
223
224static int _compareNames(const void* a, const void* b) {
225 const struct CInemaTest* ta = a;
226 const struct CInemaTest* tb = b;
227
228 return strncmp(ta->name, tb->name, sizeof(ta->name));
229}
230
231static void reduceTestList(struct CInemaTestList* tests) {
232 qsort(CInemaTestListGetPointer(tests, 0), CInemaTestListSize(tests), sizeof(struct CInemaTest), _compareNames);
233
234 size_t i;
235 for (i = 1; i < CInemaTestListSize(tests);) {
236 struct CInemaTest* cur = CInemaTestListGetPointer(tests, i);
237 struct CInemaTest* prev = CInemaTestListGetPointer(tests, i - 1);
238 if (strncmp(cur->name, prev->name, sizeof(cur->name)) != 0) {
239 ++i;
240 continue;
241 }
242 CInemaTestListShift(tests, i, 1);
243 }
244}
245
246static void testToPath(const char* testName, char* path) {
247 strncpy(path, base, PATH_MAX);
248
249 bool dotSeen = true;
250 size_t i;
251 for (i = strlen(path); testName[0] && i < PATH_MAX; ++testName) {
252 if (testName[0] == '.') {
253 dotSeen = true;
254 } else {
255 if (dotSeen) {
256 strncpy(&path[i], PATH_SEP, PATH_MAX - i);
257 i += strlen(PATH_SEP);
258 dotSeen = false;
259 if (!i) {
260 break;
261 }
262 }
263 path[i] = testName[0];
264 ++i;
265 }
266 }
267}
268
269static void _loadConfigTree(struct Table* configTree, const char* testName) {
270 char key[MAX_TEST];
271 strncpy(key, testName, sizeof(key) - 1);
272
273 struct mCoreConfig* config;
274 while (!(config = HashTableLookup(configTree, key))) {
275 char path[PATH_MAX];
276 config = malloc(sizeof(*config));
277 mCoreConfigInit(config, "cinema");
278 testToPath(key, path);
279 strncat(path, PATH_SEP, sizeof(path) - 1);
280 strncat(path, "config.ini", sizeof(path) - 1);
281 mCoreConfigLoadPath(config, path);
282 HashTableInsert(configTree, key, config);
283 char* pos = strrchr(key, '.');
284 if (pos) {
285 pos[0] = '\0';
286 } else if (key[0]) {
287 key[0] = '\0';
288 } else {
289 break;
290 }
291 }
292}
293
294static void _unloadConfigTree(const char* key, void* value, void* user) {
295 UNUSED(key);
296 UNUSED(user);
297 mCoreConfigDeinit(value);
298}
299
300static const char* _lookupValue(struct Table* configTree, const char* testName, const char* key) {
301 _loadConfigTree(configTree, testName);
302
303 char testKey[MAX_TEST];
304 strncpy(testKey, testName, sizeof(testKey) - 1);
305
306 struct mCoreConfig* config;
307 while (true) {
308 config = HashTableLookup(configTree, testKey);
309 if (!config) {
310 continue;
311 }
312 const char* str = ConfigurationGetValue(&config->configTable, "testinfo", key);
313 if (str) {
314 return str;
315 }
316 char* pos = strrchr(testKey, '.');
317 if (pos) {
318 pos[0] = '\0';
319 } else if (testKey[0]) {
320 testKey[0] = '\0';
321 } else {
322 break;
323 }
324 }
325 return NULL;
326}
327
328bool CInemaConfigGetUInt(struct Table* configTree, const char* testName, const char* key, unsigned* out) {
329 const char* charValue = _lookupValue(configTree, testName, key);
330 if (!charValue) {
331 return false;
332 }
333 char* end;
334 unsigned long value = strtoul(charValue, &end, 10);
335 if (*end) {
336 return false;
337 }
338 *out = value;
339 return true;
340}
341
342void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCore* core) {
343 _loadConfigTree(configTree, testName);
344
345 char testKey[MAX_TEST] = {0};
346 char* keyEnd = testKey;
347 const char* pos;
348 while (true) {
349 pos = strchr(testName, '.');
350 size_t maxlen = sizeof(testKey) - (keyEnd - testKey) - 1;
351 size_t len;
352 if (pos) {
353 len = pos - testName;
354 } else {
355 len = strlen(testName);
356 }
357 if (len > maxlen) {
358 len = maxlen;
359 }
360 strncpy(keyEnd, testName, len);
361 keyEnd += len;
362
363 struct mCoreConfig* config = HashTableLookup(configTree, testKey);
364 if (config) {
365 core->loadConfig(core, config);
366 }
367 if (!pos) {
368 break;
369 }
370 testName = pos + 1;
371 keyEnd[0] = '.';
372 ++keyEnd;
373 }
374}
375
376bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char* filename) {
377 if (strncmp(base, directory, strlen(base)) != 0) {
378 return false;
379 }
380 memset(test, 0, sizeof(*test));
381 strncpy(test->directory, directory, sizeof(test->directory) - 1);
382 strncpy(test->filename, filename, sizeof(test->filename) - 1);
383 directory += strlen(base) + 1;
384 strncpy(test->name, directory, sizeof(test->name) - 1);
385 char* str = strstr(test->name, PATH_SEP);
386 while (str) {
387 str[0] = '.';
388 str = strstr(str, PATH_SEP);
389 }
390 return true;
391}
392
393static bool _loadBaseline(struct VDir* dir, struct CInemaImage* image, size_t frame, enum CInemaStatus* status) {
394 char baselineName[32];
395 snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame);
396 struct VFile* baselineVF = dir->openFile(dir, baselineName, O_RDONLY);
397 if (!baselineVF) {
398 if (*status == CI_PASS) {
399 *status = CI_FAIL;
400 }
401 return false;
402 }
403
404 png_structp png = PNGReadOpen(baselineVF, 0);
405 png_infop info = png_create_info_struct(png);
406 png_infop end = png_create_info_struct(png);
407 if (!png || !info || !end || !PNGReadHeader(png, info)) {
408 PNGReadClose(png, info, end);
409 baselineVF->close(baselineVF);
410 CIerr(1, "Failed to load %s\n", baselineName);
411 *status = CI_ERROR;
412 return false;
413 }
414
415 unsigned pwidth = png_get_image_width(png, info);
416 unsigned pheight = png_get_image_height(png, info);
417 if (pheight != image->height || pwidth != image->width) {
418 PNGReadClose(png, info, end);
419 baselineVF->close(baselineVF);
420 CIerr(1, "Size mismatch for %s, expected %ux%u, got %ux%u\n", baselineName, pwidth, pheight, image->width, image->height);
421 if (*status == CI_PASS) {
422 *status = CI_FAIL;
423 }
424 return false;
425 }
426
427 image->data = malloc(pwidth * pheight * BYTES_PER_PIXEL);
428 if (!image->data) {
429 CIerr(1, "Failed to allocate baseline buffer\n");
430 *status = CI_ERROR;
431 PNGReadClose(png, info, end);
432 baselineVF->close(baselineVF);
433 return false;
434 }
435 if (!PNGReadPixels(png, info, image->data, pwidth, pheight, pwidth) || !PNGReadFooter(png, end)) {
436 CIerr(1, "Failed to read %s\n", baselineName);
437 *status = CI_ERROR;
438 free(image->data);
439 return false;
440 }
441 PNGReadClose(png, info, end);
442 baselineVF->close(baselineVF);
443 image->stride = pwidth;
444 return true;
445}
446
447static struct VDir* _makeOutDir(const char* testName) {
448 char path[PATH_MAX] = {0};
449 strncpy(path, outdir, sizeof(path) - 1);
450 char* pathEnd = path + strlen(path);
451 const char* pos;
452 while (true) {
453 pathEnd[0] = PATH_SEP[0];
454 ++pathEnd;
455 pos = strchr(testName, '.');
456 size_t maxlen = sizeof(path) - (pathEnd - path) - 1;
457 size_t len;
458 if (pos) {
459 len = pos - testName;
460 } else {
461 len = strlen(testName);
462 }
463 if (len > maxlen) {
464 len = maxlen;
465 }
466 strncpy(pathEnd, testName, len);
467 pathEnd += len;
468
469 mkdir(path, 0777);
470
471 if (!pos) {
472 break;
473 }
474 testName = pos + 1;
475 }
476 return VDirOpen(path);
477}
478
479static void _writeImage(struct VFile* vf, const struct CInemaImage* image) {
480 png_structp png = PNGWriteOpen(vf);
481 png_infop info = PNGWriteHeader(png, image->width, image->height);
482 if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data)) {
483 CIerr(0, "Could not write output image\n");
484 }
485 PNGWriteClose(png, info);
486
487 vf->close(vf);
488}
489
490static void _writeDiff(const char* testName, const struct CInemaImage* image, size_t frame, const char* type) {
491 struct VDir* dir = _makeOutDir(testName);
492 if (!dir) {
493 CIerr(0, "Could not open directory for %s\n", testName);
494 return;
495 }
496 char name[32];
497 snprintf(name, sizeof(name), "%s_%04" PRIz "u.png", type, frame);
498 struct VFile* vf = dir->openFile(dir, name, O_CREAT | O_TRUNC | O_WRONLY);
499 if (!vf) {
500 CIerr(0, "Could not open output file %s\n", name);
501 dir->close(dir);
502 return;
503 }
504 _writeImage(vf, image);
505 dir->close(dir);
506}
507
508static void _writeBaseline(struct VDir* dir, const struct CInemaImage* image, size_t frame) {
509 char baselineName[32];
510 snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame);
511 struct VFile* baselineVF = dir->openFile(dir, baselineName, O_CREAT | O_TRUNC | O_WRONLY);
512 if (baselineVF) {
513 _writeImage(baselineVF, image);
514 } else {
515 CIerr(0, "Could not open output file %s\n", baselineName);
516 }
517}
518
519void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
520 unsigned ignore = 0;
521 CInemaConfigGetUInt(configTree, test->name, "ignore", &ignore);
522 if (ignore) {
523 test->status = CI_SKIP;
524 return;
525 }
526
527 struct VDir* dir = VDirOpen(test->directory);
528 if (!dir) {
529 CIerr(0, "Failed to open test directory\n");
530 test->status = CI_ERROR;
531 return;
532 }
533 struct VFile* rom = dir->openFile(dir, test->filename, O_RDONLY);
534 if (!rom) {
535 CIerr(0, "Failed to open test\n");
536 test->status = CI_ERROR;
537 return;
538 }
539 struct mCore* core = mCoreFindVF(rom);
540 if (!core) {
541 CIerr(0, "Failed to load test\n");
542 test->status = CI_ERROR;
543 rom->close(rom);
544 return;
545 }
546 if (!core->init(core)) {
547 CIerr(0, "Failed to init test\n");
548 test->status = CI_ERROR;
549 core->deinit(core);
550 return;
551 }
552 struct CInemaImage image;
553 core->desiredVideoDimensions(core, &image.width, &image.height);
554 ssize_t bufferSize = image.width * image.height * BYTES_PER_PIXEL;
555 image.data = malloc(bufferSize);
556 image.stride = image.width;
557 if (!image.data) {
558 CIerr(0, "Failed to allocate video buffer\n");
559 test->status = CI_ERROR;
560 core->deinit(core);
561 }
562 core->setVideoBuffer(core, image.data, image.stride);
563 mCoreConfigInit(&core->config, "cinema");
564
565 unsigned limit = 9999;
566 unsigned skip = 0;
567 unsigned fail = 0;
568
569 CInemaConfigGetUInt(configTree, test->name, "frames", &limit);
570 CInemaConfigGetUInt(configTree, test->name, "skip", &skip);
571 CInemaConfigGetUInt(configTree, test->name, "fail", &fail);
572 CInemaConfigLoad(configTree, test->name, core);
573
574 core->loadROM(core, rom);
575 core->reset(core);
576
577 test->status = CI_PASS;
578
579 unsigned minFrame = core->frameCounter(core);
580 size_t frame;
581 for (frame = 0; frame < skip; ++frame) {
582 core->runFrame(core);
583 }
584 for (frame = 0; limit; ++frame, --limit) {
585 core->runFrame(core);
586 ++test->totalFrames;
587 unsigned frameCounter = core->frameCounter(core);
588 if (frameCounter <= minFrame) {
589 break;
590 }
591 CIerr(3, "Test frame: %u\n", frameCounter);
592 core->desiredVideoDimensions(core, &image.width, &image.height);
593 uint8_t* diff = NULL;
594 struct CInemaImage expected = {
595 .data = NULL,
596 .width = image.width,
597 .height = image.height,
598 .stride = image.width,
599 };
600 if (_loadBaseline(dir, &expected, frame, &test->status)) {
601 uint8_t* testPixels = image.data;
602 uint8_t* expectPixels = expected.data;
603 size_t x;
604 size_t y;
605 int max = 0;
606 bool failed = false;
607 for (y = 0; y < image.height; ++y) {
608 for (x = 0; x < image.width; ++x) {
609 size_t pix = expected.stride * y + x;
610 size_t tpix = image.stride * y + x;
611 int testR = testPixels[tpix * 4 + 0];
612 int testG = testPixels[tpix * 4 + 1];
613 int testB = testPixels[tpix * 4 + 2];
614 int expectR = expectPixels[pix * 4 + 0];
615 int expectG = expectPixels[pix * 4 + 1];
616 int expectB = expectPixels[pix * 4 + 2];
617 int r = expectR - testR;
618 int g = expectG - testG;
619 int b = expectB - testB;
620 if (r | g | b) {
621 failed = true;
622 if (diffs && !diff) {
623 diff = calloc(expected.width * expected.height, BYTES_PER_PIXEL);
624 }
625 CIerr(3, "Frame %u failed at pixel %" PRIz "ux%" PRIz "u with diff %i,%i,%i (expected %02x%02x%02x, got %02x%02x%02x)\n",
626 frameCounter, x, y, r, g, b,
627 expectR, expectG, expectB,
628 testR, testG, testB);
629 test->status = CI_FAIL;
630 if (r < 0) {
631 r = -r;
632 }
633 if (g < 0) {
634 g = -g;
635 }
636 if (b < 0) {
637 b = -b;
638 }
639
640 if (diff) {
641 if (r > max) {
642 max = r;
643 }
644 if (g > max) {
645 max = g;
646 }
647 if (b > max) {
648 max = b;
649 }
650 diff[pix * 4 + 0] = r;
651 diff[pix * 4 + 1] = g;
652 diff[pix * 4 + 2] = b;
653 }
654
655 test->totalDistance += r + g + b;
656 ++test->failedPixels;
657 }
658 }
659 }
660 if (failed) {
661 ++test->failedFrames;
662 }
663 test->totalPixels += image.height * image.width;
664 if (rebaseline && failed) {
665 _writeBaseline(dir, &image, frame);
666 }
667 if (diff) {
668 if (failed) {
669 struct CInemaImage outdiff = {
670 .data = diff,
671 .width = image.width,
672 .height = image.height,
673 .stride = image.width,
674 };
675
676 _writeDiff(test->name, &image, frame, "result");
677 _writeDiff(test->name, &expected, frame, "expected");
678 _writeDiff(test->name, &outdiff, frame, "diff");
679
680 for (y = 0; y < outdiff.height; ++y) {
681 for (x = 0; x < outdiff.width; ++x) {
682 size_t pix = outdiff.stride * y + x;
683 diff[pix * 4 + 0] = diff[pix * 4 + 0] * 255 / max;
684 diff[pix * 4 + 1] = diff[pix * 4 + 1] * 255 / max;
685 diff[pix * 4 + 2] = diff[pix * 4 + 2] * 255 / max;
686 }
687 }
688 _writeDiff(test->name, &outdiff, frame, "normalized");
689 }
690 free(diff);
691 }
692 free(expected.data);
693 } else if (test->status == CI_ERROR) {
694 break;
695 } else if (rebaseline) {
696 _writeBaseline(dir, &image, frame);
697 }
698 }
699
700 if (fail) {
701 if (test->status == CI_FAIL) {
702 test->status = CI_XFAIL;
703 } else if (test->status == CI_PASS) {
704 test->status = CI_XPASS;
705 }
706 }
707
708 free(image.data);
709 mCoreConfigDeinit(&core->config);
710 core->deinit(core);
711 dir->close(dir);
712}
713
714void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
715 // TODO: Write
716}
717
718int main(int argc, char** argv) {
719 int status = 0;
720 if (!parseCInemaArgs(argc, argv)) {
721 status = 1;
722 goto cleanup;
723 }
724
725 if (showVersion) {
726 version(argv[0]);
727 goto cleanup;
728 }
729
730 if (showUsage) {
731 usageCInema(argv[0]);
732 goto cleanup;
733 }
734
735 argc -= optind;
736 argv += optind;
737
738 if (!base[0] && !determineBase(argc, argv)) {
739 CIerr(0, "Could not determine CInema test base. Please specify manually.");
740 status = 1;
741 goto cleanup;
742 }
743#ifndef _WIN32
744 char* rbase = realpath(base, NULL);
745 strncpy(base, rbase, PATH_MAX);
746 free(rbase);
747#endif
748
749 struct CInemaTestList tests;
750 CInemaTestListInit(&tests, 0);
751
752 struct mLogger logger = { .log = _log };
753 mLogSetDefaultLogger(&logger);
754
755 if (argc > 0) {
756 size_t i;
757 for (i = 0; i < (size_t) argc; ++i) {
758 char path[PATH_MAX + 1] = {0};
759 testToPath(argv[i], path);
760
761 if (!collectTests(&tests, path)) {
762 status = 1;
763 break;
764 }
765 }
766 } else if (!collectTests(&tests, base)) {
767 status = 1;
768 }
769
770 if (CInemaTestListSize(&tests) == 0) {
771 CIerr(1, "No tests found.");
772 status = 1;
773 } else {
774 reduceTestList(&tests);
775 }
776
777 struct Table configTree;
778 HashTableInit(&configTree, 0, free);
779
780 size_t i;
781 for (i = 0; i < CInemaTestListSize(&tests); ++i) {
782 struct CInemaTest* test = CInemaTestListGetPointer(&tests, i);
783 if (dryRun) {
784 CIlog(-1, "%s\n", test->name);
785 } else {
786 CIerr(1, "%s: ", test->name);
787 CInemaTestRun(test, &configTree);
788 switch (test->status) {
789 case CI_PASS:
790 CIerr(1, "pass");
791 break;
792 case CI_FAIL:
793 status = 1;
794 CIerr(1, "fail");
795 break;
796 case CI_XPASS:
797 CIerr(1, "xpass");
798 break;
799 case CI_XFAIL:
800 CIerr(1, "xfail");
801 break;
802 case CI_SKIP:
803 CIerr(1, "skip");
804 break;
805 case CI_ERROR:
806 status = 1;
807 CIerr(1, "error");
808 break;
809 }
810 if (test->failedFrames) {
811 CIerr(2, "\n\tfailed frames: %u/%u (%1.3g%%)", test->failedFrames, test->totalFrames, test->failedFrames / (test->totalFrames * 0.01));
812 CIerr(2, "\n\tfailed pixels: %" PRIu64 "/%" PRIu64 " (%1.3g%%)", test->failedPixels, test->totalPixels, test->failedPixels / (test->totalPixels * 0.01));
813 CIerr(2, "\n\tdistance: %" PRIu64 "/%" PRIu64 " (%1.3g%%)", test->totalDistance, test->totalPixels * 765, test->totalDistance / (test->totalPixels * 7.65));
814 }
815
816 CIerr(1, "\n");
817 }
818 }
819
820 HashTableEnumerate(&configTree, _unloadConfigTree, NULL);
821 HashTableDeinit(&configTree);
822 CInemaTestListDeinit(&tests);
823
824cleanup:
825 return status;
826}