21
#include <SDL3/SDL_test.h>
27
#define COLOR_RED "\033[0;31m"
28
#define COLOR_GREEN "\033[0;32m"
29
#define COLOR_YELLOW "\033[0;93m"
30
#define COLOR_BLUE "\033[0;94m"
31
#define COLOR_END "\033[0m"
36
#define COLOR_YELLOW ""
41
#define SDLTEST_INVALID_NAME_FORMAT "(Invalid)"
44
#define SDLTEST_LOG_SUMMARY_FORMAT "%s Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_RED "Failed=%d" COLOR_END " " COLOR_BLUE "Skipped=%d" COLOR_END
45
#define SDLTEST_LOG_SUMMARY_FORMAT_OK "%s Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_GREEN "Failed=%d" COLOR_END " " COLOR_BLUE "Skipped=%d" COLOR_END
48
#define SDLTEST_FINAL_RESULT_FORMAT COLOR_YELLOW ">>> %s '%s':" COLOR_END " %s\n"
51
static Uint32 SDLTest_TestCaseTimeout = 3600;
63
char *SDLTest_GenerateRunSeed(const int length)
66
Uint64 randomContext = SDL_GetPerformanceCounter();
71
SDLTest_LogError("The length of the harness seed must be >0.");
76
seed = (char *)SDL_malloc((length + 1) * sizeof(char));
78
SDLTest_LogError("SDL_malloc for run seed output buffer failed.");
83
for (counter = 0; counter < length; counter++) {
85
int v = SDL_rand_r(&randomContext, 10 + 26);
89
ch = (char)('A' + v - 10);
109
static Uint64 SDLTest_GenerateExecKey(const char *runSeed, const char *suiteName, const char *testName, int iteration)
111
SDLTest_Md5Context md5Context;
113
char iterationString[16];
114
size_t runSeedLength;
115
size_t suiteNameLength;
116
size_t testNameLength;
117
size_t iterationStringLength;
118
size_t entireStringLength;
121
if (!runSeed || runSeed[0] == '\0') {
122
SDLTest_LogError("Invalid runSeed string.");
126
if (!suiteName || suiteName[0] == '\0') {
127
SDLTest_LogError("Invalid suiteName string.");
131
if (!testName || testName[0] == '\0') {
132
SDLTest_LogError("Invalid testName string.");
136
if (iteration <= 0) {
137
SDLTest_LogError("Invalid iteration count.");
142
SDL_memset(iterationString, 0, sizeof(iterationString));
143
(void)SDL_snprintf(iterationString, sizeof(iterationString) - 1, "%d", iteration);
146
runSeedLength = SDL_strlen(runSeed);
147
suiteNameLength = SDL_strlen(suiteName);
148
testNameLength = SDL_strlen(testName);
149
iterationStringLength = SDL_strlen(iterationString);
150
entireStringLength = runSeedLength + suiteNameLength + testNameLength + iterationStringLength + 1;
151
buffer = (char *)SDL_malloc(entireStringLength);
153
SDLTest_LogError("Failed to allocate buffer for execKey generation.");
156
(void)SDL_snprintf(buffer, entireStringLength, "%s%s%s%d", runSeed, suiteName, testName, iteration);
159
SDLTest_Md5Init(&md5Context);
160
SDLTest_Md5Update(&md5Context, (unsigned char *)buffer, (unsigned int)entireStringLength);
161
SDLTest_Md5Final(&md5Context);
163
keys = (Uint64 *)md5Context.digest;
178
static SDL_TimerID SDLTest_SetTestTimeout(int timeout, void(SDLCALL *callback)(void))
180
Uint32 timeoutInMilliseconds;
184
SDLTest_LogError("Timeout callback can't be NULL");
189
SDLTest_LogError("Timeout value must be bigger than zero.");
194
if (!SDL_WasInit(SDL_INIT_TIMER)) {
195
if (!SDL_InitSubSystem(SDL_INIT_TIMER)) {
196
SDLTest_LogError("Failed to init timer subsystem: %s", SDL_GetError());
202
timeoutInMilliseconds = timeout * 1000;
203
timerID = SDL_AddTimer(timeoutInMilliseconds, (SDL_TimerCallback)callback, 0x0);
205
SDLTest_LogError("Creation of SDL timer failed: %s", SDL_GetError());
216
#pragma aux SDLTest_BailOut aborts;
218
static SDL_NORETURN void SDLCALL SDLTest_BailOut(void)
220
SDLTest_LogError("TestCaseTimeout timer expired. Aborting test run.");
234
static int SDLTest_RunTest(SDLTest_TestSuiteReference *testSuite, const SDLTest_TestCaseReference *testCase, Uint64 execKey, SDL_bool forceTestRun)
236
SDL_TimerID timer = 0;
237
int testCaseResult = 0;
241
if (!testSuite || !testCase || !testSuite->name || !testCase->name) {
242
SDLTest_LogError("Setup failure: testSuite or testCase references NULL");
243
return TEST_RESULT_SETUP_FAILURE;
246
if (!testCase->enabled && forceTestRun == SDL_FALSE) {
247
SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, "Skipped (Disabled)");
248
return TEST_RESULT_SKIPPED;
252
SDLTest_FuzzerInit(execKey);
255
SDLTest_ResetAssertSummary();
258
timer = SDLTest_SetTestTimeout(SDLTest_TestCaseTimeout, SDLTest_BailOut);
261
if (testSuite->testSetUp) {
262
testSuite->testSetUp(0x0);
263
if (SDLTest_AssertSummaryToTestResult() == TEST_RESULT_FAILED) {
264
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Suite Setup", testSuite->name, COLOR_RED "Failed" COLOR_END);
265
return TEST_RESULT_SETUP_FAILURE;
270
testCaseResult = testCase->testCase(0x0);
273
if (testCaseResult == TEST_SKIPPED) {
275
testResult = TEST_RESULT_SKIPPED;
276
} else if (testCaseResult == TEST_STARTED) {
278
testResult = TEST_RESULT_FAILED;
279
} else if (testCaseResult == TEST_ABORTED) {
281
testResult = TEST_RESULT_FAILED;
284
testResult = SDLTest_AssertSummaryToTestResult();
288
if (testSuite->testTearDown) {
289
testSuite->testTearDown(0x0);
294
SDL_RemoveTimer(timer);
298
fuzzerCount = SDLTest_GetFuzzerInvocationCount();
299
if (fuzzerCount > 0) {
300
SDLTest_Log("Fuzzer invocations: %d", fuzzerCount);
304
if (testCaseResult == TEST_SKIPPED) {
306
SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, COLOR_BLUE "Skipped (Programmatically)" COLOR_END);
307
} else if (testCaseResult == TEST_STARTED) {
309
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, COLOR_RED "Failed (test started, but did not return TEST_COMPLETED)" COLOR_END);
310
} else if (testCaseResult == TEST_ABORTED) {
312
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, COLOR_RED "Failed (Aborted)" COLOR_END);
314
SDLTest_LogAssertSummary();
322
static void SDLTest_LogTestSuiteSummary(SDLTest_TestSuiteReference *testSuites)
326
SDLTest_TestSuiteReference *testSuite;
327
SDLTest_TestCaseReference *testCase;
331
while (&testSuites[suiteCounter]) {
332
testSuite=&testSuites[suiteCounter];
334
SDLTest_Log("Test Suite %i - %s\n", suiteCounter,
335
(testSuite->name) ? testSuite->name : SDLTEST_INVALID_NAME_FORMAT);
339
while (testSuite->testCases[testCounter]) {
340
testCase=(SDLTest_TestCaseReference *)testSuite->testCases[testCounter];
342
SDLTest_Log(" Test Case %i - %s: %s", testCounter,
343
(testCase->name) ? testCase->name : SDLTEST_INVALID_NAME_FORMAT,
344
(testCase->description) ? testCase->description : SDLTEST_INVALID_NAME_FORMAT);
351
static float GetClock(void)
353
float currentClock = SDL_GetPerformanceCounter() / (float)SDL_GetPerformanceFrequency();
372
int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *userRunSeed, Uint64 userExecKey, const char *filter, int testIterations, SDL_bool randomOrder)
374
int totalNumberOfTests = 0;
375
int failedNumberOfTests = 0;
378
int iterationCounter;
379
SDLTest_TestSuiteReference *testSuite;
380
const SDLTest_TestCaseReference *testCase;
381
const char *runSeed = NULL;
382
const char *currentSuiteName;
383
const char *currentTestName;
385
float runStartSeconds;
386
float suiteStartSeconds;
387
float testStartSeconds;
389
float suiteEndSeconds;
390
float testEndSeconds;
393
const char *suiteFilterName = NULL;
395
const char *testFilterName = NULL;
396
SDL_bool forceTestRun = SDL_FALSE;
399
int totalTestFailedCount = 0;
400
int totalTestPassedCount = 0;
401
int totalTestSkippedCount = 0;
402
int testFailedCount = 0;
403
int testPassedCount = 0;
404
int testSkippedCount = 0;
406
const SDLTest_TestCaseReference **failedTests;
407
char generatedSeed[16 + 1];
410
int *arraySuites = NULL;
413
if (testIterations < 1) {
418
if (!userRunSeed || userRunSeed[0] == '\0') {
419
char *tmp = SDLTest_GenerateRunSeed(16);
421
SDLTest_LogError("Generating a random seed failed");
424
SDL_memcpy(generatedSeed, tmp, 16 + 1);
426
runSeed = generatedSeed;
428
runSeed = userRunSeed;
432
totalTestFailedCount = 0;
433
totalTestPassedCount = 0;
434
totalTestSkippedCount = 0;
437
runStartSeconds = GetClock();
440
SDLTest_Log("::::: Test Run /w seed '%s' started\n", runSeed);
444
while (testSuites[suiteCounter]) {
445
testSuite = testSuites[suiteCounter];
448
while (testSuite->testCases[testCounter]) {
450
totalNumberOfTests++;
454
if (totalNumberOfTests == 0) {
455
SDLTest_LogError("No tests to run?");
460
failedTests = (const SDLTest_TestCaseReference **)SDL_malloc(totalNumberOfTests * sizeof(SDLTest_TestCaseReference *));
462
SDLTest_LogError("Unable to allocate cache for failed tests");
467
if (filter && filter[0] != '\0') {
470
while (testSuites[suiteCounter] && suiteFilter == 0) {
471
testSuite = testSuites[suiteCounter];
473
if (testSuite->name && SDL_strcasecmp(filter, testSuite->name) == 0) {
476
suiteFilterName = testSuite->name;
477
SDLTest_Log("Filtering: running only suite '%s'", suiteFilterName);
483
while (testSuite->testCases[testCounter] && testFilter == 0) {
484
testCase = testSuite->testCases[testCounter];
486
if (testCase->name && SDL_strcasecmp(filter, testCase->name) == 0) {
489
suiteFilterName = testSuite->name;
491
testFilterName = testCase->name;
492
SDLTest_Log("Filtering: running only test '%s' in suite '%s'", testFilterName, suiteFilterName);
498
if (suiteFilter == 0 && testFilter == 0) {
499
SDLTest_LogError("Filter '%s' did not match any test suite/case.", filter);
500
for (suiteCounter = 0; testSuites[suiteCounter]; ++suiteCounter) {
501
testSuite = testSuites[suiteCounter];
502
if (testSuite->name) {
503
SDLTest_Log("Test suite: %s", testSuite->name);
507
for (testCounter = 0; testSuite->testCases[testCounter]; ++testCounter) {
508
testCase = testSuite->testCases[testCounter];
509
SDLTest_Log(" test: %s%s", testCase->name, testCase->enabled ? "" : " (disabled)");
512
SDLTest_Log("Exit code: 2");
513
SDL_free((void *)failedTests);
517
randomOrder = SDL_FALSE;
521
while (testSuites[nbSuites]) {
525
arraySuites = SDL_malloc(nbSuites * sizeof(int));
527
return SDL_OutOfMemory();
529
for (i = 0; i < nbSuites; i++) {
538
if (userExecKey != 0) {
539
execKey = userExecKey;
542
execKey = SDLTest_GenerateExecKey(runSeed, "random testSuites", "initialisation", 1);
546
SDLTest_FuzzerInit(execKey);
552
a = SDLTest_RandomIntegerInRange(0, nbSuites - 1);
553
b = SDLTest_RandomIntegerInRange(0, nbSuites - 1);
563
tmp = arraySuites[b];
564
arraySuites[b] = arraySuites[a];
565
arraySuites[a] = tmp;
574
for (i = 0; i < nbSuites; i++) {
575
suiteCounter = arraySuites[i];
576
testSuite = testSuites[suiteCounter];
577
currentSuiteName = (testSuite->name ? testSuite->name : SDLTEST_INVALID_NAME_FORMAT);
581
if (suiteFilter == 1 && suiteFilterName && testSuite->name &&
582
SDL_strcasecmp(suiteFilterName, testSuite->name) != 0) {
584
SDLTest_Log("===== Test Suite %i: '%s' " COLOR_BLUE "skipped" COLOR_END "\n",
592
while (testSuite->testCases[nbTestCases]) {
596
arrayTestCases = SDL_malloc(nbTestCases * sizeof(int));
597
if (!arrayTestCases) {
598
return SDL_OutOfMemory();
600
for (j = 0; j < nbTestCases; j++) {
601
arrayTestCases[j] = j;
609
a = SDLTest_RandomIntegerInRange(0, nbTestCases - 1);
610
b = SDLTest_RandomIntegerInRange(0, nbTestCases - 1);
614
tmp = arrayTestCases[b];
615
arrayTestCases[b] = arrayTestCases[a];
616
arrayTestCases[a] = tmp;
623
testSkippedCount = 0;
626
suiteStartSeconds = GetClock();
629
SDLTest_Log("===== Test Suite %i: '%s' started\n",
634
for (j = 0; j < nbTestCases; j++) {
635
testCounter = arrayTestCases[j];
636
testCase = testSuite->testCases[testCounter];
637
currentTestName = (testCase->name ? testCase->name : SDLTEST_INVALID_NAME_FORMAT);
641
if (testFilter == 1 && testFilterName && testCase->name &&
642
SDL_strcasecmp(testFilterName, testCase->name) != 0) {
644
SDLTest_Log("===== Test Case %i.%i: '%s' " COLOR_BLUE "skipped" COLOR_END "\n",
650
if (testFilter == 1 && !testCase->enabled) {
651
SDLTest_Log("Force run of disabled test since test filter was set");
652
forceTestRun = SDL_TRUE;
656
testStartSeconds = GetClock();
659
SDLTest_Log(COLOR_YELLOW "----- Test Case %i.%i: '%s' started" COLOR_END,
663
if (testCase->description && testCase->description[0] != '\0') {
664
SDLTest_Log("Test Description: '%s'",
665
(testCase->description) ? testCase->description : SDLTEST_INVALID_NAME_FORMAT);
669
iterationCounter = 0;
670
while (iterationCounter < testIterations) {
673
if (userExecKey != 0) {
674
execKey = userExecKey;
676
execKey = SDLTest_GenerateExecKey(runSeed, testSuite->name, testCase->name, iterationCounter);
679
SDLTest_Log("Test Iteration %i: execKey %" SDL_PRIu64, iterationCounter, execKey);
680
testResult = SDLTest_RunTest(testSuite, testCase, execKey, forceTestRun);
682
if (testResult == TEST_RESULT_PASSED) {
684
totalTestPassedCount++;
685
} else if (testResult == TEST_RESULT_SKIPPED) {
687
totalTestSkippedCount++;
690
totalTestFailedCount++;
695
testEndSeconds = GetClock();
696
runtime = testEndSeconds - testStartSeconds;
697
if (runtime < 0.0f) {
701
if (testIterations > 1) {
703
SDLTest_Log("Runtime of %i iterations: %.1f sec", testIterations, runtime);
704
SDLTest_Log("Average Test runtime: %.5f sec", runtime / (float)testIterations);
707
SDLTest_Log("Total Test runtime: %.1f sec", runtime);
711
switch (testResult) {
712
case TEST_RESULT_PASSED:
713
SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Test", currentTestName, COLOR_GREEN "Passed" COLOR_END);
715
case TEST_RESULT_FAILED:
716
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", currentTestName, COLOR_RED "Failed" COLOR_END);
718
case TEST_RESULT_NO_ASSERT:
719
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", currentTestName, COLOR_BLUE "No Asserts" COLOR_END);
724
if (testResult == TEST_RESULT_FAILED) {
725
failedTests[failedNumberOfTests] = testCase;
726
failedNumberOfTests++;
732
suiteEndSeconds = GetClock();
733
runtime = suiteEndSeconds - suiteStartSeconds;
734
if (runtime < 0.0f) {
739
SDLTest_Log("Total Suite runtime: %.1f sec", runtime);
742
countSum = testPassedCount + testFailedCount + testSkippedCount;
743
if (testFailedCount == 0) {
744
SDLTest_Log(SDLTEST_LOG_SUMMARY_FORMAT_OK, "Suite", countSum, testPassedCount, testFailedCount, testSkippedCount);
745
SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Suite", currentSuiteName, COLOR_GREEN "Passed" COLOR_END);
747
SDLTest_LogError(SDLTEST_LOG_SUMMARY_FORMAT, "Suite", countSum, testPassedCount, testFailedCount, testSkippedCount);
748
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Suite", currentSuiteName, COLOR_RED "Failed" COLOR_END);
751
SDL_free(arrayTestCases);
755
SDL_free(arraySuites);
758
runEndSeconds = GetClock();
759
runtime = runEndSeconds - runStartSeconds;
760
if (runtime < 0.0f) {
765
SDLTest_Log("Total Run runtime: %.1f sec", runtime);
768
countSum = totalTestPassedCount + totalTestFailedCount + totalTestSkippedCount;
769
if (totalTestFailedCount == 0) {
771
SDLTest_Log(SDLTEST_LOG_SUMMARY_FORMAT_OK, "Run", countSum, totalTestPassedCount, totalTestFailedCount, totalTestSkippedCount);
772
SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Run /w seed", runSeed, COLOR_GREEN "Passed" COLOR_END);
775
SDLTest_LogError(SDLTEST_LOG_SUMMARY_FORMAT, "Run", countSum, totalTestPassedCount, totalTestFailedCount, totalTestSkippedCount);
776
SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Run /w seed", runSeed, COLOR_RED "Failed" COLOR_END);
780
if (failedNumberOfTests > 0) {
781
SDLTest_Log("Harness input to repro failures:");
782
for (testCounter = 0; testCounter < failedNumberOfTests; testCounter++) {
783
SDLTest_Log(COLOR_RED " --seed %s --filter %s" COLOR_END, runSeed, failedTests[testCounter]->name);
786
SDL_free((void *)failedTests);
788
SDLTest_Log("Exit code: %d", runResult);