dqn: Simplify testing routine

This commit is contained in:
doyle 2023-06-08 22:07:55 +10:00
parent d9116ae48e
commit 44f7a696c5
4 changed files with 1006 additions and 925 deletions

340
dqn.h
View File

@ -57,6 +57,10 @@
// #define DQN_NO_ASSERT
// Turn all assertion macros to no-ops
//
// #define DQN_NO_CHECK_BREAK
// Disable debug break when a check macro's expression fails. Instead only
// the error will be logged.
//
// #define DQN_NO_WIN32_MINIMAL_HEADER
// Define this to stop this library from defining a minimal subset of Win32
// prototypes and definitions in this file. Useful for stopping redefinition
@ -283,6 +287,30 @@
}
#endif
#define DQN_CHECK(expr) DQN_CHECKF(expr, "")
#if defined(DQN_NO_CHECK_BREAK)
#define DQN_CHECKF(expr, fmt, ...) \
((expr) ? true : (Dqn_Log_TypeFCallSite(Dqn_LogType_Warning, DQN_CALL_SITE, fmt, ## __VA_ARGS__), false))
#else
#define DQN_CHECKF(expr, fmt, ...) \
((expr) ? true : (Dqn_Log_TypeFCallSite(Dqn_LogType_Error, DQN_CALL_SITE, fmt, ## __VA_ARGS__), DQN_DEBUG_BREAK, false))
#endif
#if 0
DQN_API bool DQN_CHECKF_(bool assertion_expr, Dqn_CallSite call_site, char const *fmt, ...)
{
bool result = assertion_expr;
if (!result) {
va_list args;
va_start(args, fmt);
Dqn_Log_TypeFVCallSite(Dqn_LogType_Error, call_site, fmt, args);
va_end(args);
DQN_DEBUG_BREAK;
}
return result;
}
#endif
#if defined(__cplusplus)
#define DQN_ZERO_INIT {}
#else
@ -1310,10 +1338,11 @@ typedef void Dqn_LogProc(Dqn_String8 type, int log_type, void *user_data, Dqn_Ca
#define Dqn_Log_FV(type, fmt, args) Dqn_Log_FVCallSite(type, DQN_CALL_SITE, fmt, args)
#define Dqn_Log_F(type, fmt, ...) Dqn_Log_FCallSite(type, DQN_CALL_SITE, fmt, ## __VA_ARGS__)
DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list va);
DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...);
DQN_API void Dqn_Log_FVCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list va);
DQN_API void Dqn_Log_FCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...);
DQN_API Dqn_String8 Dqn_Log_MakeString (Dqn_Allocator allocator, bool colour, Dqn_String8 type, int log_type, Dqn_CallSite call_site, char const *fmt, va_list args);
DQN_API void Dqn_Log_TypeFVCallSite(Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, va_list va);
DQN_API void Dqn_Log_TypeFCallSite (Dqn_LogType type, Dqn_CallSite call_site, char const *fmt, ...);
DQN_API void Dqn_Log_FVCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, va_list va);
DQN_API void Dqn_Log_FCallSite (Dqn_String8 type, Dqn_CallSite call_site, char const *fmt, ...);
// =================================================================================================
// [$VMEM] Dqn_VMem | | Virtual memory allocation
@ -1488,7 +1517,7 @@ struct Dqn_ArenaStatString
struct Dqn_Arena
{
bool use_after_free_guard;
uint32_t temp_memory_count;
Dqn_String8 label; ///< Optional label to describe the arena
Dqn_ArenaBlock *head; ///< Active block the arena is allocating from
Dqn_ArenaBlock *curr; ///< Active block the arena is allocating from
@ -2424,26 +2453,17 @@ DQN_API bool Dqn_Bit_IsNotSet(uint32_t bits, uint32_t bits_to_check);
// =================================================================================================
// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts
// =================================================================================================
#if defined(NDEBUG)
#define Dqn_Safe_AssertF(expr, fmt, ...)
#define Dqn_Safe_Assert(expr, fmt, ...)
#else
#define Dqn_Safe_AssertF(expr, fmt, ...) \
Dqn_Safe_AssertF_(expr, DQN_CALL_SITE, "Safe assert triggered " #expr ": " fmt, ## __VA_ARGS__)
#define Dqn_Safe_Assert(expr) Dqn_Safe_AssertF(expr, "")
#endif
/// Assert the expression given in debug, whilst in release- assertion is
/// removed and the expression is evaluated and returned.
///
/// This function provides dual logic which allows handling of the condition
/// gracefully in release mode, but asserting in debug mode. This is an internal
/// function, prefer the @see Dqn_Safe_Assert macros.
/// function, prefer the @see DQN_CHECK macros.
///
/// @param assertion_expr[in] Expressin to assert on
/// @param fmt[in] Format string for providing a message on assertion
/// @return True if the expression evaluated to true, false otherwise.
DQN_API bool Dqn_Safe_AssertF_(bool assertion_expr, Dqn_CallSite call_site, char const *fmt, ...);
DQN_API bool DQN_CHECKF_(bool assertion_expr, Dqn_CallSite call_site, char const *fmt, ...);
// NOTE: Dqn_Safe Arithmetic
// -----------------------------------------------------------------------------
@ -3606,7 +3626,7 @@ DQN_API template <typename T> T *Dqn_VArray_Make(Dqn_VArray<T> *array, Dqn_usize
if (!Dqn_VArray_IsValid(array))
return nullptr;
if (!Dqn_Safe_AssertF((array->size + count) < array->max, "Array is out of virtual memory"))
if (!DQN_CHECKF((array->size + count) < array->max, "Array is out of virtual memory"))
return nullptr;
// TODO: Use placement new? Why doesn't this work?
@ -3696,7 +3716,7 @@ template <typename T>
Dqn_DSMap<T> Dqn_DSMap_Init(uint32_t size)
{
Dqn_DSMap<T> result = {};
if (Dqn_Safe_AssertF((size & (size - 1)) == 0, "Power-of-two size required")) {
if (DQN_CHECKF((size & (size - 1)) == 0, "Power-of-two size required")) {
result.hash_to_slot = Dqn_Allocator_NewArray(result.allocator, uint32_t, size, Dqn_ZeroMem_Yes);
if (result.hash_to_slot) {
result.slots = Dqn_Allocator_NewArray(result.allocator, Dqn_DSMapSlot<T>, size, Dqn_ZeroMem_Yes);
@ -4200,7 +4220,7 @@ DQN_API template <typename T, Dqn_usize N> T *Dqn_FArray_Make(Dqn_FArray<T, N> *
if (!Dqn_FArray_IsValid(array))
return nullptr;
if (!Dqn_Safe_AssertF((array->size + count) < DQN_ARRAY_UCOUNT(array->data), "Array is out of memory"))
if (!DQN_CHECKF((array->size + count) < DQN_ARRAY_UCOUNT(array->data), "Array is out of memory"))
return nullptr;
// TODO: Use placement new? Why doesn't this work?
@ -4892,6 +4912,78 @@ DQN_API void Dqn_Allocator_Dealloc_(DQN_LEAK_TRACE_FUNCTION Dqn_Allocator alloca
// =================================================================================================
// [$LLOG] Dqn_Log | | Library logging
// =================================================================================================
DQN_API Dqn_String8 Dqn_Log_MakeString(Dqn_Allocator allocator,
bool colour,
Dqn_String8 type,
int log_type,
Dqn_CallSite call_site,
char const *fmt,
va_list args)
{
Dqn_usize header_size_no_ansi_codes = 0;
Dqn_String8 header = {};
{
DQN_LOCAL_PERSIST Dqn_usize max_type_length = 0;
max_type_length = DQN_MAX(max_type_length, type.size);
int type_padding = DQN_CAST(int)(max_type_length - type.size);
Dqn_String8 colour_esc = {};
Dqn_String8 bold_esc = {};
Dqn_String8 reset_esc = {};
if (colour) {
bold_esc = Dqn_Print_ESCBoldString;
reset_esc = Dqn_Print_ESCResetString;
switch (log_type) {
case Dqn_LogType_Debug: break;
case Dqn_LogType_Info: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Info); break;
case Dqn_LogType_Warning: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Warning); break;
case Dqn_LogType_Error: colour_esc = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Error); break;
}
}
Dqn_String8 file_name = Dqn_String8_FileNameFromPath(call_site.file);
Dqn_DateHMSTimeString const time = Dqn_Date_HMSLocalTimeStringNow();
header = Dqn_String8_InitF(allocator,
"%.*s " // date
"%.*s " // hms
"%.*s" // colour
"%.*s" // bold
"%.*s" // type
"%*s" // type padding
"%.*s" // reset
" %.*s" // file name
":%05u ", // line number
time.date_size - 2, time.date + 2,
time.hms_size, time.hms,
colour_esc.size, colour_esc.data,
bold_esc.size, bold_esc.data,
type.size, type.data,
type_padding, "",
reset_esc.size, reset_esc.data,
file_name.size, file_name.data,
call_site.line);
header_size_no_ansi_codes = header.size - colour_esc.size - Dqn_Print_ESCResetString.size;
}
// NOTE: Header padding
// =========================================================================
Dqn_usize header_padding = 0;
{
DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0;
max_header_length = DQN_MAX(max_header_length, header_size_no_ansi_codes);
header_padding = max_header_length - header_size_no_ansi_codes;
}
// NOTE: Construct final log
// =========================================================================
Dqn_String8 user_msg = Dqn_String8_InitFV(allocator, fmt, args);
Dqn_String8 result = Dqn_String8_Allocate(allocator, header.size + header_padding + user_msg.size, Dqn_ZeroMem_No);
DQN_MEMCPY(result.data, header.data, header.size);
DQN_MEMSET(result.data + header.size, ' ', header_padding);
DQN_MEMCPY(result.data + header.size + header_padding, user_msg.data, user_msg.size);
return result;
}
DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_String8 type, int log_type, void *user_data, Dqn_CallSite call_site, char const *fmt, va_list args)
{
(void)log_type;
@ -4910,69 +5002,14 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_String8 type, int log_type, void *use
// NOTE: Generate the log header
// =========================================================================
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_usize header_size_no_ansi_codes = 0;
Dqn_String8 header = {};
{
DQN_LOCAL_PERSIST Dqn_usize max_type_length = 0;
max_type_length = DQN_MAX(max_type_length, type.size);
int type_padding = DQN_CAST(int)(max_type_length - type.size);
Dqn_String8 colour = {};
Dqn_String8 bold = {};
Dqn_String8 reset = {};
if (!dqn_library.log_no_colour) {
bold = Dqn_Print_ESCBoldString;
reset = Dqn_Print_ESCResetString;
switch (log_type) {
case Dqn_LogType_Debug: break;
case Dqn_LogType_Info: colour = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Info); break;
case Dqn_LogType_Warning: colour = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Warning); break;
case Dqn_LogType_Error: colour = Dqn_Print_ESCColourFgU32String(Dqn_LogTypeColourU32_Error); break;
}
}
Dqn_String8 file_name = Dqn_String8_FileNameFromPath(call_site.file);
Dqn_DateHMSTimeString const time = Dqn_Date_HMSLocalTimeStringNow();
header = Dqn_String8_InitF(scratch.allocator,
"%.*s " // date
"%.*s " // hms
"%.*s" // colour
"%.*s" // bold
"%.*s" // type
"%*s" // type padding
"%.*s" // reset
" %.*s" // file name
":%05u ", // line number
time.date_size - 2, time.date + 2,
time.hms_size, time.hms,
colour.size, colour.data,
bold.size, bold.data,
type.size, type.data,
type_padding, "",
reset.size, reset.data,
file_name.size, file_name.data,
call_site.line);
header_size_no_ansi_codes = header.size - colour.size - Dqn_Print_ESCResetString.size;
}
// NOTE: Header padding
// =========================================================================
Dqn_usize header_padding = 0;
{
DQN_LOCAL_PERSIST Dqn_usize max_header_length = 0;
max_header_length = DQN_MAX(max_header_length, header_size_no_ansi_codes);
header_padding = max_header_length - header_size_no_ansi_codes;
}
// NOTE: Construct final log
// =========================================================================
Dqn_String8 msg = Dqn_String8_InitFV(scratch.allocator, fmt, args);
Dqn_String8 log_line = Dqn_String8_Allocate(scratch.allocator, header.size + header_padding + msg.size, Dqn_ZeroMem_No);
DQN_MEMCPY(log_line.data, header.data, header.size);
DQN_MEMSET(log_line.data + header.size, ' ', header_padding);
DQN_MEMCPY(log_line.data + header.size + header_padding, msg.data, msg.size);
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_String8 log_line = Dqn_Log_MakeString(scratch.allocator,
!dqn_library.log_no_colour,
type,
log_type,
call_site,
fmt,
args);
// NOTE: Print log
// =========================================================================
@ -6362,12 +6399,15 @@ DQN_API void Dqn_Arena_Reset(Dqn_Arena *arena, Dqn_ZeroMem zero_mem)
DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena)
{
Dqn_ArenaTempMemory result = {};
result.arena = arena;
result.head = arena->head;
result.curr = arena->curr;
result.tail = arena->tail;
result.curr_used = (arena->curr) ? arena->curr->used : 0;
result.stats = arena->stats;
if (arena) {
arena->temp_memory_count++;
result.arena = arena;
result.head = arena->head;
result.curr = arena->curr;
result.tail = arena->tail;
result.curr_used = (arena->curr) ? arena->curr->used : 0;
result.stats = arena->stats;
}
return result;
}
@ -6376,8 +6416,12 @@ DQN_API void Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemor
if (!scope.arena)
return;
Dqn_Arena *arena = scope.arena;
if (!DQN_CHECKF(arena->temp_memory_count > 0, "End temp memory has been called without a matching begin pair on the arena"))
return;
// NOTE: Revert arena stats
Dqn_Arena *arena = scope.arena;
arena->temp_memory_count--;
arena->stats.capacity = scope.stats.capacity;
arena->stats.used = scope.stats.used;
arena->stats.wasted = scope.stats.wasted;
@ -6597,6 +6641,9 @@ DQN_API void Dqn_Arena_Free_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_ZeroM
if (!arena)
return;
if (!DQN_CHECKF(arena->temp_memory_count == 0, "You cannot free an arena whilst in an temp memory region"))
return;
while (arena->tail) {
Dqn_ArenaBlock *block = arena->tail;
arena->tail = block->prev;
@ -7148,52 +7195,39 @@ DQN_API bool Dqn_Bit_IsNotSet(uint64_t bits, uint64_t bits_to_check)
// =================================================================================================
// [$SAFE] Dqn_Safe | | Safe arithmetic, casts, asserts
// =================================================================================================
DQN_API bool Dqn_Safe_AssertF_(bool assertion_expr, Dqn_CallSite call_site, char const *fmt, ...)
{
bool result = assertion_expr;
if (!result) {
va_list args;
va_start(args, fmt);
Dqn_Log_TypeFVCallSite(Dqn_LogType_Error, call_site, fmt, args);
va_end(args);
DQN_DEBUG_BREAK;
}
return result;
}
DQN_API int64_t Dqn_Safe_AddI64(int64_t a, int64_t b)
{
int64_t result = Dqn_Safe_AssertF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX;
int64_t result = DQN_CHECKF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX;
return result;
}
DQN_API int64_t Dqn_Safe_MulI64(int64_t a, int64_t b)
{
int64_t result = Dqn_Safe_AssertF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX;
int64_t result = DQN_CHECKF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX;
return result;
}
DQN_API uint64_t Dqn_Safe_AddU64(uint64_t a, uint64_t b)
{
uint64_t result = Dqn_Safe_AssertF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX;
uint64_t result = DQN_CHECKF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX;
return result;
}
DQN_API uint64_t Dqn_Safe_SubU64(uint64_t a, uint64_t b)
{
uint64_t result = Dqn_Safe_AssertF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0;
uint64_t result = DQN_CHECKF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0;
return result;
}
DQN_API uint64_t Dqn_Safe_MulU64(uint64_t a, uint64_t b)
{
uint64_t result = Dqn_Safe_AssertF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX;
uint64_t result = DQN_CHECKF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX;
return result;
}
DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b)
{
uint32_t result = Dqn_Safe_AssertF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0;
uint32_t result = DQN_CHECKF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0;
return result;
}
@ -7203,31 +7237,31 @@ DQN_API uint32_t Dqn_Safe_SubU32(uint32_t a, uint32_t b)
// the highest possible rank (unsigned > signed).
DQN_API int Dqn_Safe_SaturateCastUSizeToInt(Dqn_usize val)
{
int result = Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= INT_MAX) ? DQN_CAST(int)val : INT_MAX;
int result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT_MAX) ? DQN_CAST(int)val : INT_MAX;
return result;
}
DQN_API int8_t Dqn_Safe_SaturateCastUSizeToI8(Dqn_usize val)
{
int8_t result = Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= INT8_MAX) ? DQN_CAST(int8_t)val : INT8_MAX;
int8_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT8_MAX) ? DQN_CAST(int8_t)val : INT8_MAX;
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastUSizeToI16(Dqn_usize val)
{
int16_t result = Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= INT16_MAX) ? DQN_CAST(int16_t)val : INT16_MAX;
int16_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT16_MAX) ? DQN_CAST(int16_t)val : INT16_MAX;
return result;
}
DQN_API int32_t Dqn_Safe_SaturateCastUSizeToI32(Dqn_usize val)
{
int32_t result = Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= INT32_MAX) ? DQN_CAST(int32_t)val : INT32_MAX;
int32_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT32_MAX) ? DQN_CAST(int32_t)val : INT32_MAX;
return result;
}
DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val)
{
int64_t result = Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= INT64_MAX) ? DQN_CAST(int64_t)val : INT64_MAX;
int64_t result = DQN_CHECK(DQN_CAST(uintmax_t)val <= INT64_MAX) ? DQN_CAST(int64_t)val : INT64_MAX;
return result;
}
@ -7237,25 +7271,25 @@ DQN_API int64_t Dqn_Safe_SaturateCastUSizeToI64(Dqn_usize val)
// match the highest rank operand.
DQN_API uint8_t Dqn_Safe_SaturateCastUSizeToU8(Dqn_usize val)
{
uint8_t result = Dqn_Safe_Assert(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX;
uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX;
return result;
}
DQN_API uint16_t Dqn_Safe_SaturateCastUSizeToU16(Dqn_usize val)
{
uint16_t result = Dqn_Safe_Assert(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX;
uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX;
return result;
}
DQN_API uint32_t Dqn_Safe_SaturateCastUSizeToU32(Dqn_usize val)
{
uint32_t result = Dqn_Safe_Assert(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX;
uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX;
return result;
}
DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64(Dqn_usize val)
{
uint64_t result = Dqn_Safe_Assert(val <= UINT64_MAX) ? DQN_CAST(uint64_t)val : UINT64_MAX;
uint64_t result = DQN_CHECK(val <= UINT64_MAX) ? DQN_CAST(uint64_t)val : UINT64_MAX;
return result;
}
@ -7265,25 +7299,25 @@ DQN_API uint64_t Dqn_Safe_SaturateCastUSizeToU64(Dqn_usize val)
// match the highest rank operand.
DQN_API unsigned int Dqn_Safe_SaturateCastU64ToUInt(uint64_t val)
{
unsigned int result = Dqn_Safe_Assert(val <= UINT8_MAX) ? DQN_CAST(unsigned int)val : UINT_MAX;
unsigned int result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(unsigned int)val : UINT_MAX;
return result;
}
DQN_API uint8_t Dqn_Safe_SaturateCastU64ToU8(uint64_t val)
{
uint8_t result = Dqn_Safe_Assert(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX;
uint8_t result = DQN_CHECK(val <= UINT8_MAX) ? DQN_CAST(uint8_t)val : UINT8_MAX;
return result;
}
DQN_API uint16_t Dqn_Safe_SaturateCastU64ToU16(uint64_t val)
{
uint16_t result = Dqn_Safe_Assert(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX;
uint16_t result = DQN_CHECK(val <= UINT16_MAX) ? DQN_CAST(uint16_t)val : UINT16_MAX;
return result;
}
DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val)
{
uint32_t result = Dqn_Safe_Assert(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX;
uint32_t result = DQN_CHECK(val <= UINT32_MAX) ? DQN_CAST(uint32_t)val : UINT32_MAX;
return result;
}
@ -7294,35 +7328,35 @@ DQN_API uint32_t Dqn_Safe_SaturateCastU64ToU32(uint64_t val)
// match the highest rank operand.
DQN_API int Dqn_Safe_SaturateCastISizeToInt(Dqn_isize val)
{
Dqn_Safe_Assert(val >= INT_MIN && val <= INT_MAX);
DQN_ASSERT(val >= INT_MIN && val <= INT_MAX);
int result = DQN_CAST(int)DQN_CLAMP(val, INT_MIN, INT_MAX);
return result;
}
DQN_API int8_t Dqn_Safe_SaturateCastISizeToI8(Dqn_isize val)
{
Dqn_Safe_Assert(val >= INT8_MIN && val <= INT8_MAX);
DQN_ASSERT(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX);
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastISizeToI16(Dqn_isize val)
{
Dqn_Safe_Assert(val >= INT16_MIN && val <= INT16_MAX);
DQN_ASSERT(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX);
return result;
}
DQN_API int32_t Dqn_Safe_SaturateCastISizeToI32(Dqn_isize val)
{
Dqn_Safe_Assert(val >= INT32_MIN && val <= INT32_MAX);
DQN_ASSERT(val >= INT32_MIN && val <= INT32_MAX);
int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX);
return result;
}
DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val)
{
Dqn_Safe_Assert(val >= INT64_MIN && val <= INT64_MAX);
DQN_ASSERT(val >= INT64_MIN && val <= INT64_MAX);
int64_t result = DQN_CAST(int64_t)DQN_CLAMP(val, INT64_MIN, INT64_MAX);
return result;
}
@ -7335,8 +7369,8 @@ DQN_API int64_t Dqn_Safe_SaturateCastISizeToI64(Dqn_isize val)
DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val)
{
unsigned int result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT_MAX))
result = DQN_CAST(unsigned int)val;
else
result = UINT_MAX;
@ -7347,8 +7381,8 @@ DQN_API unsigned int Dqn_Safe_SaturateCastISizeToUInt(Dqn_isize val)
DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val)
{
uint8_t result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT8_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX))
result = DQN_CAST(uint8_t)val;
else
result = UINT8_MAX;
@ -7359,8 +7393,8 @@ DQN_API uint8_t Dqn_Safe_SaturateCastISizeToU8(Dqn_isize val)
DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val)
{
uint16_t result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT16_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX))
result = DQN_CAST(uint16_t)val;
else
result = UINT16_MAX;
@ -7371,8 +7405,8 @@ DQN_API uint16_t Dqn_Safe_SaturateCastISizeToU16(Dqn_isize val)
DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val)
{
uint32_t result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT32_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT32_MAX))
result = DQN_CAST(uint32_t)val;
else
result = UINT32_MAX;
@ -7383,8 +7417,8 @@ DQN_API uint32_t Dqn_Safe_SaturateCastISizeToU32(Dqn_isize val)
DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val)
{
uint64_t result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT64_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT64_MAX))
result = DQN_CAST(uint64_t)val;
else
result = UINT64_MAX;
@ -7398,28 +7432,28 @@ DQN_API uint64_t Dqn_Safe_SaturateCastISizeToU64(Dqn_isize val)
// match the highest rank operand.
DQN_API Dqn_isize Dqn_Safe_SaturateCastI64ToISize(int64_t val)
{
Dqn_Safe_Assert(val >= DQN_ISIZE_MIN && val <= DQN_ISIZE_MAX);
DQN_CHECK(val >= DQN_ISIZE_MIN && val <= DQN_ISIZE_MAX);
Dqn_isize result = DQN_CAST(int64_t)DQN_CLAMP(val, DQN_ISIZE_MIN, DQN_ISIZE_MAX);
return result;
}
DQN_API int8_t Dqn_Safe_SaturateCastI64ToI8(int64_t val)
{
Dqn_Safe_Assert(val >= INT8_MIN && val <= INT8_MAX);
DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX);
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastI64ToI16(int64_t val)
{
Dqn_Safe_Assert(val >= INT16_MIN && val <= INT16_MAX);
DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX);
return result;
}
DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val)
{
Dqn_Safe_Assert(val >= INT32_MIN && val <= INT32_MAX);
DQN_CHECK(val >= INT32_MIN && val <= INT32_MAX);
int32_t result = DQN_CAST(int32_t)DQN_CLAMP(val, INT32_MIN, INT32_MAX);
return result;
}
@ -7428,14 +7462,14 @@ DQN_API int32_t Dqn_Safe_SaturateCastI64ToI32(int64_t val)
// -----------------------------------------------------------------------------
DQN_API int8_t Dqn_Safe_SaturateCastIntToI8(int val)
{
Dqn_Safe_Assert(val >= INT8_MIN && val <= INT8_MAX);
DQN_CHECK(val >= INT8_MIN && val <= INT8_MAX);
int8_t result = DQN_CAST(int8_t)DQN_CLAMP(val, INT8_MIN, INT8_MAX);
return result;
}
DQN_API int16_t Dqn_Safe_SaturateCastIntToI16(int val)
{
Dqn_Safe_Assert(val >= INT16_MIN && val <= INT16_MAX);
DQN_CHECK(val >= INT16_MIN && val <= INT16_MAX);
int16_t result = DQN_CAST(int16_t)DQN_CLAMP(val, INT16_MIN, INT16_MAX);
return result;
}
@ -7443,8 +7477,8 @@ DQN_API int16_t Dqn_Safe_SaturateCastIntToI16(int val)
DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val)
{
uint8_t result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT8_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT8_MAX))
result = DQN_CAST(uint8_t)val;
else
result = UINT8_MAX;
@ -7455,8 +7489,8 @@ DQN_API uint8_t Dqn_Safe_SaturateCastIntToU8(int val)
DQN_API uint16_t Dqn_Safe_SaturateCastIntToU16(int val)
{
uint16_t result = 0;
if (Dqn_Safe_Assert(val >= DQN_CAST(Dqn_isize)0)) {
if (Dqn_Safe_Assert(DQN_CAST(uintmax_t)val <= UINT16_MAX))
if (DQN_CHECK(val >= DQN_CAST(Dqn_isize)0)) {
if (DQN_CHECK(DQN_CAST(uintmax_t)val <= UINT16_MAX))
result = DQN_CAST(uint16_t)val;
else
result = UINT16_MAX;
@ -7468,7 +7502,7 @@ DQN_API uint32_t Dqn_Safe_SaturateCastIntToU32(int val)
{
static_assert(sizeof(val) <= sizeof(uint32_t), "Sanity check to allow simplifying of casting");
uint32_t result = 0;
if (Dqn_Safe_Assert(val >= 0))
if (DQN_CHECK(val >= 0))
result = DQN_CAST(uint32_t)val;
return result;
}
@ -7477,7 +7511,7 @@ DQN_API uint64_t Dqn_Safe_SaturateCastIntToU64(int val)
{
static_assert(sizeof(val) <= sizeof(uint64_t), "Sanity check to allow simplifying of casting");
uint64_t result = 0;
if (Dqn_Safe_Assert(val >= 0))
if (DQN_CHECK(val >= 0))
result = DQN_CAST(uint64_t)val;
return result;
}
@ -7727,7 +7761,7 @@ DQN_API bool Dqn_Bin_BytesToHexBuffer(void const *src, Dqn_usize src_size, char
if (!src || !dest)
return false;
if (!Dqn_Safe_Assert(dest_size >= src_size * 2))
if (!DQN_CHECK(dest_size >= src_size * 2))
return false;
char const *HEX = "0123456789abcdef";
@ -8433,7 +8467,7 @@ DQN_API bool Dqn_Win_NetHandleSetRequestHeaderCString8(Dqn_WinNetHandle *handle,
if (handle->state < Dqn_WinNetHandleState_HttpMethodReady)
return false;
if (!Dqn_Safe_Assert(handle->http_handle))
if (!DQN_CHECK(handle->http_handle))
return false;
unsigned long modifier = 0;
@ -8489,7 +8523,7 @@ DQN_API Dqn_WinNetHandleResponse Dqn_Win_NetHandleSendRequest(Dqn_WinNetHandle *
handle->state = Dqn_WinNetHandleState_RequestGood;
unsigned long buffer_size = 0;
int query_result = HttpQueryInfoA(handle->http_handle, HTTP_QUERY_RAW_HEADERS_CRLF, nullptr, &buffer_size, nullptr);
if (!Dqn_Safe_Assert(query_result != ERROR_INSUFFICIENT_BUFFER))
if (!DQN_CHECK(query_result != ERROR_INSUFFICIENT_BUFFER))
return result;
result.raw_headers = Dqn_String8_Allocate(allocator, buffer_size, Dqn_ZeroMem_No);
@ -9326,8 +9360,8 @@ DQN_API char *Dqn_Fs_ReadCString8_(DQN_LEAK_TRACE_FUNCTION char const *path, Dqn
}
unsigned long const bytes_desired = DQN_CAST(unsigned long)win_file_size.QuadPart;
if (!Dqn_Safe_AssertF(bytes_desired == win_file_size.QuadPart,
"Current implementation doesn't support >4GiB, implement Win32 overlapped IO")) {
if (!DQN_CHECKF(bytes_desired == win_file_size.QuadPart,
"Current implementation doesn't support >4GiB, implement Win32 overlapped IO")) {
return nullptr;
}

View File

@ -1,256 +0,0 @@
#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
// 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.
//
// #define DQN_TESTER_RESULT_LPAD
// Define this to a number to specify how much to pad the output of the test
// result line before the test result is printed.
//
// #define DQN_TESTER_RESULT_PAD_CHAR
// Define this to a character to specify the default character to use for
// padding. By default this is '.'
//
// #define DQN_TESTER_SPACING
// Define this to a number to specify the number of spaces between the group
// declaration and the test output in the group.
//
// #define DQN_TESTER_BAD_COLOR
// Define this to a terminal color code to specify what color errors will be
// presented as.
//
// #define DQN_TESTER_GOOD_COLOR
// Define this to a terminal color code to specify what color sucess will be
// presented as.
// NOTE: Macros
// -----------------------------------------------------------------------------
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#if !defined(DQN_TESTER_RESULT_LPAD)
#define DQN_TESTER_RESULT_LPAD 90
#endif
#if !defined(DQN_TESTER_RESULT_PAD_CHAR)
#define DQN_TESTER_RESULT_PAD_CHAR '.'
#endif
#if !defined(DQN_TESTER_SPACING)
#define DQN_TESTER_SPACING 2
#endif
#if !defined(DQN_TESTER_BAD_COLOR)
#define DQN_TESTER_BAD_COLOR "\x1b[31m"
#endif
#if !defined(DQN_TESTER_GOOD_COLOR)
#define DQN_TESTER_GOOD_COLOR "\x1b[32m"
#endif
#define DQN_TESTER_COLOR_RESET "\x1b[0m"
#define DQN_TESTER_TOKEN_COMBINE2(x, y) x ## y_
#define DQN_TESTER_TOKEN_COMBINE(x, y) DQN_TESTER_TOKEN_COMBINE2(x, y)
/// Creates <prefix><line_number>_ .e. group_123_
#define DQN_TESTER_UNIQUE_NAME(prefix) \
DQN_TESTER_TOKEN_COMBINE(DQN_TESTER_TOKEN_COMBINE(prefix, __LINE__), _)
#define DQN_TESTER_GROUP(group, fmt, ...) \
for (Dqn_Tester *test_group_ = (group = Dqn_Tester_BeginGroup(fmt, ## __VA_ARGS__), &group); \
!test_group_->finished; \
Dqn_Tester_EndGroup(test_group_))
#define DQN_TESTER_TEST(fmt, ...) \
for (int DQN_TESTER_UNIQUE_NAME(dummy_) = (Dqn_Tester_Begin(test_group_, fmt, ## __VA_ARGS__), 0); \
(void)DQN_TESTER_UNIQUE_NAME(dummy_), test_group_->state == Dqn_TesterTestState_TestBegun; \
Dqn_Tester_End(test_group_))
#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, "%*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)->state = Dqn_TesterTestState_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)->state = Dqn_TesterTestState_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 enum Dqn_TesterTestState {
Dqn_TesterTestState_Nil,
Dqn_TesterTestState_TestBegun,
Dqn_TesterTestState_TestFailed,
} Dqn_TesterTestState;
typedef struct Dqn_Tester {
int num_tests_in_group;
int num_tests_ok_in_group;
int log_count;
Dqn_TesterTestState state;
bool finished;
} Dqn_Tester;
Dqn_Tester Dqn_Tester_BeginGroupV(char const *fmt, va_list args);
Dqn_Tester Dqn_Tester_BeginGroup(char const *fmt, ...);
void Dqn_Tester_EndGroup(Dqn_Tester *test);
void Dqn_Tester_BeginV(Dqn_Tester *test, char const *fmt, va_list args);
void Dqn_Tester_Begin(Dqn_Tester *test, char const *fmt, ...);
void Dqn_Tester_End(Dqn_Tester *test);
// NOTE: Implementation
// -----------------------------------------------------------------------------
#if defined(DQN_TESTER_IMPLEMENTATION)
Dqn_Tester Dqn_Tester_BeginGroupV(char const *fmt, va_list args)
{
fprintf(stdout, fmt, args);
fputc('\n', stdout);
Dqn_Tester result = {};
return result;
}
Dqn_Tester Dqn_Tester_BeginGroup(char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Tester result = Dqn_Tester_BeginGroupV(fmt, args);
va_end(args);
return result;
}
void Dqn_Tester_EndGroup(Dqn_Tester *test)
{
if (test->finished)
return;
test->finished = true;
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");
}
void Dqn_Tester_BeginV(Dqn_Tester *test, char const *fmt, va_list args)
{
assert(test->state == Dqn_TesterTestState_Nil &&
"Nesting a unit test within another unit test is not allowed, ensure"
"the first test has finished by calling Dqn_Tester_End");
test->num_tests_in_group++;
test->state = Dqn_TesterTestState_TestBegun;
test->log_count = 0;
int size_required = 0;
{
va_list args_copy;
va_copy(args_copy, args);
size_required = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
}
printf("%*s", DQN_TESTER_SPACING, "");
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_Tester_Begin(Dqn_Tester *test, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_Tester_BeginV(test, fmt, args);
va_end(args);
}
void Dqn_Tester_End(Dqn_Tester *test)
{
assert(test->state != Dqn_TesterTestState_Nil && "Test was marked as ended but a test was never commenced using Dqn_Tester_Begin");
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->state == Dqn_TesterTestState_TestFailed) {
fprintf(stdout, DQN_TESTER_BAD_COLOR " FAILED");
} 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) {
putc('\n', stdout);
}
test->state = Dqn_TesterTestState_Nil;
}
#endif // DQN_TESTER_IMPLEMENTATION
#endif // DQN_TESTER_H

File diff suppressed because it is too large Load Diff

215
dqn_utest.h Normal file
View File

@ -0,0 +1,215 @@
#if !defined(DQN_UTEST_H)
#define DQN_UTEST_H
//
// NOTE: Overview
// -----------------------------------------------------------------------------
// A super minimal testing framework, most of the logic here is the pretty
// printing of test results.
// NOTE: Configuration
// -----------------------------------------------------------------------------
// #define DQN_UTEST_IMPLEMENTATION
// 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.
//
// #define DQN_UTEST_RESULT_LPAD
// Define this to a number to specify how much to pad the output of the test
// result line before the test result is printed.
//
// #define DQN_UTEST_RESULT_PAD_CHAR
// Define this to a character to specify the default character to use for
// padding. By default this is '.'
//
// #define DQN_UTEST_SPACING
// Define this to a number to specify the number of spaces between the group
// declaration and the test output in the group.
//
// #define DQN_UTEST_BAD_COLOR
// Define this to a terminal color code to specify what color errors will be
// presented as.
//
// #define DQN_UTEST_GOOD_COLOR
// Define this to a terminal color code to specify what color sucess will be
// presented as.
// NOTE: Macros
// -----------------------------------------------------------------------------
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#if !defined(DQN_UTEST_RESULT_LPAD)
#define DQN_UTEST_RESULT_LPAD 90
#endif
#if !defined(DQN_UTEST_RESULT_PAD_CHAR)
#define DQN_UTEST_RESULT_PAD_CHAR '.'
#endif
#if !defined(DQN_UTEST_SPACING)
#define DQN_UTEST_SPACING 2
#endif
#if !defined(DQN_UTEST_BAD_COLOR)
#define DQN_UTEST_BAD_COLOR "\x1b[31m"
#endif
#if !defined(DQN_UTEST_GOOD_COLOR)
#define DQN_UTEST_GOOD_COLOR "\x1b[32m"
#endif
#define DQN_UTEST_COLOR_RESET "\x1b[0m"
#define DQN_UTEST_GROUP(test, fmt, ...) \
for (Dqn_UTest *test_var_ = (printf(fmt "\n", ## __VA_ARGS__), &test); \
test_var_ != nullptr; \
Dqn_UTest_PrintStats(&test), test_var_ = nullptr)
#define DQN_UTEST_TEST(fmt, ...) \
for (int dummy_ = (Dqn_UTest_Begin(test_var_, fmt, ## __VA_ARGS__), 0); \
(void)dummy_, test_var_->state == Dqn_UTestState_TestBegun; \
Dqn_UTest_End(test_var_))
#define DQN_UTEST_ASSERTF(test, expr, fmt, ...) \
DQN_UTEST_ASSERTF_AT((test), __FILE__, __LINE__, (expr), fmt, ##__VA_ARGS__)
#define DQN_UTEST_ASSERT(test, expr) \
DQN_UTEST_ASSERT_AT((test), __FILE__, __LINE__, (expr))
// 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.
#define DQN_UTEST_LOG(fmt, ...) \
fprintf(stdout, "%*s" fmt "\n", DQN_UTEST_SPACING * 2, "", ##__VA_ARGS__)
#define DQN_UTEST_ASSERTF_AT(test, file, line, expr, fmt, ...) \
do { \
if (!(expr)) { \
(test)->state = Dqn_UTestState_TestFailed; \
fprintf(stderr, \
"%*sAssertion Triggered\n" \
"%*sFile: %s:%d\n" \
"%*sExpression: [" #expr "]\n" \
"%*sReason: " fmt "\n", \
DQN_UTEST_SPACING * 2, \
"", \
DQN_UTEST_SPACING * 3, \
"", \
file, \
line, \
DQN_UTEST_SPACING * 3, \
"", \
DQN_UTEST_SPACING * 3, \
"", \
##__VA_ARGS__); \
} \
} while (0)
#define DQN_UTEST_ASSERT_AT(test, file, line, expr) \
do { \
if (!(expr)) { \
(test)->state = Dqn_UTestState_TestFailed; \
fprintf(stderr, \
"%*sFile: %s:%d\n" \
"%*sExpression: [" #expr "]\n", \
DQN_UTEST_SPACING * 2, \
"", \
file, \
line, \
DQN_UTEST_SPACING * 2, \
""); \
} \
} while (0)
// NOTE: Header
// -----------------------------------------------------------------------------
typedef enum Dqn_UTestState {
Dqn_UTestState_Nil,
Dqn_UTestState_TestBegun,
Dqn_UTestState_TestFailed,
} Dqn_UTestState;
typedef struct Dqn_UTest {
int num_tests_in_group;
int num_tests_ok_in_group;
Dqn_UTestState state;
bool finished;
char name[1024];
size_t name_size;
} Dqn_UTest;
void Dqn_UTest_PrintStats(Dqn_UTest *test);
void Dqn_UTest_BeginV(Dqn_UTest *test, char const *fmt, va_list args);
void Dqn_UTest_Begin(Dqn_UTest *test, char const *fmt, ...);
void Dqn_UTest_End(Dqn_UTest *test);
// NOTE: Implementation
// -----------------------------------------------------------------------------
#if defined(DQN_UTEST_IMPLEMENTATION)
void Dqn_UTest_PrintStats(Dqn_UTest *test)
{
if (test->finished)
return;
test->finished = true;
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_UTEST_COLOR_RESET,
all_clear ? DQN_UTEST_GOOD_COLOR : DQN_UTEST_BAD_COLOR,
test->num_tests_ok_in_group,
test->num_tests_in_group,
all_clear ? "OK" : "FAILED");
}
void Dqn_UTest_BeginV(Dqn_UTest *test, char const *fmt, va_list args)
{
assert(test->state == Dqn_UTestState_Nil &&
"Nesting a unit test within another unit test is not allowed, ensure"
"the first test has finished by calling Dqn_UTest_End");
test->num_tests_in_group++;
test->state = Dqn_UTestState_TestBegun;
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);
}
void Dqn_UTest_Begin(Dqn_UTest *test, char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_UTest_BeginV(test, fmt, args);
va_end(args);
}
void Dqn_UTest_End(Dqn_UTest *test)
{
assert(test->state != Dqn_UTestState_Nil && "Test was marked as ended but a test was never commenced using Dqn_UTest_Begin");
int pad_size = DQN_UTEST_RESULT_LPAD - (DQN_UTEST_SPACING + test->name_size);
if (pad_size < 0)
pad_size = 0;
char pad_buffer[DQN_UTEST_RESULT_LPAD] = {};
memset(pad_buffer, DQN_UTEST_RESULT_PAD_CHAR, pad_size);
printf("%*s%.*s%.*s", DQN_UTEST_SPACING, "", (int)test->name_size, test->name, pad_size, pad_buffer);
if (test->state == Dqn_UTestState_TestFailed) {
printf(DQN_UTEST_BAD_COLOR " FAILED");
} else {
printf(DQN_UTEST_GOOD_COLOR " OK");
test->num_tests_ok_in_group++;
}
printf(DQN_UTEST_COLOR_RESET "\n");
test->state = Dqn_UTestState_Nil;
}
#endif // DQN_UTEST_IMPLEMENTATION
#endif // DQN_UTEST_H