game/axl_koan.h

255 lines
8.5 KiB
C
Raw Permalink Normal View History

2025-12-01 19:39:11 +03:00
/*
* koan.h minimalistic unit-testing library
* Author: NukeBird, 2025
* License: public domain / MIT as you wish
*/
#ifndef KOAN_H
#define KOAN_H
#include <inttypes.h>
#include <math.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <io.h>
#define koan_isatty(_f) _isatty(_fileno(_f))
#else
#include <unistd.h>
#define koan_isatty(_f) isatty(fileno(_f))
#endif
struct koan_test
{
const char* name;
void (*func)(jmp_buf);
struct koan_test* next;
};
static struct koan_test* koan_tests = NULL;
static struct koan_test** koan_tail = &koan_tests;
static void koan_add_test(const char* name, void (*func)(jmp_buf))
{
struct koan_test* test = malloc(sizeof *test);
if (!test) {
fprintf(stderr, "koan: out of memory for test registration\n");
exit(EXIT_FAILURE);
}
test->name = name;
test->func = func;
test->next = NULL;
*koan_tail = test;
koan_tail = &test->next;
}
static char* koan_fail_msg = NULL;
/* ------------------------------------------------------------------ */
/* Assertion macros */
/* ------------------------------------------------------------------ */
#define KOAN_FAIL(reason) \
do { \
size_t _sz = strlen(reason) + strlen(__FILE__) + 128; \
char* _buf = malloc(_sz); \
if (_buf) { \
snprintf(_buf, _sz, "%s\n at %s:%d", reason, __FILE__, __LINE__); \
} else { \
_buf = "koan: malloc failed while formatting error message"; \
} \
koan_fail_msg = _buf; \
longjmp(koan_jmp_buf, 1); \
} while (0)
#define ASSERT_TRUE(cond) \
do { if (!(cond)) { char _r[256]; snprintf(_r, sizeof(_r), "ASSERT_TRUE failed: (%s) is false", #cond); KOAN_FAIL(_r); } } while (0)
#define ASSERT_FALSE(cond) \
do { if (cond) { char _r[256]; snprintf(_r, sizeof(_r), "ASSERT_FALSE failed: (%s) is true", #cond); KOAN_FAIL(_r); } } while (0)
#define ASSERT_INT_EQ(expected, actual) \
do { \
int64_t _e = (int64_t)(expected); \
int64_t _a = (int64_t)(actual); \
if (_e != _a) { \
char _r[256]; \
snprintf(_r, sizeof(_r), "ASSERT_INT_EQ failed: expected %" PRId64 " but got %" PRId64, _e, _a); \
KOAN_FAIL(_r); \
} \
} while (0)
#define ASSERT_UINT_EQ(expected, actual) \
do { \
uint64_t _e = (uint64_t)(expected); \
uint64_t _a = (uint64_t)(actual); \
if (_e != _a) { \
char _r[256]; \
snprintf(_r, sizeof(_r), "ASSERT_UINT_EQ failed: expected %" PRIu64 " but got %" PRIu64, _e, _a); \
KOAN_FAIL(_r); \
} \
} while (0)
#define ASSERT_DOUBLE_EQ(expected, actual, eps) \
do { \
double _e = (double)(expected); \
double _a = (double)(actual); \
double _eps = (double)(eps); \
if (fabs(_e - _a) > _eps) { \
char _r[512]; \
snprintf(_r, sizeof(_r), "ASSERT_DOUBLE_EQ failed: expected %g but got %g (diff %g > eps %g)", \
_e, _a, fabs(_e - _a), _eps); \
KOAN_FAIL(_r); \
} \
} while (0)
#define ASSERT_STR_EQ(expected, actual) \
do { \
const char* _e = (expected); \
const char* _a = (actual); \
if ((_e == NULL || _a == NULL) ? (_e != _a) : strcmp(_e, _a)) { \
char _r[512]; \
snprintf(_r, sizeof(_r), "ASSERT_STR_EQ failed: expected \"%s\" but got \"%s\"", \
_e ? _e : "(null)", _a ? _a : "(null)"); \
KOAN_FAIL(_r); \
} \
} while (0)
#define ASSERT_PTR_EQ(expected, actual) \
do { \
const void* _e = (const void*)(expected); \
const void* _a = (const void*)(actual); \
if (_e != _a) { \
char _r[256]; \
snprintf(_r, sizeof(_r), "ASSERT_PTR_EQ failed: expected %p but got %p", _e, _a); \
KOAN_FAIL(_r); \
} \
} while (0)
#define ASSERT_NULL(ptr) ASSERT_PTR_EQ(NULL, (ptr))
#define ASSERT_NOT_NULL(ptr) ASSERT_TRUE((ptr) != NULL)
/* ------------------------------------------------------------------ */
/* Koan definition */
/* ------------------------------------------------------------------ */
#define KOAN(name) \
static void koan_test_##name(jmp_buf); \
__attribute__((constructor)) static void koan_register_##name(void) \
{ koan_add_test(#name, koan_test_##name); } \
static void koan_test_##name(jmp_buf koan_jmp_buf)
/* ------------------------------------------------------------------ */
/* Time formatting */
/* ------------------------------------------------------------------ */
static void koan_format_time(double secs, char* buf, size_t bufsz)
{
double ns = secs * 1e9;
if (ns < 1e3) snprintf(buf, bufsz, "%.0f ns", ns);
else if (ns < 1e6) snprintf(buf, bufsz, "%.2f µs", ns / 1e3);
else if (ns < 1e9) snprintf(buf, bufsz, "%.2f ms", ns / 1e6);
else snprintf(buf, bufsz, "%.3f s", secs);
}
/* ------------------------------------------------------------------ */
/* Progress bar (final only) */
/* ------------------------------------------------------------------ */
static void koan_print_bar(int done, int total, int passed, size_t failed, double elapsed)
{
const int w = 40;
int pos = (int)((float)done * w / total);
printf("[");
for (int i = 0; i < w; ++i) putchar(i < pos ? '=' : (i == pos ? '>' : '-'));
printf("] %3d%% %d/%d passed:%d failed:%zu %.3fs\n",
(int)((float)done * 100 / total), done, total, passed, failed, elapsed);
}
/* ------------------------------------------------------------------ */
/* Run all koans — simple sequential mode only */
/* ------------------------------------------------------------------ */
struct koan_failure { const char* name; const char* msg; };
int koan_run_all(void)
{
printf("\n");
int total = 0;
for (struct koan_test* t = koan_tests; t; t = t->next) total++;
if (total == 0) {
printf("koan: no koans — no enlightenment.\n");
return 0;
}
int colorful = koan_isatty(stdout);
const char* G = colorful ? "\033[32m" : "";
const char* R = colorful ? "\033[31m" : "";
const char* Y = colorful ? "\033[33m" : "";
const char* N = colorful ? "\033[0m" : "";
const char* OK = colorful ? "PASS " : "[PASS] ";
const char* FAIL = colorful ? "FAIL " : "[FAIL] ";
printf("%sEnlightening %d %s...%s\n\n", Y, total, total == 1 ? "koan" : "koans", N);
struct koan_failure* failures = NULL;
size_t fail_cap = 0, fail_cnt = 0;
int passed = 0;
clock_t start = clock();
for (struct koan_test* t = koan_tests; t; t = t->next) {
koan_fail_msg = NULL;
clock_t ts = clock();
jmp_buf env;
int result = setjmp(env);
if (result == 0) {
t->func(env);
double sec = (double)(clock() - ts) / CLOCKS_PER_SEC;
char tbuf[32];
koan_format_time(sec, tbuf, sizeof tbuf);
printf(" %s%s%s (%s)%s\n", G, OK, t->name, tbuf, N);
passed++;
} else {
double sec = (double)(clock() - ts) / CLOCKS_PER_SEC;
char tbuf[32];
koan_format_time(sec, tbuf, sizeof tbuf);
printf(" %s%s%s (%s)%s\n", R, FAIL, t->name, tbuf, N);
if (fail_cnt == fail_cap) {
fail_cap = fail_cap ? fail_cap * 2 : 16;
failures = realloc(failures, fail_cap * sizeof *failures);
if (!failures) exit(1);
}
failures[fail_cnt].name = t->name;
failures[fail_cnt].msg = koan_fail_msg ? koan_fail_msg : "unknown failure";
fail_cnt++;
}
}
double total_time = (double)(clock() - start) / CLOCKS_PER_SEC;
koan_print_bar(total, total, passed, fail_cnt, total_time);
printf("\n");
if (fail_cnt == 0) {
printf("%sAll %d %s achieved enlightenment in %.3f s!%s\n", G, total,
total == 1 ? "koan" : "koans", total_time, N);
free(failures);
return 0;
} else {
printf("%s%zu / %d %s did not achieve enlightenment (%.3f s)%s\n\n", R,
fail_cnt, total, fail_cnt == 1 ? "koan" : "koans", total_time, N);
printf("%s--- Failed Koans ---%s\n\n", R, N);
for (size_t i = 0; i < fail_cnt; ++i) {
printf(" %sKoan \"%s\" failed%s\n", R, failures[i].name, N);
printf(" %s\n\n", failures[i].msg);
free((void*)failures[i].msg);
}
free(failures);
printf("%sEnlightenment not achieved.%s\n", R, N);
return 1;
}
}
#endif /* KOAN_H */