/* pl_test.h Do this: #define PL_TEST_IMPLEMENTATION before you include this file in *one* C or C++ file to create the implementation. // i.e. it should look like this: #include ... #include ... #include ... #define PL_TEST_IMPLEMENTATION #include "pl_test.h" Notes: * for console color output on windows, define "PL_TEST_WIN32_COLOR" before including the implementation */ // library version (format XYYZZ) #define PL_TEST_VERSION "1.0.0" #define PL_TEST_VERSION_NUM 10000 /* Index of this file: // [SECTION] header mess // [SECTION] includes // [SECTION] public api // [SECTION] structs // [SECTION] private // [SECTION] c file */ //----------------------------------------------------------------------------- // [SECTION] header mess //----------------------------------------------------------------------------- #ifndef PL_TEST_H #define PL_TEST_H //----------------------------------------------------------------------------- // [SECTION] includes //----------------------------------------------------------------------------- #include // bool #include // uint32_t //----------------------------------------------------------------------------- // [SECTION] forward declarations & basic types //----------------------------------------------------------------------------- // forward declarations typedef struct _plTestContext plTestContext; typedef struct _plTestOptions plTestOptions; typedef void (*PL_TEST_FUNCTION)(void*); //----------------------------------------------------------------------------- // [SECTION] macros //----------------------------------------------------------------------------- #define pl_test_register_test(TEST, DATA) pl__test_register_test((TEST), (DATA), #TEST) //----------------------------------------------------------------------------- // [SECTION] public api //----------------------------------------------------------------------------- plTestContext* pl_create_test_context(plTestOptions); // tests void pl_test_run_suite(const char* pcSuiteName); bool pl_test_finish(void); // booleans bool pl_test_expect_true (bool bValue, const char* pcMsg); bool pl_test_expect_false(bool bValue, const char* pcMsg); // integers bool pl_test_expect_int_equal (int iValue0, int iValue1, const char* pcMsg); bool pl_test_expect_int_not_equal (int iValue0, int iValue1, const char* pcMsg); bool pl_test_expect_uint32_equal (uint32_t uValue0, uint32_t uValue1, const char* pcMsg); bool pl_test_expect_uint32_not_equal(uint32_t uValue0, uint32_t uValue1, const char* pcMsg); bool pl_test_expect_uint64_equal (uint64_t uValue0, uint64_t uValue1, const char* pcMsg); bool pl_test_expect_uint64_not_equal(uint64_t uValue0, uint64_t uValue1, const char* pcMsg); // floating point bool pl_test_expect_float_near_equal (float fValue0, float fValue1, float fError, const char* pcMsg); bool pl_test_expect_float_near_not_equal (float fValue0, float fValue1, float fError, const char* pcMsg); bool pl_test_expect_double_near_equal (double dValue0, double dValue1, double dError, const char* pcMsg); bool pl_test_expect_double_near_not_equal(double dValue0, double dValue1, double dError, const char* pcMsg); // strings bool pl_test_expect_string_equal (const char* pcValue0, const char* pcValue1, const char* pcMsg); bool pl_test_expect_string_not_equal(const char* pcValue0, const char* pcValue1, const char* pcMsg); //----------------------------------------------------------------------------- // [SECTION] structs //----------------------------------------------------------------------------- typedef struct _plTestOptions { bool bPrintSuiteResults; bool bPrintAllPassedChecks; bool bPrintColor; } plTestOptions; //----------------------------------------------------------------------------- // [SECTION] private //----------------------------------------------------------------------------- void pl__test_register_test(PL_TEST_FUNCTION tTest, void* pData, const char* pcName); #endif // PL_TEST_H //----------------------------------------------------------------------------- // [SECTION] c file //----------------------------------------------------------------------------- /* Index of this file: // [SECTION] header mess // [SECTION] includes // [SECTION] public api implementation */ //----------------------------------------------------------------------------- // [SECTION] header mess //----------------------------------------------------------------------------- #ifdef PL_TEST_IMPLEMENTATION #if defined(PL_TEST_WIN32_COLOR) && defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include static DWORD gtOriginalMode = 0; static HANDLE gtStdOutHandle = 0; static bool gbActiveColor = 0; #endif //----------------------------------------------------------------------------- // [SECTION] includes //----------------------------------------------------------------------------- #include #include #include //----------------------------------------------------------------------------- // [SECTION] internal api //----------------------------------------------------------------------------- static void pl__test_print_va (const char* cPFormat, va_list args); void static pl__test_print_red (const char* cPFormat, const char* pcMsg, ...); void static pl__test_print_green(const char* cPFormat, const char* pcMsg, ...); //----------------------------------------------------------------------------- // [SECTION] internal structs //----------------------------------------------------------------------------- typedef struct _plTest { bool bFailureOccured; const char* pcName; PL_TEST_FUNCTION tTest; void* pData; } plTest; typedef struct _plTestContext { plTest* atTests; plTest* ptCurrentTest; uint32_t uTestSize; uint32_t uTestCapacity; uint32_t uFailedTest; plTestOptions tOptions; uint32_t uTotalPassedTests; uint32_t uTotalFailedTests; } plTestContext; //----------------------------------------------------------------------------- // [SECTION] global context //----------------------------------------------------------------------------- plTestContext* gptTestContext = NULL; //----------------------------------------------------------------------------- // [SECTION] public api implementation //----------------------------------------------------------------------------- plTestContext* pl_create_test_context(plTestOptions tOptions) { #if defined(PL_TEST_WIN32_COLOR) && defined(_WIN32) DWORD tCurrentMode = 0; gbActiveColor = true; gtStdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE); if(gtStdOutHandle == INVALID_HANDLE_VALUE) gbActiveColor = false; else if(!GetConsoleMode(gtStdOutHandle, &tCurrentMode)) gbActiveColor = false; gtOriginalMode = tCurrentMode; tCurrentMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; // enable ANSI escape codes if(!SetConsoleMode(gtStdOutHandle, tCurrentMode)) gbActiveColor = false; if(!gbActiveColor) tOptions.bPrintColor = false; #endif gptTestContext = (plTestContext*)malloc(sizeof(plTestContext)); memset(gptTestContext, 0, sizeof(plTestContext)); gptTestContext->uTestCapacity = 64; gptTestContext->tOptions = tOptions; gptTestContext->atTests = (plTest*)malloc(gptTestContext->uTestCapacity * sizeof(plTest)); memset(gptTestContext->atTests, 0, sizeof(gptTestContext->uTestCapacity * sizeof(plTest))); return gptTestContext; } void pl__test_register_test(PL_TEST_FUNCTION tTest, void* pData, const char* pcName) { gptTestContext->uTestSize++; if(gptTestContext->uTestSize == gptTestContext->uTestCapacity) { plTest* atOldTests = gptTestContext->atTests; gptTestContext->atTests = (plTest*)malloc(gptTestContext->uTestCapacity * sizeof(plTest) * 2); memset(gptTestContext->atTests, 0, sizeof(gptTestContext->uTestCapacity * sizeof(plTest) * 2)); memcpy(gptTestContext->atTests, atOldTests, gptTestContext->uTestCapacity * sizeof(plTest)); free(atOldTests); gptTestContext->uTestCapacity *= 2; } gptTestContext->atTests[gptTestContext->uTestSize - 1].pcName = pcName; gptTestContext->atTests[gptTestContext->uTestSize - 1].tTest = tTest; gptTestContext->atTests[gptTestContext->uTestSize - 1].bFailureOccured = false; gptTestContext->atTests[gptTestContext->uTestSize - 1].pData = pData; } void pl_test_run_suite(const char* pcSuiteName) { printf("\n------%s suite------\n\n", pcSuiteName); for(uint32_t i = 0; i < gptTestContext->uTestSize; i++) { gptTestContext->ptCurrentTest = &gptTestContext->atTests[i]; gptTestContext->ptCurrentTest->bFailureOccured = false; printf("running -> \"%s\"\n", gptTestContext->ptCurrentTest->pcName); gptTestContext->ptCurrentTest->tTest(gptTestContext->ptCurrentTest->pData); if(gptTestContext->ptCurrentTest->bFailureOccured) { pl__test_print_red("%s", NULL, " -> failed"); gptTestContext->uFailedTest++; } else pl__test_print_green("%s", NULL, " passed"); } if(gptTestContext->tOptions.bPrintSuiteResults) { printf("\nPassed: "); pl__test_print_green("%u", NULL, gptTestContext->uTestSize - gptTestContext->uFailedTest); printf("Failed: "); pl__test_print_red("%u", NULL, gptTestContext->uFailedTest); } // printf("\n------End tests------\n\n"); // reset context gptTestContext->uTotalPassedTests += gptTestContext->uTestSize - gptTestContext->uFailedTest; gptTestContext->uTotalFailedTests += gptTestContext->uFailedTest; gptTestContext->uTestSize = 0; gptTestContext->uFailedTest = 0; gptTestContext->ptCurrentTest = NULL; memset(gptTestContext->atTests, 0, sizeof(plTest) * gptTestContext->uTestCapacity); } bool pl_test_finish(void) { printf("\n------Results------\n"); printf("\nTests passed: "); pl__test_print_green("%u", NULL, gptTestContext->uTotalPassedTests); printf("Tests failed: "); pl__test_print_red("%u", NULL, gptTestContext->uTotalFailedTests); #if defined(PL_TEST_WIN32_COLOR) && defined(_WIN32) if(gbActiveColor) { if(!SetConsoleMode(gtStdOutHandle, gtOriginalMode)) exit(GetLastError()); } #endif return gptTestContext->uTotalFailedTests == 0; } bool pl_test_expect_true(bool bValue, const char* pcMsg) { if(bValue) { if(gptTestContext->tOptions.bPrintAllPassedChecks) pl__test_print_green("Value: true | Expected Value: true", pcMsg); return true; } pl__test_print_red("Value: false | Expected Value: true", pcMsg); gptTestContext->ptCurrentTest->bFailureOccured = true; return false; } bool pl_test_expect_false(bool bValue, const char* pcMsg) { if(bValue) { pl__test_print_red("Value: true | Expected Value: false", pcMsg); gptTestContext->ptCurrentTest->bFailureOccured = true; return false; } if(gptTestContext->tOptions.bPrintAllPassedChecks) pl__test_print_green("Value: false | Expected Value: false", pcMsg); return true; } #define pl__test_expect_equal(value0, value1, pcMsg, format) \ if((value0) == (value1)) \ { \ if(gptTestContext->tOptions.bPrintAllPassedChecks) \ pl__test_print_green(format " equals " format " | Equality Expected", (pcMsg), (value0), (value1)); \ return true; \ } \ pl__test_print_red(format " does not equal " format " | Equality Expected", (pcMsg), (value0), (value1)); \ gptTestContext->ptCurrentTest->bFailureOccured = true; \ return false; #define pl__test_expect_not_equal(value0, value1, pcMsg, format) \ if((value0) == (value1)) \ { \ pl__test_print_red(format " equals " format " | Equality Not Expected", (pcMsg), (value0), (value1)); \ gptTestContext->ptCurrentTest->bFailureOccured = true; \ return false; \ } \ if(gptTestContext->tOptions.bPrintAllPassedChecks) \ pl__test_print_green(format " does not equal " format " | Equality Expected", (pcMsg), (value0), (value1)); \ return true; bool pl_test_expect_int_equal(int iValue0, int iValue1, const char* pcMsg) { pl__test_expect_equal(iValue0, iValue1, pcMsg, "%i"); } bool pl_test_expect_int_not_equal(int iValue0, int iValue1, const char* pcMsg) { pl__test_expect_not_equal(iValue0, iValue1, pcMsg, "%i"); } bool pl_test_expect_uint64_equal(uint64_t uValue0, uint64_t uValue1, const char* pcMsg) { pl__test_expect_equal(uValue0, uValue1, pcMsg, "%llu"); } bool pl_test_expect_uint64_not_equal(uint64_t uValue0, uint64_t uValue1, const char* pcMsg) { pl__test_expect_not_equal(uValue0, uValue1, pcMsg, "%llu"); } bool pl_test_expect_uint32_equal(uint32_t uValue0, uint32_t uValue1, const char* pcMsg) { pl__test_expect_equal(uValue0, uValue1, pcMsg, "%u"); } bool pl_test_expect_uint32_not_equal(uint32_t uValue0, uint32_t uValue1, const char* pcMsg) { pl__test_expect_not_equal(uValue0, uValue1, pcMsg, "%u"); } bool pl_test_expect_float_near_equal(float fValue0, float fValue1, float fError, const char* pcMsg) { return pl_test_expect_double_near_equal((double)fValue0, (double)fValue1, (double)fError, pcMsg); } bool pl_test_expect_float_near_not_equal(float fValue0, float fValue1, float fError, const char* pcMsg) { return pl_test_expect_double_near_not_equal((double)fValue0, (double)fValue1, (double)fError, pcMsg); } bool pl_test_expect_double_near_equal(double dValue0, double dValue1, double dError, const char* pcMsg) { if(dValue0 >= dValue1 - dError && dValue0 <= dValue1 + dError) { if(gptTestContext->tOptions.bPrintAllPassedChecks) pl__test_print_green("%0.6f equals %0.6f | Equality Expected within %0.6f", pcMsg, dValue0, dValue1, dError); return true; } pl__test_print_red("%0.6f does not equal %0.6f | Equality Expected within %0.6f", pcMsg, dValue0, dValue1, dError); gptTestContext->ptCurrentTest->bFailureOccured = true; return false; } bool pl_test_expect_double_near_not_equal(double dValue0, double dValue1, double dError, const char* pcMsg) { if(dValue0 >= dValue1 - dError && dValue0 <= dValue1 + dError) { pl__test_print_red("%0.f equals %0.6f | Equality Not Expected within %0.6f", pcMsg, dValue0, dValue1, dError); gptTestContext->ptCurrentTest->bFailureOccured = true; return false; } if(gptTestContext->tOptions.bPrintAllPassedChecks) pl__test_print_green("%0.6f does not equal %0.6f | Equality Not Expected within %0.6f", pcMsg, dValue0, dValue1, dError); return true; } bool pl_test_expect_string_equal(const char* pcValue0, const char* pcValue1, const char* pcMsg) { if(strcmp(pcValue0, pcValue1) == 0) { if(gptTestContext->tOptions.bPrintAllPassedChecks) pl__test_print_green("\"%s\" equals \"%s\" | Equality Expected", pcMsg, pcValue0, pcValue1); return true; } pl__test_print_red("\"%s\" does not equal \"%s\" | Equality Expected", pcMsg, pcValue0, pcValue1); gptTestContext->ptCurrentTest->bFailureOccured = true; return false; } bool pl_test_expect_string_not_equal(const char* pcValue0, const char* pcValue1, const char* pcMsg) { if(strcmp(pcValue0, pcValue1) == 0) { if(gptTestContext->tOptions.bPrintAllPassedChecks) pl__test_print_green("\"%s\" equals \"%s\" | Equality Not Expected", pcMsg, pcValue0, pcValue1); gptTestContext->ptCurrentTest->bFailureOccured = true; return false; } pl__test_print_red("\"%s\" does not equal \"%s\" | Equality Not Expected", pcMsg, pcValue0, pcValue1); return true; } //----------------------------------------------------------------------------- // [SECTION] internal api implementation //----------------------------------------------------------------------------- static void pl__test_print_va(const char* cPFormat, va_list args) { char dest[1024]; vsnprintf(dest, 1024, cPFormat, args); printf("%s", dest); } void static pl__test_print_red(const char* cPFormat, const char* pcMsg, ...) { if(gptTestContext->tOptions.bPrintColor) { #ifdef _WIN32 printf(""); #else printf("\033[91m"); #endif } va_list argptr; va_start(argptr, pcMsg); pl__test_print_va(cPFormat, argptr); va_end(argptr); if(pcMsg) printf(" %s\n", pcMsg); else printf("\n"); if(gptTestContext->tOptions.bPrintColor) { #ifdef _WIN32 printf(""); #else printf("\033[0m"); #endif } } static void pl__test_print_green(const char* cPFormat, const char* pcMsg, ...) { if(gptTestContext->tOptions.bPrintColor) { #ifdef _WIN32 printf(""); #else printf("\033[92m"); #endif } va_list argptr; va_start(argptr, pcMsg); pl__test_print_va(cPFormat, argptr); va_end(argptr); if(pcMsg) printf(" %s\n", pcMsg); else printf("\n"); if(gptTestContext->tOptions.bPrintColor) { #ifdef _WIN32 printf(""); #else printf("\033[0m"); #endif } } #endif // PL_TEST_IMPLEMENTATION