// ================================================================ // // 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