From 7ce8e90d803afaa646aca07a89f7433e7a46c37a Mon Sep 17 00:00:00 2001 From: Andre Renaud Date: Tue, 20 Jan 2026 08:16:18 +1300 Subject: [PATCH 1/2] bump acutest to 31751b4089c93b46a9fd8a8183a695f772de66de from upstream --- tests/acutest.h | 730 ++++++++++++++++++++++++++++++------------------ 1 file changed, 465 insertions(+), 265 deletions(-) diff --git a/tests/acutest.h b/tests/acutest.h index 8a61b14..199f6f3 100644 --- a/tests/acutest.h +++ b/tests/acutest.h @@ -2,7 +2,7 @@ * Acutest -- Another C/C++ Unit Test facility * * - * Copyright 2013-2020 Martin Mitas + * Copyright 2013-2023 Martin Mitáš * Copyright 2019 Garrett D'Amore * * Permission is hereby granted, free of charge, to any person obtaining a @@ -28,6 +28,19 @@ #define ACUTEST_H +/* Try to auto-detect whether we need to disable C++ exception handling. + * If the detection fails, you may always define TEST_NO_EXCEPTIONS before + * including "acutest.h" manually. */ +#ifdef __cplusplus + #if (__cplusplus >= 199711L && !defined __cpp_exceptions) || \ + ((defined(__GNUC__) || defined(__clang__)) && !defined __EXCEPTIONS) + #ifndef TEST_NO_EXCEPTIONS + #define TEST_NO_EXCEPTIONS + #endif + #endif +#endif + + /************************ *** Public interface *** ************************/ @@ -81,8 +94,10 @@ * TEST_CHECK(ptr->member2 > 200); * } */ -#define TEST_CHECK_(cond,...) acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__) -#define TEST_CHECK(cond) acutest_check_((cond), __FILE__, __LINE__, "%s", #cond) +#define TEST_CHECK_(cond,...) \ + acutest_check_(!!(cond), __FILE__, __LINE__, __VA_ARGS__) +#define TEST_CHECK(cond) \ + acutest_check_(!!(cond), __FILE__, __LINE__, "%s", #cond) /* These macros are the same as TEST_CHECK_ and TEST_CHECK except that if the @@ -102,17 +117,18 @@ */ #define TEST_ASSERT_(cond,...) \ do { \ - if(!acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__)) \ + if(!acutest_check_(!!(cond), __FILE__, __LINE__, __VA_ARGS__)) \ acutest_abort_(); \ } while(0) #define TEST_ASSERT(cond) \ do { \ - if(!acutest_check_((cond), __FILE__, __LINE__, "%s", #cond)) \ + if(!acutest_check_(!!(cond), __FILE__, __LINE__, "%s", #cond)) \ acutest_abort_(); \ } while(0) #ifdef __cplusplus +#ifndef TEST_NO_EXCEPTIONS /* Macros to verify that the code (the 1st argument) throws exception of given * type (the 2nd argument). (Note these macros are only available in C++.) * @@ -159,6 +175,7 @@ if(msg_ != NULL) \ acutest_message_("%s", msg_); \ } while(0) +#endif /* #ifndef TEST_NO_EXCEPTIONS */ #endif /* #ifdef __cplusplus */ @@ -245,7 +262,17 @@ #endif -/* Common test initialiation/clean-up +/* Macros for marking the test as SKIPPED. + * Note it can only be used at the beginning of a test, before any other + * checking. + * + * Once used, the best practice is to return from the test routine as soon + * as possible. + */ +#define TEST_SKIP(...) acutest_skip_(__FILE__, __LINE__, __VA_ARGS__) + + +/* Common test initialisation/clean-up * * In some test suites, it may be needed to perform some sort of the same * initialization and/or clean-up in all the tests. @@ -272,10 +299,45 @@ /* The unit test files should not rely on anything below. */ +#include + +/* Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers */ +#if defined(__GNUC__) || defined(__clang__) + #define ACUTEST_ATTRIBUTE_(attr) __attribute__((attr)) +#else + #define ACUTEST_ATTRIBUTE_(attr) +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +enum acutest_state_ { + ACUTEST_STATE_INITIAL = -4, + ACUTEST_STATE_SELECTED = -3, + ACUTEST_STATE_NEEDTORUN = -2, + + /* By the end all tests should be in one of the following: */ + ACUTEST_STATE_EXCLUDED = -1, + ACUTEST_STATE_SUCCESS = 0, + ACUTEST_STATE_FAILED = 1, + ACUTEST_STATE_SKIPPED = 2 +}; + +int acutest_check_(int cond, const char* file, int line, const char* fmt, ...); +void acutest_case_(const char* fmt, ...); +void acutest_message_(const char* fmt, ...); +void acutest_dump_(const char* title, const void* addr, size_t size); +void acutest_abort_(void) ACUTEST_ATTRIBUTE_(noreturn); +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#ifndef TEST_NO_MAIN + #include #include #include -#include #include #include @@ -306,9 +368,20 @@ #include #endif +#if defined(__APPLE__) + #define ACUTEST_MACOS_ + #include + #include + #include + #include + #include +#endif + #ifdef __cplusplus +#ifndef TEST_NO_EXCEPTIONS #include #endif +#endif #ifdef __has_include #if __has_include() @@ -316,13 +389,6 @@ #endif #endif -/* Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers */ -#if defined(__GNUC__) || defined(__clang__) - #define ACUTEST_ATTRIBUTE_(attr) __attribute__((attr)) -#else - #define ACUTEST_ATTRIBUTE_(attr) -#endif - /* Note our global private identifiers end with '_' to mitigate risk of clash * with the unit tests implementation. */ @@ -345,47 +411,32 @@ struct acutest_test_ { }; struct acutest_test_data_ { - unsigned char flags; + enum acutest_state_ state; double duration; }; -enum { - ACUTEST_FLAG_RUN_ = 1 << 0, - ACUTEST_FLAG_SUCCESS_ = 1 << 1, - ACUTEST_FLAG_FAILURE_ = 1 << 2, -}; extern const struct acutest_test_ acutest_list_[]; -int acutest_check_(int cond, const char* file, int line, const char* fmt, ...); -void acutest_case_(const char* fmt, ...); -void acutest_message_(const char* fmt, ...); -void acutest_dump_(const char* title, const void* addr, size_t size); -void acutest_abort_(void) ACUTEST_ATTRIBUTE_(noreturn); - - -#ifndef TEST_NO_MAIN static char* acutest_argv0_ = NULL; -static size_t acutest_list_size_ = 0; +static int acutest_list_size_ = 0; static struct acutest_test_data_* acutest_test_data_ = NULL; -static size_t acutest_count_ = 0; static int acutest_no_exec_ = -1; static int acutest_no_summary_ = 0; static int acutest_tap_ = 0; -static int acutest_skip_mode_ = 0; +static int acutest_exclude_mode_ = 0; static int acutest_worker_ = 0; static int acutest_worker_index_ = 0; static int acutest_cond_failed_ = 0; -static int acutest_was_aborted_ = 0; static FILE *acutest_xml_output_ = NULL; -static int acutest_stat_failed_units_ = 0; -static int acutest_stat_run_units_ = 0; - static const struct acutest_test_* acutest_current_test_ = NULL; static int acutest_current_index_ = 0; static char acutest_case_name_[TEST_CASE_MAXSIZE] = ""; +static int acutest_test_check_count_ = 0; +static int acutest_test_skip_count_ = 0; +static char acutest_test_skip_reason_[256] = ""; static int acutest_test_already_logged_ = 0; static int acutest_case_already_logged_ = 0; static int acutest_verbose_level_ = 2; @@ -396,6 +447,18 @@ static int acutest_timer_ = 0; static int acutest_abort_has_jmp_buf_ = 0; static jmp_buf acutest_abort_jmp_buf_; +static int +acutest_count_(enum acutest_state_ state) +{ + int i, n; + + for(i = 0, n = 0; i < acutest_list_size_; i++) { + if(acutest_test_data_[i].state == state) + n++; + } + + return n; +} static void acutest_cleanup_(void) @@ -410,6 +473,7 @@ acutest_exit_(int exit_code) exit(exit_code); } + #if defined ACUTEST_WIN_ typedef LARGE_INTEGER acutest_timer_type_; static LARGE_INTEGER acutest_timer_freq_; @@ -465,18 +529,7 @@ acutest_exit_(int exit_code) static double acutest_timer_diff_(struct timespec start, struct timespec end) { - double endns; - double startns; - - endns = end.tv_sec; - endns *= 1e9; - endns += end.tv_nsec; - - startns = start.tv_sec; - startns *= 1e9; - startns += start.tv_nsec; - - return ((endns - startns)/ 1e9); + return (double)(end.tv_sec - start.tv_sec) + (double)(end.tv_nsec - start.tv_nsec) / 1e9; } static void @@ -514,11 +567,13 @@ acutest_exit_(int exit_code) #endif #define ACUTEST_COLOR_DEFAULT_ 0 -#define ACUTEST_COLOR_GREEN_ 1 -#define ACUTEST_COLOR_RED_ 2 -#define ACUTEST_COLOR_DEFAULT_INTENSIVE_ 3 -#define ACUTEST_COLOR_GREEN_INTENSIVE_ 4 -#define ACUTEST_COLOR_RED_INTENSIVE_ 5 +#define ACUTEST_COLOR_RED_ 1 +#define ACUTEST_COLOR_GREEN_ 2 +#define ACUTEST_COLOR_YELLOW_ 3 +#define ACUTEST_COLOR_DEFAULT_INTENSIVE_ 10 +#define ACUTEST_COLOR_RED_INTENSIVE_ 11 +#define ACUTEST_COLOR_GREEN_INTENSIVE_ 12 +#define ACUTEST_COLOR_YELLOW_INTENSIVE_ 13 static int ACUTEST_ATTRIBUTE_(format (printf, 2, 3)) acutest_colored_printf_(int color, const char* fmt, ...) @@ -540,10 +595,12 @@ acutest_colored_printf_(int color, const char* fmt, ...) { const char* col_str; switch(color) { - case ACUTEST_COLOR_GREEN_: col_str = "\033[0;32m"; break; case ACUTEST_COLOR_RED_: col_str = "\033[0;31m"; break; - case ACUTEST_COLOR_GREEN_INTENSIVE_: col_str = "\033[1;32m"; break; + case ACUTEST_COLOR_GREEN_: col_str = "\033[0;32m"; break; + case ACUTEST_COLOR_YELLOW_: col_str = "\033[0;33m"; break; case ACUTEST_COLOR_RED_INTENSIVE_: col_str = "\033[1;31m"; break; + case ACUTEST_COLOR_GREEN_INTENSIVE_: col_str = "\033[1;32m"; break; + case ACUTEST_COLOR_YELLOW_INTENSIVE_: col_str = "\033[1;33m"; break; case ACUTEST_COLOR_DEFAULT_INTENSIVE_: col_str = "\033[1m"; break; default: col_str = "\033[0m"; break; } @@ -562,11 +619,13 @@ acutest_colored_printf_(int color, const char* fmt, ...) GetConsoleScreenBufferInfo(h, &info); switch(color) { - case ACUTEST_COLOR_GREEN_: attr = FOREGROUND_GREEN; break; case ACUTEST_COLOR_RED_: attr = FOREGROUND_RED; break; - case ACUTEST_COLOR_GREEN_INTENSIVE_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; + case ACUTEST_COLOR_GREEN_: attr = FOREGROUND_GREEN; break; + case ACUTEST_COLOR_YELLOW_: attr = FOREGROUND_RED | FOREGROUND_GREEN; break; case ACUTEST_COLOR_RED_INTENSIVE_: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case ACUTEST_COLOR_GREEN_INTENSIVE_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; case ACUTEST_COLOR_DEFAULT_INTENSIVE_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case ACUTEST_COLOR_YELLOW_INTENSIVE_: attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; default: attr = 0; break; } if(attr != 0) @@ -581,6 +640,35 @@ acutest_colored_printf_(int color, const char* fmt, ...) #endif } +static const char* +acutest_basename_(const char* path) +{ + const char* name; + + name = strrchr(path, '/'); + if(name != NULL) + name++; + else + name = path; + +#ifdef ACUTEST_WIN_ + { + const char* alt_name; + + alt_name = strrchr(path, '\\'); + if(alt_name != NULL) + alt_name++; + else + alt_name = path; + + if(alt_name > name) + name = alt_name; + } +#endif + + return name; +} + static void acutest_begin_test_line_(const struct acutest_test_* test) { @@ -603,26 +691,36 @@ acutest_begin_test_line_(const struct acutest_test_* test) } static void -acutest_finish_test_line_(int result) +acutest_finish_test_line_(enum acutest_state_ state) { if(acutest_tap_) { - const char* str = (result == 0) ? "ok" : "not ok"; + printf("%s %d - %s%s\n", + (state == ACUTEST_STATE_SUCCESS || state == ACUTEST_STATE_SKIPPED) ? "ok" : "not ok", + acutest_current_index_ + 1, + acutest_current_test_->name, + (state == ACUTEST_STATE_SKIPPED) ? " # SKIP" : ""); - printf("%s %d - %s\n", str, acutest_current_index_ + 1, acutest_current_test_->name); - - if(result == 0 && acutest_timer_) { + if(state == ACUTEST_STATE_SUCCESS && acutest_timer_) { printf("# Duration: "); acutest_timer_print_diff_(); printf("\n"); } } else { - int color = (result == 0) ? ACUTEST_COLOR_GREEN_INTENSIVE_ : ACUTEST_COLOR_RED_INTENSIVE_; - const char* str = (result == 0) ? "OK" : "FAILED"; + int color; + const char* str; + + switch(state) { + case ACUTEST_STATE_SUCCESS: color = ACUTEST_COLOR_GREEN_INTENSIVE_; str = "OK"; break; + case ACUTEST_STATE_SKIPPED: color = ACUTEST_COLOR_YELLOW_INTENSIVE_; str = "SKIPPED"; break; + case ACUTEST_STATE_FAILED: /* Fall through. */ + default: color = ACUTEST_COLOR_RED_INTENSIVE_; str = "FAILED"; break; + } + printf("[ "); acutest_colored_printf_(color, "%s", str); printf(" ]"); - if(result == 0 && acutest_timer_) { + if(state == ACUTEST_STATE_SUCCESS && acutest_timer_) { printf(" "); acutest_timer_print_diff_(); } @@ -649,6 +747,51 @@ acutest_line_indent_(int level) printf("%.*s", n, spaces); } +void ACUTEST_ATTRIBUTE_(format (printf, 3, 4)) +acutest_skip_(const char* file, int line, const char* fmt, ...) +{ + va_list args; + size_t reason_len; + + va_start(args, fmt); + vsnprintf(acutest_test_skip_reason_, sizeof(acutest_test_skip_reason_), fmt, args); + va_end(args); + acutest_test_skip_reason_[sizeof(acutest_test_skip_reason_)-1] = '\0'; + + /* Remove final dot, if provided; that collides with our other logic. */ + reason_len = strlen(acutest_test_skip_reason_); + if(acutest_test_skip_reason_[reason_len-1] == '.') + acutest_test_skip_reason_[reason_len-1] = '\0'; + + if(acutest_test_check_count_ > 0) { + acutest_check_(0, file, line, "Cannot skip, already performed some checks"); + return; + } + + if(acutest_verbose_level_ >= 2) { + const char *result_str = "skipped"; + int result_color = ACUTEST_COLOR_YELLOW_; + + if(!acutest_test_already_logged_ && acutest_current_test_ != NULL) + acutest_finish_test_line_(ACUTEST_STATE_SKIPPED); + acutest_test_already_logged_++; + + acutest_line_indent_(1); + + if(file != NULL) { + file = acutest_basename_(file); + printf("%s:%d: ", file, line); + } + + printf("%s... ", acutest_test_skip_reason_); + acutest_colored_printf_(result_color, "%s", result_str); + printf("\n"); + acutest_test_already_logged_++; + } + + acutest_test_skip_count_++; +} + int ACUTEST_ATTRIBUTE_(format (printf, 4, 5)) acutest_check_(int cond, const char* file, int line, const char* fmt, ...) { @@ -656,19 +799,28 @@ acutest_check_(int cond, const char* file, int line, const char* fmt, ...) int result_color; int verbose_level; + if(acutest_test_skip_count_) { + /* We've skipped the test. We shouldn't be here: The test implementation + * should have already return before. So lets suppress the following + * output. */ + cond = 1; + goto skip_check; + } + if(cond) { result_str = "ok"; result_color = ACUTEST_COLOR_GREEN_; verbose_level = 3; } else { if(!acutest_test_already_logged_ && acutest_current_test_ != NULL) - acutest_finish_test_line_(-1); + acutest_finish_test_line_(ACUTEST_STATE_FAILED); + + acutest_test_failures_++; + acutest_test_already_logged_++; result_str = "failed"; result_color = ACUTEST_COLOR_RED_; verbose_level = 2; - acutest_test_failures_++; - acutest_test_already_logged_++; } if(acutest_verbose_level_ >= verbose_level) { @@ -683,20 +835,8 @@ acutest_check_(int cond, const char* file, int line, const char* fmt, ...) acutest_line_indent_(acutest_case_name_[0] ? 2 : 1); if(file != NULL) { -#ifdef ACUTEST_WIN_ - const char* lastsep1 = strrchr(file, '\\'); - const char* lastsep2 = strrchr(file, '/'); - if(lastsep1 == NULL) - lastsep1 = file-1; - if(lastsep2 == NULL) - lastsep2 = file-1; - file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; -#else - const char* lastsep = strrchr(file, '/'); - if(lastsep != NULL) - file = lastsep+1; -#endif - printf("%s:%d: Check ", file, line); + file = acutest_basename_(file); + printf("%s:%d: ", file, line); } va_start(args, fmt); @@ -709,6 +849,9 @@ acutest_check_(int cond, const char* file, int line, const char* fmt, ...) acutest_test_already_logged_++; } + acutest_test_check_count_++; + +skip_check: acutest_cond_failed_ = (cond == 0); return !acutest_cond_failed_; } @@ -866,7 +1009,9 @@ acutest_abort_(void) } else { if(acutest_current_test_ != NULL) acutest_fini_(acutest_current_test_->name); - abort(); + fflush(stdout); + fflush(stderr); + acutest_exit_(ACUTEST_STATE_FAILED); } } @@ -880,28 +1025,6 @@ acutest_list_names_(void) printf(" %s\n", test->name); } -static void -acutest_remember_(int i) -{ - if(acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_) - return; - - acutest_test_data_[i].flags |= ACUTEST_FLAG_RUN_; - acutest_count_++; -} - -static void -acutest_set_success_(int i, int success) -{ - acutest_test_data_[i].flags = (unsigned char) (acutest_test_data_[i].flags | (success ? ACUTEST_FLAG_SUCCESS_ : ACUTEST_FLAG_FAILURE_)); -} - -static void -acutest_set_duration_(int i, double duration) -{ - acutest_test_data_[i].duration = duration; -} - static int acutest_name_contains_word_(const char* name, const char* pattern) { @@ -926,15 +1049,15 @@ acutest_name_contains_word_(const char* name, const char* pattern) } static int -acutest_lookup_(const char* pattern) +acutest_select_(const char* pattern) { int i; int n = 0; /* Try exact match. */ - for(i = 0; i < (int) acutest_list_size_; i++) { + for(i = 0; i < acutest_list_size_; i++) { if(strcmp(acutest_list_[i].name, pattern) == 0) { - acutest_remember_(i); + acutest_test_data_[i].state = ACUTEST_STATE_SELECTED; n++; break; } @@ -943,9 +1066,9 @@ acutest_lookup_(const char* pattern) return n; /* Try word match. */ - for(i = 0; i < (int) acutest_list_size_; i++) { + for(i = 0; i < acutest_list_size_; i++) { if(acutest_name_contains_word_(acutest_list_[i].name, pattern)) { - acutest_remember_(i); + acutest_test_data_[i].state = ACUTEST_STATE_SELECTED; n++; } } @@ -953,9 +1076,9 @@ acutest_lookup_(const char* pattern) return n; /* Try relaxed match. */ - for(i = 0; i < (int) acutest_list_size_; i++) { + for(i = 0; i < acutest_list_size_; i++) { if(strstr(acutest_list_[i].name, pattern) != NULL) { - acutest_remember_(i); + acutest_test_data_[i].state = ACUTEST_STATE_SELECTED; n++; } } @@ -991,20 +1114,23 @@ acutest_error_(const char* fmt, ...) } /* Call directly the given test unit function. */ -static int +static enum acutest_state_ acutest_do_run_(const struct acutest_test_* test, int index) { - int status = -1; + enum acutest_state_ state = ACUTEST_STATE_FAILED; - acutest_was_aborted_ = 0; acutest_current_test_ = test; acutest_current_index_ = index; acutest_test_failures_ = 0; acutest_test_already_logged_ = 0; + acutest_test_check_count_ = 0; + acutest_test_skip_count_ = 0; acutest_cond_failed_ = 0; #ifdef __cplusplus +#ifndef TEST_NO_EXCEPTIONS try { +#endif #endif acutest_init_(test->name); acutest_begin_test_line_(test); @@ -1015,49 +1141,60 @@ acutest_do_run_(const struct acutest_test_* test, int index) if(!acutest_worker_) { acutest_abort_has_jmp_buf_ = 1; - if(setjmp(acutest_abort_jmp_buf_) != 0) { - acutest_was_aborted_ = 1; + if(setjmp(acutest_abort_jmp_buf_) != 0) goto aborted; - } } acutest_timer_get_time_(´st_timer_start_); test->func(); + aborted: acutest_abort_has_jmp_buf_ = 0; acutest_timer_get_time_(´st_timer_end_); + if(acutest_test_failures_ > 0) + state = ACUTEST_STATE_FAILED; + else if(acutest_test_skip_count_ > 0) + state = ACUTEST_STATE_SKIPPED; + else + state = ACUTEST_STATE_SUCCESS; + + if(!acutest_test_already_logged_) + acutest_finish_test_line_(state); + if(acutest_verbose_level_ >= 3) { acutest_line_indent_(1); - if(acutest_test_failures_ == 0) { - acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS: "); - printf("All conditions have passed.\n"); - - if(acutest_timer_) { - acutest_line_indent_(1); - printf("Duration: "); - acutest_timer_print_diff_(); - printf("\n"); - } - } else { - acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: "); - if(!acutest_was_aborted_) { + switch(state) { + case ACUTEST_STATE_SUCCESS: + acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS: "); + printf("All conditions have passed.\n"); + + if(acutest_timer_) { + acutest_line_indent_(1); + printf("Duration: "); + acutest_timer_print_diff_(); + printf("\n"); + } + break; + + case ACUTEST_STATE_SKIPPED: + acutest_colored_printf_(ACUTEST_COLOR_YELLOW_INTENSIVE_, "SKIPPED: "); + printf("%s.\n", acutest_test_skip_reason_); + break; + + default: + acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: "); printf("%d condition%s %s failed.\n", acutest_test_failures_, (acutest_test_failures_ == 1) ? "" : "s", (acutest_test_failures_ == 1) ? "has" : "have"); - } else { - printf("Aborted.\n"); - } + break; } printf("\n"); - } else if(acutest_verbose_level_ >= 1 && acutest_test_failures_ == 0) { - acutest_finish_test_line_(0); } - status = (acutest_test_failures_ == 0) ? 0 : -1; - #ifdef __cplusplus +#ifndef TEST_NO_EXCEPTIONS } catch(std::exception& e) { const char* what = e.what(); acutest_check_(0, NULL, 0, "Threw std::exception"); @@ -1078,13 +1215,14 @@ acutest_do_run_(const struct acutest_test_* test, int index) printf("C++ exception.\n\n"); } } +#endif #endif acutest_fini_(test->name); acutest_case_(NULL); acutest_current_test_ = NULL; - return status; + return state; } /* Trigger the unit test. If possible (and not suppressed) it starts a child @@ -1093,7 +1231,7 @@ acutest_do_run_(const struct acutest_test_* test, int index) static void acutest_run_(const struct acutest_test_* test, int index, int master_index) { - int failed = 1; + enum acutest_state_ state = ACUTEST_STATE_FAILED; acutest_timer_type_ start, end; acutest_current_test_ = test; @@ -1114,21 +1252,16 @@ acutest_run_(const struct acutest_test_* test, int index, int master_index) pid = fork(); if(pid == (pid_t)-1) { acutest_error_("Cannot fork. %s [%d]", strerror(errno), errno); - failed = 1; } else if(pid == 0) { /* Child: Do the test. */ acutest_worker_ = 1; - failed = (acutest_do_run_(test, index) != 0); - acutest_exit_(failed ? 1 : 0); + state = acutest_do_run_(test, index); + acutest_exit_((int) state); } else { /* Parent: Wait until child terminates and analyze its exit code. */ waitpid(pid, &exit_code, 0); if(WIFEXITED(exit_code)) { - switch(WEXITSTATUS(exit_code)) { - case 0: failed = 0; break; /* test has passed. */ - case 1: /* noop */ break; /* "normal" failure. */ - default: acutest_error_("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); - } + state = (enum acutest_state_) WEXITSTATUS(exit_code); } else if(WIFSIGNALED(exit_code)) { char tmp[32]; const char* signame; @@ -1141,7 +1274,7 @@ acutest_run_(const struct acutest_test_* test, int index, int master_index) case SIGSEGV: signame = "SIGSEGV"; break; case SIGILL: signame = "SIGILL"; break; case SIGTERM: signame = "SIGTERM"; break; - default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; + default: snprintf(tmp, sizeof(tmp), "signal %d", WTERMSIG(exit_code)); signame = tmp; break; } acutest_error_("Test interrupted by %s.", signame); } else { @@ -1158,7 +1291,7 @@ acutest_run_(const struct acutest_test_* test, int index, int master_index) /* Windows has no fork(). So we propagate all info into the child * through a command line arguments. */ - _snprintf(buffer, sizeof(buffer)-1, + snprintf(buffer, sizeof(buffer), "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", acutest_argv0_, index, acutest_timer_ ? "--time" : "", acutest_tap_ ? "--tap" : "", acutest_verbose_level_, @@ -1171,40 +1304,35 @@ acutest_run_(const struct acutest_test_* test, int index, int master_index) GetExitCodeProcess(processInfo.hProcess, &exitCode); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); - failed = (exitCode != 0); - if(exitCode > 1) { - switch(exitCode) { - case 3: acutest_error_("Aborted."); break; - case 0xC0000005: acutest_error_("Access violation."); break; - default: acutest_error_("Test ended in an unexpected way [%lu].", exitCode); break; - } + switch(exitCode) { + case 0: state = ACUTEST_STATE_SUCCESS; break; + case 1: state = ACUTEST_STATE_FAILED; break; + case 2: state = ACUTEST_STATE_SKIPPED; break; + case 3: acutest_error_("Aborted."); break; + case 0xC0000005: acutest_error_("Access violation."); break; + default: acutest_error_("Test ended in an unexpected way [%lu].", exitCode); break; } } else { acutest_error_("Cannot create unit test subprocess [%ld].", GetLastError()); - failed = 1; } #else /* A platform where we don't know how to run child process. */ - failed = (acutest_do_run_(test, index) != 0); + state = acutest_do_run_(test, index); #endif } else { /* Child processes suppressed through --no-exec. */ - failed = (acutest_do_run_(test, index) != 0); + state = acutest_do_run_(test, index); } acutest_timer_get_time_(&end); acutest_current_test_ = NULL; - acutest_stat_run_units_++; - if(failed) - acutest_stat_failed_units_++; - - acutest_set_success_(master_index, !failed); - acutest_set_duration_(master_index, acutest_timer_diff_(start, end)); + acutest_test_data_[master_index].state = state; + acutest_test_data_[master_index].duration = acutest_timer_diff_(start, end); } #if defined(ACUTEST_WIN_) @@ -1316,7 +1444,7 @@ acutest_cmdline_read_(const ACUTEST_CMDLINE_OPTION_* options, int argc, char** a if(opt->flags & (ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ | ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) { ret = callback(opt->id, argv[i]+2+len+1); } else { - sprintf(auxbuf, "--%s", opt->longname); + snprintf(auxbuf, sizeof(auxbuf), "--%s", opt->longname); ret = callback(ACUTEST_CMDLINE_OPTID_BOGUSARG_, auxbuf); } break; @@ -1387,12 +1515,12 @@ acutest_help_(void) { printf("Usage: %s [options] [test...]\n", acutest_argv0_); printf("\n"); - printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); + printf("Run the specified unit tests; or if the option '--exclude' is used, run all\n"); printf("tests in the suite but those listed. By default, if no tests are specified\n"); printf("on the command line, all unit tests in the suite are run.\n"); printf("\n"); printf("Options:\n"); - printf(" -s, --skip Execute all unit tests but the listed ones\n"); + printf(" -X, --exclude Execute all unit tests but the listed ones\n"); printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); printf(" (WHEN is one of 'auto', 'always', 'never')\n"); printf(" -E, --no-exec Same as --exec=never\n"); @@ -1427,7 +1555,8 @@ acutest_help_(void) } static const ACUTEST_CMDLINE_OPTION_ acutest_cmdline_options_[] = { - { 's', "skip", 's', 0 }, + { 'X', "exclude", 'X', 0 }, + { 's', "skip", 'X', 0 }, /* kept for compatibility, use --exclude instead */ { 0, "exec", 'e', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, { 'E', "no-exec", 'E', 0 }, #if defined ACUTEST_WIN_ @@ -1454,8 +1583,8 @@ static int acutest_cmdline_callback_(int id, const char* arg) { switch(id) { - case 's': - acutest_skip_mode_ = 1; + case 'X': + acutest_exclude_mode_ = 1; break; case 'e': @@ -1549,7 +1678,7 @@ acutest_cmdline_callback_(int id, const char* arg) break; case 0: - if(acutest_lookup_(arg) == 0) { + if(acutest_select_(arg) == 0) { fprintf(stderr, "%s: Unrecognized unit test '%s'\n", acutest_argv0_, arg); fprintf(stderr, "Try '%s --list' for list of unit tests.\n", acutest_argv0_); acutest_exit_(2); @@ -1578,61 +1707,107 @@ acutest_cmdline_callback_(int id, const char* arg) return 0; } - -#ifdef ACUTEST_LINUX_ static int -acutest_is_tracer_present_(void) +acutest_under_debugger_(void) { - /* Must be large enough so the line 'TracerPid: ${PID}' can fit in. */ - static const int OVERLAP = 32; - - char buf[256+OVERLAP+1]; - int tracer_present = 0; - int fd; - size_t n_read = 0; +#ifdef ACUTEST_LINUX_ + /* Scan /proc/self/status for line "TracerPid: [PID]". If such line exists + * and the PID is non-zero, we're being debugged. */ + { + static const int OVERLAP = 32; + int fd; + char buf[512]; + size_t n_read; + pid_t tracer_pid = 0; + + /* Little trick so that we can treat the 1st line the same as any other + * and detect line start easily. */ + buf[0] = '\n'; + n_read = 1; + + fd = open("/proc/self/status", O_RDONLY); + if(fd != -1) { + while(1) { + static const char pattern[] = "\nTracerPid:"; + const char* field; + + while(n_read < sizeof(buf) - 1) { + ssize_t n; + + n = read(fd, buf + n_read, sizeof(buf) - 1 - n_read); + if(n <= 0) + break; + n_read += (size_t)n; + } + buf[n_read] = '\0'; - fd = open("/proc/self/status", O_RDONLY); - if(fd == -1) - return 0; + field = strstr(buf, pattern); + if(field != NULL && field < buf + sizeof(buf) - OVERLAP) { + tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); + break; + } - while(1) { - static const char pattern[] = "TracerPid:"; - const char* field; + if(n_read == sizeof(buf) - 1) { + /* Move the tail with the potentially incomplete line we're + * be looking for to the beginning of the buffer. + * (The OVERLAP must be large enough so the searched line + * can fit in completely.) */ + memmove(buf, buf + sizeof(buf) - 1 - OVERLAP, OVERLAP); + n_read = OVERLAP; + } else { + break; + } + } - while(n_read < sizeof(buf) - 1) { - ssize_t n; + close(fd); - n = read(fd, buf + n_read, sizeof(buf) - 1 - n_read); - if(n <= 0) - break; - n_read += (size_t)n; + if(tracer_pid != 0) + return 1; } - buf[n_read] = '\0'; + } +#endif - field = strstr(buf, pattern); - if(field != NULL && field < buf + sizeof(buf) - OVERLAP) { - pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); - tracer_present = (tracer_pid != 0); - break; - } +#ifdef ACUTEST_MACOS_ + /* See https://developer.apple.com/library/archive/qa/qa1361/_index.html */ + { + int mib[4]; + struct kinfo_proc info; + size_t size; - if(n_read == sizeof(buf)-1) { - memmove(buf, buf + sizeof(buf)-1 - OVERLAP, (size_t) OVERLAP); - n_read = (size_t) OVERLAP; - } else { - break; - } + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + size = sizeof(info); + info.kp_proc.p_flag = 0; + sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + + if(info.kp_proc.p_flag & P_TRACED) + return 1; } +#endif - close(fd); - return tracer_present; -} +#ifdef ACUTEST_WIN_ + if(IsDebuggerPresent()) + return 1; #endif +#ifdef RUNNING_ON_VALGRIND + /* We treat Valgrind as a debugger of sorts. + * (Macro RUNNING_ON_VALGRIND is provided by , if available.) */ + if(RUNNING_ON_VALGRIND) + return 1; +#endif + + return 0; +} + int main(int argc, char** argv) { - int i; + int i, index; + int exit_code = 1; acutest_argv0_ = argv[0]; @@ -1653,7 +1828,7 @@ main(int argc, char** argv) for(i = 0; acutest_list_[i].func != NULL; i++) acutest_list_size_++; - acutest_test_data_ = (struct acutest_test_data_*)calloc(acutest_list_size_, sizeof(struct acutest_test_data_)); + acutest_test_data_ = (struct acutest_test_data_*)calloc((size_t)acutest_list_size_, sizeof(struct acutest_test_data_)); if(acutest_test_data_ == NULL) { fprintf(stderr, "Out of memory.\n"); acutest_exit_(2); @@ -1672,33 +1847,39 @@ main(int argc, char** argv) #endif #endif - /* By default, we want to run all tests. */ - if(acutest_count_ == 0) { + /* Determine what to run. */ + if(acutest_count_(ACUTEST_STATE_SELECTED) > 0) { + enum acutest_state_ if_selected; + enum acutest_state_ if_unselected; + + if(!acutest_exclude_mode_) { + if_selected = ACUTEST_STATE_NEEDTORUN; + if_unselected = ACUTEST_STATE_EXCLUDED; + } else { + if_selected = ACUTEST_STATE_EXCLUDED; + if_unselected = ACUTEST_STATE_NEEDTORUN; + } + + for(i = 0; acutest_list_[i].func != NULL; i++) { + if(acutest_test_data_[i].state == ACUTEST_STATE_SELECTED) + acutest_test_data_[i].state = if_selected; + else + acutest_test_data_[i].state = if_unselected; + } + } else { + /* By default, we want to run all tests. */ for(i = 0; acutest_list_[i].func != NULL; i++) - acutest_remember_(i); + acutest_test_data_[i].state = ACUTEST_STATE_NEEDTORUN; } - /* Guess whether we want to run unit tests as child processes. */ + /* By default, we want to suppress running tests as child processes if we + * run just one test, or if we're under debugger: Debugging tests is then + * so much easier. */ if(acutest_no_exec_ < 0) { - acutest_no_exec_ = 0; - - if(acutest_count_ <= 1) { + if(acutest_count_(ACUTEST_STATE_NEEDTORUN) <= 1 || acutest_under_debugger_()) acutest_no_exec_ = 1; - } else { -#ifdef ACUTEST_WIN_ - if(IsDebuggerPresent()) - acutest_no_exec_ = 1; -#endif -#ifdef ACUTEST_LINUX_ - if(acutest_is_tracer_present_()) - acutest_no_exec_ = 1; -#endif -#ifdef RUNNING_ON_VALGRIND - /* RUNNING_ON_VALGRIND is provided by optionally included */ - if(RUNNING_ON_VALGRIND) - acutest_no_exec_ = 1; -#endif - } + else + acutest_no_exec_ = 0; } if(acutest_tap_) { @@ -1712,37 +1893,38 @@ main(int argc, char** argv) acutest_no_summary_ = 1; if(!acutest_worker_) - printf("1..%d\n", (int) acutest_count_); + printf("1..%d\n", acutest_count_(ACUTEST_STATE_NEEDTORUN)); } - int index = acutest_worker_index_; + index = acutest_worker_index_; for(i = 0; acutest_list_[i].func != NULL; i++) { - int run = (acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_); - if (acutest_skip_mode_) /* Run all tests except those listed. */ - run = !run; - if(run) + if(acutest_test_data_[i].state == ACUTEST_STATE_NEEDTORUN) acutest_run_(´st_list_[i], index++, i); } /* Write a summary */ if(!acutest_no_summary_ && acutest_verbose_level_ >= 1) { + int n_run, n_success, n_failed ; + + n_run = acutest_list_size_ - acutest_count_(ACUTEST_STATE_EXCLUDED); + n_success = acutest_count_(ACUTEST_STATE_SUCCESS); + n_failed = acutest_count_(ACUTEST_STATE_FAILED); + if(acutest_verbose_level_ >= 3) { acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Summary:\n"); - printf(" Count of all unit tests: %4d\n", (int) acutest_list_size_); - printf(" Count of run unit tests: %4d\n", acutest_stat_run_units_); - printf(" Count of failed unit tests: %4d\n", acutest_stat_failed_units_); - printf(" Count of skipped unit tests: %4d\n", (int) acutest_list_size_ - acutest_stat_run_units_); + printf(" Count of run unit tests: %4d\n", n_run); + printf(" Count of successful unit tests: %4d\n", n_success); + printf(" Count of failed unit tests: %4d\n", n_failed); } - if(acutest_stat_failed_units_ == 0) { + if(n_failed == 0) { acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS:"); - printf(" All unit tests have passed.\n"); + printf(" No unit tests have failed.\n"); } else { acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED:"); printf(" %d of %d unit tests %s failed.\n", - acutest_stat_failed_units_, acutest_stat_run_units_, - (acutest_stat_failed_units_ == 1) ? "has" : "have"); + n_failed, n_run, (n_failed == 1) ? "has" : "have"); } if(acutest_verbose_level_ >= 3) @@ -1750,34 +1932,52 @@ main(int argc, char** argv) } if (acutest_xml_output_) { -#if defined ACUTEST_UNIX_ - char *suite_name = basename(argv[0]); -#elif defined ACUTEST_WIN_ - char suite_name[_MAX_FNAME]; - _splitpath(argv[0], NULL, NULL, suite_name, NULL); -#else - const char *suite_name = argv[0]; -#endif + const char* suite_name = acutest_basename_(argv[0]); fprintf(acutest_xml_output_, "\n"); - fprintf(acutest_xml_output_, "\n", - suite_name, (int)acutest_list_size_, acutest_stat_failed_units_, acutest_stat_failed_units_, - (int)acutest_list_size_ - acutest_stat_run_units_); + fprintf(acutest_xml_output_, "\n", + suite_name, + (int)acutest_list_size_, + acutest_count_(ACUTEST_STATE_FAILED), + acutest_count_(ACUTEST_STATE_SKIPPED) + acutest_count_(ACUTEST_STATE_EXCLUDED)); for(i = 0; acutest_list_[i].func != NULL; i++) { struct acutest_test_data_ *details = ´st_test_data_[i]; + const char* str_state; fprintf(acutest_xml_output_, " \n", acutest_list_[i].name, details->duration); - if (details->flags & ACUTEST_FLAG_FAILURE_) - fprintf(acutest_xml_output_, " \n"); - if (!(details->flags & ACUTEST_FLAG_FAILURE_) && !(details->flags & ACUTEST_FLAG_SUCCESS_)) - fprintf(acutest_xml_output_, " \n"); + + switch(details->state) { + case ACUTEST_STATE_SUCCESS: str_state = NULL; break; + case ACUTEST_STATE_EXCLUDED: /* Fall through. */ + case ACUTEST_STATE_SKIPPED: str_state = ""; break; + case ACUTEST_STATE_FAILED: /* Fall through. */ + default: str_state = ""; break; + } + + if(str_state != NULL) + fprintf(acutest_xml_output_, " %s\n", str_state); fprintf(acutest_xml_output_, " \n"); } fprintf(acutest_xml_output_, "\n"); fclose(acutest_xml_output_); } - acutest_cleanup_(); + if(acutest_worker_ && acutest_count_(ACUTEST_STATE_EXCLUDED)+1 == acutest_list_size_) { + /* If we are the child process, we need to propagate the test state + * without any moderation. */ + for(i = 0; acutest_list_[i].func != NULL; i++) { + if(acutest_test_data_[i].state != ACUTEST_STATE_EXCLUDED) { + exit_code = (int) acutest_test_data_[i].state; + break; + } + } + } else { + if(acutest_count_(ACUTEST_STATE_FAILED) > 0) + exit_code = 1; + else + exit_code = 0; + } - return (acutest_stat_failed_units_ == 0) ? 0 : 1; + acutest_cleanup_(); + return exit_code; } From 37c38fb4a145ee9d7b67b7477c1e3cb9f7c5274d Mon Sep 17 00:00:00 2001 From: Andre Renaud Date: Tue, 20 Jan 2026 08:17:42 +1300 Subject: [PATCH 2/2] size_t --- tests/acutest.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acutest.h b/tests/acutest.h index 199f6f3..f45e52f 100644 --- a/tests/acutest.h +++ b/tests/acutest.h @@ -1714,7 +1714,7 @@ acutest_under_debugger_(void) /* Scan /proc/self/status for line "TracerPid: [PID]". If such line exists * and the PID is non-zero, we're being debugged. */ { - static const int OVERLAP = 32; + static const size_t OVERLAP = 32; int fd; char buf[512]; size_t n_read;