/* * koan.h — minimalistic unit-testing library * Author: NukeBird, 2025 * License: public domain / MIT — as you wish */ #ifndef KOAN_H #define KOAN_H #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #define koan_isatty(_f) _isatty(_fileno(_f)) #else #include #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 */