diff --git a/build.bat b/build.bat index d460c57..1c57b1b 100644 --- a/build.bat +++ b/build.bat @@ -15,7 +15,8 @@ pushd Build REM Z7 Combine multi-debug files to one debug file REM wd4201 Nonstandard extension used: nameless struct/union REM Tp Treat header file as CPP source file - set flags=-MT -EHa -GR- -Od -Oi -Z7 -wd4201 -D DQN_TEST_WITH_MAIN %code_dir%Dqn_Tests.cpp -link -nologo + set compile_flags=-MT -EHa -GR- -Od -Oi -Z7 -wd4201 -D DQN_TEST_WITH_MAIN + set linker_flags=-link -nologo set msvc_flags=-fsanitize=address -D STBSP__ASAN=__declspec(no_sanitize_address) set clang_flags=-fsanitize=address -fsanitize=undefined @@ -29,7 +30,7 @@ pushd Build if not exist msvc mkdir msvc pushd msvc - cl %msvc_flags% %flags% + cl %compile_flags% %msvc_flags% %code_dir%Dqn_Tests.cpp %link_flags% popd REM ------------------------------------------------------------------------ @@ -42,6 +43,6 @@ pushd Build if not exist clang mkdir clang pushd clang - clang-cl %clang_flags% %flags% + clang-cl %compile_flags% %clang_flags% %code_dir%Dqn_Tests.cpp %link_flags% popd popd diff --git a/dqn_tester.h b/dqn_tester.h index 45f992a..ef3940d 100644 --- a/dqn_tester.h +++ b/dqn_tester.h @@ -1,12 +1,11 @@ #if !defined(DQN_TESTER_H) #define DQN_TESTER_H -// ----------------------------------------------------------------------------- +// // NOTE: Overview // ----------------------------------------------------------------------------- // A super minimal testing framework, most of the logic here is the pretty // printing of test results. -// ----------------------------------------------------------------------------- // NOTE: Configuration // ----------------------------------------------------------------------------- // #define DQN_TESTER_IMPLEMENTATION @@ -34,11 +33,11 @@ // Define this to a terminal color code to specify what color sucess will be // presented as. -// ----------------------------------------------------------------------------- // NOTE: Macros // ----------------------------------------------------------------------------- #include #include +#include #if !defined(DQN_TESTER_RESULT_LPAD) #define DQN_TESTER_RESULT_LPAD 90 @@ -63,99 +62,115 @@ #define DQN_TESTER_COLOR_RESET "\x1b[0m" #define DQN_TESTER_BEGIN_GROUP(fmt, ...) fprintf(stdout, fmt "\n", ##__VA_ARGS__) -#define DQN_TESTER_END_GROUP(test) \ - do \ - { \ - bool all_clear = (test)->num_tests_ok_in_group == (test)->num_tests_in_group; \ - fprintf(stdout, \ - "%s\n %02d/%02d tests passed -- %s\n\n" DQN_TESTER_COLOR_RESET, \ - all_clear ? DQN_TESTER_GOOD_COLOR : DQN_TESTER_BAD_COLOR, \ - (test)->num_tests_ok_in_group, \ - (test)->num_tests_in_group, \ - all_clear ? "OK" : "FAILED"); \ +#define DQN_TESTER_END_GROUP(test) \ + do { \ + bool all_clear = (test)->num_tests_ok_in_group == (test)->num_tests_in_group; \ + fprintf(stdout, \ + "%s\n %02d/%02d tests passed -- %s\n\n" DQN_TESTER_COLOR_RESET, \ + all_clear ? DQN_TESTER_GOOD_COLOR : DQN_TESTER_BAD_COLOR, \ + (test)->num_tests_ok_in_group, \ + (test)->num_tests_in_group, \ + all_clear ? "OK" : "FAILED"); \ } while (0) -#define DQN_TESTER_ASSERTF(test, expr, fmt, ...) DQN_TESTER_ASSERTF_AT((test), __FILE__, __LINE__, (expr), fmt, ## __VA_ARGS__) +#define DQN_TESTER_ASSERTF(test, expr, fmt, ...) \ + DQN_TESTER_ASSERTF_AT((test), __FILE__, __LINE__, (expr), fmt, ##__VA_ARGS__) #define DQN_TESTER_ASSERT(test, expr) DQN_TESTER_ASSERT_AT((test), __FILE__, __LINE__, (expr)) -#define DQN_TESTER_LOG(test, fmt, ...) \ - do \ - { \ - if ((test)->log_count++ == 0) fprintf(stdout, "\n"); \ - fprintf(stdout, "%*s" fmt "\n", DQN_TESTER_SPACING * 2, "", ##__VA_ARGS__); \ +#define DQN_TESTER_LOG(test, fmt, ...) \ + do { \ + if ((test)->log_count++ == 0) { \ + fprintf(stdout, "\n"); \ + } \ + fprintf(stdout, "%*sLog: " fmt "\n", DQN_TESTER_SPACING * 2, "", ##__VA_ARGS__); \ } while (0) -#define DQN_TESTER_ASSERTF_AT(test, file, line, expr, fmt, ...) \ - do \ - { \ - if (!(expr)) \ - { \ - if ((test)->log_count++ == 0) fprintf(stdout, "\n"); \ - (test)->failed = true; \ - fprintf(stderr, \ - "%*sFile: %s:%d\n" \ - "%*sExpression: [" #expr "]\n" \ - "%*sReason: " fmt "\n", \ - DQN_TESTER_SPACING * 2, \ - "", \ - file, \ - line, \ - DQN_TESTER_SPACING * 2, \ - "", \ - DQN_TESTER_SPACING * 2, \ - "", \ - ##__VA_ARGS__); \ - } \ +#define DQN_TESTER_ASSERTF_AT(test, file, line, expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + if ((test)->log_count++ == 0) { \ + fprintf(stdout, "\n"); \ + } \ + (test)->state = Dqn_TesterState_TestFailed; \ + fprintf(stderr, \ + "%*sAssertion Triggered\n" \ + "%*sFile: %s:%d\n" \ + "%*sExpression: [" #expr "]\n" \ + "%*sReason: " fmt "\n", \ + DQN_TESTER_SPACING * 2, \ + "", \ + DQN_TESTER_SPACING * 3, \ + "", \ + file, \ + line, \ + DQN_TESTER_SPACING * 3, \ + "", \ + DQN_TESTER_SPACING * 3, \ + "", \ + ##__VA_ARGS__); \ + } \ } while (0) -#define DQN_TESTER_ASSERT_AT(test, file, line, expr) \ - do \ - { \ - if (!(expr)) \ - { \ - if ((test)->log_count++ == 0) fprintf(stdout, "\n"); \ - (test)->failed = true; \ - fprintf(stderr, \ - "%*sFile: %s:%d\n" \ - "%*sExpression: [" #expr "]\n", \ - DQN_TESTER_SPACING * 2, \ - "", \ - file, \ - line, \ - DQN_TESTER_SPACING * 2, \ - ""); \ - } \ +#define DQN_TESTER_ASSERT_AT(test, file, line, expr) \ + do { \ + if (!(expr)) { \ + if ((test)->log_count++ == 0) { \ + fprintf(stdout, "\n"); \ + } \ + (test)->state = Dqn_TesterState_TestFailed; \ + fprintf(stderr, \ + "%*sFile: %s:%d\n" \ + "%*sExpression: [" #expr "]\n", \ + DQN_TESTER_SPACING * 2, \ + "", \ + file, \ + line, \ + DQN_TESTER_SPACING * 2, \ + ""); \ + } \ } while (0) -// ----------------------------------------------------------------------------- // NOTE: Header // ----------------------------------------------------------------------------- -typedef struct Dqn_Tester -{ - int num_tests_in_group; - int num_tests_ok_in_group; - int log_count; - bool failed; +enum Dqn_TesterState { + Dqn_TesterState_Nil, + Dqn_TesterState_TestBegun, + Dqn_TesterState_TestFailed, +}; + +typedef struct Dqn_Tester { + int num_tests_in_group; + int num_tests_ok_in_group; + int log_count; + Dqn_TesterState state; } Dqn_Tester; +void Dqn_TesterBeginV(Dqn_Tester *test, char const *fmt, va_list args); void Dqn_TesterBegin(Dqn_Tester *test, char const *fmt, ...); void Dqn_TesterEnd(Dqn_Tester *test); +#if defined(__cplusplus) +struct Dqn_TesterBeginScopedTest { + Dqn_TesterBeginScopedTest(Dqn_Tester *test, char const *fmt, ...); + ~Dqn_TesterBeginScopedTest(); + Dqn_Tester *test; +}; +#endif // __cplusplus #endif // DQN_TESTER_H -// ----------------------------------------------------------------------------- // NOTE: Implementation // ----------------------------------------------------------------------------- #if defined(DQN_TESTER_IMPLEMENTATION) -void Dqn_TesterBegin(Dqn_Tester *test, char const *fmt, ...) +void Dqn_TesterBeginV(Dqn_Tester *test, char const *fmt, va_list args) { - test->num_tests_in_group++; - test->failed = false; - test->log_count = 0; + assert(test->state == Dqn_TesterState_Nil && + "Nesting a unit test within another unit test is not allowed, ensure" + "the first test has finished by calling Dqn_TesterEnd"); - va_list args; - va_start(args, fmt); + test->num_tests_in_group++; + test->state = Dqn_TesterState_TestBegun; + test->log_count = 0; int size_required = 0; { @@ -169,33 +184,55 @@ void Dqn_TesterBegin(Dqn_Tester *test, char const *fmt, ...) vprintf(fmt, args); for (int pad = DQN_TESTER_SPACING + size_required; pad < DQN_TESTER_RESULT_LPAD; pad++) putc(DQN_TESTER_RESULT_PAD_CHAR, stdout); +} + +void Dqn_TesterBegin(Dqn_Tester *test, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_TesterBeginV(test, fmt, args); va_end(args); } void Dqn_TesterEnd(Dqn_Tester *test) { - if (test->log_count != 0) - { + assert(test->state != Dqn_TesterState_Nil && "Test was marked as ended but a test was never commenced using Dqn_TesterBegin"); + if (test->log_count != 0) { // NOTE: We try and print the result on the same line as the test name, // but if there were logs printed throughout the test then we must print // the result on a new line. - printf("%*s", DQN_TESTER_SPACING, ""); for (int pad = DQN_TESTER_SPACING; pad < DQN_TESTER_RESULT_LPAD; pad++) putc(DQN_TESTER_RESULT_PAD_CHAR, stdout); } - if (test->failed) + if (test->state == Dqn_TesterState_TestFailed) { fprintf(stdout, DQN_TESTER_BAD_COLOR " FAILED"); - else - { + } else { fprintf(stdout, DQN_TESTER_GOOD_COLOR " OK"); test->num_tests_ok_in_group++; } fprintf(stdout, DQN_TESTER_COLOR_RESET "\n"); - - if (test->log_count != 0) + if (test->log_count != 0) { putc('\n', stdout); + } + + test->state = Dqn_TesterState_Nil; } + +#if defined(__cplusplus) +Dqn_TesterBeginScopedTest::Dqn_TesterBeginScopedTest(Dqn_Tester *test, char const *fmt, ...) +: test(test) +{ + va_list args; + va_start(args, fmt); + Dqn_TesterBeginV(test, fmt, args); + va_end(args); +} + +Dqn_TesterBeginScopedTest::~Dqn_TesterBeginScopedTest() { + Dqn_TesterEnd(test); +} +#endif // __cplusplus #endif // DQN_TESTER_IMPLEMENTATION