diff --git a/src/platform/test/CMakeLists.txt b/src/platform/test/CMakeLists.txt index 4f7056d53..8882b8d94 100644 --- a/src/platform/test/CMakeLists.txt +++ b/src/platform/test/CMakeLists.txt @@ -42,4 +42,5 @@ if(BUILD_CINEMA) enable_testing() add_executable(${BINARY_NAME}-cinema ${CMAKE_CURRENT_SOURCE_DIR}/cinema-main.c) target_link_libraries(${BINARY_NAME}-cinema ${BINARY_NAME} ${PLATFORM_LIBRARY}) + set_target_properties(${BINARY_NAME}-cinema PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") endif() diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index eebae90a9..60bc2fe79 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -40,6 +41,7 @@ enum CInemaStatus { CI_FAIL, CI_XPASS, CI_XFAIL, + CI_ERROR, CI_SKIP }; @@ -47,7 +49,6 @@ struct CInemaTest { char directory[MAX_TEST]; char filename[MAX_TEST]; char name[MAX_TEST]; - struct mCoreConfig config; enum CInemaStatus status; int failedFrames; }; @@ -55,12 +56,20 @@ struct CInemaTest { DECLARE_VECTOR(CInemaTestList, struct CInemaTest) DEFINE_VECTOR(CInemaTestList, struct CInemaTest) +DECLARE_VECTOR(ImageList, void*) +DEFINE_VECTOR(ImageList, void*) + static bool showVersion = false; static bool showUsage = false; static char base[PATH_MAX] = {0}; static bool dryRun = false; static int verbosity = 0; +bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); +void CInemaTestRun(struct CInemaTest*); + +static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args); + ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) { if (verbosity < minlevel) { return; @@ -81,9 +90,6 @@ ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) va_end(args); } -bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); -void CInemaTestDeinit(struct CInemaTest*); - static bool parseCInemaArgs(int argc, char* const* argv) { int ch; int index = 0; @@ -139,7 +145,7 @@ static bool determineBase(int argc, char* const* argv) { } static bool collectTests(struct CInemaTestList* tests, const char* path) { - CIerr(1, "Considering path %s\n", path); + CIerr(2, "Considering path %s\n", path); struct VDir* dir = VDirOpen(path); if (!dir) { return false; @@ -197,7 +203,6 @@ static void reduceTestList(struct CInemaTestList* tests) { ++i; continue; } - CInemaTestDeinit(cur); CInemaTestListShift(tests, i, 1); } } @@ -218,7 +223,134 @@ bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char* return true; } -void CInemaTestDeinit(struct CInemaTest* test) { +void CInemaTestRun(struct CInemaTest* test) { + struct VDir* dir = VDirOpen(test->directory); + if (!dir) { + CIerr(0, "Failed to open test directory\n"); + test->status = CI_ERROR; + return; + } + struct VFile* rom = dir->openFile(dir, test->filename, O_RDONLY); + if (!rom) { + CIerr(0, "Failed to open test\n"); + test->status = CI_ERROR; + return; + } + struct mCore* core = mCoreFindVF(rom); + if (!core) { + CIerr(0, "Failed to load test\n"); + test->status = CI_ERROR; + rom->close(rom); + return; + } + if (!core->init(core)) { + CIerr(0, "Failed to init test\n"); + test->status = CI_ERROR; + core->deinit(core); + return; + } + unsigned width, height; + core->desiredVideoDimensions(core, &width, &height); + ssize_t bufferSize = width * height * BYTES_PER_PIXEL; + void* buffer = malloc(bufferSize); + if (!buffer) { + CIerr(0, "Failed to allocate video buffer\n"); + test->status = CI_ERROR; + core->deinit(core); + } + core->setVideoBuffer(core, buffer, width); + mCoreConfigInit(&core->config, "cinema"); + + core->loadROM(core, rom); + core->reset(core); + + test->status = CI_PASS; + + unsigned minFrame = core->frameCounter(core); + unsigned limit = 9999; + size_t frame; + for (frame = 0; limit; ++frame, --limit) { + char baselineName[32]; + snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame); + core->runFrame(core); + unsigned frameCounter = core->frameCounter(core); + if (frameCounter <= minFrame) { + break; + } + CIerr(2, "Test frame: %u\n", frameCounter); + + struct VFile* baselineVF = dir->openFile(dir, baselineName, O_RDONLY); + if (!baselineVF) { + test->status = CI_FAIL; + } else { + png_structp png = PNGReadOpen(baselineVF, 0); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end || !PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + CIerr(1, "Failed to load %s\n", baselineName); + test->status = CI_ERROR; + } else { + unsigned pwidth = png_get_image_width(png, info); + unsigned pheight = png_get_image_height(png, info); + unsigned twidth, theight; + core->desiredVideoDimensions(core, &twidth, &theight); + if (pheight != theight || pwidth != twidth) { + PNGReadClose(png, info, end); + CIerr(1, "Size mismatch for %s, expected %ux%u, got %ux%u\n", baselineName, pwidth, pheight, twidth, theight); + test->status = CI_FAIL; + } else { + uint8_t* pixels = malloc(pwidth * pheight * BYTES_PER_PIXEL); + if (!pixels) { + CIerr(1, "Failed to allocate baseline buffer\n"); + test->status = CI_ERROR; + } else { + if (!PNGReadPixels(png, info, pixels, pwidth, pheight, pwidth) || !PNGReadFooter(png, end)) { + CIerr(1, "Failed to read %s\n", baselineName); + test->status = CI_ERROR; + } else { + uint8_t* testPixels = buffer; + size_t x; + size_t y; + for (y = 0; y < theight; ++y) { + for (x = 0; x < twidth; ++x) { + size_t pix = pwidth * y + x; + size_t tpix = width * y + x; + int testR = testPixels[tpix * 4 + 0]; + int testG = testPixels[tpix * 4 + 1]; + int testB = testPixels[tpix * 4 + 2]; + int expectR = pixels[pix * 4 + 0]; + int expectG = pixels[pix * 4 + 1]; + int expectB = pixels[pix * 4 + 2]; + int r = expectR - testR; + int g = expectG - testG; + int b = expectB - testB; + if (r | g | b) { + CIerr(2, "Frame %u failed at pixel %" PRIz "ux%" PRIz "u with diff %i,%i,%i (expected %02x%02x%02x, got %02x%02x%02x)\n", + frameCounter, x, y, r, g, b, + expectR, expectG, expectB, + testR, testG, testB); + test->status = CI_FAIL; + ++test->failedFrames; + } + } + } + } + } + PNGReadClose(png, info, end); + free(pixels); + } + } + baselineVF->close(baselineVF); + } + } + + free(buffer); + core->deinit(core); + dir->close(dir); +} + +void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { // TODO: Write } @@ -256,6 +388,9 @@ int main(int argc, char** argv) { struct CInemaTestList tests; CInemaTestListInit(&tests, 0); + struct mLogger logger = { .log = _log }; + mLogSetDefaultLogger(&logger); + if (argc > 0) { size_t i; for (i = 0; i < (size_t) argc; ++i) { @@ -298,20 +433,38 @@ int main(int argc, char** argv) { reduceTestList(&tests); } - if (dryRun) { - size_t i; - for (i = 0; i < CInemaTestListSize(&tests); ++i) { - struct CInemaTest* test = CInemaTestListGetPointer(&tests, i); - CIlog(-1, "%s\n", test->name); - } - } else { - // TODO: Write - } - size_t i; for (i = 0; i < CInemaTestListSize(&tests); ++i) { struct CInemaTest* test = CInemaTestListGetPointer(&tests, i); - CInemaTestDeinit(test); + if (dryRun) { + CIlog(-1, "%s\n", test->name); + } else { + CIerr(1, "%s: ", test->name); + CInemaTestRun(test); + switch (test->status) { + case CI_PASS: + CIerr(1, "pass"); + break; + case CI_FAIL: + status = 1; + CIerr(1, "fail"); + break; + case CI_XPASS: + CIerr(1, "xpass"); + break; + case CI_XFAIL: + CIerr(1, "xfail"); + break; + case CI_SKIP: + CIerr(1, "skip"); + break; + case CI_ERROR: + status = 1; + CIerr(1, "error"); + break; + } + CIerr(1, "\n"); + } } CInemaTestListDeinit(&tests);