// ================================================================ // // test.h // https://guattari.tech/git/kit // // Header-only unit-testing and microbenchmarks library for C. // // // - Define a unique KIT_TEST_FILE for each file to avoid name // collisions. // // - Define KIT_TEST_IMPLEMENTATION to include the implementation. // // // Optional settings // // - KIT_TESTS_SIZE_LIMIT // - KIT_TEST_ASSERTIONS_LIMIT // - KIT_BENCHS_SIZE_LIMIT // - KIT_BENCH_MAX_REPEATS // - KIT_BENCH_MAX_CYCLES // - KIT_BENCH_REPEATS_DEFAULT_1 // - KIT_BENCH_REPEATS_DEFAULT_2 // // // Usage example // // // foo.test.c // #define KIT_TEST_FILE foo // This is required if you want to // // use multiple test files. // #include "test.h" // TEST("foo") { // REQUIRE(1); // REQUIRE_EQ(2 + 2, 4); // } // // // main.c // #define KIT_TEST_IMPLEMENTATION // #include "test.h" // int main(int argc, char **argv) { // return run_tests(argc, argv); // } // // ================================================================ #ifndef KIT_TEST_H #define KIT_TEST_H #ifdef __GNUC__ # pragma GCC diagnostic ignored "-Wunused-value" # pragma GCC diagnostic ignored "-Wunused-parameter" #endif #ifdef __cplusplus extern "C" { #endif #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #ifndef KIT_TEST_FILE # define KIT_TEST_FILE kit_test #endif #ifndef KIT_TESTS_SIZE_LIMIT # define KIT_TESTS_SIZE_LIMIT 0x1000 #endif #ifndef KIT_TEST_ASSERTIONS_LIMIT # define KIT_TEST_ASSERTIONS_LIMIT 0x50 #endif #ifndef KIT_BENCHS_SIZE_LIMIT # define KIT_BENCHS_SIZE_LIMIT 200 #endif #ifndef KIT_BENCH_MAX_REPEATS # define KIT_BENCH_MAX_REPEATS 4000 #endif #ifndef KIT_BENCH_MAX_CYCLES # define KIT_BENCH_MAX_CYCLES 40 #endif #ifndef KIT_BENCH_REPEATS_DEFAULT_1 # define KIT_BENCH_REPEATS_DEFAULT_1 40 #endif #ifndef KIT_BENCH_REPEATS_DEFAULT_2 # define KIT_BENCH_REPEATS_DEFAULT_2 400 #endif typedef void (*kit_test_report_fn)(int test_index, int line, int64_t value, int64_t expected); typedef void (*kit_test_run_fn)( int kit_test_index_, kit_test_report_fn kit_test_report_fn_); typedef struct { char const *test_name; char const *test_file; kit_test_run_fn test_fn; int assertions; int line[KIT_TEST_ASSERTIONS_LIMIT]; int status[KIT_TEST_ASSERTIONS_LIMIT]; int64_t value[KIT_TEST_ASSERTIONS_LIMIT]; int64_t expected[KIT_TEST_ASSERTIONS_LIMIT]; int signal; } kit_test_case_t; typedef struct { int size; kit_test_case_t v[KIT_TESTS_SIZE_LIMIT]; } kit_tests_list_t; extern kit_tests_list_t kit_tests_list; #define KIT_TEST_CONCAT4_(a, b, c, d) a##b##c##d #define KIT_TEST_CONCAT3_(a, b, c) KIT_TEST_CONCAT4_(a, b, _, c) #ifdef __cplusplus # define KIT_TEST_ON_START_(f) \ static void f(void); \ static int KIT_TEST_CONCAT3_(_kit_test_init_, __LINE__, \ f) = (f(), 0); \ static void f(void) #else # ifdef _MSC_VER # pragma section(".CRT$XCU", read) # define KIT_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 KIT_TEST_ON_START_(f) KIT_TEST_ON_START_2_(f, "") # else # define KIT_TEST_ON_START_(f) KIT_TEST_ON_START_2_(f, "_") # endif # else # define KIT_TEST_ON_START_(f) \ static void f(void) __attribute__((constructor)); \ static void f(void) # endif #endif void kit_test_register(char const *name, char const *file, kit_test_run_fn fn); #define KIT_TEST(name) \ static void KIT_TEST_CONCAT3_(kit_test_run_, __LINE__, \ KIT_TEST_FILE)(int, \ kit_test_report_fn); \ KIT_TEST_ON_START_( \ KIT_TEST_CONCAT3_(kit_test_case_, __LINE__, KIT_TEST_FILE)) { \ kit_test_register( \ name, __FILE__, \ KIT_TEST_CONCAT3_(kit_test_run_, __LINE__, KIT_TEST_FILE)); \ } \ static void KIT_TEST_CONCAT3_(kit_test_run_, __LINE__, \ KIT_TEST_FILE)( \ int kit_test_index_, kit_test_report_fn kit_test_report_fn_) #define KIT_REQUIRE(...) \ kit_test_report_fn_(kit_test_index_, __LINE__, (__VA_ARGS__), 1) #define KIT_REQUIRE_EQ(...) \ kit_test_report_fn_(kit_test_index_, __LINE__, __VA_ARGS__) int kit_run_tests(int argc, char **argv); typedef void (*kit_bench_set_repeats_limit_fn)(int bench_index, int repeats_limit); typedef int (*kit_bench_loop_fn)(int bench_index); typedef void (*kit_bench_begin_fn)(int bench_index); typedef void (*kit_bench_end_fn)(int bench_index); typedef void (*kit_bench_run_fn)( int kit_bench_index_, kit_bench_set_repeats_limit_fn kit_bench_set_repeats_limit_, kit_bench_loop_fn kit_bench_loop_, kit_bench_begin_fn kit_bench_begin_, kit_bench_end_fn kit_bench_end_); typedef struct { char const *bench_name; char const *bench_file; kit_bench_run_fn bench_fn; int64_t sec[KIT_BENCH_MAX_REPEATS]; int32_t nsec[KIT_BENCH_MAX_REPEATS]; int64_t duration_nsec[KIT_BENCH_MAX_REPEATS]; int64_t duration_sorted_nsec[KIT_BENCH_MAX_REPEATS]; int repeats; int cycles_size; int cycles[KIT_BENCH_MAX_CYCLES]; int cycle; int signal; int ready; } kit_benchmark_t; typedef struct { int size; kit_benchmark_t v[KIT_BENCHS_SIZE_LIMIT]; } kit_benchs_list_t; extern kit_benchs_list_t kit_benchs_list; void kit_bench_register(char const *name, char const *file, kit_bench_run_fn fn); #define KIT_BENCHMARK(name) \ static void KIT_TEST_CONCAT3_(kit_bench_run_, __LINE__, \ KIT_TEST_FILE)( \ int, kit_bench_set_repeats_limit_fn, kit_bench_loop_fn, \ kit_bench_begin_fn, kit_bench_end_fn); \ KIT_TEST_ON_START_( \ KIT_TEST_CONCAT3_(kit_benchmark_, __LINE__, KIT_TEST_FILE)) { \ kit_bench_register( \ name, __FILE__, \ KIT_TEST_CONCAT3_(kit_bench_run_, __LINE__, KIT_TEST_FILE)); \ } \ static void KIT_TEST_CONCAT3_(kit_bench_run_, __LINE__, \ KIT_TEST_FILE)( \ int kit_bench_index_, \ kit_bench_set_repeats_limit_fn kit_bench_set_repeats_limit_, \ kit_bench_loop_fn kit_bench_loop_, \ kit_bench_begin_fn kit_bench_begin_, \ kit_bench_end_fn kit_bench_end_) #define KIT_BENCHMARK_REPEAT(repeats_limit_) \ kit_bench_set_repeats_limit_(kit_bench_index_, repeats_limit_) #define KIT_BENCHMARK_BEGIN \ while (kit_bench_loop_(kit_bench_index_)) { \ kit_bench_begin_(kit_bench_index_); \ { #define KIT_BENCHMARK_END \ } \ kit_bench_end_(kit_bench_index_); \ } // FIXME // #define KIT_DO_NOT_OPTIMIZE(x) \ do { \ volatile void *bench_ptr_ = &(x); \ (void) bench_ptr_; \ } while (0) int kit_run_benchmarks(int argc, char **argv); #define TEST KIT_TEST #define REQUIRE KIT_REQUIRE #define REQUIRE_EQ KIT_REQUIRE_EQ #define BENCHMARK KIT_BENCHMARK #define BENCHMARK_REPEAT KIT_BENCHMARK_REPEAT #define BENCHMARK_BEGIN KIT_BENCHMARK_BEGIN #define BENCHMARK_END KIT_BENCHMARK_END #define DO_NOT_OPTIMIZE KIT_DO_NOT_OPTIMIZE #define test_register kit_test_register #define run_tests kit_run_tests #define bench_register kit_bench_register #define run_benchmarks kit_run_benchmarks #ifdef __cplusplus } #endif #endif // ================================================================ // // Implementation // // ================================================================ #if defined(KIT_TEST_IMPLEMENTATION) && !defined(KIT_TEST_H_IMPL) #define KIT_TEST_H_IMPL #ifdef __cplusplus extern "C" { #endif #ifndef KIT_TIME_H # define KIT_TIME_H # include # ifndef TIME_UTC # define TIME_UTC 1 # endif # ifdef KIT_REQUIRE_TIMESPEC_GET # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN 1 # endif # include # define KIT_TIMESPEC_IMPL_UNIX_EPOCH_IN_TICKS \ 116444736000000000ull # define KIT_TIMESPEC_IMPL_TICKS_PER_SECONDS 10000000ull static int timespec_get(struct timespec *ts, int base) { if (ts == NULL || base != TIME_UTC) return 0; FILETIME ft; ULARGE_INTEGER date; LONGLONG ticks; GetSystemTimeAsFileTime(&ft); date.HighPart = ft.dwHighDateTime; date.LowPart = ft.dwLowDateTime; ticks = (LONGLONG) (date.QuadPart - KIT_TIMESPEC_IMPL_UNIX_EPOCH_IN_TICKS); ts->tv_sec = ticks / KIT_TIMESPEC_IMPL_TICKS_PER_SECONDS; ts->tv_nsec = (ticks % KIT_TIMESPEC_IMPL_TICKS_PER_SECONDS) * 100; return base; } # endif #endif #include #include #include #include #include enum { kit_white_, kit_blue_, kit_light_, kit_yellow_, kit_red_, kit_green_ }; static char const *kit_color_codes_[] = { "\x1b[38m", "\x1b[34m", "\x1b[37m", "\x1b[33m", "\x1b[31m", "\x1b[32m" }; static int kit_print_color_(int c) { return printf("%s", kit_color_codes_[c]); } static int kit_signums_[] = { SIGINT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM }; static char const *kit_signames_[64]; static void kit_signames_init_(void) { memset(kit_signames_, 0, sizeof kit_signames_); kit_signames_[SIGINT] = "Interactive attention signal"; kit_signames_[SIGILL] = "Illegal instruction"; kit_signames_[SIGABRT] = "Abnormal termination"; kit_signames_[SIGFPE] = "Erroneous arithmetic operation"; kit_signames_[SIGSEGV] = "Invalid access to storage"; kit_signames_[SIGTERM] = "Termination request"; } kit_tests_list_t kit_tests_list = { 0 }; static void kit_report_(int i, int line, int64_t value, int64_t expected) { int n = kit_tests_list.v[i].assertions++; if (n >= KIT_TEST_ASSERTIONS_LIMIT) return; kit_tests_list.v[i].line[n] = line; kit_tests_list.v[i].status[n] = value == expected; kit_tests_list.v[i].value[n] = value; kit_tests_list.v[i].expected[n] = expected; } static int64_t ns_to_ms(int64_t ns) { return (ns + 500000) / 1000000; } static int64_t sec_to_ms(int64_t sec) { return 1000 * sec; } void kit_test_register(char const *name, char const *file, kit_test_run_fn fn) { int n = kit_tests_list.size++; if (n < KIT_TESTS_SIZE_LIMIT) { kit_tests_list.v[n].test_fn = fn; kit_tests_list.v[n].test_name = name; kit_tests_list.v[n].test_file = file; kit_tests_list.v[n].assertions = 0; } } static jmp_buf kit_test_restore_execution; static void kit_test_handle_signal(int signum) { longjmp(kit_test_restore_execution, signum); } static void kit_test_setup_signals() { for (unsigned i = 0; i < sizeof kit_signums_ / sizeof *kit_signums_; i++) signal(kit_signums_[i], kit_test_handle_signal); } static int kit_run_test_(volatile int i) { int signum = setjmp(kit_test_restore_execution); if (signum != 0) { kit_tests_list.v[i].signal = signum; return 0; } kit_tests_list.v[i].test_fn(i, kit_report_); return 1; } int kit_run_tests(int argc, char **argv) { kit_signames_init_(); int success_count = 0; int fail_assertion_count = 0; int total_assertion_count = 0; int status = 0; int quiet = 0; int no_color = 0; int line_width = 20; int carriage_return = 1; int i, j; char const *specific_test = NULL; kit_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 || kit_print_color_(kit_light_); quiet || printf("Run tests matching "); no_color || kit_print_color_(kit_white_); quiet || printf("*%s*", specific_test); no_color || kit_print_color_(kit_light_); quiet || printf("\n\n"); } char const *file = NULL; ptrdiff_t file_root = -1; int tests_total = 0; for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT; i++) { if (specific_test != NULL && strstr(kit_tests_list.v[i].test_name, specific_test) == NULL) continue; tests_total++; int l = 2 + (int) strlen(kit_tests_list.v[i].test_name); if (line_width < l) line_width = l; } if (tests_total > 0) { char const *s = kit_tests_list.v[0].test_file; for (j = 1; j < kit_tests_list.size && j < KIT_TESTS_SIZE_LIMIT; j++) { if (specific_test != NULL && strstr(kit_tests_list.v[j].test_name, specific_test) == NULL) continue; if (strcmp(s, kit_tests_list.v[j].test_file) == 0) continue; int k = 0; for (; s[k] != '\0' && kit_tests_list.v[j].test_file[k] != '\0' && s[k] == kit_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 < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT; i++) { if (specific_test != NULL && strstr(kit_tests_list.v[i].test_name, specific_test) == NULL) continue; if (file == NULL || strcmp(file, kit_tests_list.v[i].test_file) != 0) { if (file != NULL) quiet || printf("\n"); file = kit_tests_list.v[i].test_file; no_color || kit_print_color_(kit_blue_); quiet || printf("* "); no_color || kit_print_color_(kit_white_); quiet || printf("%s\n", file + file_root); } !carriage_return || no_color || kit_print_color_(kit_yellow_); carriage_return || no_color || kit_print_color_(kit_light_); quiet || printf("` %s ", kit_tests_list.v[i].test_name); !carriage_return || quiet || printf("\r"); quiet || fflush(stdout); struct timespec begin, end; timespec_get(&begin, TIME_UTC); int test_status = kit_run_test_(i); timespec_get(&end, TIME_UTC); int duration = (int) (ns_to_ms(end.tv_nsec - begin.tv_nsec) + sec_to_ms(end.tv_sec - begin.tv_sec)); for (j = 0; j < kit_tests_list.v[i].assertions && j < KIT_TEST_ASSERTIONS_LIMIT; j++) if (kit_tests_list.v[i].status[j] == 0) { fail_assertion_count++; test_status = 0; } if (kit_tests_list.v[i].assertions > KIT_TEST_ASSERTIONS_LIMIT) test_status = 0; total_assertion_count += kit_tests_list.v[i].assertions; !carriage_return || no_color || kit_print_color_(kit_light_); !carriage_return || quiet || printf("` %s ", kit_tests_list.v[i].test_name); int l = (int) strlen(kit_tests_list.v[i].test_name); quiet || printf("%*c", line_width - l, ' '); if (test_status == 0) { no_color || kit_print_color_(kit_red_); quiet || printf("FAIL"); no_color || kit_print_color_(kit_light_); duration == 0 || quiet || printf(" %d ms", duration); quiet || printf("\n"); status = 1; } else { no_color || kit_print_color_(kit_green_); quiet || printf("OK"); no_color || kit_print_color_(kit_light_); duration == 0 || quiet || printf(" %d ms", duration); quiet || printf("\n"); success_count++; } quiet || fflush(stdout); } no_color || kit_print_color_(kit_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 || kit_print_color_(kit_light_); if (!quiet && status != 0) { int have_kit_report_s = 0; for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT; i++) { if (specific_test != NULL && strstr(kit_tests_list.v[i].test_name, specific_test) == NULL) continue; if (kit_tests_list.v[i].signal != 0) { int signum = kit_tests_list.v[i].signal; if (signum >= 0 && signum < (int) (sizeof kit_signames_ / sizeof *kit_signames_) && kit_signames_[signum] != NULL) { no_color || kit_print_color_(kit_light_); printf("Signal \"%s\" (%d) for \"", kit_signames_[signum], signum); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_name); no_color || kit_print_color_(kit_light_); printf("\" in \""); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_file + file_root); no_color || kit_print_color_(kit_light_); printf("\"!\n"); } else { no_color || kit_print_color_(kit_light_); printf("Unknown signal (%d) for \"", signum); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_name); no_color || kit_print_color_(kit_light_); printf("\" in \""); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_file + file_root); no_color || kit_print_color_(kit_light_); printf("\"!\n"); } have_kit_report_s = 1; } if (kit_tests_list.v[i].assertions > KIT_TEST_ASSERTIONS_LIMIT) { no_color || kit_print_color_(kit_light_); printf("Too many assertions for \""); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_name); no_color || kit_print_color_(kit_light_); printf("\" in \""); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_file + file_root); no_color || kit_print_color_(kit_light_); printf("\"!\n"); have_kit_report_s = 1; } } have_kit_report_s &&printf("\n"); } if (!quiet && status != 0) { for (i = 0; i < kit_tests_list.size && i < KIT_TESTS_SIZE_LIMIT; i++) { if (specific_test != NULL && strstr(kit_tests_list.v[i].test_name, specific_test) == NULL) continue; if (kit_tests_list.v[i].assertions <= KIT_TEST_ASSERTIONS_LIMIT) for (j = 0; j < kit_tests_list.v[i].assertions; j++) if (!kit_tests_list.v[i].status[j]) { no_color || kit_print_color_(kit_light_); printf("Assertion on line "); no_color || kit_print_color_(kit_white_); printf("%d", kit_tests_list.v[i].line[j]); no_color || kit_print_color_(kit_light_); printf(" in \""); no_color || kit_print_color_(kit_white_); printf("%s", kit_tests_list.v[i].test_file + file_root); no_color || kit_print_color_(kit_light_); printf("\" failed.\n"); no_color || kit_print_color_(kit_red_); printf(" -> "); no_color || kit_print_color_(kit_light_); printf("Got wrong value "); no_color || kit_print_color_(kit_white_); printf("%10lld", (long long) kit_tests_list.v[i].value[j]); no_color || kit_print_color_(kit_light_); printf(" ("); no_color || kit_print_color_(kit_white_); printf("0x%08llx", (unsigned long long) kit_tests_list.v[i].value[j]); no_color || kit_print_color_(kit_light_); printf(")\n"); no_color || kit_print_color_(kit_green_); printf(" -> "); no_color || kit_print_color_(kit_light_); printf("Expected value "); no_color || kit_print_color_(kit_white_); printf("%10lld", (long long) kit_tests_list.v[i].expected[j]); no_color || kit_print_color_(kit_light_); printf(" ("); no_color || kit_print_color_(kit_white_); printf( "0x%08llx", (unsigned long long) kit_tests_list.v[i].expected[j]); no_color || kit_print_color_(kit_light_); printf(")\n\n"); } } } if (kit_tests_list.size > KIT_TESTS_SIZE_LIMIT) { no_color || kit_print_color_(kit_light_); quiet || printf("Too many tests!\n\n"); status = 1; } if (status == 0) { no_color || kit_print_color_(kit_green_); quiet || printf("OK\n"); } else { no_color || kit_print_color_(kit_red_); quiet || printf("FAILED\n"); } no_color || kit_print_color_(kit_light_); quiet || printf("\n"); return status; } kit_benchs_list_t kit_benchs_list = { 0 }; static void bench_set_repeats_limit(int i, int repeats_limit) { if (kit_benchs_list.v[i].ready) return; if (kit_benchs_list.v[i].cycles_size >= KIT_BENCH_MAX_CYCLES) return; kit_benchs_list.v[i].cycles[kit_benchs_list.v[i].cycles_size] = repeats_limit; kit_benchs_list.v[i].cycles_size++; } static int bench_loop(int i) { if (!kit_benchs_list.v[i].ready) return 0; return kit_benchs_list.v[i].repeats < kit_benchs_list.v[i].cycles[kit_benchs_list.v[i].cycle]; } static void bench_begin(int i) { int n = kit_benchs_list.v[i].repeats++; if (n >= KIT_BENCH_MAX_REPEATS) return; struct timespec tv; timespec_get(&tv, TIME_UTC); kit_benchs_list.v[i].sec[n] = (int64_t) tv.tv_sec; kit_benchs_list.v[i].nsec[n] = (int64_t) tv.tv_nsec; } static void bench_end(int i) { int n = kit_benchs_list.v[i].repeats - 1; if (n < 0 || n >= KIT_BENCH_MAX_REPEATS) return; struct timespec tv; timespec_get(&tv, TIME_UTC); int64_t sec = ((int64_t) tv.tv_sec) - kit_benchs_list.v[i].sec[n]; int64_t nsec = ((int64_t) tv.tv_nsec) - kit_benchs_list.v[i].nsec[n]; kit_benchs_list.v[i].duration_nsec[n] = sec * 1000000000 + nsec; } void kit_bench_register(char const *name, char const *file, kit_bench_run_fn fn) { int n = kit_benchs_list.size++; if (n < KIT_BENCHS_SIZE_LIMIT) { kit_benchmark_t *bench = kit_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 kit_bench_setup_signals() { for (unsigned i = 0; i < sizeof kit_signums_ / sizeof *kit_signums_; i++) signal(kit_signums_[i], kit_test_handle_signal); } static int kit_run_bench_(volatile int i) { int signum = setjmp(kit_test_restore_execution); if (signum != 0) { kit_benchs_list.v[i].signal = signum; return 0; } kit_benchs_list.v[i].bench_fn(i, bench_set_repeats_limit, bench_loop, bench_begin, bench_end); return 1; } static int kit_compare_64_(void const *x_, void const *y_) { int64_t const *x = (int64_t const *) x_; int64_t const *y = (int64_t const *) y_; return *x - *y; } static int kit_compare_32_(void const *x_, void const *y_) { int const *x = (int const *) x_; int const *y = (int const *) y_; return *x - *y; } int kit_run_benchmarks(int argc, char **argv) { int success_count = 0; int status = 0; int no_color = 0; int line_width = 20; int carriage_return = 1; char const *specific_bench = NULL; kit_bench_setup_signals(); for (int 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 || kit_print_color_(kit_light_); printf("Run benchmarks matching "); no_color || kit_print_color_(kit_white_); printf("*%s*", specific_bench); no_color || kit_print_color_(kit_light_); printf("\n\n"); } char const *file = NULL; ptrdiff_t file_root = -1; int benchs_total = 0; for (int i = 0; i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) { if (specific_bench != NULL && strstr(kit_benchs_list.v[i].bench_name, specific_bench) == NULL) continue; benchs_total++; int l = 2 + (int) strlen(kit_benchs_list.v[i].bench_name); if (line_width < l) line_width = l; } if (benchs_total > 0) { char const *s = kit_benchs_list.v[0].bench_file; for (int j = 1; j < kit_benchs_list.size && j < KIT_BENCHS_SIZE_LIMIT; j++) { kit_benchmark_t *bench = kit_benchs_list.v + j; if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) continue; if (strcmp(s, bench->bench_file) == 0) continue; int 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 (int i = 0; s[i] != '\0'; i++) if (s[i] == '/' || s[i] == '\\') file_root = i + 1; } } no_color || kit_print_color_(kit_blue_); printf("# "); no_color || kit_print_color_(kit_light_); printf("BENCHMARK"); printf("%*c", line_width - 9, ' '); no_color || kit_print_color_(kit_green_); printf(" LOW "); no_color || kit_print_color_(kit_light_); printf("|"); no_color || kit_print_color_(kit_blue_); printf(" MEDIAN "); no_color || kit_print_color_(kit_light_); printf("|"); no_color || kit_print_color_(kit_yellow_); printf(" HIGH\n"); no_color || kit_print_color_(kit_light_); printf(" (in microseconds)\n\n"); /* Prepare cycles. */ for (int i = 0; i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) { kit_benchmark_t *bench = kit_benchs_list.v + i; if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) continue; kit_run_bench_(i); if (bench->cycles_size == 0) { bench->cycles_size = 2; bench->cycles[0] = KIT_BENCH_REPEATS_DEFAULT_1; bench->cycles[1] = KIT_BENCH_REPEATS_DEFAULT_2; } qsort(bench->cycles, bench->cycles_size, sizeof *bench->cycles, kit_compare_32_); kit_benchs_list.v[i].ready = 1; } /* Run cycles. */ for (int cycle = 0; cycle < KIT_BENCH_MAX_CYCLES; cycle++) { /* Prepare cycle. */ int cycles_done = 1; for (int i = 0; i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) { kit_benchmark_t *bench = kit_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 (int i = 0; i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) { kit_benchmark_t *bench = kit_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 || kit_print_color_(kit_blue_); printf("* "); no_color || kit_print_color_(kit_white_); printf("%s\n", file + file_root); } !carriage_return || no_color || kit_print_color_(kit_yellow_); carriage_return || no_color || kit_print_color_(kit_light_); printf("` %s ", bench->bench_name); !carriage_return || printf("\r"); fflush(stdout); int bench_status = kit_run_bench_(i); if (bench->repeats > KIT_BENCH_MAX_REPEATS) bench_status = 0; !carriage_return || no_color || kit_print_color_(kit_light_); !carriage_return || printf("` %s ", bench->bench_name); int l = (int) strlen(bench->bench_name); printf("%*c", line_width - l, ' '); if (bench->repeats <= 0) { no_color || kit_print_color_(kit_yellow_); printf(" 0 runs\n"); success_count++; } else if (bench_status == 0) { no_color || kit_print_color_(kit_red_); printf(" FAIL\n"); status = 1; } else { int 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, kit_compare_64_); int64_t average = bench->duration_sorted_nsec[repeats / 2]; int64_t floor = bench->duration_sorted_nsec[repeats / 20]; int64_t roof = bench->duration_sorted_nsec[repeats - repeats / 20 - 1]; no_color || kit_print_color_(kit_white_); printf("%-9g", (double) floor * 0.001); no_color || kit_print_color_(kit_light_); printf("| "); no_color || kit_print_color_(kit_white_); printf("%-9g", (double) average * 0.001); no_color || kit_print_color_(kit_light_); printf("| "); no_color || kit_print_color_(kit_white_); printf("%-9g", (double) roof * 0.001); no_color || kit_print_color_(kit_light_); printf(" %d runs\n", repeats); success_count++; } } } printf("\n"); if (status != 0) { for (int i = 0; i < kit_benchs_list.size && i < KIT_BENCHS_SIZE_LIMIT; i++) { kit_benchmark_t *bench = kit_benchs_list.v + i; if (specific_bench != NULL && strstr(bench->bench_name, specific_bench) == NULL) continue; if (bench->signal != 0) { int signum = bench->signal; if (signum >= 0 && signum < (int) (sizeof kit_signames_ / sizeof *kit_signames_) && kit_signames_[signum] != NULL) { no_color || kit_print_color_(kit_light_); printf("Signal \"%s\" (%d) for \"", kit_signames_[signum], signum); no_color || kit_print_color_(kit_white_); printf("%s", bench->bench_name); no_color || kit_print_color_(kit_light_); printf("\" in \""); no_color || kit_print_color_(kit_white_); printf("%s", bench->bench_file + file_root); no_color || kit_print_color_(kit_light_); printf("\"!.\n"); } else { no_color || kit_print_color_(kit_light_); printf("Unknown signal (%d) for \"", signum); no_color || kit_print_color_(kit_white_); printf("%s", bench->bench_name); no_color || kit_print_color_(kit_light_); printf("\" in \""); no_color || kit_print_color_(kit_white_); printf("%s", bench->bench_file + file_root); no_color || kit_print_color_(kit_light_); printf("\"!.\n"); } } } printf("\n"); } if (kit_benchs_list.size > KIT_BENCHS_SIZE_LIMIT) { no_color || kit_print_color_(kit_light_); printf("Too many benchmarks!\n\n"); status = 1; } if (status == 0) { no_color || kit_print_color_(kit_green_); printf("DONE\n"); } else { no_color || kit_print_color_(kit_red_); printf("DONE WITH ERRORS\n"); } no_color || kit_print_color_(kit_light_); printf("\n"); return status; } #ifdef __cplusplus } #endif #endif