Dqn/Standalone/dqn_utest.h

231 lines
8.6 KiB
C
Raw Normal View History

2025-02-14 00:27:42 +11:00
#if !defined(DN_UTEST_H)
#define DN_UTEST_H
/*
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$$\
// $$ | $$ |\__$$ __|$$ _____|$$ __$$\\__$$ __|
// $$ | $$ | $$ | $$ | $$ / \__| $$ |
// $$ | $$ | $$ | $$$$$\ \$$$$$$\ $$ |
// $$ | $$ | $$ | $$ __| \____$$\ $$ |
// $$ | $$ | $$ | $$ | $$\ $$ | $$ |
// \$$$$$$ | $$ | $$$$$$$$\ \$$$$$$ | $$ |
// \______/ \__| \________| \______/ \__|
//
2025-02-14 00:27:42 +11:00
// dn_utest.h -- Extremely minimal unit testing framework
//
////////////////////////////////////////////////////////////////////////////////////////////////////
2023-06-08 22:07:55 +10:00
//
// A super minimal testing framework, most of the logic here is the pretty
// printing of test results.
//
// NOTE: Configuration /////////////////////////////////////////////////////////////////////////////
2023-08-26 00:00:44 +10:00
//
2025-02-14 00:27:42 +11:00
// #define DN_UTEST_IMPLEMENTATION
2023-06-08 22:07:55 +10:00
// Define this in one and only one C++ file to enable the implementation
// code of the header file. This will also automatically enable the JSMN
// implementation.
//
2025-02-14 00:27:42 +11:00
// #define DN_UTEST_RESULT_LPAD
2023-06-08 22:07:55 +10:00
// Define this to a number to specify how much to pad the output of the test
// result line before the test result is printed.
//
2025-02-14 00:27:42 +11:00
// #define DN_UTEST_RESULT_PAD_CHAR
2023-06-08 22:07:55 +10:00
// Define this to a character to specify the default character to use for
// padding. By default this is '.'
//
2025-02-14 00:27:42 +11:00
// #define DN_UTEST_SPACING
2023-06-08 22:07:55 +10:00
// Define this to a number to specify the number of spaces between the group
// declaration and the test output in the group.
//
2025-02-14 00:27:42 +11:00
// #define DN_UTEST_BAD_COLOR
2023-06-08 22:07:55 +10:00
// Define this to a terminal color code to specify what color errors will be
// presented as.
//
2025-02-14 00:27:42 +11:00
// #define DN_UTEST_GOOD_COLOR
2023-06-08 22:07:55 +10:00
// Define this to a terminal color code to specify what color sucess will be
// presented as.
//
////////////////////////////////////////////////////////////////////////////////////////////////////
*/
2023-06-08 22:07:55 +10:00
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
2023-06-08 22:07:55 +10:00
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
#if !defined(DN_UTEST_RESULT_LPAD)
#define DN_UTEST_RESULT_LPAD 90
2023-06-08 22:07:55 +10:00
#endif
2025-02-14 00:27:42 +11:00
#if !defined(DN_UTEST_RESULT_PAD_CHAR)
#define DN_UTEST_RESULT_PAD_CHAR '.'
2023-06-08 22:07:55 +10:00
#endif
2025-02-14 00:27:42 +11:00
#if !defined(DN_UTEST_SPACING)
#define DN_UTEST_SPACING 2
2023-06-08 22:07:55 +10:00
#endif
2025-02-14 00:27:42 +11:00
#if !defined(DN_UTEST_BAD_COLOR)
#define DN_UTEST_BAD_COLOR "\x1b[31m"
2023-06-08 22:07:55 +10:00
#endif
2025-02-14 00:27:42 +11:00
#if !defined(DN_UTEST_GOOD_COLOR)
#define DN_UTEST_GOOD_COLOR "\x1b[32m"
2023-06-08 22:07:55 +10:00
#endif
2025-02-14 00:27:42 +11:00
#define DN_UTEST_COLOR_RESET "\x1b[0m"
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
#define DN_UTEST_GROUP(test, fmt, ...) \
for (DN_UTest *test_var_ = (printf(fmt "\n", ## __VA_ARGS__), &test); \
2023-06-08 22:07:55 +10:00
test_var_ != nullptr; \
2025-02-14 00:27:42 +11:00
DN_UTest_PrintStats(&test), test_var_ = nullptr)
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
#define DN_UTEST_TEST(fmt, ...) \
for (int dummy_ = (DN_UTest_Begin(test_var_, fmt, ## __VA_ARGS__), 0); \
(void)dummy_, test_var_->state == DN_UTestState_TestBegun; \
DN_UTest_End(test_var_))
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
#define DN_UTEST_ASSERTF(test, expr, fmt, ...) \
DN_UTEST_ASSERTF_AT((test), __FILE__, __LINE__, (expr), fmt, ##__VA_ARGS__)
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
#define DN_UTEST_ASSERT(test, expr) \
DN_UTEST_ASSERT_AT((test), __FILE__, __LINE__, (expr))
2023-06-08 22:07:55 +10:00
// TODO: Fix the logs. They print before the tests, we should accumulate logs
// per test, then, dump them on test on. But to do this nicely without crappy
// mem management we need to implement an arena.
2025-02-14 00:27:42 +11:00
#define DN_UTEST_LOG(fmt, ...) \
fprintf(stdout, "%*s" fmt "\n", DN_UTEST_SPACING * 2, "", ##__VA_ARGS__)
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
#define DN_UTEST_ASSERTF_AT(test, file, line, expr, fmt, ...) \
2023-06-08 22:07:55 +10:00
do { \
if (!(expr)) { \
2025-02-14 00:27:42 +11:00
(test)->state = DN_UTestState_TestFailed; \
2023-06-08 22:07:55 +10:00
fprintf(stderr, \
"%*sAssertion Triggered\n" \
"%*sFile: %s:%d\n" \
"%*sExpression: [" #expr "]\n" \
"%*sReason: " fmt "\n\n", \
2025-02-14 00:27:42 +11:00
DN_UTEST_SPACING * 2, \
2023-06-08 22:07:55 +10:00
"", \
2025-02-14 00:27:42 +11:00
DN_UTEST_SPACING * 3, \
2023-06-08 22:07:55 +10:00
"", \
file, \
line, \
2025-02-14 00:27:42 +11:00
DN_UTEST_SPACING * 3, \
2023-06-08 22:07:55 +10:00
"", \
2025-02-14 00:27:42 +11:00
DN_UTEST_SPACING * 3, \
2023-06-08 22:07:55 +10:00
"", \
##__VA_ARGS__); \
} \
} while (0)
2025-02-14 00:27:42 +11:00
#define DN_UTEST_ASSERT_AT(test, file, line, expr) \
2023-06-08 22:07:55 +10:00
do { \
if (!(expr)) { \
2025-02-14 00:27:42 +11:00
(test)->state = DN_UTestState_TestFailed; \
2023-06-08 22:07:55 +10:00
fprintf(stderr, \
"%*sFile: %s:%d\n" \
"%*sExpression: [" #expr "]\n\n", \
2025-02-14 00:27:42 +11:00
DN_UTEST_SPACING * 2, \
2023-06-08 22:07:55 +10:00
"", \
file, \
line, \
2025-02-14 00:27:42 +11:00
DN_UTEST_SPACING * 2, \
2023-06-08 22:07:55 +10:00
""); \
} \
} while (0)
// NOTE: Header ////////////////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
typedef enum DN_UTestState {
DN_UTestState_Nil,
DN_UTestState_TestBegun,
DN_UTestState_TestFailed,
} DN_UTestState;
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
typedef struct DN_UTest {
2023-06-08 22:07:55 +10:00
int num_tests_in_group;
int num_tests_ok_in_group;
2025-02-14 00:27:42 +11:00
DN_UTestState state;
2023-06-08 22:07:55 +10:00
bool finished;
char name[256];
2023-06-08 22:07:55 +10:00
size_t name_size;
2025-02-14 00:27:42 +11:00
} DN_UTest;
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
void DN_UTest_PrintStats(DN_UTest *test);
void DN_UTest_BeginV(DN_UTest *test, char const *fmt, va_list args);
void DN_UTest_Begin(DN_UTest *test, char const *fmt, ...);
void DN_UTest_End(DN_UTest *test);
#endif // DN_UTEST_H
2023-06-08 22:07:55 +10:00
// NOTE: Implementation ////////////////////////////////////////////////////////////////////////////
2025-02-14 00:27:42 +11:00
#if defined(DN_UTEST_IMPLEMENTATION)
void DN_UTest_PrintStats(DN_UTest *test)
2023-06-08 22:07:55 +10:00
{
if (test->finished)
return;
test->finished = true;
bool all_clear = test->num_tests_ok_in_group == test->num_tests_in_group;
fprintf(stdout,
2025-02-14 00:27:42 +11:00
"%s\n %02d/%02d tests passed -- %s\n\n" DN_UTEST_COLOR_RESET,
all_clear ? DN_UTEST_GOOD_COLOR : DN_UTEST_BAD_COLOR,
2023-06-08 22:07:55 +10:00
test->num_tests_ok_in_group,
test->num_tests_in_group,
all_clear ? "OK" : "FAILED");
}
2025-02-14 00:27:42 +11:00
void DN_UTest_BeginV(DN_UTest *test, char const *fmt, va_list args)
2023-06-08 22:07:55 +10:00
{
2025-02-14 00:27:42 +11:00
assert(test->state == DN_UTestState_Nil &&
2023-06-08 22:07:55 +10:00
"Nesting a unit test within another unit test is not allowed, ensure"
2025-02-14 00:27:42 +11:00
"the first test has finished by calling DN_UTest_End");
2023-06-08 22:07:55 +10:00
test->num_tests_in_group++;
2025-02-14 00:27:42 +11:00
test->state = DN_UTestState_TestBegun;
2023-06-08 22:07:55 +10:00
test->name_size = 0;
{
va_list args_copy;
va_copy(args_copy, args);
test->name_size = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
}
assert(test->name_size < sizeof(test->name));
vsnprintf(test->name, sizeof(test->name), fmt, args);
}
2025-02-14 00:27:42 +11:00
void DN_UTest_Begin(DN_UTest *test, char const *fmt, ...)
2023-06-08 22:07:55 +10:00
{
va_list args;
va_start(args, fmt);
2025-02-14 00:27:42 +11:00
DN_UTest_BeginV(test, fmt, args);
2023-06-08 22:07:55 +10:00
va_end(args);
}
2025-02-14 00:27:42 +11:00
void DN_UTest_End(DN_UTest *test)
2023-06-08 22:07:55 +10:00
{
2025-02-14 00:27:42 +11:00
assert(test->state != DN_UTestState_Nil && "Test was marked as ended but a test was never commenced using DN_UTest_Begin");
size_t pad_size = DN_UTEST_RESULT_LPAD - (DN_UTEST_SPACING + test->name_size);
2023-06-08 22:07:55 +10:00
if (pad_size < 0)
pad_size = 0;
2025-02-14 00:27:42 +11:00
char pad_buffer[DN_UTEST_RESULT_LPAD] = {};
memset(pad_buffer, DN_UTEST_RESULT_PAD_CHAR, pad_size);
2023-06-08 22:07:55 +10:00
2025-02-14 00:27:42 +11:00
printf("%*s%.*s%.*s", DN_UTEST_SPACING, "", (int)test->name_size, test->name, (int)pad_size, pad_buffer);
if (test->state == DN_UTestState_TestFailed) {
printf(DN_UTEST_BAD_COLOR " FAILED");
2023-06-08 22:07:55 +10:00
} else {
2025-02-14 00:27:42 +11:00
printf(DN_UTEST_GOOD_COLOR " OK");
2023-06-08 22:07:55 +10:00
test->num_tests_ok_in_group++;
}
2025-02-14 00:27:42 +11:00
printf(DN_UTEST_COLOR_RESET "\n");
test->state = DN_UTestState_Nil;
2023-06-08 22:07:55 +10:00
}
2025-02-14 00:27:42 +11:00
#endif // DN_UTEST_IMPLEMENTATION