From 51c3fca3bf79d1c4d200fcb1fd48d24dc03e5d7a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Jul 2020 16:57:09 -0700 Subject: [PATCH 01/20] Util: Refactor TLS out of platform-specific APIs --- CMakeLists.txt | 2 +- include/mgba-util/platform/posix/threading.h | 13 +++++++++++ include/mgba-util/platform/psp2/threading.h | 16 +++++++++++++ .../mgba-util/platform/windows/threading.h | 13 +++++++++++ include/mgba-util/threading.h | 21 +++++++++++++++++ src/core/thread.c | 23 ++++++------------- 6 files changed, 71 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24e5b96bd..15f27a1fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_C_STANDARD 99) if(NOT MSVC) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) - if(SWITCH) + if(SWITCH OR 3DS) set(CMAKE_C_STANDARD 11) set(CMAKE_C_EXTENSIONS ON) elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "4.3") diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index 64340c852..32d2ea4b1 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -24,6 +24,7 @@ typedef THREAD_ENTRY (*ThreadEntry)(void*); typedef pthread_t Thread; typedef pthread_mutex_t Mutex; typedef pthread_cond_t Condition; +typedef pthread_key_t ThreadLocal; static inline int MutexInit(Mutex* mutex) { return pthread_mutex_init(mutex, 0); @@ -101,6 +102,18 @@ static inline int ThreadSetName(const char* name) { #endif } +static inline void ThreadLocalInitKey(ThreadLocal* key) { + pthread_key_create(key, 0); +} + +static inline void ThreadLocalSetKey(ThreadLocal key, void* value) { + pthread_setspecific(key, value); +} + +static inline void* ThreadLocalGetValue(ThreadLocal key) { + return pthread_getspecific(key); +} + CXX_GUARD_END #endif diff --git a/include/mgba-util/platform/psp2/threading.h b/include/mgba-util/platform/psp2/threading.h index 36364ac95..96e21e851 100644 --- a/include/mgba-util/platform/psp2/threading.h +++ b/include/mgba-util/platform/psp2/threading.h @@ -17,6 +17,7 @@ typedef struct { } Condition; #define THREAD_ENTRY int typedef THREAD_ENTRY (*ThreadEntry)(void*); +typedef int ThreadLocal; static inline int MutexInit(Mutex* mutex) { Mutex id = sceKernelCreateMutex("mutex", 0, 0, 0); @@ -143,4 +144,19 @@ static inline int ThreadSetName(const char* name) { UNUSED(name); return -1; } + +static inline void ThreadLocalInitKey(ThreadLocal* key) { + static int base = 0x90; + *key = __atomic_fetch_add(&base, 1, __ATOMIC_SEQ_CST); +} + +static inline void ThreadLocalSetKey(ThreadLocal key, void* value) { + void** tls = sceKernelGetTLSAddr(key); + *tls = value; +} + +static inline void* ThreadLocalGetValue(ThreadLocal key) { + void** tls = sceKernelGetTLSAddr(key); + return *tls; +} #endif diff --git a/include/mgba-util/platform/windows/threading.h b/include/mgba-util/platform/windows/threading.h index 8ea73d3c1..d7a7b5532 100644 --- a/include/mgba-util/platform/windows/threading.h +++ b/include/mgba-util/platform/windows/threading.h @@ -16,6 +16,7 @@ typedef THREAD_ENTRY ThreadEntry(LPVOID); typedef HANDLE Thread; typedef CRITICAL_SECTION Mutex; typedef CONDITION_VARIABLE Condition; +typedef DWORD ThreadLocal; static inline int MutexInit(Mutex* mutex) { InitializeCriticalSection(mutex); @@ -88,4 +89,16 @@ static inline int ThreadSetName(const char* name) { return -1; } +static inline void ThreadLocalInitKey(ThreadLocal* key) { + *key = TlsAlloc(); +} + +static inline void ThreadLocalSetKey(ThreadLocal key, void* value) { + TlsSetValue(key, value); +} + +static inline void* ThreadLocalGetValue(ThreadLocal key) { + return TlsGetValue(key); +} + #endif diff --git a/include/mgba-util/threading.h b/include/mgba-util/threading.h index 3a2a4102f..779a533a0 100644 --- a/include/mgba-util/threading.h +++ b/include/mgba-util/threading.h @@ -11,6 +11,12 @@ CXX_GUARD_START #ifndef DISABLE_THREADING +#if __STDC_VERSION__ >= 201112L +#define ThreadLocal _Thread_local void* +#define ThreadLocalInitKey(X) +#define ThreadLocalSetKey(K, V) K = V +#define ThreadLocalGetValue(K) K +#endif #ifdef USE_PTHREADS #include #elif defined(_WIN32) @@ -40,6 +46,7 @@ typedef void* Thread; typedef void* Mutex; #endif typedef void* Condition; +typedef int ThreadLocal; static inline int MutexInit(Mutex* mutex) { UNUSED(mutex); @@ -93,6 +100,20 @@ static inline int ConditionWake(Condition* cond) { UNUSED(cond); return 0; } + +static inline void ThreadLocalInitKey(ThreadLocal* key) { + UNUSED(key); +} + +static inline void ThreadLocalSetKey(ThreadLocal key, void* value) { + UNUSED(key); + UNUSED(value); +} + +static inline void* ThreadLocalGetValue(ThreadLocal key) { + UNUSED(key); + return NULL; +} #endif CXX_GUARD_END diff --git a/src/core/thread.c b/src/core/thread.c index 887b7fa3f..218dc92cf 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -16,23 +16,22 @@ #ifndef DISABLE_THREADING static const float _defaultFPSTarget = 60.f; +static ThreadLocal _contextKey; #ifdef USE_PTHREADS -static pthread_key_t _contextKey; static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT; static void _createTLS(void) { - pthread_key_create(&_contextKey, 0); + ThreadLocalInitKey(&_contextKey); } #elif _WIN32 -static DWORD _contextKey; static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT; static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) { UNUSED(once); UNUSED(param); UNUSED(context); - _contextKey = TlsAlloc(); + ThreadLocalInitKey(&_contextKey); return TRUE; } #endif @@ -144,12 +143,11 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { struct mCoreThread* threadContext = context; #ifdef USE_PTHREADS pthread_once(&_contextOnce, _createTLS); - pthread_setspecific(_contextKey, threadContext); #elif _WIN32 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); - TlsSetValue(_contextKey, threadContext); #endif + ThreadLocalSetKey(_contextKey, threadContext); ThreadSetName("CPU Thread"); #if !defined(_WIN32) && defined(USE_PTHREADS) @@ -620,21 +618,14 @@ void mCoreThreadStopWaiting(struct mCoreThread* threadContext) { MutexUnlock(&threadContext->impl->stateMutex); } +struct mCoreThread* mCoreThreadGet(void) { #ifdef USE_PTHREADS -struct mCoreThread* mCoreThreadGet(void) { pthread_once(&_contextOnce, _createTLS); - return pthread_getspecific(_contextKey); -} #elif _WIN32 -struct mCoreThread* mCoreThreadGet(void) { InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0); - return TlsGetValue(_contextKey); -} -#else -struct mCoreThread* mCoreThreadGet(void) { - return NULL; -} #endif + return ThreadLocalGetValue(_contextKey); +} static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { UNUSED(logger); From 6bdae813be19a8ad8fb06bbb42657277dfa69cde Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 13 Jul 2020 00:49:30 -0700 Subject: [PATCH 02/20] Test: Initial threading work in CInema --- src/platform/test/cinema-main.c | 153 +++++++++++++++++++++++--------- 1 file changed, 109 insertions(+), 44 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 1bc317755..228cde08d 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -32,11 +33,13 @@ #include #define MAX_TEST 200 +#define MAX_JOBS 128 static const struct option longOpts[] = { { "base", required_argument, 0, 'b' }, { "diffs", no_argument, 0, 'd' }, { "help", no_argument, 0, 'h' }, + { "jobs", required_argument, 0, 'j' }, { "dry-run", no_argument, 0, 'n' }, { "outdir", required_argument, 0, 'o' }, { "quiet", no_argument, 0, 'q' }, @@ -46,7 +49,7 @@ static const struct option longOpts[] = { { 0, 0, 0, 0 } }; -static const char shortOpts[] = "b:dhno:qrv"; +static const char shortOpts[] = "b:dhj:no:qrv"; enum CInemaStatus { CI_PASS, @@ -91,8 +94,17 @@ static bool diffs = false; static bool rebaseline = false; static int verbosity = 0; +static struct Table configTree; +static Mutex configMutex; + +static int jobs = 1; +static size_t jobIndex = 0; +static Mutex jobMutex; +static Thread jobThreads[MAX_JOBS]; +static int jobStatus; + bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); -void CInemaTestRun(struct CInemaTest*, struct Table* configTree); +void CInemaTestRun(struct CInemaTest*); bool CInemaConfigGetUInt(struct Table* configTree, const char* testName, const char* key, unsigned* value); void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCore* core); @@ -142,6 +154,9 @@ static bool parseCInemaArgs(int argc, char* const* argv) { case 'h': showUsage = true; break; + case 'j': + jobs = atoi(optarg); + break; case 'n': dryRun = true; break; @@ -552,9 +567,11 @@ static void _writeBaseline(struct VDir* dir, const struct CInemaImage* image, si } } -void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) { +void CInemaTestRun(struct CInemaTest* test) { unsigned ignore = 0; - CInemaConfigGetUInt(configTree, test->name, "ignore", &ignore); + MutexLock(&configMutex); + CInemaConfigGetUInt(&configTree, test->name, "ignore", &ignore); + MutexUnlock(&configMutex); if (ignore) { test->status = CI_SKIP; return; @@ -603,11 +620,13 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) { unsigned fail = 0; unsigned video = 0; - CInemaConfigGetUInt(configTree, test->name, "frames", &limit); - CInemaConfigGetUInt(configTree, test->name, "skip", &skip); - CInemaConfigGetUInt(configTree, test->name, "fail", &fail); - CInemaConfigGetUInt(configTree, test->name, "video", &video); - CInemaConfigLoad(configTree, test->name, core); + MutexLock(&configMutex); + CInemaConfigGetUInt(&configTree, test->name, "frames", &limit); + CInemaConfigGetUInt(&configTree, test->name, "skip", &skip); + CInemaConfigGetUInt(&configTree, test->name, "fail", &fail); + CInemaConfigGetUInt(&configTree, test->name, "video", &video); + CInemaConfigLoad(&configTree, test->name, core); + MutexUnlock(&configMutex); struct VFile* save = VFileMemChunk(NULL, 0); core->loadROM(core, rom); @@ -825,6 +844,69 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) { dir->close(dir); } +static bool CInemaTask(struct CInemaTestList* tests, size_t i) { + bool success = true; + struct CInemaTest* test = CInemaTestListGetPointer(tests, i); + if (dryRun) { + CIlog(-1, "%s\n", test->name); + } else { + CIlog(1, "%s: ", test->name); + fflush(stdout); + CInemaTestRun(test); + switch (test->status) { + case CI_PASS: + CIlog(1, "pass\n"); + break; + case CI_FAIL: + success = false; + CIlog(1, "fail\n"); + break; + case CI_XPASS: + CIlog(1, "xpass\n"); + break; + case CI_XFAIL: + CIlog(1, "xfail\n"); + break; + case CI_SKIP: + CIlog(1, "skip\n"); + break; + case CI_ERROR: + success = false; + CIlog(1, "error\n"); + break; + } + if (test->failedFrames) { + CIlog(2, "\tfailed frames: %u/%u (%1.3g%%)\n", test->failedFrames, test->totalFrames, test->failedFrames / (test->totalFrames * 0.01)); + CIlog(2, "\tfailed pixels: %" PRIu64 "/%" PRIu64 " (%1.3g%%)\n", test->failedPixels, test->totalPixels, test->failedPixels / (test->totalPixels * 0.01)); + CIlog(2, "\tdistance: %" PRIu64 "/%" PRIu64 " (%1.3g%%)\n", test->totalDistance, test->totalPixels * 765, test->totalDistance / (test->totalPixels * 7.65)); + } + } + return success; +} + +static THREAD_ENTRY CInemaJob(void* context) { + struct CInemaTestList* tests = context; + bool success = true; + while (true) { + size_t i; + MutexLock(&jobMutex); + i = jobIndex; + ++jobIndex; + MutexUnlock(&jobMutex); + if (i >= CInemaTestListSize(tests)) { + break; + } + if (!CInemaTask(tests, i)) { + success = false; + } + } + MutexLock(&jobMutex); + if (!success) { + jobStatus = 1; + } + MutexUnlock(&jobMutex); +} + void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { UNUSED(log); if (verbosity < 0) { @@ -918,48 +1000,31 @@ int main(int argc, char** argv) { reduceTestList(&tests); } - struct Table configTree; HashTableInit(&configTree, 0, free); + MutexInit(&configMutex); - size_t i; - for (i = 0; i < CInemaTestListSize(&tests); ++i) { - struct CInemaTest* test = CInemaTestListGetPointer(&tests, i); - if (dryRun) { - CIlog(-1, "%s\n", test->name); - } else { - CIlog(1, "%s: ", test->name); - fflush(stdout); - CInemaTestRun(test, &configTree); - switch (test->status) { - case CI_PASS: - CIlog(1, "pass\n"); - break; - case CI_FAIL: + if (jobs == 1) { + size_t i; + for (i = 0; i < CInemaTestListSize(&tests); ++i) { + bool success = CInemaTask(&tests, i); + if (!success) { status = 1; - CIlog(1, "fail\n"); - break; - case CI_XPASS: - CIlog(1, "xpass\n"); - break; - case CI_XFAIL: - CIlog(1, "xfail\n"); - break; - case CI_SKIP: - CIlog(1, "skip\n"); - break; - case CI_ERROR: - status = 1; - CIlog(1, "error\n"); - break; - } - if (test->failedFrames) { - CIlog(2, "\tfailed frames: %u/%u (%1.3g%%)\n", test->failedFrames, test->totalFrames, test->failedFrames / (test->totalFrames * 0.01)); - CIlog(2, "\tfailed pixels: %" PRIu64 "/%" PRIu64 " (%1.3g%%)\n", test->failedPixels, test->totalPixels, test->failedPixels / (test->totalPixels * 0.01)); - CIlog(2, "\tdistance: %" PRIu64 "/%" PRIu64 " (%1.3g%%)\n", test->totalDistance, test->totalPixels * 765, test->totalDistance / (test->totalPixels * 7.65)); } } + } else { + MutexInit(&jobMutex); + int i; + for (i = 0; i < jobs; ++i) { + ThreadCreate(&jobThreads[i], CInemaJob, &tests); + } + for (i = 0; i < jobs; ++i) { + ThreadJoin(&jobThreads[i]); + } + MutexDeinit(&jobMutex); + status = jobStatus; } + MutexDeinit(&configMutex); HashTableEnumerate(&configTree, _unloadConfigTree, NULL); HashTableDeinit(&configTree); CInemaTestListDeinit(&tests); From 0fd6532b3817c375836002716c90a21d1111a197 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 13 Jul 2020 01:19:19 -0700 Subject: [PATCH 03/20] Test: Threaded string builder on *nix --- CMakeLists.txt | 5 +++ src/platform/test/cinema-main.c | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15f27a1fe..43db3dcfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -292,6 +292,7 @@ include(CheckIncludeFiles) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) check_function_exists(strlcpy HAVE_STRLCPY) +check_function_exists(vasprintf HAVE_VASPRINTF) if(NOT DEFINED PSP2) check_function_exists(localtime_r HAVE_LOCALTIME_R) endif() @@ -374,6 +375,10 @@ if(HAVE_STRLCPY) list(APPEND FUNCTION_DEFINES HAVE_STRLCPY) endif() +if(HAVE_VASPRINTF) + list(APPEND FUNCTION_DEFINES HAVE_VASPRINTF) +endif() + if(HAVE_LOCALTIME_R) list(APPEND FUNCTION_DEFINES HAVE_LOCALTIME_R) endif() diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 228cde08d..719720a21 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -85,6 +85,11 @@ DEFINE_VECTOR(CInemaTestList, struct CInemaTest) DECLARE_VECTOR(ImageList, void*) DEFINE_VECTOR(ImageList, void*) +struct StringBuilder { + struct StringList out; + struct StringList err; +}; + static bool showVersion = false; static bool showUsage = false; static char base[PATH_MAX] = {0}; @@ -102,6 +107,7 @@ static size_t jobIndex = 0; static Mutex jobMutex; static Thread jobThreads[MAX_JOBS]; static int jobStatus; +static ThreadLocal stringBuilder; bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); void CInemaTestRun(struct CInemaTest*); @@ -117,7 +123,16 @@ ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) } va_list args; va_start(args, format); +#ifdef HAVE_VASPRINTF + struct StringBuilder* builder = ThreadLocalGetValue(stringBuilder); + if (!builder) { + vprintf(format, args); + } else { + vasprintf(StringListAppend(&builder->out), format, args); + } +#else vprintf(format, args); +#endif va_end(args); } @@ -127,10 +142,39 @@ ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) } va_list args; va_start(args, format); +#ifdef HAVE_VASPRINTF + struct StringBuilder* builder = ThreadLocalGetValue(stringBuilder); + if (!builder) { + vfprintf(stderr, format, args); + } else { + vasprintf(StringListAppend(&builder->err), format, args); + } +#else vfprintf(stderr, format, args); +#endif va_end(args); } +void CIflush(struct StringList* list, FILE* out) { + size_t len = 0; + size_t i; + for (i = 0; i < StringListSize(list); ++i) { + len += strlen(*StringListGetPointer(list, i)); + } + char* string = calloc(len + 1, sizeof(char)); + char* cur = string; + for (i = 0; i < StringListSize(list); ++i) { + char* brick = *StringListGetPointer(list, i); + size_t portion = strlen(brick); + memcpy(cur, brick, portion); + free(brick); + cur += portion; + } + fputs(string, out); + free(string); + StringListClear(list); +} + static bool parseCInemaArgs(int argc, char* const* argv) { int ch; int index = 0; @@ -886,6 +930,11 @@ static bool CInemaTask(struct CInemaTestList* tests, size_t i) { static THREAD_ENTRY CInemaJob(void* context) { struct CInemaTestList* tests = context; + struct StringBuilder builder; + StringListInit(&builder.out, 0); + StringListInit(&builder.err, 0); + ThreadLocalSetKey(stringBuilder, &builder); + bool success = true; while (true) { size_t i; @@ -899,12 +948,20 @@ static THREAD_ENTRY CInemaJob(void* context) { if (!CInemaTask(tests, i)) { success = false; } + CIflush(&builder.out, stdout); + CIflush(&builder.err, stderr); } MutexLock(&jobMutex); if (!success) { jobStatus = 1; } MutexUnlock(&jobMutex); + + CIflush(&builder.out, stdout); + StringListDeinit(&builder.out); + + CIflush(&builder.err, stderr); + StringListDeinit(&builder.err); } void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { @@ -935,6 +992,9 @@ void _log(struct mLogger* log, int category, enum mLogLevel level, const char* f } int main(int argc, char** argv) { + ThreadLocalInitKey(&stringBuilder); + ThreadLocalSetKey(stringBuilder, NULL); + int status = 0; if (!parseCInemaArgs(argc, argv)) { status = 1; From ba2175f5c513288d7c1feba3dad6a964cb7a22b4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Jul 2020 23:50:28 -0700 Subject: [PATCH 04/20] GB: Allow pausing event loop while CPU is blocked --- CHANGES | 1 + include/mgba/internal/gb/serialize.h | 2 ++ src/gb/gb.c | 5 ++++- src/gb/serialize.c | 5 +++++ src/gb/video.c | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d357e2e64..b371d2288 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,7 @@ Other fixes: - SM83: Simplify register pair access on big endian - Wii: Fix pixelated filtering on interframe blending (fixes mgba.io/i/1830) Misc: + - GB: Allow pausing event loop while CPU is blocked - Debugger: Keep track of global cycle count - FFmpeg: Add looping option for GIF/APNG - FFmpeg: Use range coder for FFV1 to reduce output size diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index be5d17e2f..7d3a73be5 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -235,6 +235,8 @@ DECL_BIT(GBSerializedCpuFlags, Condition, 0); DECL_BIT(GBSerializedCpuFlags, IrqPending, 1); DECL_BIT(GBSerializedCpuFlags, DoubleSpeed, 2); DECL_BIT(GBSerializedCpuFlags, EiPending, 3); +DECL_BIT(GBSerializedCpuFlags, Halted, 4); +DECL_BIT(GBSerializedCpuFlags, Blocked, 5); DECL_BITFIELD(GBSerializedTimerFlags, uint8_t); DECL_BIT(GBSerializedTimerFlags, IrqPending, 0); diff --git a/src/gb/gb.c b/src/gb/gb.c index 3f9dda81a..7aea9eaad 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -681,7 +681,7 @@ void GBProcessEvents(struct SM83Core* cpu) { nextEvent = cycles; do { nextEvent = mTimingTick(&gb->timing, nextEvent); - } while (gb->cpuBlocked); + } while (gb->cpuBlocked && !gb->earlyExit); cpu->nextEvent = nextEvent; if (cpu->halted) { @@ -695,6 +695,9 @@ void GBProcessEvents(struct SM83Core* cpu) { } } while (cpu->cycles >= cpu->nextEvent); gb->earlyExit = false; + if (gb->cpuBlocked) { + cpu->cycles = cpu->nextEvent; + } } void GBSetInterrupts(struct SM83Core* cpu, bool enable) { diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 539f87c2a..3ae5cbf10 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -56,6 +56,8 @@ void GBSerialize(struct GB* gb, struct GBSerializedState* state) { flags = GBSerializedCpuFlagsSetIrqPending(flags, gb->cpu->irqPending); flags = GBSerializedCpuFlagsSetDoubleSpeed(flags, gb->doubleSpeed); flags = GBSerializedCpuFlagsSetEiPending(flags, mTimingIsScheduled(&gb->timing, &gb->eiPending)); + flags = GBSerializedCpuFlagsSetHalted(flags, gb->cpu->halted); + flags = GBSerializedCpuFlagsSetBlocked(flags, gb->cpuBlocked); STORE_32LE(flags, 0, &state->cpu.flags); STORE_32LE(gb->eiPending.when - mTimingCurrentTime(&gb->timing), 0, &state->cpu.eiPending); @@ -173,6 +175,9 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { gb->cpu->condition = GBSerializedCpuFlagsGetCondition(flags); gb->cpu->irqPending = GBSerializedCpuFlagsGetIrqPending(flags); gb->doubleSpeed = GBSerializedCpuFlagsGetDoubleSpeed(flags); + gb->cpu->halted = GBSerializedCpuFlagsGetHalted(flags); + gb->cpuBlocked = GBSerializedCpuFlagsGetBlocked(flags); + gb->audio.timingFactor = gb->doubleSpeed + 1; LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); diff --git a/src/gb/video.c b/src/gb/video.c index fa5404c5a..85522ddf8 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -379,6 +379,7 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat GBFrameEnded(video->p); mCoreSyncPostFrame(video->p->sync); ++video->frameCounter; + video->p->earlyExit = true; GBFrameStarted(video->p); } From 287fd86e6adc4ddc7209596495e1843c0bc503d8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Jul 2020 23:51:04 -0700 Subject: [PATCH 05/20] GBA: Allow pausing event loop while CPU is blocked --- CHANGES | 1 + include/mgba/internal/gba/serialize.h | 1 + src/gba/gba.c | 6 ++---- src/gba/serialize.c | 2 ++ src/gba/video.c | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b371d2288..3d666de74 100644 --- a/CHANGES +++ b/CHANGES @@ -56,6 +56,7 @@ Other fixes: - Wii: Fix pixelated filtering on interframe blending (fixes mgba.io/i/1830) Misc: - GB: Allow pausing event loop while CPU is blocked + - GBA: Allow pausing event loop while CPU is blocked - Debugger: Keep track of global cycle count - FFmpeg: Add looping option for GIF/APNG - FFmpeg: Use range coder for FFV1 to reduce output size diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index a5dabae2c..a8ea138e4 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -236,6 +236,7 @@ DECL_BITFIELD(GBASerializedMiscFlags, uint32_t); DECL_BIT(GBASerializedMiscFlags, Halted, 0); DECL_BIT(GBASerializedMiscFlags, POSTFLG, 1); DECL_BIT(GBASerializedMiscFlags, IrqPending, 2); +DECL_BIT(GBASerializedMiscFlags, Blocked, 3); struct GBASerializedState { uint32_t versionMagic; diff --git a/src/gba/gba.c b/src/gba/gba.c index a1512c1f0..14573c556 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -286,7 +286,7 @@ static void GBAProcessEvents(struct ARMCore* cpu) { } #endif nextEvent = mTimingTick(&gba->timing, cycles < nextEvent ? nextEvent : cycles); - } while (gba->cpuBlocked); + } while (gba->cpuBlocked && !gba->earlyExit); cpu->nextEvent = nextEvent; if (cpu->halted) { @@ -305,11 +305,9 @@ static void GBAProcessEvents(struct ARMCore* cpu) { } } gba->earlyExit = false; -#ifndef NDEBUG if (gba->cpuBlocked) { - mLOG(GBA, FATAL, "CPU is blocked!"); + cpu->cycles = cpu->nextEvent; } -#endif } #ifdef USE_DEBUGGERS diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 00a80c6b7..e8f1b215a 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -67,6 +67,7 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { miscFlags = GBASerializedMiscFlagsFillIrqPending(miscFlags); STORE_32(gba->irqEvent.when - mTimingCurrentTime(&gba->timing), 0, &state->nextIrq); } + miscFlags = GBASerializedMiscFlagsSetBlocked(miscFlags, gba->cpuBlocked); STORE_32(miscFlags, 0, &state->miscFlags); GBAMemorySerialize(&gba->memory, state); @@ -185,6 +186,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { LOAD_32(when, 0, &state->nextIrq); mTimingSchedule(&gba->timing, &gba->irqEvent, when); } + gba->cpuBlocked = GBASerializedMiscFlagsGetBlocked(miscFlags); GBAVideoDeserialize(&gba->video, state); GBAMemoryDeserialize(&gba->memory, state); diff --git a/src/gba/video.c b/src/gba/video.c index 751a49295..785cc80bd 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -175,6 +175,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { video->frameskipCounter = video->frameskip; } ++video->frameCounter; + video->p->earlyExit = true; break; case VIDEO_VERTICAL_TOTAL_PIXELS - 1: video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat); From 1084f378c12735988fd555d45ef1451efe6becde Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 30 Jul 2020 19:02:30 -0700 Subject: [PATCH 06/20] CMake: Fix ctest not detecting tests at root --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43db3dcfb..27d9f09bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -965,6 +965,9 @@ endif() if(NOT USE_CMOCKA) set(BUILD_SUITE OFF) endif() +if(BUILD_TEST OR BUILD_SUITE OR BUILD_CINEMA) + enable_testing() +endif() add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/test ${CMAKE_CURRENT_BINARY_DIR}/test) if(BUILD_EXAMPLE) From f7a6533068ee48c6572c571cf13e4482b0bb8e47 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 Jun 2020 00:47:53 -0700 Subject: [PATCH 07/20] Test: Switch from using Python for CInema to C impl --- src/platform/python/CMakeLists.txt | 7 +++---- src/platform/test/CMakeLists.txt | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt index 742ec69ca..7004519ae 100644 --- a/src/platform/python/CMakeLists.txt +++ b/src/platform/python/CMakeLists.txt @@ -54,9 +54,8 @@ add_custom_target(${BINARY_NAME}-py-bdist WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${BINARY_NAME}-py) -file(GLOB BASE_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test_*.py) -file(GLOB SUBTESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py) -foreach(TEST IN LISTS BASE_TESTS SUBTESTS) +file(GLOB TESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py) +foreach(TEST IN LISTS TESTS) if(APPLE) set(PATH DYLD_LIBRARY_PATH) elseif(WIN32) @@ -64,7 +63,7 @@ foreach(TEST IN LISTS BASE_TESTS SUBTESTS) else() set(PATH LD_LIBRARY_PATH) endif() - string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/(tests/.*/)?test_" "" TEST_NAME "${TEST}") + string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/tests/(.*/)?test_" "" TEST_NAME "${TEST}") string(REPLACE ".py" "" TEST_NAME "${TEST_NAME}") add_test(NAME python-${TEST_NAME} COMMAND ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST} diff --git a/src/platform/test/CMakeLists.txt b/src/platform/test/CMakeLists.txt index 8882b8d94..c81b339f8 100644 --- a/src/platform/test/CMakeLists.txt +++ b/src/platform/test/CMakeLists.txt @@ -43,4 +43,5 @@ if(BUILD_CINEMA) 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}") + add_test(cinema ${BINARY_NAME}-cinema -v) endif() From 3b30aef14b332b3f4ffd5cd077db6dab6e44eef1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 Jul 2020 02:54:47 -0700 Subject: [PATCH 08/20] Test: Flush logs if they get too full --- src/platform/test/cinema-main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 719720a21..3f49e1ae1 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -34,6 +34,7 @@ #define MAX_TEST 200 #define MAX_JOBS 128 +#define LOG_THRESHOLD 1000000 static const struct option longOpts[] = { { "base", required_argument, 0, 'b' }, @@ -117,6 +118,8 @@ void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCo static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args); +void CIflush(struct StringList* list, FILE* out); + ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) { if (verbosity < minlevel) { return; @@ -128,6 +131,9 @@ ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) if (!builder) { vprintf(format, args); } else { + if (StringListSize(&builder->out) > LOG_THRESHOLD) { + CIflush(&builder->out, stdout); + } vasprintf(StringListAppend(&builder->out), format, args); } #else @@ -147,6 +153,9 @@ ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) if (!builder) { vfprintf(stderr, format, args); } else { + if (StringListSize(&builder->err) > LOG_THRESHOLD) { + CIflush(&builder->err, stderr); + } vasprintf(StringListAppend(&builder->err), format, args); } #else From ee50cc7656dfdffebbb168fe235a5227d435255c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 Jul 2020 02:55:11 -0700 Subject: [PATCH 09/20] Test: End test early if a fatal error occurs --- src/platform/test/cinema-main.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 3f49e1ae1..a59f5209e 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -109,6 +109,7 @@ static Mutex jobMutex; static Thread jobThreads[MAX_JOBS]; static int jobStatus; static ThreadLocal stringBuilder; +static ThreadLocal currentTest; bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); void CInemaTestRun(struct CInemaTest*); @@ -771,6 +772,9 @@ void CInemaTestRun(struct CInemaTest* test) { } else { baselineFound = _loadBaselinePNG(dir, &expected, frame, &test->status); } + if (test->status == CI_ERROR) { + break; + } if (baselineFound) { uint8_t* testPixels = image.data; uint8_t* expectPixels = expected.data; @@ -864,8 +868,6 @@ void CInemaTestRun(struct CInemaTest* test) { free(diff); } free(expected.data); - } else if (test->status == CI_ERROR) { - break; } else if (rebaseline && !video) { _writeBaseline(dir, &image, frame); } else if (!rebaseline) { @@ -905,7 +907,10 @@ static bool CInemaTask(struct CInemaTestList* tests, size_t i) { } else { CIlog(1, "%s: ", test->name); fflush(stdout); + ThreadLocalSetKey(currentTest, test); CInemaTestRun(test); + ThreadLocalSetKey(currentTest, NULL); + switch (test->status) { case CI_PASS: CIlog(1, "pass\n"); @@ -975,6 +980,10 @@ static THREAD_ENTRY CInemaJob(void* context) { void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { UNUSED(log); + if (level == mLOG_FATAL) { + struct CInemaTest* test = ThreadLocalGetValue(currentTest); + test->status = CI_ERROR; + } if (verbosity < 0) { return; } @@ -1071,6 +1080,8 @@ int main(int argc, char** argv) { HashTableInit(&configTree, 0, free); MutexInit(&configMutex); + ThreadLocalInitKey(¤tTest); + ThreadLocalSetKey(currentTest, NULL); if (jobs == 1) { size_t i; From 9f370be824bae63978f3c90e2598fc888f662162 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 Jul 2020 02:55:27 -0700 Subject: [PATCH 10/20] Test: Clamp job count --- src/platform/test/cinema-main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index a59f5209e..1f60a3161 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -210,6 +210,12 @@ static bool parseCInemaArgs(int argc, char* const* argv) { break; case 'j': jobs = atoi(optarg); + if (jobs > MAX_JOBS) { + jobs = MAX_JOBS; + } + if (jobs < 1) { + jobs = 1; + } break; case 'n': dryRun = true; From f1d90e5f72c07ada7380685ec4bb5ceabe92fdb7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 Jul 2020 15:25:48 -0700 Subject: [PATCH 11/20] Test: Add option to only rebaseline missing tests --- src/platform/test/cinema-main.c | 85 ++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 1f60a3161..25b202711 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -45,12 +45,13 @@ static const struct option longOpts[] = { { "outdir", required_argument, 0, 'o' }, { "quiet", no_argument, 0, 'q' }, { "rebaseline", no_argument, 0, 'r' }, + { "rebaseline-missing", no_argument, 0, 'R' }, { "verbose", no_argument, 0, 'v' }, { "version", no_argument, 0, '\0' }, { 0, 0, 0, 0 } }; -static const char shortOpts[] = "b:dhj:no:qrv"; +static const char shortOpts[] = "b:dhj:no:qRrv"; enum CInemaStatus { CI_PASS, @@ -61,6 +62,12 @@ enum CInemaStatus { CI_SKIP }; +enum CInemaRebaseline { + CI_R_NONE = 0, + CI_R_FAILING, + CI_R_MISSING, +}; + struct CInemaTest { char directory[MAX_TEST]; char filename[MAX_TEST]; @@ -97,7 +104,7 @@ static char base[PATH_MAX] = {0}; static char outdir[PATH_MAX] = {'.'}; static bool dryRun = false; static bool diffs = false; -static bool rebaseline = false; +static enum CInemaRebaseline rebaseline = CI_R_NONE; static int verbosity = 0; static struct Table configTree; @@ -228,7 +235,10 @@ static bool parseCInemaArgs(int argc, char* const* argv) { --verbosity; break; case 'r': - rebaseline = true; + rebaseline = CI_R_FAILING; + break; + case 'R': + rebaseline = CI_R_MISSING; break; case 'v': ++verbosity; @@ -250,6 +260,7 @@ static void usageCInema(const char* arg0) { puts(" -o, --output [DIR] Path to output applicable results"); puts(" -q, --quiet Decrease log verbosity (can be repeated)"); puts(" -r, --rebaseline Rewrite the baseline for failing tests"); + puts(" -R, --rebaseline-missing Write missing baselines tests only"); puts(" -v, --verbose Increase log verbosity (can be repeated)"); puts(" --version Print version and exit"); } @@ -710,30 +721,46 @@ void CInemaTestRun(struct CInemaTest* test) { struct FFmpegDecoder decoder; struct FFmpegEncoder encoder; struct CInemaStream stream = {0}; + + char baselineName[PATH_MAX]; + snprintf(baselineName, sizeof(baselineName), "%s" PATH_SEP "baseline.mkv", test->directory); + bool exists = access(baselineName, 0) == 0; + + char tmpBaselineName[PATH_MAX]; + snprintf(tmpBaselineName, sizeof(tmpBaselineName), "%s" PATH_SEP ".baseline.mkv", test->directory); + if (video) { - char fname[PATH_MAX]; - snprintf(fname, sizeof(fname), "%s" PATH_SEP "baseline.mkv", test->directory); - if (rebaseline) { - FFmpegEncoderInit(&encoder); + FFmpegEncoderInit(&encoder); + FFmpegDecoderInit(&decoder); + + if (rebaseline == CI_R_FAILING || (rebaseline == CI_R_MISSING && !exists)) { FFmpegEncoderSetAudio(&encoder, NULL, 0); FFmpegEncoderSetVideo(&encoder, "png", 0, 0); FFmpegEncoderSetContainer(&encoder, "mkv"); FFmpegEncoderSetDimensions(&encoder, image.width, image.height); - if (!FFmpegEncoderOpen(&encoder, fname)) { + + const char* usedFname = baselineName; + if (exists) { + usedFname = tmpBaselineName; + } + if (!FFmpegEncoderOpen(&encoder, usedFname)) { CIerr(1, "Failed to save baseline video\n"); } else { core->setAVStream(core, &encoder.d); } - } else { - FFmpegDecoderInit(&decoder); + } + + if (exists) { stream.d.postVideoFrame = _cinemaVideoFrame; stream.d.videoDimensionsChanged = _cinemaDimensionsChanged; stream.status = &test->status; decoder.out = &stream.d; - if (!FFmpegDecoderOpen(&decoder, fname)) { + if (!FFmpegDecoderOpen(&decoder, baselineName)) { CIerr(1, "Failed to load baseline video\n"); } + } else if (!rebaseline) { + test->status = CI_FAIL; } } #else @@ -763,7 +790,7 @@ void CInemaTestRun(struct CInemaTest* test) { if (video) { baselineFound = false; #ifdef USE_FFMPEG - if (!rebaseline && FFmpegDecoderIsOpen(&decoder)) { + if (FFmpegDecoderIsOpen(&decoder)) { stream.image = &expected; while (!expected.data) { if (!FFmpegDecoderRead(&decoder)) { @@ -845,7 +872,7 @@ void CInemaTestRun(struct CInemaTest* test) { ++test->failedFrames; } test->totalPixels += image.height * image.width; - if (rebaseline && failed) { + if (rebaseline == CI_R_FAILING && !video && failed) { _writeBaseline(dir, &image, frame); } if (diff) { @@ -881,6 +908,28 @@ void CInemaTestRun(struct CInemaTest* test) { } } +#ifdef USE_FFMPEG + if (video) { + if (FFmpegEncoderIsOpen(&encoder)) { + FFmpegEncoderClose(&encoder); + if (exists) { + if (test->status == CI_FAIL) { +#ifdef _WIN32 + MoveFileEx(tmpBaselineName, baselineName, MOVEFILE_REPLACE_EXISTING); +#else + rename(tmpBaselineName, baselineName); +#endif + } else { + remove(tmpBaselineName); + } + } + } + if (FFmpegDecoderIsOpen(&decoder)) { + FFmpegDecoderClose(&decoder); + } + } +#endif + if (fail) { if (test->status == CI_FAIL) { test->status = CI_XFAIL; @@ -889,16 +938,6 @@ void CInemaTestRun(struct CInemaTest* test) { } } -#ifdef USE_FFMPEG - if (video) { - if (rebaseline) { - FFmpegEncoderClose(&encoder); - } else { - FFmpegDecoderClose(&decoder); - } - } -#endif - free(image.data); mCoreConfigDeinit(&core->config); core->deinit(core); From 18ea9502cd40b5b5ef288dcc92cbbef38aafcfea Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 21 Jul 2020 23:49:26 -0700 Subject: [PATCH 12/20] Test: Allow logging to mark messages as repeating --- src/platform/test/cinema-main.c | 164 ++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 62 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 25b202711..0e1af2349 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -94,8 +94,15 @@ DECLARE_VECTOR(ImageList, void*) DEFINE_VECTOR(ImageList, void*) struct StringBuilder { - struct StringList out; - struct StringList err; + struct StringList lines; + struct StringList partial; + unsigned repeat; + +}; + +struct CInemaLogStream { + struct StringBuilder err; + struct StringBuilder out; }; static bool showVersion = false; @@ -115,7 +122,7 @@ static size_t jobIndex = 0; static Mutex jobMutex; static Thread jobThreads[MAX_JOBS]; static int jobStatus; -static ThreadLocal stringBuilder; +static ThreadLocal logStream; static ThreadLocal currentTest; bool CInemaTestInit(struct CInemaTest*, const char* directory, const char* filename); @@ -126,53 +133,9 @@ void CInemaConfigLoad(struct Table* configTree, const char* testName, struct mCo static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args); -void CIflush(struct StringList* list, FILE* out); +void CIflush(struct StringBuilder* list, FILE* file); -ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) { - if (verbosity < minlevel) { - return; - } - va_list args; - va_start(args, format); -#ifdef HAVE_VASPRINTF - struct StringBuilder* builder = ThreadLocalGetValue(stringBuilder); - if (!builder) { - vprintf(format, args); - } else { - if (StringListSize(&builder->out) > LOG_THRESHOLD) { - CIflush(&builder->out, stdout); - } - vasprintf(StringListAppend(&builder->out), format, args); - } -#else - vprintf(format, args); -#endif - va_end(args); -} - -ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) { - if (verbosity < minlevel) { - return; - } - va_list args; - va_start(args, format); -#ifdef HAVE_VASPRINTF - struct StringBuilder* builder = ThreadLocalGetValue(stringBuilder); - if (!builder) { - vfprintf(stderr, format, args); - } else { - if (StringListSize(&builder->err) > LOG_THRESHOLD) { - CIflush(&builder->err, stderr); - } - vasprintf(StringListAppend(&builder->err), format, args); - } -#else - vfprintf(stderr, format, args); -#endif - va_end(args); -} - -void CIflush(struct StringList* list, FILE* out) { +static char* _compileStringList(struct StringList* list) { size_t len = 0; size_t i; for (i = 0; i < StringListSize(list); ++i) { @@ -187,9 +150,80 @@ void CIflush(struct StringList* list, FILE* out) { free(brick); cur += portion; } + StringListClear(list); + return string; +} + +static void _logToStream(FILE* file, const char* format, va_list args) { +#ifdef HAVE_VASPRINTF + struct CInemaLogStream* stream = ThreadLocalGetValue(logStream); + if (!stream) { + vfprintf(file, format, args); + } else { + struct StringBuilder* builder = &stream->out; + if (file == stderr) { + builder = &stream->err; + } + if (StringListSize(&builder->lines) > LOG_THRESHOLD) { + CIflush(builder, file); + } + char** line = StringListAppend(&builder->partial); + vasprintf(line, format, args); + size_t len = strlen(*line); + if (len && (*line)[len - 1] == '\n') { + char* string = _compileStringList(&builder->partial); + size_t linecount = StringListSize(&builder->lines); + if (linecount && strcmp(string, *StringListGetPointer(&builder->lines, linecount - 1)) == 0) { + ++builder->repeat; + free(string); + } else { + if (builder->repeat > 1) { + asprintf(StringListAppend(&builder->lines), "The previous message was repeated %u times.\n", builder->repeat); + } + *StringListAppend(&builder->lines) = string; + builder->repeat = 1; + } + } + } +#else + vfprintf(file, format, args); +#endif +} + +ATTRIBUTE_FORMAT(printf, 2, 3) void CIlog(int minlevel, const char* format, ...) { + if (verbosity < minlevel) { + return; + } + va_list args; + va_start(args, format); + _logToStream(stdout, format, args); + va_end(args); +} + +ATTRIBUTE_FORMAT(printf, 2, 3) void CIerr(int minlevel, const char* format, ...) { + if (verbosity < minlevel) { + return; + } + va_list args; + va_start(args, format); + _logToStream(stderr, format, args); + va_end(args); +} + +void CIflush(struct StringBuilder* builder, FILE* out) { + if (StringListSize(&builder->partial)) { + *StringListAppend(&builder->lines) = _compileStringList(&builder->partial); + } +#ifdef HAVE_VASPRINTF + if (builder->repeat > 1) { + asprintf(StringListAppend(&builder->lines), "The previous message was repeated %u times.\n", builder->repeat); + } +#endif + + char* string = _compileStringList(&builder->lines); + builder->repeat = 0; fputs(string, out); free(string); - StringListClear(list); } static bool parseCInemaArgs(int argc, char* const* argv) { @@ -989,10 +1023,14 @@ static bool CInemaTask(struct CInemaTestList* tests, size_t i) { static THREAD_ENTRY CInemaJob(void* context) { struct CInemaTestList* tests = context; - struct StringBuilder builder; - StringListInit(&builder.out, 0); - StringListInit(&builder.err, 0); - ThreadLocalSetKey(stringBuilder, &builder); + struct CInemaLogStream stream; + StringListInit(&stream.out.lines, 0); + StringListInit(&stream.out.partial, 0); + stream.out.repeat = 0; + StringListInit(&stream.err.lines, 0); + StringListInit(&stream.err.partial, 0); + stream.err.repeat = 0; + ThreadLocalSetKey(logStream, &stream); bool success = true; while (true) { @@ -1007,8 +1045,8 @@ static THREAD_ENTRY CInemaJob(void* context) { if (!CInemaTask(tests, i)) { success = false; } - CIflush(&builder.out, stdout); - CIflush(&builder.err, stderr); + CIflush(&stream.out, stdout); + CIflush(&stream.err, stderr); } MutexLock(&jobMutex); if (!success) { @@ -1016,11 +1054,13 @@ static THREAD_ENTRY CInemaJob(void* context) { } MutexUnlock(&jobMutex); - CIflush(&builder.out, stdout); - StringListDeinit(&builder.out); + CIflush(&stream.out, stdout); + StringListDeinit(&stream.out.lines); + StringListDeinit(&stream.out.partial); - CIflush(&builder.err, stderr); - StringListDeinit(&builder.err); + CIflush(&stream.err, stderr); + StringListDeinit(&stream.err.lines); + StringListDeinit(&stream.err.partial); } void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { @@ -1055,8 +1095,8 @@ void _log(struct mLogger* log, int category, enum mLogLevel level, const char* f } int main(int argc, char** argv) { - ThreadLocalInitKey(&stringBuilder); - ThreadLocalSetKey(stringBuilder, NULL); + ThreadLocalInitKey(&logStream); + ThreadLocalSetKey(logStream, NULL); int status = 0; if (!parseCInemaArgs(argc, argv)) { From 57530a32b471efbfd1dbbc89c22a93119e12f55a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Jul 2020 16:55:05 -0700 Subject: [PATCH 13/20] Test: Add rudimentary input playback --- src/platform/test/cinema-main.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 0e1af2349..55c664d84 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -427,7 +427,7 @@ static void _unloadConfigTree(const char* key, void* value, void* user) { mCoreConfigDeinit(value); } -static const char* _lookupValue(struct Table* configTree, const char* testName, const char* key) { +static const char* CInemaConfigGet(struct Table* configTree, const char* testName, const char* key) { _loadConfigTree(configTree, testName); char testKey[MAX_TEST]; @@ -456,7 +456,7 @@ static const char* _lookupValue(struct Table* configTree, const char* testName, } bool CInemaConfigGetUInt(struct Table* configTree, const char* testName, const char* key, unsigned* out) { - const char* charValue = _lookupValue(configTree, testName, key); + const char* charValue = CInemaConfigGet(configTree, testName, key); if (!charValue) { return false; } @@ -672,6 +672,29 @@ static void _writeBaseline(struct VDir* dir, const struct CInemaImage* image, si } } +static bool _updateInput(struct mCore* core, size_t frame, const char** input) { + if (!*input || !*input[0]) { + return false; + } + char* end; + uint32_t start = strtoul(*input, &end, 10); + if (end[0] != ':') { + return false; + } + if (start != frame) { + return true; + } + ++end; + *input = end; + uint32_t keys = strtoul(*input, &end, 16); + if (end[0] == ',') { + ++end; + } + *input = end; + core->setKeys(core, keys); + return true; +} + void CInemaTestRun(struct CInemaTest* test) { unsigned ignore = 0; MutexLock(&configMutex); @@ -724,12 +747,14 @@ void CInemaTestRun(struct CInemaTest* test) { unsigned skip = 0; unsigned fail = 0; unsigned video = 0; + const char* input = NULL; MutexLock(&configMutex); CInemaConfigGetUInt(&configTree, test->name, "frames", &limit); CInemaConfigGetUInt(&configTree, test->name, "skip", &skip); CInemaConfigGetUInt(&configTree, test->name, "fail", &fail); CInemaConfigGetUInt(&configTree, test->name, "video", &video); + input = CInemaConfigGet(&configTree, test->name, "input"); CInemaConfigLoad(&configTree, test->name, core); MutexUnlock(&configMutex); @@ -805,6 +830,7 @@ void CInemaTestRun(struct CInemaTest* test) { #endif for (frame = 0; limit; ++frame, --limit) { + _updateInput(core, frame, &input); core->runFrame(core); ++test->totalFrames; unsigned frameCounter = core->frameCounter(core); From dd31a888620ad6e8481a4e688f38dc078e75fc37 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 23 Jul 2020 01:35:04 -0700 Subject: [PATCH 14/20] Test: Switch baselines from png/mkv to zmbv/avi --- src/platform/test/cinema-main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 55c664d84..ffd9c4839 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -782,11 +782,11 @@ void CInemaTestRun(struct CInemaTest* test) { struct CInemaStream stream = {0}; char baselineName[PATH_MAX]; - snprintf(baselineName, sizeof(baselineName), "%s" PATH_SEP "baseline.mkv", test->directory); + snprintf(baselineName, sizeof(baselineName), "%s" PATH_SEP "baseline.avi", test->directory); bool exists = access(baselineName, 0) == 0; char tmpBaselineName[PATH_MAX]; - snprintf(tmpBaselineName, sizeof(tmpBaselineName), "%s" PATH_SEP ".baseline.mkv", test->directory); + snprintf(tmpBaselineName, sizeof(tmpBaselineName), "%s" PATH_SEP ".baseline.avi", test->directory); if (video) { FFmpegEncoderInit(&encoder); @@ -794,8 +794,8 @@ void CInemaTestRun(struct CInemaTest* test) { if (rebaseline == CI_R_FAILING || (rebaseline == CI_R_MISSING && !exists)) { FFmpegEncoderSetAudio(&encoder, NULL, 0); - FFmpegEncoderSetVideo(&encoder, "png", 0, 0); - FFmpegEncoderSetContainer(&encoder, "mkv"); + FFmpegEncoderSetVideo(&encoder, "zmbv", 0, 0); + FFmpegEncoderSetContainer(&encoder, "avi"); FFmpegEncoderSetDimensions(&encoder, image.width, image.height); const char* usedFname = baselineName; From 874cd47baffe0c4c5b30d3de3736c348bbf85de0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 25 Jul 2020 00:19:43 -0700 Subject: [PATCH 15/20] Test: Fix out-of-date CInema usage info --- src/platform/test/cinema-main.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index ffd9c4839..9a4b847be 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -286,12 +286,13 @@ static bool parseCInemaArgs(int argc, char* const* argv) { } static void usageCInema(const char* arg0) { - printf("usage: %s [-dhnqv] [-b BASE] [-o DIR] [--version] [test...]\n", arg0); - puts(" -b, --base [BASE] Path to the CInema base directory"); + printf("usage: %s [-dhnqrRv] [-j JOBS] [-b BASE] [-o DIR] [--version] [test...]\n", arg0); + puts(" -b, --base BASE Path to the CInema base directory"); puts(" -d, --diffs Output image diffs from failures"); puts(" -h, --help Print this usage and exit"); + puts(" -j, --jobs JOBS Run a number of jobs in parallel"); puts(" -n, --dry-run List all collected tests instead of running them"); - puts(" -o, --output [DIR] Path to output applicable results"); + puts(" -o, --output DIR Path to output applicable results"); puts(" -q, --quiet Decrease log verbosity (can be repeated)"); puts(" -r, --rebaseline Rewrite the baseline for failing tests"); puts(" -R, --rebaseline-missing Write missing baselines tests only"); From ba932c45477a3ccb1866b5fcab91c8eb6dc956c1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Jul 2020 23:20:15 -0700 Subject: [PATCH 16/20] Test: Refactor out image comparison --- src/platform/test/cinema-main.c | 123 +++++++++++++++++--------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 9a4b847be..31e07b236 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -696,6 +696,68 @@ static bool _updateInput(struct mCore* core, size_t frame, const char** input) { return true; } +static bool _compareImages(struct CInemaTest* restrict test, const struct CInemaImage* restrict image, const struct CInemaImage* restrict expected, int* restrict max, uint8_t** restrict outdiff) { + const uint8_t* testPixels = image->data; + const uint8_t* expectPixels = expected->data; + uint8_t* diff = NULL; + size_t x; + size_t y; + bool failed = false; + for (y = 0; y < image->height; ++y) { + for (x = 0; x < image->width; ++x) { + size_t pix = expected->stride * y + x; + size_t tpix = image->stride * y + x; + int testR = testPixels[tpix * 4 + 0]; + int testG = testPixels[tpix * 4 + 1]; + int testB = testPixels[tpix * 4 + 2]; + int expectR = expectPixels[pix * 4 + 0]; + int expectG = expectPixels[pix * 4 + 1]; + int expectB = expectPixels[pix * 4 + 2]; + int r = expectR - testR; + int g = expectG - testG; + int b = expectB - testB; + if (r | g | b) { + failed = true; + if (outdiff && !diff) { + diff = calloc(expected->stride * expected->height, BYTES_PER_PIXEL); + *outdiff = diff; + } + test->status = CI_FAIL; + if (r < 0) { + r = -r; + } + if (g < 0) { + g = -g; + } + if (b < 0) { + b = -b; + } + + if (diff) { + if (r > *max) { + *max = r; + } + if (g > *max) { + *max = g; + } + if (b > *max) { + *max = b; + } + diff[pix * 4 + 0] = r; + diff[pix * 4 + 1] = g; + diff[pix * 4 + 2] = b; + } + + if (test) { + test->totalDistance += r + g + b; + ++test->failedPixels; + } + } + } + } + return !failed; +} + void CInemaTestRun(struct CInemaTest* test) { unsigned ignore = 0; MutexLock(&configMutex); @@ -870,65 +932,8 @@ void CInemaTestRun(struct CInemaTest* test) { break; } if (baselineFound) { - uint8_t* testPixels = image.data; - uint8_t* expectPixels = expected.data; - size_t x; - size_t y; int max = 0; - bool failed = false; - for (y = 0; y < image.height; ++y) { - for (x = 0; x < image.width; ++x) { - size_t pix = expected.stride * y + x; - size_t tpix = image.stride * y + x; - int testR = testPixels[tpix * 4 + 0]; - int testG = testPixels[tpix * 4 + 1]; - int testB = testPixels[tpix * 4 + 2]; - int expectR = expectPixels[pix * 4 + 0]; - int expectG = expectPixels[pix * 4 + 1]; - int expectB = expectPixels[pix * 4 + 2]; - int r = expectR - testR; - int g = expectG - testG; - int b = expectB - testB; - if (r | g | b) { - failed = true; - if (diffs && !diff) { - diff = calloc(expected.stride * expected.height, BYTES_PER_PIXEL); - } - CIlog(3, "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; - if (r < 0) { - r = -r; - } - if (g < 0) { - g = -g; - } - if (b < 0) { - b = -b; - } - - if (diff) { - if (r > max) { - max = r; - } - if (g > max) { - max = g; - } - if (b > max) { - max = b; - } - diff[pix * 4 + 0] = r; - diff[pix * 4 + 1] = g; - diff[pix * 4 + 2] = b; - } - - test->totalDistance += r + g + b; - ++test->failedPixels; - } - } - } + bool failed = _compareImages(test, &image, &expected, &max, diffs ? &diff : NULL); if (failed) { ++test->failedFrames; } @@ -949,6 +954,8 @@ void CInemaTestRun(struct CInemaTest* test) { _writeDiff(test->name, &expected, frame, "expected"); _writeDiff(test->name, &outdiff, frame, "diff"); + size_t x; + size_t y; for (y = 0; y < outdiff.height; ++y) { for (x = 0; x < outdiff.width; ++x) { size_t pix = outdiff.stride * y + x; From 2a1dc92399627865abcb78f5954d6a346a2479d5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Jul 2020 23:45:18 -0700 Subject: [PATCH 17/20] Test: Add way to detect if an xfail test output changes --- src/platform/test/cinema-main.c | 92 +++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 31e07b236..3ec9cad8a 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -521,9 +521,9 @@ bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char* return true; } -static bool _loadBaselinePNG(struct VDir* dir, struct CInemaImage* image, size_t frame, enum CInemaStatus* status) { +static bool _loadBaselinePNG(struct VDir* dir, const char* type, struct CInemaImage* image, size_t frame, enum CInemaStatus* status) { char baselineName[32]; - snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame); + snprintf(baselineName, sizeof(baselineName), "%s_%04" PRIz "u.png", type, frame); struct VFile* baselineVF = dir->openFile(dir, baselineName, O_RDONLY); if (!baselineVF) { if (*status == CI_PASS) { @@ -758,6 +758,37 @@ static bool _compareImages(struct CInemaTest* restrict test, const struct CInema return !failed; } +void _writeDiffSet(struct CInemaImage* expected, const char* name, uint8_t* diff, int frame, int max, bool xfail) { + struct CInemaImage outdiff = { + .data = diff, + .width = expected->width, + .height = expected->height, + .stride = expected->stride, + }; + + if (xfail) { + _writeDiff(name, expected, frame, "xexpected"); + _writeDiff(name, &outdiff, frame, "xdiff"); + } else { + _writeDiff(name, expected, frame, "expected"); + _writeDiff(name, &outdiff, frame, "diff"); + } + + size_t x; + size_t y; + for (y = 0; y < outdiff.height; ++y) { + for (x = 0; x < outdiff.width; ++x) { + size_t pix = outdiff.stride * y + x; + diff[pix * 4 + 0] = diff[pix * 4 + 0] * 255 / max; + diff[pix * 4 + 1] = diff[pix * 4 + 1] * 255 / max; + diff[pix * 4 + 2] = diff[pix * 4 + 2] * 255 / max; + } + } + if (xfail) { + _writeDiff(name, &outdiff, frame, "xnormalized"); + } +} + void CInemaTestRun(struct CInemaTest* test) { unsigned ignore = 0; MutexLock(&configMutex); @@ -892,6 +923,7 @@ void CInemaTestRun(struct CInemaTest* test) { } #endif + bool xdiff = false; for (frame = 0; limit; ++frame, --limit) { _updateInput(core, frame, &input); core->runFrame(core); @@ -900,6 +932,9 @@ void CInemaTestRun(struct CInemaTest* test) { if (frameCounter <= minFrame) { break; } + if (test->status == CI_ERROR) { + break; + } CIlog(3, "Test frame: %u\n", frameCounter); core->desiredVideoDimensions(core, &image.width, &image.height); uint8_t* diff = NULL; @@ -926,14 +961,15 @@ void CInemaTestRun(struct CInemaTest* test) { } #endif } else { - baselineFound = _loadBaselinePNG(dir, &expected, frame, &test->status); + baselineFound = _loadBaselinePNG(dir, "baseline", &expected, frame, &test->status); } if (test->status == CI_ERROR) { break; } + bool failed = false; if (baselineFound) { int max = 0; - bool failed = _compareImages(test, &image, &expected, &max, diffs ? &diff : NULL); + failed = !_compareImages(test, &image, &expected, &max, diffs ? &diff : NULL); if (failed) { ++test->failedFrames; } @@ -943,28 +979,8 @@ void CInemaTestRun(struct CInemaTest* test) { } if (diff) { if (failed) { - struct CInemaImage outdiff = { - .data = diff, - .width = expected.width, - .height = expected.height, - .stride = expected.stride, - }; - _writeDiff(test->name, &image, frame, "result"); - _writeDiff(test->name, &expected, frame, "expected"); - _writeDiff(test->name, &outdiff, frame, "diff"); - - size_t x; - size_t y; - for (y = 0; y < outdiff.height; ++y) { - for (x = 0; x < outdiff.width; ++x) { - size_t pix = outdiff.stride * y + x; - diff[pix * 4 + 0] = diff[pix * 4 + 0] * 255 / max; - diff[pix * 4 + 1] = diff[pix * 4 + 1] * 255 / max; - diff[pix * 4 + 2] = diff[pix * 4 + 2] * 255 / max; - } - } - _writeDiff(test->name, &outdiff, frame, "normalized"); + _writeDiffSet(&expected, test->name, diff, frame, max, false); } free(diff); } @@ -974,6 +990,30 @@ void CInemaTestRun(struct CInemaTest* test) { } else if (!rebaseline) { test->status = CI_FAIL; } + + if (fail && failed) { + if (video) { + // TODO + baselineFound = false; + } else { + baselineFound = _loadBaselinePNG(dir, "xbaseline", &expected, frame, &test->status); + } + + if (baselineFound) { + int max = 0; + failed = !_compareImages(test, &image, &expected, &max, diffs ? &diff : NULL); + if (diff) { + if (failed) { + _writeDiffSet(&expected, test->name, diff, frame, max, true); + } + free(diff); + } + if (failed) { + xdiff = true; + } + free(expected.data); + } + } } #ifdef USE_FFMPEG @@ -999,7 +1039,7 @@ void CInemaTestRun(struct CInemaTest* test) { #endif if (fail) { - if (test->status == CI_FAIL) { + if (test->status == CI_FAIL && !xdiff) { test->status = CI_XFAIL; } else if (test->status == CI_PASS) { test->status = CI_XPASS; From b6395e56832542f46dc1a915fb00dd2fbc5e1336 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 25 Jul 2020 00:45:46 -0700 Subject: [PATCH 18/20] Test: Add xbaseline writing --- .../boot_hwio-dmg0/xbaseline_0000.png | Bin 0 -> 879 bytes .../boot_regs-dmg0/xbaseline_0000.png | Bin 0 -> 1554 bytes .../oam_dma_start/xbaseline_0000.png | Bin 0 -> 1352 bytes .../acceptance/push_timing/xbaseline_0000.png | Bin 0 -> 1344 bytes .../timer/rapid_toggle/xbaseline_0000.png | Bin 0 -> 411 bytes .../sprite_priority/xbaseline_0000.png | Bin 0 -> 711 bytes .../bits/unused_hwio-C/xbaseline_0000.png | Bin 0 -> 1095 bytes .../misc/boot_hwio-C/xbaseline_0000.png | Bin 0 -> 846 bytes src/platform/test/cinema-main.c | 23 ++++++++++++++---- 9 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/acceptance/oam_dma_start/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/acceptance/push_timing/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/xbaseline_0000.png create mode 100644 cinema/gb/mooneye-gb/misc/boot_hwio-C/xbaseline_0000.png diff --git a/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/xbaseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..691abb25db340e25d66c0ae9b98bd9eb64902e63 GIT binary patch literal 879 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|T>}r;B4q#hkadJj)gvFfasu zc>FJ1jr*4-mlWegPS?LvA3HXjR%0_<|Jv&O=UwZ9Rqhy{FRrQov*km;j?G4X6?J7T zC4w&>y!`R<(I!6K?Uo^_bM8)?=H_>Oxye5PZ>y3$br0XJo34F#o&N4GCpCC2PVGFn z{p`)BntS(@)c-kqvQ7S-aF|Ap!+Epz=l=HQ%S_IFOaHTK(p~P$<%${Wo-BM?w(hD~ z)GoW1pD+J@?0b9dCE59p&kE(Ge!nW3C|a(s*=zhn_|~1v%iMI$e;>5{#d~ho`gbDx z4$fZud-bL1%De6PU%jvRb@5%y(l=h;^tWr)H2e1p?>D=9_~?Howy0P9$F)uV{<%N- zt}B1x>j%%L9Fm!O_wC(Z&5w`GWj??5_odl3`Msqlmj4y8o!R)1hbKM3g88td#JK|n zY;DoYUhPRM6VspT&*OihPuTv7vHor2^Hm>n{=Hl%_x%3O7jN8-eRf(JIc46>sh9rb zJ(bBRp0@1&n#3}_^(KGMe71eK|L%!%f0urrKJWgXGmkT-zRv&qwQ`C3>e^=QwlmH1 zBJj9bIyy5d3QSU?^p3&{Lgr!#czJ` z@s<9DuXeq7{%hZ=PwQh(Se(kfoHwoD&*XXOb-TjnPSS~7_Nwre{2rG|jwf!Zdp|n= n)ska}dEMAx4ti*eG@)8XXKwAkN7A#3KpDx?)z4*}Q$iB}y`QdA literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/xbaseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..7051512ec8b884c7656bd67b015a1e3d6d139178 GIT binary patch literal 1554 zcmZ`(X)qfI6iz9zb?mm*vBlD^qgb~zQL1HA5*6xF!K_y!U3_%$xVk`@Z+3pK-92-gj^x005AN z!=TQ)wRAVKB}8^JV0?WX01%smL#y|D7R)u=(}bC!9|*W)2vocUhB8V z#NNtLJ9LCj`Iyf96y79?(*Isjd7qSwn0T5<>MvKR_dA)1&USRt{E)ssmlP^ROkFYC zK^@np@q-PLOzx4{a>N^%Ec9AOVd&e~TGNjKCB#%#e}M%^NAnbHvt`o%x!WB`*pLlw?xwYDPtD*h$<-2ZzXA4_1W%XkTv91c6 z;_*)1Eg1f!@S+Vdb}WyX*ynrOFC$*ECYc$bF8M#0XZ#|L0gsJn-#2}K)ju#Ik8SKQ z9oqLJSh{%by~2c;5hy1%gTAh_9-w2w&JGQf#TI4}5RXQCU?J)qtr|euw7a?I0kU@e zn)(AoBdie9n#T!gk|kB3`J+rPY=)iJb3rpvw}pQ|SUHvYVQ37dr%JDs_iB&SyCR>g z34iq=GmZ*TEG*Wf#s1jDfMz*D%y#lk-_o&mt>#7Reng+&l z>?E|LR&Nm%y%BFD*MXPg4_dYPDND&jDQ}&$ge1vkCWVL!ASd$>_&4ETy@Ee&U*+tx;tf%7|B9av*T) zpVP{H9&a*Nw!4;n683g3oH9TBSvHw;LD&|=npL@#$|G35U8?H1@oWi+x~#4X@8%O4 z9+o%@2qt7%BN-W3Salf#ax-NJN4mFd$Lh%#8wA^`U2Z?b+THN4zIKT_VhH(1b_+;m zO8h|3PTTEI9*sw*mR4&rjnvp1{F2us^&rUEmj|2>e_8{{^>;S@r^>yu3WcrY9S#ie$&VfnMLU za%2dorC|o~IO#Kg&c4#-l{0Fd3_2-5_PN8TXa^8jG4RB>;JBFA z7RLu*y1d!ds-Kg5`RpHteUb(<#}|gR>%2{RFcWbD)%l!7IdqyESkD(6jF#qDCLG`Y zx%ViMjf#F*UsMi#80U?Olx1M~?is-CMg-`13D?kyaoDG=AzgD7dKRpW_|-s#N5vwr z*JI54$ndf%H372>&q~XCF^ftKRkyZmQFJsQJ~(PMjtH=vz?Bx&K)CZT3zZp>n+;*k zp2NLSaLCEdx?R6J1UW*F?%tD)WV)ScTG`{iRiidxE#-;fD0;^XJ?v(2EyBjkj zqwh`x2J@ohg)^yPeY^LBW6E$=5w=?i^;77;-08f)sQ}OAq4VuPl#bQCpjojm?Cc9h z2Zsuz*Hv!Ywsalp>L~rso%E_Q5ylHlIXt<7nk<$4rZjAi~`m@{t_Xa8~NGUjF zwAkv22D|vX@J-)EI=#v}dj49TH$0g)1DcZz7-5Of>4b336QNDEdA7Qd%#E*n!&ll1Cn)*D-hjqjuRpyW Me#!y**xK*fA7MKOR{#J2 literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/acceptance/oam_dma_start/xbaseline_0000.png b/cinema/gb/mooneye-gb/acceptance/oam_dma_start/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..70c31f4a64de6cf9e56fdec0b6197ce73183bd51 GIT binary patch literal 1352 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|P6+r;B4q#hkZuFHT!xz~S;( z?*ISkciGeD-t+KKxsVk3J$2btw_B4WcxI*E-sV*Bvv@nRiWnCUA1hlsQ?qm9!G#AN z1SAw(3p%w)=Xm<`EftYs%dUGye7A3xn`&nE_NXp@@YSe~IRv?zGsnd z&VNq0zeDwWN7qutCHw9lPCOb~^g;UIU5{TeJMuNxi3*19+;#u+y?*^|Zzg~J;cs*8 z*T+?h;-9baDo?JR`a;WjXVTw40hxEQ_VPYl6tAEiI%Rd+*?+TV+TNEmJQ?|WwueE| zJf_F|GWSk0Av;mSz(Pbq=5cS|C&L9MFC9;x+w=F8-Olcn9QMu|x!*Kdm@LuR-&OtB zX~O(MiPoz;N zy;kdBNAGZFBh5q-{J--%QJ$WkLbJCYR%5et# zY{xITR9_9$zB=dXv}b!Q#MF*FeZ5d({lT?+E*$L(w_P)7|196V8vOTOhFPC~ur%-G z)kpoaxlUbw%+Z901r z``E6VJQZY@O75ag@e`FB9#<4p<_P!BVHe;3_}?y@2z6(05wYf4MUs zA1s->CPY{6b3$S>Pi4XO$wzHw*vwnvYx42X40h{nE0TntPs(z?^GWCJ6x;OZ85{L( zeP@}KZt#8YUH^$U{jQy}{KsY;d6)T#g-Xti1;0gaaPY0#@bd}x-pk&{&i}JfYI^@B zMYHnBr}bZcsa^l~c*3`gneV^7V3K^j=(}p%-!)m~Z1&sl{XV2Nmq#^uwUbPu9C`wS z#2y#Vx%CIUzCMgi`TV@|%X`=Da_sa7EXqn9Pu)#TnUWW-p1pU>~OL_-WPgjJjufA$&2f zFY*-bERs2@9xQY5_Rs5WMbD*bUNYs#$fh?X~i z!pC-Uf8D<0)|y&9!T;rkcA2ZTzPj`&zv%PtMN_XQyGn1Lc}crccvAU2757r_GkZL{ zp4zC)HGgTHI59?A92nQ%rU#upvFGUd*(RS{Kl|<7@wxWmRMU;2GxAr|{uP>!FYdd0_ky>l%!$eZ&Kc$_kNl8yzgIaqrf*iAx_1Bk zLZAgFwfV2vo_w4x<7plFx$;B#iTBG5oA!S4{YSeU_h%NJvpSPH_%XN>(Ef-;Z z+`a>5D~zzHdzNFd{zS}6#cRiBbf;YkyS-zB*wzz~D>H+Z74In7b0od)OO)2(X8-B? zT3fw}kU->$6?|oA=qyy%-s;WmampbhV(^_ZxdMp6l#6y|3t! z;0|m4OHWvRY!qdj*S?nCw#!vKV&?J_A9qIA?w@|@)cN(f0)NeK*DIZn^$425ei&Xx c5#@o;;=BbM@@q4)-9aU!r>mdKI;Vst0I*+>_W%F@ literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/acceptance/push_timing/xbaseline_0000.png b/cinema/gb/mooneye-gb/acceptance/push_timing/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..9fb7102863e51da8830d8337f9adce2d7d16dfa3 GIT binary patch literal 1344 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|P6&r;B4q#hkZu?@n5yz|->a z%AWt))p~b|bbXYZHnq(AmsfUt{)~-{iY%$OzwZ@@`FFOg;fPb?!G#AN1SAw_7+8o% z$Z+wTTRvq|?(Ls9Rdw!d>wQ=%WOsaiaKG1VySGP`{e!Q@Z8Rx~3E%wxY0mF`+2x=0 zzHOC}VbA`wWkrOD;@j8r_@viOntWgCPsHSP(*4Ru_bM&Y%3oi-|5xRrwD~pX{vWB% zy}y3dx7&9@D{XGvQ{8oHRm{hobt_D+MZNEPmGs{sY?Z`*soHP*XI}htPHOU;^Pd%k z))=ln@Qa<#ebWI{XY#SKEiYcMI7Y~;SuUgO*xT^OPxO{4{oz>}pCEqZ%7l%7b>Dp! zZ~D`-cyp)SzW!;xSw4oDoxI8qFFt(Uxb6GY$*%rR{J$iRX9(*~TCQWi%=-VC$&p{K zJlOn8YU+;GX;IdDU#{MlF!lJE$IIPLH z6Gc;thu=jakDeDyE8FDsI6=A2_i4I`@$46?w`$H^V^eo*BGhDKOTKNsy~oY2_s1=} z^Oq+zO(v&ZQtzhu_nHkq3jYWdp7Akz^UOI-Pcm$}-!=IklI|S`dcQv7XXA?bbkLh8 zBV)s4)^krRZWx%(Q?GcFqWaNz%G4<7ON_HK3ap-|NXp-zWu#`u_svh%di%n^tOmd$&u?&3DVoPL);Whx@s?Z|YL{29#ZW`tD{oZZZJ5#eWM&eq3AZk;w(#=1S6Q6I) z$UQf4$KGVWJ%$rro36fj@mA55{#|CvJ{);_Z0GeonbnOu;$5xxojR>Ik?n_cUG|0> za$Tz=Zuh!{>NxM-ls75&;J*mbrYHXYT|aLwTUhw`$gH)w=I*=2WJc>Kb47HTtkhOz*Xt?1$Qm z^HQo;Y=2*SQF6}tO?3q`FP^OWx-a8!$TPodHT)hLJj#%7-TX}|6*Yq_m{ zTJoRyuWeRqSFa1ncrCd1?CLqJhgKX^&iE`SR%MbnfxS_F@}_3{grvrTHCg@p5?4p( zH1?Ku^*qTa42u17aM`YP&+51T6?J^)FzJwmgdBFq;d1l;uH>sBaSp4kK_#Q7tDnm{ Hr-UW|rdW+E literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/xbaseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe7e5937a4a0bdc08a3f6336d8a678ac75ae6ca GIT binary patch literal 411 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|R4{r;B4q#hkYn895mg1P(ZC zt$%Lb%JEq5U_y18SnTt1hI~nx^ny7D3)zm3GRO&pJFMSKS!8D~ezp}9=$@{AF6*2U FngEd%oml_? literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png b/cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..2201a6ac50f260a6df2f69e1bf3e3bf476028e4b GIT binary patch literal 711 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Qf^r;B4q#hkad0*jgq7#J=t zdCa+2JSd1XdUD+#}M?WUzGM4RDV{av3`xL>Mom@sD% znr^Uxdj0J=9foZJyW(4pM$|h_V^4qT9DRECYfbiW|Cj5YzxdI9yVz}V(E4^0U60G#Jtjk{C`+D1J&UzE!kZiUPnHlpJ_1~Ix z>Gsp=z7qu>_HO!m-o=}if{dE`{RmD6T+x-_nRN0@Nq;J emKYo$p?!?nozi0Sr7|yq5~!!EpUXO@geCyFDmo?r literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/xbaseline_0000.png b/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b20ce1253e0c13c459234e1287ed23d56d4f9d GIT binary patch literal 1095 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|N`6r;B4q#hkaZZ%@Qy z-t`K#UIK55zl&V?dM#$DzF48WtBc^?UGr~o=j2JSAKhit{Qau+(goM$3mtH1GTA?tWntTN7@5 z==GwWDL<5s*k=8zO)1!tyg{ty?UzX>*S6bl>T-Ly;{v1m!2%H;6hbaI{pp&BDerWf z{2yMrW%9xP@$LWP?fhEyu=c0uobt_EAJ5*lr~Ko`unLtue_1`V&K}I( zx?Mg}y==nQg9Y2UA6oFYeYWO5*BrLoP5J)HFN>PKXgUVWOMCXet-EAr^x^;eR;|g` ztmhTqRL$>bJpbPAn&W#cEuMy~GhTmJ%5GQE)$GdE50%xL93wY>X}-CyyWiVNNN($c zhO1&_0_}D0lTs&`{{J8Q?%m3Q!gsREuS}nHJ&*VOx;Wp%v5xZ=%=DMr`fC#3r@{$a zmG4v<$UHw-P{$tmaaXe7)!F;!1synDkac~R)x?`~_ucvMesB5b`}1e@)J+IpZsIuC zf9}>y}ZnLPyF)izf`>M_dKP| z7S;cxKIB{uzWQ<5*Uqmpmkv+j3XE6o_%$#6kyZ1WUu$J_ENdn%e18P!4A#Se!b`f literal 0 HcmV?d00001 diff --git a/cinema/gb/mooneye-gb/misc/boot_hwio-C/xbaseline_0000.png b/cinema/gb/mooneye-gb/misc/boot_hwio-C/xbaseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..cb37cb619c559ad7defcd227f00231c8a4e60760 GIT binary patch literal 846 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|T?Dr;B4q#hkadE|#q_5Mc-` z`1pVSnc5B7PbWQ5VccSLt}aWLt)O95V%+u8dCyB@m-l`-VRQcW@$KD9?U%AQ7hd{w zRPa;ilji4-pRd-Iipnoukuc}(JT_(a$6s=eO;C+b-M24o=dJS}cV)Y~%+HB zUR+`8|0uj>`+=D@&aZR5(m$N^y%qQBenRK}Iop)~-w)p768~svv;5{-S-$+BKY1nF z&xHPau&6M;zNpvsWnJ_d#r|(fyNsC+OG=zOP{7t^Y%r(sArH@XpND(W3dPo&)mxfW z&e5MQ_F`uG+xeV*$6w04+E=%)TkqYIvbYsy-It7a2hG0EyKi>xxl3RCv-xb#eYSr1 z|Ib2;@6+w$qW4wKc7Lv2eRcZ#zqdU_=N@bfpD(#yj{UxA=AM&Q_3lSnKkv3msPCMo z6@LBPZ`-cNGBfVy?(6?OFZhGXa_)zJWO%Muh{yi9{P1_z{WWEqZ-m$K%`5*O)1DW& z@1m~G`hC}bCF>ryTYm4fb$-!rll|qPLEm>)>^t<$@WYL|@T0fiuHPjkt{eQr@+)(G x;5+dvvU9iZSf6iy;3LrB^aKlZFON{6@BH7H#ol#9^Zoclose(dir); } -static void _writeBaseline(struct VDir* dir, const struct CInemaImage* image, size_t frame) { +static void _writeBaseline(struct VDir* dir, const char* type, const struct CInemaImage* image, size_t frame) { char baselineName[32]; - snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame); + snprintf(baselineName, sizeof(baselineName), "%s_%04" PRIz "u.png", type, frame); struct VFile* baselineVF = dir->openFile(dir, baselineName, O_CREAT | O_TRUNC | O_WRONLY); if (baselineVF) { _writeImage(baselineVF, image); @@ -975,7 +981,7 @@ void CInemaTestRun(struct CInemaTest* test) { } test->totalPixels += image.height * image.width; if (rebaseline == CI_R_FAILING && !video && failed) { - _writeBaseline(dir, &image, frame); + _writeBaseline(dir, "baseline", &image, frame); } if (diff) { if (failed) { @@ -983,10 +989,11 @@ void CInemaTestRun(struct CInemaTest* test) { _writeDiffSet(&expected, test->name, diff, frame, max, false); } free(diff); + diff = NULL; } free(expected.data); } else if (rebaseline && !video) { - _writeBaseline(dir, &image, frame); + _writeBaseline(dir, "baseline", &image, frame); } else if (!rebaseline) { test->status = CI_FAIL; } @@ -1007,11 +1014,17 @@ void CInemaTestRun(struct CInemaTest* test) { _writeDiffSet(&expected, test->name, diff, frame, max, true); } free(diff); + diff = NULL; } if (failed) { + if (xbaseline == CI_R_FAILING && !video) { + _writeBaseline(dir, "xbaseline", &image, frame); + } xdiff = true; } free(expected.data); + } else if (xbaseline && !video) { + _writeBaseline(dir, "xbaseline", &image, frame); } } } From 0c015461027bd5c2ee09c764c4cbbc6a53c4f5e4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 26 Jul 2020 03:22:45 -0700 Subject: [PATCH 19/20] Test: Avoid tentatively encoding videos --- src/platform/test/cinema-main.c | 94 ++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 123ce479e..d8086752a 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -795,6 +795,59 @@ void _writeDiffSet(struct CInemaImage* expected, const char* name, uint8_t* diff } } +#ifdef USE_FFMPEG +static void _replayBaseline(struct CInemaTest* test, struct FFmpegEncoder* encoder, const struct CInemaImage* image, int frame) { + char baselineName[PATH_MAX]; + snprintf(baselineName, sizeof(baselineName), "%s" PATH_SEP ".baseline.avi", test->directory); + + if (!FFmpegEncoderOpen(encoder, baselineName)) { + CIerr(1, "Failed to save baseline video\n"); + test->status = CI_ERROR; + return; + } + + snprintf(baselineName, sizeof(baselineName), "%s" PATH_SEP "baseline.avi", test->directory); + + + struct CInemaImage buffer = { + .data = NULL, + .width = image->width, + .height = image->height, + .stride = image->width, + }; + struct FFmpegDecoder decoder; + struct CInemaStream stream = {0}; + stream.d.postVideoFrame = _cinemaVideoFrame; + stream.d.videoDimensionsChanged = _cinemaDimensionsChanged; + stream.status = &test->status; + stream.image = &buffer; + + FFmpegDecoderInit(&decoder); + decoder.out = &stream.d; + + if (!FFmpegDecoderOpen(&decoder, baselineName)) { + CIerr(1, "Failed to load baseline video\n"); + test->status = CI_ERROR; + return; + } + + int i; + for (i = 0; i < frame; ++i) { + while (!buffer.data) { + if (!FFmpegDecoderRead(&decoder)) { + CIlog(1, "Failed to read more frames. EOF?\n"); + test->status = CI_FAIL; + break; + } + } + encoder->d.postVideoFrame(&encoder->d, buffer.data, buffer.stride); + free(buffer.data); + buffer.data = NULL; + } + FFmpegDecoderClose(&decoder); +} +#endif + void CInemaTestRun(struct CInemaTest* test) { unsigned ignore = 0; MutexLock(&configMutex); @@ -885,24 +938,17 @@ void CInemaTestRun(struct CInemaTest* test) { snprintf(baselineName, sizeof(baselineName), "%s" PATH_SEP "baseline.avi", test->directory); bool exists = access(baselineName, 0) == 0; - char tmpBaselineName[PATH_MAX]; - snprintf(tmpBaselineName, sizeof(tmpBaselineName), "%s" PATH_SEP ".baseline.avi", test->directory); - if (video) { FFmpegEncoderInit(&encoder); FFmpegDecoderInit(&decoder); - if (rebaseline == CI_R_FAILING || (rebaseline == CI_R_MISSING && !exists)) { - FFmpegEncoderSetAudio(&encoder, NULL, 0); - FFmpegEncoderSetVideo(&encoder, "zmbv", 0, 0); - FFmpegEncoderSetContainer(&encoder, "avi"); - FFmpegEncoderSetDimensions(&encoder, image.width, image.height); + FFmpegEncoderSetAudio(&encoder, NULL, 0); + FFmpegEncoderSetVideo(&encoder, "zmbv", 0, 0); + FFmpegEncoderSetContainer(&encoder, "avi"); + FFmpegEncoderSetDimensions(&encoder, image.width, image.height); - const char* usedFname = baselineName; - if (exists) { - usedFname = tmpBaselineName; - } - if (!FFmpegEncoderOpen(&encoder, usedFname)) { + if (rebaseline && !exists) { + if (!FFmpegEncoderOpen(&encoder, baselineName)) { CIerr(1, "Failed to save baseline video\n"); } else { core->setAVStream(core, &encoder.d); @@ -978,6 +1024,16 @@ void CInemaTestRun(struct CInemaTest* test) { failed = !_compareImages(test, &image, &expected, &max, diffs ? &diff : NULL); if (failed) { ++test->failedFrames; +#ifdef USE_FFMPEG + if (video && exists && rebaseline && !FFmpegEncoderIsOpen(&encoder)) { + _replayBaseline(test, &encoder, &image, frame); + if (test->status == CI_ERROR) { + break; + } + encoder.d.postVideoFrame(&encoder.d, image.data, image.stride); + core->setAVStream(core, &encoder.d); + } +#endif } test->totalPixels += image.height * image.width; if (rebaseline == CI_R_FAILING && !video && failed) { @@ -1033,16 +1089,14 @@ void CInemaTestRun(struct CInemaTest* test) { if (video) { if (FFmpegEncoderIsOpen(&encoder)) { FFmpegEncoderClose(&encoder); - if (exists) { - if (test->status == CI_FAIL) { + if (exists && rebaseline) { + char tmpBaselineName[PATH_MAX]; + snprintf(tmpBaselineName, sizeof(tmpBaselineName), "%s" PATH_SEP ".baseline.avi", test->directory); #ifdef _WIN32 - MoveFileEx(tmpBaselineName, baselineName, MOVEFILE_REPLACE_EXISTING); + MoveFileEx(tmpBaselineName, baselineName, MOVEFILE_REPLACE_EXISTING); #else - rename(tmpBaselineName, baselineName); + rename(tmpBaselineName, baselineName); #endif - } else { - remove(tmpBaselineName); - } } } if (FFmpegDecoderIsOpen(&decoder)) { From 89de06a610caea5f84fcbad139fe481bf8dae943 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 26 Jul 2020 21:56:09 -0700 Subject: [PATCH 20/20] Test: Add wildcard matching --- include/mgba-util/string.h | 1 + src/platform/test/cinema-main.c | 85 +++++++++++++++++++++++++++++++++ src/util/string.c | 30 ++++++++++++ 3 files changed, 116 insertions(+) diff --git a/include/mgba-util/string.h b/include/mgba-util/string.h index 306a35257..381869814 100644 --- a/include/mgba-util/string.h +++ b/include/mgba-util/string.h @@ -45,6 +45,7 @@ const char* hex4(const char* line, uint8_t* out); void rtrim(char* string); ssize_t parseQuotedString(const char* unparsed, ssize_t unparsedLen, char* parsed, ssize_t parsedLen); +bool wildcard(const char* search, const char* string); CXX_GUARD_END diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index d8086752a..4c18d40ae 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -403,6 +403,84 @@ static void testToPath(const char* testName, char* path) { path[i] = '\0'; } +static bool globTests(struct CInemaTestList* tests, const char* glob, const char* ancestors) { + bool success = true; + const char* next = strpbrk(glob, "*."); + + char path[PATH_MAX]; + if (!next) { + testToPath(glob, path); + return collectTests(tests, path); + } else if (next[0] == '.') { + char subtest[MAX_TEST]; + if (!ancestors) { + strncpy(subtest, glob, next - glob); + } else { + size_t len = strlen(ancestors) + (next - glob) + 2; + if (len > sizeof(subtest)) { + len = sizeof(subtest); + } + snprintf(subtest, len, "%s.%s", ancestors, glob); + } + return globTests(tests, next + 1, subtest); + } else if (next[0] == '*') { + char globBuffer[MAX_TEST]; + const char* subglob; + + next = strchr(next, '.'); + if (!next) { + subglob = glob; + } else { + size_t len = next - glob + 1; + if (len > sizeof(globBuffer)) { + len = sizeof(globBuffer); + } + strncpy(globBuffer, glob, len - 1); + subglob = globBuffer; + } + bool hasMoreGlobs = next && strchr(next, '*'); + + struct VDir* dir; + if (ancestors) { + testToPath(ancestors, path); + dir = VDirOpen(path); + } else { + dir = VDirOpen(base); + } + if (!dir) { + return false; + } + + struct VDirEntry* dirent = dir->listNext(dir); + while (dirent) { + const char* name = dirent->name(dirent); + if (dirent->type(dirent) != VFS_DIRECTORY || strncmp(name, ".", 2) == 0 || strncmp(name, "..", 3) == 0) { + dirent = dir->listNext(dir); + continue; + } + if (wildcard(subglob, name)) { + char newgen[MAX_TEST]; + if (ancestors) { + snprintf(newgen, sizeof(newgen), "%s.%s", ancestors, name); + } else { + strlcpy(newgen, name, sizeof(newgen)); + } + if (next && hasMoreGlobs) { + globTests(tests, next + 1, newgen); + } else { + testToPath(newgen, path); + collectTests(tests, path); + } + } + dirent = dir->listNext(dir); + } + + return true; + } else { + abort(); + } +} + static void _loadConfigTree(struct Table* configTree, const char* testName) { char key[MAX_TEST]; strlcpy(key, testName, sizeof(key)); @@ -1285,6 +1363,13 @@ int main(int argc, char** argv) { if (argc > 0) { size_t i; for (i = 0; i < (size_t) argc; ++i) { + if (strchr(argv[i], '*')) { + if (!globTests(&tests, argv[i], NULL)) { + status = 1; + break; + } + continue; + } char path[PATH_MAX + 1] = {0}; testToPath(argv[i], path); diff --git a/src/util/string.c b/src/util/string.c index 102a8cfbc..6a60abf48 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -518,4 +518,34 @@ ssize_t parseQuotedString(const char* unparsed, ssize_t unparsedLen, char* parse } } return -1; +} + +bool wildcard(const char* search, const char* string) { + while (true) { + if (search[0] == '*') { + while (search[0] == '*') { + ++search; + } + if (!search[0]) { + return true; + } + while (string[0]) { + if (string[0] == search[0] && wildcard(search, string)) { + return true; + } + ++string; + } + return false; + } else if (!search[0]) { + return !string[0]; + } else if (!string[0]) { + return false; + } else if (string[0] != search[0]) { + return false; + } else { + ++search; + ++string; + } + } + return false; } \ No newline at end of file