2 Simple DirectMedia Layer
3 Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
22 #include "SDL_config.h"
31 /* Invalid test name/description message format */
32 const char *SDLTest_InvalidNameFormat = "(Invalid)";
34 /* Log summary message format */
35 const char *SDLTest_LogSummaryFormat = "%s Summary: Total=%d Passed=%d Failed=%d Skipped=%d";
37 /* Final result message format */
38 const char *SDLTest_FinalResultFormat = ">>> %s '%s': %s\n";
40 /*! \brief Timeout for single test case execution */
41 static Uint32 SDLTest_TestCaseTimeout = 3600;
44 * Generates a random run seed string for the harness. The generated seed
45 * will contain alphanumeric characters (0-9A-Z).
47 * Note: The returned string needs to be deallocated by the caller.
49 * \param length The length of the seed string to generate
51 * \returns The generated seed string
54 SDLTest_GenerateRunSeed(const int length)
57 SDLTest_RandomContext randomContext;
62 SDLTest_LogError("The length of the harness seed must be >0.");
66 // Allocate output buffer
67 seed = (char *)SDL_malloc((length + 1) * sizeof(char));
69 SDLTest_LogError("SDL_malloc for run seed output buffer failed.");
73 // Generate a random string of alphanumeric characters
74 SDLTest_RandomInitTime(&randomContext);
75 for (counter = 0; counter < length - 1; ++counter) {
76 unsigned int number = SDLTest_Random(&randomContext);
77 char ch = (char) (number % (91 - 48)) + 48;
78 if (ch >= 58 && ch <= 64) {
89 * Generates an execution key for the fuzzer.
91 * \param runSeed The run seed to use
92 * \param suiteName The name of the test suite
93 * \param testName The name of the test
94 * \param iteration The iteration count
96 * \returns The generated execution key to initialize the fuzzer with.
100 SDLTest_GenerateExecKey(char *runSeed, char *suiteName, char *testName, int iteration)
102 SDLTest_Md5Context md5Context;
104 char iterationString[16];
105 Uint32 runSeedLength;
106 Uint32 suiteNameLength;
107 Uint32 testNameLength;
108 Uint32 iterationStringLength;
109 Uint32 entireStringLength;
112 if (runSeed == NULL || SDL_strlen(runSeed)==0) {
113 SDLTest_LogError("Invalid runSeed string.");
117 if (suiteName == NULL || SDL_strlen(suiteName)==0) {
118 SDLTest_LogError("Invalid suiteName string.");
122 if (testName == NULL || SDL_strlen(testName)==0) {
123 SDLTest_LogError("Invalid testName string.");
127 if (iteration <= 0) {
128 SDLTest_LogError("Invalid iteration count.");
132 // Convert iteration number into a string
133 SDL_memset(iterationString, 0, sizeof(iterationString));
134 SDL_snprintf(iterationString, sizeof(iterationString) - 1, "%d", iteration);
136 // Combine the parameters into single string
137 runSeedLength = SDL_strlen(runSeed);
138 suiteNameLength = SDL_strlen(suiteName);
139 testNameLength = SDL_strlen(testName);
140 iterationStringLength = SDL_strlen(iterationString);
141 entireStringLength = runSeedLength + suiteNameLength + testNameLength + iterationStringLength + 1;
142 buffer = (char *)SDL_malloc(entireStringLength);
143 if (buffer == NULL) {
144 SDLTest_LogError("SDL_malloc failed to allocate buffer for execKey generation.");
147 SDL_snprintf(buffer, entireStringLength, "%s%s%s%d", runSeed, suiteName, testName, iteration);
149 // Hash string and use half of the digest as 64bit exec key
150 SDLTest_Md5Init(&md5Context);
151 SDLTest_Md5Update(&md5Context, (unsigned char *)buffer, entireStringLength);
152 SDLTest_Md5Final(&md5Context);
154 keys = (Uint64 *)md5Context.digest;
160 * \brief Set timeout handler for test.
162 * Note: SDL_Init(SDL_INIT_TIMER) will be called if it wasn't done so before.
164 * \param timeout Timeout interval in seconds.
165 * \param callback Function that will be called after timeout has elapsed.
167 * \return Timer id or -1 on failure.
170 SDLTest_SetTestTimeout(int timeout, void (*callback)())
172 Uint32 timeoutInMilliseconds;
175 if (callback == NULL) {
176 SDLTest_LogError("Timeout callback can't be NULL");
181 SDLTest_LogError("Timeout value must be bigger than zero.");
185 /* Init SDL timer if not initialized before */
186 if (SDL_WasInit(SDL_INIT_TIMER) == 0) {
187 if (SDL_InitSubSystem(SDL_INIT_TIMER)) {
188 SDLTest_LogError("Failed to init timer subsystem: %s", SDL_GetError());
194 timeoutInMilliseconds = timeout * 1000;
195 timerID = SDL_AddTimer(timeoutInMilliseconds, (SDL_TimerCallback)callback, 0x0);
197 SDLTest_LogError("Creation of SDL timer failed: %s", SDL_GetError());
205 * \brief Timeout handler. Aborts test run and exits harness process.
210 SDLTest_LogError("TestCaseTimeout timer expired. Aborting test run.");
211 exit(TEST_ABORTED); // bail out from the test
215 * \brief Execute a test using the given execution key.
217 * \param testSuite Suite containing the test case.
218 * \param testCase Case to execute.
219 * \param execKey Execution key for the fuzzer.
221 * \returns Test case result.
224 SDLTest_RunTest(SDLTest_TestSuiteReference *testSuite, SDLTest_TestCaseReference *testCase, Uint64 execKey)
226 SDL_TimerID timer = 0;
230 if (testSuite==NULL || testCase==NULL || testSuite->name==NULL || testCase->name==NULL)
232 SDLTest_LogError("Setup failure: testSuite or testCase references NULL");
233 return TEST_RESULT_SETUP_FAILURE;
236 if (!testCase->enabled)
238 SDLTest_Log((char *)SDLTest_FinalResultFormat, "Test", testCase->name, "Skipped (Disabled)");
239 return TEST_RESULT_SKIPPED;
244 SDLTest_FuzzerInit(execKey);
246 // Reset assert tracker
247 SDLTest_ResetAssertSummary();
250 timer = SDLTest_SetTestTimeout(SDLTest_TestCaseTimeout, SDLTest_BailOut);
252 // Maybe run suite initalizer function
253 if (testSuite->testSetUp) {
254 testSuite->testSetUp(0x0);
255 if (SDLTest_AssertSummaryToTestResult() == TEST_RESULT_FAILED) {
256 SDLTest_LogError((char *)SDLTest_FinalResultFormat, "Suite Setup", testSuite->name, "Failed");
257 return TEST_RESULT_SETUP_FAILURE;
261 // Run test case function
262 testCase->testCase(0x0);
263 testResult = SDLTest_AssertSummaryToTestResult();
265 // Maybe run suite cleanup function (ignore failed asserts)
266 if (testSuite->testTearDown) {
267 testSuite->testTearDown(0x0);
270 // Cancel timeout timer
272 SDL_RemoveTimer(timer);
275 // Report on asserts and fuzzer usage
276 fuzzerCount = SDLTest_GetFuzzerInvocationCount();
277 if (fuzzerCount > 0) {
278 SDLTest_Log("Fuzzer invocations: %d", fuzzerCount);
280 SDLTest_LogAssertSummary();
285 /* Prints summary of all suites/tests contained in the given reference */
286 void SDLTest_LogTestSuiteSummary(SDLTest_TestSuiteReference *testSuites)
290 SDLTest_TestSuiteReference *testSuite;
291 SDLTest_TestCaseReference *testCase;
293 // Loop over all suites
295 while(&testSuites[suiteCounter]) {
296 testSuite=&testSuites[suiteCounter];
298 SDLTest_Log("Test Suite %i - %s\n", suiteCounter,
299 (testSuite->name) ? testSuite->name : SDLTest_InvalidNameFormat);
301 // Loop over all test cases
303 while(testSuite->testCases[testCounter])
305 testCase=(SDLTest_TestCaseReference *)testSuite->testCases[testCounter];
307 SDLTest_Log(" Test Case %i - %s: %s", testCounter,
308 (testCase->name) ? testCase->name : SDLTest_InvalidNameFormat,
309 (testCase->description) ? testCase->description : SDLTest_InvalidNameFormat);
314 /* Gets a timer value in seconds */
317 float currentClock = (float)clock();
318 return currentClock / (float)CLOCKS_PER_SEC;
322 * \brief Execute a test suite using the given run seend and execution key.
324 * The filter string is matched to the suite name (full comparison) to select a single suite,
325 * or if no suite matches, it is matched to the test names (full comparison) to select a single test.
327 * \param testSuites Suites containing the test case.
328 * \param userRunSeed Custom run seed provided by user, or NULL to autogenerate one.
329 * \param userExecKey Custom execution key provided by user, or 0 to autogenerate one.
330 * \param filter Filter specification. NULL disables. Case sensitive.
331 * \param testIterations Number of iterations to run each test case.
333 * \returns Test run result; 0 when all tests passed, 1 if any tests failed.
335 int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *userRunSeed, Uint64 userExecKey, const char *filter, int testIterations)
339 int iterationCounter;
340 SDLTest_TestSuiteReference *testSuite;
341 SDLTest_TestCaseReference *testCase;
342 const char *runSeed = NULL;
343 char *currentSuiteName;
344 char *currentTestName;
346 float runStartSeconds;
347 float suiteStartSeconds;
348 float testStartSeconds;
350 float suiteEndSeconds;
351 float testEndSeconds;
354 char *suiteFilterName = NULL;
356 char *testFilterName = NULL;
359 Uint32 totalTestFailedCount = 0;
360 Uint32 totalTestPassedCount = 0;
361 Uint32 totalTestSkippedCount = 0;
362 Uint32 testFailedCount = 0;
363 Uint32 testPassedCount = 0;
364 Uint32 testSkippedCount = 0;
366 char *logFormat = (char *)SDLTest_LogSummaryFormat;
368 // Sanitize test iterations
369 if (testIterations < 1) {
373 // Generate run see if we don't have one already
374 if (userRunSeed == NULL || SDL_strlen(userRunSeed) == 0) {
375 runSeed = SDLTest_GenerateRunSeed(16);
376 if (runSeed == NULL) {
377 SDLTest_LogError("Generating a random seed failed");
381 runSeed = userRunSeed;
385 // Reset per-run counters
386 totalTestFailedCount = 0;
387 totalTestPassedCount = 0;
388 totalTestSkippedCount = 0;
390 // Take time - run start
391 runStartSeconds = GetClock();
393 // Log run with fuzzer parameters
394 SDLTest_Log("::::: Test Run /w seed '%s' started\n", runSeed);
396 // Initialize filtering
397 if (filter != NULL && SDL_strlen(filter) > 0) {
398 /* Loop over all suites to check if we have a filter match */
400 while (testSuites[suiteCounter] && suiteFilter == 0) {
401 testSuite=(SDLTest_TestSuiteReference *)testSuites[suiteCounter];
403 if (testSuite->name != NULL && SDL_strcmp(filter, testSuite->name) == 0) {
404 /* Matched a suite name */
406 suiteFilterName = testSuite->name;
407 SDLTest_Log("Filtering: running only suite '%s'", suiteFilterName);
411 /* Within each suite, loop over all test cases to check if we have a filter match */
413 while (testSuite->testCases[testCounter] && testFilter == 0)
415 testCase=(SDLTest_TestCaseReference *)testSuite->testCases[testCounter];
417 if (testCase->name != NULL && SDL_strcmp(filter, testCase->name) == 0) {
418 /* Matched a test name */
420 suiteFilterName = testSuite->name;
422 testFilterName = testCase->name;
423 SDLTest_Log("Filtering: running only test '%s' in suite '%s'", testFilterName, suiteFilterName);
429 if (suiteFilter == 0 && testFilter == 0) {
430 SDLTest_LogError("Filter '%s' did not match any test suite/case.", filter);
431 SDLTest_Log("Exit code: 2");
436 // Loop over all suites
438 while(testSuites[suiteCounter]) {
439 testSuite=(SDLTest_TestSuiteReference *)testSuites[suiteCounter];
440 currentSuiteName = (char *)((testSuite->name) ? testSuite->name : SDLTest_InvalidNameFormat);
443 // Filter suite if flag set and we have a name
444 if (suiteFilter == 1 && suiteFilterName != NULL && testSuite->name != NULL &&
445 SDL_strcmp(suiteFilterName, testSuite->name) != 0) {
447 SDLTest_Log("===== Test Suite %i: '%s' skipped\n",
452 // Reset per-suite counters
455 testSkippedCount = 0;
457 // Take time - suite start
458 suiteStartSeconds = GetClock();
461 SDLTest_Log("===== Test Suite %i: '%s' started\n",
465 // Loop over all test cases
467 while(testSuite->testCases[testCounter])
469 testCase=(SDLTest_TestCaseReference *)testSuite->testCases[testCounter];
470 currentTestName = (char *)((testCase->name) ? testCase->name : SDLTest_InvalidNameFormat);
473 // Filter tests if flag set and we have a name
474 if (testFilter == 1 && testFilterName != NULL && testCase->name != NULL &&
475 SDL_strcmp(testFilterName, testCase->name) != 0) {
477 SDLTest_Log("===== Test Case %i.%i: '%s' skipped\n",
483 // Take time - test start
484 testStartSeconds = GetClock();
487 SDLTest_Log("----- Test Case %i.%i: '%s' started",
491 if (testCase->description != NULL && SDL_strlen(testCase->description)>0) {
492 SDLTest_Log("Test Description: '%s'",
493 (testCase->description) ? testCase->description : SDLTest_InvalidNameFormat);
496 // Loop over all iterations
497 iterationCounter = 0;
498 while(iterationCounter < testIterations)
502 if (userExecKey != 0) {
503 execKey = userExecKey;
505 execKey = SDLTest_GenerateExecKey((char *)runSeed, testSuite->name, testCase->name, iterationCounter);
508 SDLTest_Log("Test Iteration %i: execKey %llu", iterationCounter, execKey);
509 testResult = SDLTest_RunTest(testSuite, testCase, execKey);
511 if (testResult == TEST_RESULT_PASSED) {
513 totalTestPassedCount++;
514 } else if (testResult == TEST_RESULT_SKIPPED) {
516 totalTestSkippedCount++;
519 totalTestFailedCount++;
523 // Take time - test end
524 testEndSeconds = GetClock();
525 runtime = testEndSeconds - testStartSeconds;
526 if (runtime < 0.0f) runtime = 0.0f;
528 if (testIterations > 1) {
530 SDLTest_Log("Runtime of %i iterations: %.1f sec", testIterations, runtime);
531 SDLTest_Log("Average Test runtime: %.5f sec", runtime / (float)testIterations);
534 SDLTest_Log("Total Test runtime: %.1f sec", runtime);
537 // Log final test result
538 switch (testResult) {
539 case TEST_RESULT_PASSED:
540 SDLTest_Log((char *)SDLTest_FinalResultFormat, "Test", currentTestName, "Passed");
542 case TEST_RESULT_FAILED:
543 SDLTest_LogError((char *)SDLTest_FinalResultFormat, "Test", currentTestName, "Failed");
545 case TEST_RESULT_NO_ASSERT:
546 SDLTest_LogError((char *)SDLTest_FinalResultFormat,"Test", currentTestName, "No Asserts");
553 // Take time - suite end
554 suiteEndSeconds = GetClock();
555 runtime = suiteEndSeconds - suiteStartSeconds;
556 if (runtime < 0.0f) runtime = 0.0f;
559 SDLTest_Log("Total Suite runtime: %.1f sec", runtime);
561 // Log summary and final Suite result
562 countSum = testPassedCount + testFailedCount + testSkippedCount;
563 if (testFailedCount == 0)
565 SDLTest_Log(logFormat, "Suite", countSum, testPassedCount, testFailedCount, testSkippedCount);
566 SDLTest_Log((char *)SDLTest_FinalResultFormat, "Suite", currentSuiteName, "Passed");
570 SDLTest_LogError(logFormat, "Suite", countSum, testPassedCount, testFailedCount, testSkippedCount);
571 SDLTest_LogError((char *)SDLTest_FinalResultFormat, "Suite", currentSuiteName, "Failed");
577 // Take time - run end
578 runEndSeconds = GetClock();
579 runtime = runEndSeconds - runStartSeconds;
580 if (runtime < 0.0f) runtime = 0.0f;
583 SDLTest_Log("Total Run runtime: %.1f sec", runtime);
585 // Log summary and final run result
586 countSum = totalTestPassedCount + totalTestFailedCount + totalTestSkippedCount;
587 if (totalTestFailedCount == 0)
590 SDLTest_Log(logFormat, "Run", countSum, totalTestPassedCount, totalTestFailedCount, totalTestSkippedCount);
591 SDLTest_Log((char *)SDLTest_FinalResultFormat, "Run /w seed", runSeed, "Passed");
596 SDLTest_LogError(logFormat, "Run", countSum, totalTestPassedCount, totalTestFailedCount, totalTestSkippedCount);
597 SDLTest_LogError((char *)SDLTest_FinalResultFormat, "Run /w seed", runSeed, "Failed");
600 SDLTest_Log("Exit code: %d", runResult);