From 40c43d19bb65542c8406207775def43ee875462d Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Fri, 23 Aug 2024 01:11:27 +0200 Subject: rf64.c, test.c (incomplete) --- examples/test.c | 989 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 989 insertions(+) create mode 100644 examples/test.c (limited to 'examples/test.c') diff --git a/examples/test.c b/examples/test.c new file mode 100644 index 0000000..c5e17bd --- /dev/null +++ b/examples/test.c @@ -0,0 +1,989 @@ +// ================================================================ +// +// test.c +// +// Unit-testing and microbenchmarks modular library for C +// +// ---------------------------------------------------------------- +// +// Optional settings +// +// - MAX_NUM_TESTS +// - MAX_NUM_TEST_ASSECTIONS +// - MAX_NUM_BENCHS +// - MAX_NUM_BENCH_REPEATS +// - MAX_NUM_BENCH_CYCLES +// - NUM_BENCH_REPEATS_DEFAULT_1 +// - NUM_BENCH_REPEATS_DEFAULT_2 +// +// ---------------------------------------------------------------- +// +// Usage +// +// - Define a unique TEST_FILE for each file to avoid name +// collisions. +// +// Example +// +// // foo.test.c +// #define TEST_FILE foo // This is required if you want to +// // use multiple test files. +// #define TEST_HEADER // Do not include the implementation. +// #include "test.c" +// TEST("foo") { +// REQUIRE(1); +// REQUIRE_EQ(2 + 2, 4); +// } +// +// // main.c +// #include "test.c" +// int main(int argc, char **argv) { +// return run_tests(argc, argv); +// } +// +// ---------------------------------------------------------------- +// +// (C) 2024 Mitya Selivanov , MIT License +// +// ================================================================ + +#ifndef TYPES_HEADER_GUARD_ +#define TYPES_HEADER_GUARD_ + +typedef signed char i8; +typedef signed short i16; +typedef signed i32; +typedef signed long long i64; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned u32; +typedef unsigned long long u64; +typedef char c8; +typedef int c32; +typedef signed char b8; +typedef float f32; +typedef double f64; + +#endif // TYPES_HEADER_GUARD_ + +// ================================================================ + +#ifndef TEST_HEADER_GUARD_ +#define TEST_HEADER_GUARD_ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wunused-value" +# pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifndef TEST_FILE +#define TEST_FILE test +#endif + +#ifndef MAX_NUM_TESTS +#define MAX_NUM_TESTS 0x1000 +#endif + +#ifndef MAX_NUM_TEST_ASSECTIONS +#define MAX_NUM_TEST_ASSECTIONS 0x50 +#endif + +#ifndef MAX_NUM_BENCHS +#define MAX_NUM_BENCHS 200 +#endif + +#ifndef MAX_NUM_BENCH_REPEATS +#define MAX_NUM_BENCH_REPEATS 4000 +#endif + +#ifndef MAX_NUM_BENCH_CYCLES +#define MAX_NUM_BENCH_CYCLES 40 +#endif + +#ifndef NUM_BENCH_REPEATS_DEFAULT_1 +#define NUM_BENCH_REPEATS_DEFAULT_1 40 +#endif + +#ifndef NUM_BENCH_REPEATS_DEFAULT_2 +#define NUM_BENCH_REPEATS_DEFAULT_2 400 +#endif + +typedef void (*test_report_fn)(i32 test_index, i32 line, i64 value, i64 expected); +typedef void (*test_run_fn) (i32 test_index_, test_report_fn test_report_fn_); + +typedef struct { + c8 const *test_name; + c8 const *test_file; + test_run_fn test_fn; + i32 assertions; + i32 line [MAX_NUM_TEST_ASSECTIONS]; + i32 status [MAX_NUM_TEST_ASSECTIONS]; + i64 value [MAX_NUM_TEST_ASSECTIONS]; + i64 expected[MAX_NUM_TEST_ASSECTIONS]; + i32 signal; +} Test_Case; + +typedef struct { + i32 size; + Test_Case v[MAX_NUM_TESTS]; +} Tests_List; + +extern Tests_List tests_list; + +#define TEST_CONCAT4_(a, b, c, d) a##b##c##d +#define TEST_CONCAT3_(a, b, c) TEST_CONCAT4_(a, b, _, c) + +#ifdef __cplusplus +# define TEST_ON_START_(f) \ + static void f(void); \ + static i32 TEST_CONCAT3_(_test_init_, __LINE__, f) = (f(), 0); \ + static void f(void) +#else +# ifdef _MSC_VER +# pragma section(".CRT$XCU", read) +# define TEST_ON_START_2_(f, p) \ + static void f(void); \ + __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ + __pragma(comment(linker, "/include:" p #f "_")) static void f(void) +# ifdef _WIN64 +# define TEST_ON_START_(f) TEST_ON_START_2_(f, "") +# else +# define TEST_ON_START_(f) TEST_ON_START_2_(f, "_") +# endif +# else +# define TEST_ON_START_(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) +# endif +#endif + +void test_register(c8 const *name, c8 const *file, test_run_fn fn); + +#define TEST(name) \ + static void TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__)(i32, test_report_fn); \ + TEST_ON_START_(TEST_CONCAT3_(test_case_, TEST_FILE, __LINE__)) { \ + test_register(name, __FILE__, TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__)); \ + } \ + static void TEST_CONCAT3_(test_run_, TEST_FILE, __LINE__) \ + (i32 test_index_, test_report_fn test_report_fn_) + +#define REQUIRE(...) test_report_fn_(test_index_, __LINE__, (__VA_ARGS__), 1) +#define REQUIRE_EQ(...) test_report_fn_(test_index_, __LINE__, __VA_ARGS__) + +i32 run_tests(i32 argc, c8 **argv); + +// ================================================================ + +typedef void (*bench_set_repeats_limit_fn)(i32 bench_index, i32 repeats_limit); +typedef i32 (*bench_loop_fn) (i32 bench_index); +typedef void (*bench_begin_fn) (i32 bench_index); +typedef void (*bench_end_fn) (i32 bench_index); + +typedef void (*bench_run_fn)( + i32 bench_index_, + bench_set_repeats_limit_fn bench_set_repeats_limit_, + bench_loop_fn bench_loop_, + bench_begin_fn bench_begin_, + bench_end_fn bench_end_ +); + +typedef struct { + c8 const *bench_name; + c8 const *bench_file; + bench_run_fn bench_fn; + i64 sec [MAX_NUM_BENCH_REPEATS]; + i32 nsec [MAX_NUM_BENCH_REPEATS]; + i64 duration_nsec [MAX_NUM_BENCH_REPEATS]; + i64 duration_sorted_nsec[MAX_NUM_BENCH_REPEATS]; + i32 repeats; + i32 cycles_size; + i32 cycles[MAX_NUM_BENCH_CYCLES]; + i32 cycle; + i32 signal; + i32 ready; +} Benchmark; + +typedef struct { + i32 size; + Benchmark v[MAX_NUM_BENCHS]; +} Benchs_List; + +extern Benchs_List benchs_list; + +void bench_register(c8 const *name, c8 const *file, bench_run_fn fn); + +#define BENCHMARK(name) \ + static void TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__) \ + (i32, bench_set_repeats_limit_fn, bench_loop_fn, bench_begin_fn, bench_end_fn); \ + TEST_ON_START_(TEST_CONCAT3_(benchmark_, TEST_FILE, __LINE__)) { \ + bench_register(name, __FILE__, TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__)); \ + } \ + static void TEST_CONCAT3_(bench_run_, TEST_FILE, __LINE__)( \ + i32 bench_index_, \ + bench_set_repeats_limit_fn bench_set_repeats_limit_, \ + bench_loop_fn bench_loop_, \ + bench_begin_fn bench_begin_, \ + bench_end_fn bench_end_ \ + ) + +#define BENCHMARK_REPEAT(repeats_limit_) \ + bench_set_repeats_limit_(bench_index_, repeats_limit_) + +#define BENCHMARK_BEGIN \ + while (bench_loop_(bench_index_)) { \ + bench_begin_(bench_index_); { + +#define BENCHMARK_END \ + } bench_end_(bench_index_); } + +// FIXME +// Does this work reliably? +// +#define DO_NOT_OPTIMIZE(x) \ + do { \ + volatile void *bench_ptr_ = &(x); \ + (void) bench_ptr_; \ + } while (0) + +i32 run_benchmarks(i32 argc, c8 **argv); + +#ifdef __cplusplus +} +#endif + +#endif // TEST_HEADER_GUARD_ + +// ================================================================ +// +// Implementation +// +// ================================================================ + +#ifndef TEST_HEADER +#ifndef TEST_IMPL_GUARD_ +#define TEST_IMPL_GUARD_ + +#include +#include +#include +#include +#include +#include + +enum { + white_, + blue_, + light_, + yellow_, + red_, + green_ +}; + +static c8 const *color_codes_[] = { + "\x1b[38m", "\x1b[34m", + "\x1b[37m", "\x1b[33m", + "\x1b[31m", "\x1b[32m" +}; + +static i32 print_color_(i32 c) { + return printf("%s", color_codes_[c]); +} + +static i32 signums_[] = { SIGINT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM }; + +static c8 const *signames_[64]; + +static void signames_init_(void) { + memset(signames_, 0, sizeof signames_); + signames_[SIGINT] = "Interactive attention signal"; + signames_[SIGILL] = "Illegal instruction"; + signames_[SIGABRT] = "Abnormal termination"; + signames_[SIGFPE] = "Erroneous arithmetic operation"; + signames_[SIGSEGV] = "Invalid access to storage"; + signames_[SIGTERM] = "Termination request"; +} + +Tests_List tests_list = { 0 }; + +static void report_(i32 i, i32 line, i64 value, i64 expected) { + i32 n = tests_list.v[i].assertions++; + + if (n >= MAX_NUM_TEST_ASSECTIONS) + return; + + tests_list.v[i].line[n] = line; + tests_list.v[i].status[n] = value == expected; + tests_list.v[i].value[n] = value; + tests_list.v[i].expected[n] = expected; +} + +static i64 ns_to_ms(i64 ns) { + return (ns + 500000) / 1000000; +} + +static i64 sec_to_ms(int64_t sec) { + return 1000 * sec; +} + +void test_register(c8 const *name, c8 const *file, test_run_fn fn) { + i32 n = tests_list.size++; + if (n < MAX_NUM_TESTS) { + tests_list.v[n].test_fn = fn; + tests_list.v[n].test_name = name; + tests_list.v[n].test_file = file; + tests_list.v[n].assertions = 0; + } +} + +static jmp_buf test_restore_execution; + +static void test_handle_signal(i32 signum) { + longjmp(test_restore_execution, signum); +} + +static void test_setup_signals() { + for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) + signal(signums_[i], test_handle_signal); +} + +static i32 run_test_(volatile i32 i) { + i32 signum = setjmp(test_restore_execution); + + if (signum != 0) { + tests_list.v[i].signal = signum; + return 0; + } + + tests_list.v[i].test_fn(i, report_); + return 1; +} + +i32 run_tests(i32 argc, c8 **argv) { + signames_init_(); + + i32 success_count = 0; + i32 fail_assertion_count = 0; + i32 total_assertion_count = 0; + i32 status = 0; + i32 quiet = 0; + i32 no_color = 0; + i32 line_width = 20; + i32 carriage_return = 1; + + i32 i, j; + + c8 const *specific_test = NULL; + + test_setup_signals(); + + for (i = 0; i < argc; i++) + if (strcmp("--no-term-color", argv[i]) == 0) + no_color = 1; + else if (strcmp("--no-carriage-return", argv[i]) == 0) + carriage_return = 0; + else if (strcmp("--quiet", argv[i]) == 0) + quiet = 1; + else if (strcmp("--match", argv[i]) == 0) + specific_test = argv[++i]; + + quiet && (no_color = 1); + + if (specific_test != NULL) { + no_color || print_color_(light_); + quiet || printf("Run tests matching "); + no_color || print_color_(white_); + quiet || printf("*%s*", specific_test); + no_color || print_color_(light_); + quiet || printf("\n\n"); + } + + c8 const *file = NULL; + i64 file_root = -1; + i32 tests_total = 0; + + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; + i++) { + if (specific_test != NULL && + strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + tests_total++; + i32 l = 2 + (int) strlen(tests_list.v[i].test_name); + if (line_width < l) + line_width = l; + } + + if (tests_total > 0) { + c8 const *s = tests_list.v[0].test_file; + + for (j = 1; j < tests_list.size && j < MAX_NUM_TESTS; + j++) { + if (specific_test != NULL && + strstr(tests_list.v[j].test_name, specific_test) == + NULL) + continue; + if (strcmp(s, tests_list.v[j].test_file) == 0) + continue; + i32 k = 0; + for (; + s[k] != '\0' && tests_list.v[j].test_file[k] != '\0' && + s[k] == tests_list.v[j].test_file[k]; + k++) { } + if (file_root == -1 || file_root > k) + file_root = k; + } + + if (file_root == -1) { + for (i = 0; s[i] != '\0'; i++) + if (s[i] == '/' || s[i] == '\\') + file_root = i + 1; + } + } + + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { + if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + if (file == NULL || strcmp(file, tests_list.v[i].test_file) != 0) { + if (file != NULL) + quiet || printf("\n"); + file = tests_list.v[i].test_file; + no_color || print_color_(blue_); + quiet || printf("* "); + no_color || print_color_(white_); + quiet || printf("%s\n", file + file_root); + } + + !carriage_return || no_color || print_color_(yellow_); + carriage_return || no_color || print_color_(light_); + quiet || printf("` %s ", tests_list.v[i].test_name); + !carriage_return || quiet || printf("\r"); + quiet || fflush(stdout); + + struct timespec begin, end; + timespec_get(&begin, TIME_UTC); + + i32 test_status = run_test_(i); + + timespec_get(&end, TIME_UTC); + i32 duration = (i32) (ns_to_ms(end.tv_nsec - begin.tv_nsec) + sec_to_ms(end.tv_sec - begin.tv_sec)); + + for (j = 0; j < tests_list.v[i].assertions && j < MAX_NUM_TEST_ASSECTIONS; j++) + if (tests_list.v[i].status[j] == 0) { + fail_assertion_count++; + test_status = 0; + } + + if (tests_list.v[i].assertions > MAX_NUM_TEST_ASSECTIONS) + test_status = 0; + + total_assertion_count += tests_list.v[i].assertions; + + !carriage_return || no_color || print_color_(light_); + !carriage_return || quiet || printf("` %s ", tests_list.v[i].test_name); + + i32 l = (i32) strlen(tests_list.v[i].test_name); + quiet || printf("%*c", line_width - l, ' '); + + if (test_status == 0) { + no_color || print_color_(red_); + quiet || printf("FAIL"); + no_color || print_color_(light_); + duration == 0 || quiet || printf(" %d ms", duration); + quiet || printf("\n"); + status = 1; + } else { + no_color || print_color_(green_); + quiet || printf("OK"); + no_color || print_color_(light_); + duration == 0 || quiet || printf(" %d ms", duration); + quiet || printf("\n"); + success_count++; + } + + quiet || fflush(stdout); + } + + no_color || print_color_(white_); + quiet || printf("\n%d of %d tests passed.\n", success_count, tests_total); + quiet || printf("%d of %d assertions passed.\n\n", total_assertion_count - fail_assertion_count, total_assertion_count); + no_color || print_color_(light_); + + if (!quiet && status != 0) { + i32 have_report_s = 0; + + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { + if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + if (tests_list.v[i].signal != 0) { + i32 signum = tests_list.v[i].signal; + if (signum >= 0 && signum < (int) (sizeof signames_ / sizeof *signames_) && signames_[signum] != NULL) { + no_color || print_color_(light_); + printf("Signal \"%s\" (%d) for \"", signames_[signum], signum); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\"!\n"); + } else { + no_color || print_color_(light_); + printf("Unknown signal (%d) for \"", signum); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\"!\n"); + } + have_report_s = 1; + } + if (tests_list.v[i].assertions > + MAX_NUM_TEST_ASSECTIONS) { + no_color || print_color_(light_); + printf("Too many assertions for \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\"!\n"); + have_report_s = 1; + } + } + + have_report_s && printf("\n"); + } + + if (!quiet && status != 0) { + for (i = 0; i < tests_list.size && i < MAX_NUM_TESTS; i++) { + if (specific_test != NULL && strstr(tests_list.v[i].test_name, specific_test) == NULL) + continue; + + if (tests_list.v[i].assertions <= MAX_NUM_TEST_ASSECTIONS) + for (j = 0; j < tests_list.v[i].assertions; j++) + if (!tests_list.v[i].status[j]) { + no_color || print_color_(light_); + printf("Assertion on line "); + no_color || print_color_(white_); + printf("%d", tests_list.v[i].line[j]); + no_color || print_color_(light_); + printf(" in \""); + no_color || print_color_(white_); + printf("%s", tests_list.v[i].test_file + file_root); + no_color || print_color_(light_); + printf("\" failed.\n"); + no_color || print_color_(red_); + printf(" -> "); + no_color || print_color_(light_); + printf("Got wrong value "); + no_color || print_color_(white_); + printf("%10lld", (i64) tests_list.v[i].value[j]); + no_color || print_color_(light_); + printf(" ("); + no_color || print_color_(white_); + printf("0x%08llx", (u64) tests_list.v[i].value[j]); + no_color || print_color_(light_); + printf(")\n"); + no_color || print_color_(green_); + printf(" -> "); + no_color || print_color_(light_); + printf("Expected value "); + no_color || print_color_(white_); + printf("%10lld", (i64) tests_list.v[i].expected[j]); + no_color || print_color_(light_); + printf(" ("); + no_color || print_color_(white_); + printf("0x%08llx", (u64) tests_list.v[i].expected[j]); + no_color || print_color_(light_); + printf(")\n\n"); + } + } + } + + if (tests_list.size > MAX_NUM_TESTS) { + no_color || print_color_(light_); + quiet || printf("Too many tests!\n\n"); + status = 1; + } + + if (status == 0) { + no_color || print_color_(green_); + quiet || printf("OK\n"); + } else { + no_color || print_color_(red_); + quiet || printf("FAILED\n"); + } + + no_color || print_color_(light_); + quiet || printf("\n"); + return status; +} + +// ================================================================ + +Benchs_List benchs_list = { 0 }; + +static void bench_set_repeats_limit(i32 i, i32 repeats_limit) { + if (benchs_list.v[i].ready) + return; + if (benchs_list.v[i].cycles_size >= MAX_NUM_BENCH_CYCLES) + return; + benchs_list.v[i].cycles[benchs_list.v[i].cycles_size] = repeats_limit; + benchs_list.v[i].cycles_size++; +} + +static i32 bench_loop(i32 i) { + if (!benchs_list.v[i].ready) + return 0; + return benchs_list.v[i].repeats < benchs_list.v[i].cycles[benchs_list.v[i].cycle]; +} + +static void bench_begin(i32 i) { + i32 n = benchs_list.v[i].repeats++; + + if (n >= MAX_NUM_BENCH_REPEATS) + return; + + struct timespec tv; + timespec_get(&tv, TIME_UTC); + + benchs_list.v[i].sec[n] = (int64_t) tv.tv_sec; + benchs_list.v[i].nsec[n] = (int64_t) tv.tv_nsec; +} + +static void bench_end(i32 i) { + i32 n = benchs_list.v[i].repeats - 1; + + if (n < 0 || n >= MAX_NUM_BENCH_REPEATS) + return; + + struct timespec tv; + timespec_get(&tv, TIME_UTC); + + i64 sec = ((i64) tv.tv_sec) - benchs_list.v[i].sec[n]; + i64 nsec = ((i64) tv.tv_nsec) - benchs_list.v[i].nsec[n]; + + benchs_list.v[i].duration_nsec[n] = sec * 1000000000 + nsec; +} + +void bench_register(c8 const *name, c8 const *file, bench_run_fn fn) { + i32 n = benchs_list.size++; + if (n < MAX_NUM_BENCHS) { + Benchmark *bench = benchs_list.v + n; + + bench->bench_fn = fn; + bench->bench_name = name; + bench->bench_file = file; + bench->cycles_size = 0; + bench->ready = 0; + } +} + +static void bench_setup_signals() { + for (u32 i = 0; i < sizeof signums_ / sizeof *signums_; i++) + signal(signums_[i], test_handle_signal); +} + +static i32 run_bench_(volatile i32 i) { + i32 signum = setjmp(test_restore_execution); + + if (signum != 0) { + benchs_list.v[i].signal = signum; + return 0; + } + + benchs_list.v[i].bench_fn(i, bench_set_repeats_limit, bench_loop, bench_begin, bench_end); + return 1; +} + +static i32 compare_64_(void const *x_, void const *y_) { + i64 const *x = (i64 const *) x_; + i64 const *y = (i64 const *) y_; + return *x - *y; +} + +static i32 compare_32_(void const *x_, void const *y_) { + i32 const *x = (i32 const *) x_; + i32 const *y = (i32 const *) y_; + return *x - *y; +} + +i32 run_benchmarks(i32 argc, c8 **argv) { + i32 success_count = 0; + i32 status = 0; + i32 no_color = 0; + i32 line_width = 20; + i32 carriage_return = 1; + + c8 const *specific_bench = NULL; + + bench_setup_signals(); + + for (i32 i = 0; i < argc; i++) + if (strcmp("--no-term-color", argv[i]) == 0) + no_color = 1; + else if (strcmp("--no-carriage-return", argv[i]) == 0) + carriage_return = 0; + else if (strcmp("--match", argv[i]) == 0) + specific_bench = argv[++i]; + + if (specific_bench != NULL) { + no_color || print_color_(light_); + printf("Run benchmarks matching "); + no_color || print_color_(white_); + printf("*%s*", specific_bench); + no_color || print_color_(light_); + printf("\n\n"); + } + + c8 const *file = NULL; + i64 file_root = -1; + i32 benchs_total = 0; + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + if (specific_bench != NULL && strstr(benchs_list.v[i].bench_name, specific_bench) == NULL) + continue; + benchs_total++; + i32 l = 2 + (int) strlen(benchs_list.v[i].bench_name); + if (line_width < l) + line_width = l; + } + + if (benchs_total > 0) { + c8 const *s = benchs_list.v[0].bench_file; + + for (i32 j = 1; j < benchs_list.size && j < MAX_NUM_BENCHS; j++) { + Benchmark *bench = benchs_list.v + j; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (strcmp(s, bench->bench_file) == 0) + continue; + i32 k = 0; + for (; s[k] != '\0' && bench->bench_file[k] != '\0' && s[k] == bench->bench_file[k]; k++) { } + if (file_root == -1 || file_root > k) + file_root = k; + } + + if (file_root == -1) { + for (i32 i = 0; s[i] != '\0'; i++) + if (s[i] == '/' || s[i] == '\\') + file_root = i + 1; + } + } + + no_color || print_color_(blue_); + printf("# "); + no_color || print_color_(light_); + printf("BENCHMARK"); + printf("%*c", line_width - 9, ' '); + no_color || print_color_(green_); + printf(" LOW "); + no_color || print_color_(light_); + printf("|"); + no_color || print_color_(blue_); + printf(" MEDIAN "); + no_color || print_color_(light_); + printf("|"); + no_color || print_color_(yellow_); + printf(" HIGH\n"); + no_color || print_color_(light_); + printf(" (in microseconds)\n\n"); + + // Prepare cycles. + // + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + + run_bench_(i); + + if (bench->cycles_size == 0) { + bench->cycles_size = 2; + bench->cycles[0] = NUM_BENCH_REPEATS_DEFAULT_1; + bench->cycles[1] = NUM_BENCH_REPEATS_DEFAULT_2; + } + + qsort(bench->cycles, bench->cycles_size, sizeof *bench->cycles, compare_32_); + + benchs_list.v[i].ready = 1; + } + + // Run cycles. + // + + for (i32 cycle = 0; cycle < MAX_NUM_BENCH_CYCLES; cycle++) { + // Prepare cycle. + // + + i32 cycles_done = 1; + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (cycle >= bench->cycles_size) + continue; + + bench->repeats = 0; + bench->cycle = cycle; + cycles_done = 0; + } + + if (cycles_done) + break; + + // Run benchmarks. + // + + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (cycle >= bench->cycles_size) + continue; + + if (file == NULL || strcmp(file, bench->bench_file) != 0) { + if (file != NULL) + printf("\n"); + file = bench->bench_file; + no_color || print_color_(blue_); + printf("* "); + no_color || print_color_(white_); + printf("%s\n", file + file_root); + } + + !carriage_return || no_color || print_color_(yellow_); + carriage_return || no_color || print_color_(light_); + printf("` %s ", bench->bench_name); + !carriage_return || printf("\r"); + fflush(stdout); + + i32 bench_status = run_bench_(i); + + if (bench->repeats > MAX_NUM_BENCH_REPEATS) + bench_status = 0; + + !carriage_return || no_color || print_color_(light_); + !carriage_return || printf("` %s ", bench->bench_name); + + i32 l = (i32) strlen(bench->bench_name); + printf("%*c", line_width - l, ' '); + + if (bench->repeats <= 0) { + no_color || print_color_(yellow_); + printf(" 0 runs\n"); + success_count++; + } else if (bench_status == 0) { + no_color || print_color_(red_); + printf(" FAIL\n"); + status = 1; + } else { + i32 repeats = bench->repeats; + + memcpy(bench->duration_sorted_nsec, bench->duration_nsec, repeats * sizeof *bench->duration_sorted_nsec); + qsort(bench->duration_sorted_nsec, repeats, sizeof *bench->duration_sorted_nsec, compare_64_); + + i64 average = bench->duration_sorted_nsec[repeats / 2]; + i64 floor = bench->duration_sorted_nsec[repeats / 20]; + i64 roof = bench->duration_sorted_nsec[repeats - repeats / 20 - 1]; + + no_color || print_color_(white_); + printf("%-9g", (f64) floor * 0.001); + no_color || print_color_(light_); + printf("| "); + no_color || print_color_(white_); + printf("%-9g", (f64) average * 0.001); + no_color || print_color_(light_); + printf("| "); + no_color || print_color_(white_); + printf("%-9g", (f64) roof * 0.001); + no_color || print_color_(light_); + printf(" %d runs\n", repeats); + success_count++; + } + } + } + + printf("\n"); + + if (status != 0) { + for (i32 i = 0; i < benchs_list.size && i < MAX_NUM_BENCHS; i++) { + Benchmark *bench = benchs_list.v + i; + + if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) + continue; + if (bench->signal != 0) { + i32 signum = bench->signal; + if (signum >= 0 && signum < (i32) (sizeof signames_ / sizeof *signames_) && signames_[signum] != NULL) { + no_color || print_color_(light_); + printf("Signal \"%s\" (%d) for \"", signames_[signum], signum); + no_color || print_color_(white_); + printf("%s", bench->bench_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", bench->bench_file + file_root); + no_color || print_color_(light_); + printf("\"!.\n"); + } else { + no_color || print_color_(light_); + printf("Unknown signal (%d) for \"", signum); + no_color || print_color_(white_); + printf("%s", bench->bench_name); + no_color || print_color_(light_); + printf("\" in \""); + no_color || print_color_(white_); + printf("%s", bench->bench_file + file_root); + no_color || print_color_(light_); + printf("\"!.\n"); + } + } + } + + printf("\n"); + } + + if (benchs_list.size > MAX_NUM_BENCHS) { + no_color || print_color_(light_); + printf("Too many benchmarks!\n\n"); + status = 1; + } + + if (status == 0) { + no_color || print_color_(green_); + printf("DONE\n"); + } else { + no_color || print_color_(red_); + printf("DONE WITH ERRORS\n"); + } + + no_color || print_color_(light_); + printf("\n"); + return status; +} + +#endif // TEST_IMPL_GUARD_ +#endif // TEST_HEADER -- cgit v1.2.3