tester: Update the testing library

This commit is contained in:
doyle 2022-06-27 23:39:24 +10:00
parent f629c019e6
commit 2665e315e6
2 changed files with 120 additions and 82 deletions

View File

@ -15,7 +15,8 @@ pushd Build
REM Z7 Combine multi-debug files to one debug file REM Z7 Combine multi-debug files to one debug file
REM wd4201 Nonstandard extension used: nameless struct/union REM wd4201 Nonstandard extension used: nameless struct/union
REM Tp Treat header file as CPP source file 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 msvc_flags=-fsanitize=address -D STBSP__ASAN=__declspec(no_sanitize_address)
set clang_flags=-fsanitize=address -fsanitize=undefined set clang_flags=-fsanitize=address -fsanitize=undefined
@ -29,7 +30,7 @@ pushd Build
if not exist msvc mkdir msvc if not exist msvc mkdir msvc
pushd msvc pushd msvc
cl %msvc_flags% %flags% cl %compile_flags% %msvc_flags% %code_dir%Dqn_Tests.cpp %link_flags%
popd popd
REM ------------------------------------------------------------------------ REM ------------------------------------------------------------------------
@ -42,6 +43,6 @@ pushd Build
if not exist clang mkdir clang if not exist clang mkdir clang
pushd clang pushd clang
clang-cl %clang_flags% %flags% clang-cl %compile_flags% %clang_flags% %code_dir%Dqn_Tests.cpp %link_flags%
popd popd
popd popd

View File

@ -1,12 +1,11 @@
#if !defined(DQN_TESTER_H) #if !defined(DQN_TESTER_H)
#define DQN_TESTER_H #define DQN_TESTER_H
// ----------------------------------------------------------------------------- //
// NOTE: Overview // NOTE: Overview
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// A super minimal testing framework, most of the logic here is the pretty // A super minimal testing framework, most of the logic here is the pretty
// printing of test results. // printing of test results.
// -----------------------------------------------------------------------------
// NOTE: Configuration // NOTE: Configuration
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// #define DQN_TESTER_IMPLEMENTATION // #define DQN_TESTER_IMPLEMENTATION
@ -34,11 +33,11 @@
// Define this to a terminal color code to specify what color sucess will be // Define this to a terminal color code to specify what color sucess will be
// presented as. // presented as.
// -----------------------------------------------------------------------------
// NOTE: Macros // NOTE: Macros
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <assert.h>
#if !defined(DQN_TESTER_RESULT_LPAD) #if !defined(DQN_TESTER_RESULT_LPAD)
#define DQN_TESTER_RESULT_LPAD 90 #define DQN_TESTER_RESULT_LPAD 90
@ -63,99 +62,115 @@
#define DQN_TESTER_COLOR_RESET "\x1b[0m" #define DQN_TESTER_COLOR_RESET "\x1b[0m"
#define DQN_TESTER_BEGIN_GROUP(fmt, ...) fprintf(stdout, fmt "\n", ##__VA_ARGS__) #define DQN_TESTER_BEGIN_GROUP(fmt, ...) fprintf(stdout, fmt "\n", ##__VA_ARGS__)
#define DQN_TESTER_END_GROUP(test) \ #define DQN_TESTER_END_GROUP(test) \
do \ do { \
{ \ bool all_clear = (test)->num_tests_ok_in_group == (test)->num_tests_in_group; \
bool all_clear = (test)->num_tests_ok_in_group == (test)->num_tests_in_group; \ fprintf(stdout, \
fprintf(stdout, \ "%s\n %02d/%02d tests passed -- %s\n\n" DQN_TESTER_COLOR_RESET, \
"%s\n %02d/%02d tests passed -- %s\n\n" DQN_TESTER_COLOR_RESET, \ all_clear ? DQN_TESTER_GOOD_COLOR : DQN_TESTER_BAD_COLOR, \
all_clear ? DQN_TESTER_GOOD_COLOR : DQN_TESTER_BAD_COLOR, \ (test)->num_tests_ok_in_group, \
(test)->num_tests_ok_in_group, \ (test)->num_tests_in_group, \
(test)->num_tests_in_group, \ all_clear ? "OK" : "FAILED"); \
all_clear ? "OK" : "FAILED"); \
} while (0) } 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_ASSERT(test, expr) DQN_TESTER_ASSERT_AT((test), __FILE__, __LINE__, (expr))
#define DQN_TESTER_LOG(test, fmt, ...) \ #define DQN_TESTER_LOG(test, fmt, ...) \
do \ do { \
{ \ if ((test)->log_count++ == 0) { \
if ((test)->log_count++ == 0) fprintf(stdout, "\n"); \ fprintf(stdout, "\n"); \
fprintf(stdout, "%*s" fmt "\n", DQN_TESTER_SPACING * 2, "", ##__VA_ARGS__); \ } \
fprintf(stdout, "%*sLog: " fmt "\n", DQN_TESTER_SPACING * 2, "", ##__VA_ARGS__); \
} while (0) } while (0)
#define DQN_TESTER_ASSERTF_AT(test, file, line, expr, fmt, ...) \ #define DQN_TESTER_ASSERTF_AT(test, file, line, expr, fmt, ...) \
do \ do { \
{ \ if (!(expr)) { \
if (!(expr)) \ if ((test)->log_count++ == 0) { \
{ \ fprintf(stdout, "\n"); \
if ((test)->log_count++ == 0) fprintf(stdout, "\n"); \ } \
(test)->failed = true; \ (test)->state = Dqn_TesterState_TestFailed; \
fprintf(stderr, \ fprintf(stderr, \
"%*sFile: %s:%d\n" \ "%*sAssertion Triggered\n" \
"%*sExpression: [" #expr "]\n" \ "%*sFile: %s:%d\n" \
"%*sReason: " fmt "\n", \ "%*sExpression: [" #expr "]\n" \
DQN_TESTER_SPACING * 2, \ "%*sReason: " fmt "\n", \
"", \ DQN_TESTER_SPACING * 2, \
file, \ "", \
line, \ DQN_TESTER_SPACING * 3, \
DQN_TESTER_SPACING * 2, \ "", \
"", \ file, \
DQN_TESTER_SPACING * 2, \ line, \
"", \ DQN_TESTER_SPACING * 3, \
##__VA_ARGS__); \ "", \
} \ DQN_TESTER_SPACING * 3, \
"", \
##__VA_ARGS__); \
} \
} while (0) } while (0)
#define DQN_TESTER_ASSERT_AT(test, file, line, expr) \ #define DQN_TESTER_ASSERT_AT(test, file, line, expr) \
do \ do { \
{ \ if (!(expr)) { \
if (!(expr)) \ if ((test)->log_count++ == 0) { \
{ \ fprintf(stdout, "\n"); \
if ((test)->log_count++ == 0) fprintf(stdout, "\n"); \ } \
(test)->failed = true; \ (test)->state = Dqn_TesterState_TestFailed; \
fprintf(stderr, \ fprintf(stderr, \
"%*sFile: %s:%d\n" \ "%*sFile: %s:%d\n" \
"%*sExpression: [" #expr "]\n", \ "%*sExpression: [" #expr "]\n", \
DQN_TESTER_SPACING * 2, \ DQN_TESTER_SPACING * 2, \
"", \ "", \
file, \ file, \
line, \ line, \
DQN_TESTER_SPACING * 2, \ DQN_TESTER_SPACING * 2, \
""); \ ""); \
} \ } \
} while (0) } while (0)
// -----------------------------------------------------------------------------
// NOTE: Header // NOTE: Header
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
typedef struct Dqn_Tester enum Dqn_TesterState {
{ Dqn_TesterState_Nil,
int num_tests_in_group; Dqn_TesterState_TestBegun,
int num_tests_ok_in_group; Dqn_TesterState_TestFailed,
int log_count; };
bool failed;
typedef struct Dqn_Tester {
int num_tests_in_group;
int num_tests_ok_in_group;
int log_count;
Dqn_TesterState state;
} Dqn_Tester; } 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_TesterBegin(Dqn_Tester *test, char const *fmt, ...);
void Dqn_TesterEnd(Dqn_Tester *test); 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 #endif // DQN_TESTER_H
// -----------------------------------------------------------------------------
// NOTE: Implementation // NOTE: Implementation
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if defined(DQN_TESTER_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++; assert(test->state == Dqn_TesterState_Nil &&
test->failed = false; "Nesting a unit test within another unit test is not allowed, ensure"
test->log_count = 0; "the first test has finished by calling Dqn_TesterEnd");
va_list args; test->num_tests_in_group++;
va_start(args, fmt); test->state = Dqn_TesterState_TestBegun;
test->log_count = 0;
int size_required = 0; int size_required = 0;
{ {
@ -169,33 +184,55 @@ void Dqn_TesterBegin(Dqn_Tester *test, char const *fmt, ...)
vprintf(fmt, args); vprintf(fmt, args);
for (int pad = DQN_TESTER_SPACING + size_required; pad < DQN_TESTER_RESULT_LPAD; pad++) for (int pad = DQN_TESTER_SPACING + size_required; pad < DQN_TESTER_RESULT_LPAD; pad++)
putc(DQN_TESTER_RESULT_PAD_CHAR, stdout); 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); va_end(args);
} }
void Dqn_TesterEnd(Dqn_Tester *test) 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, // 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 // but if there were logs printed throughout the test then we must print
// the result on a new line. // the result on a new line.
printf("%*s", DQN_TESTER_SPACING, ""); printf("%*s", DQN_TESTER_SPACING, "");
for (int pad = DQN_TESTER_SPACING; pad < DQN_TESTER_RESULT_LPAD; pad++) for (int pad = DQN_TESTER_SPACING; pad < DQN_TESTER_RESULT_LPAD; pad++)
putc(DQN_TESTER_RESULT_PAD_CHAR, stdout); putc(DQN_TESTER_RESULT_PAD_CHAR, stdout);
} }
if (test->failed) if (test->state == Dqn_TesterState_TestFailed) {
fprintf(stdout, DQN_TESTER_BAD_COLOR " FAILED"); fprintf(stdout, DQN_TESTER_BAD_COLOR " FAILED");
else } else {
{
fprintf(stdout, DQN_TESTER_GOOD_COLOR " OK"); fprintf(stdout, DQN_TESTER_GOOD_COLOR " OK");
test->num_tests_ok_in_group++; test->num_tests_ok_in_group++;
} }
fprintf(stdout, DQN_TESTER_COLOR_RESET "\n"); fprintf(stdout, DQN_TESTER_COLOR_RESET "\n");
if (test->log_count != 0) {
if (test->log_count != 0)
putc('\n', stdout); 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 #endif // DQN_TESTER_IMPLEMENTATION