diff options
Diffstat (limited to 'src/lib-test/test-common.c')
-rw-r--r-- | src/lib-test/test-common.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/src/lib-test/test-common.c b/src/lib-test/test-common.c new file mode 100644 index 0000000..feea7ec --- /dev/null +++ b/src/lib-test/test-common.c @@ -0,0 +1,465 @@ +/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" + +#include <stdio.h> +#include <setjmp.h> /* for fatal tests */ + +static bool test_deinit_lib; + +/* To test the firing of i_assert, we need non-local jumps, i.e. setjmp */ +static volatile bool expecting_fatal = FALSE; +static jmp_buf fatal_jmpbuf; + +#define OUT_NAME_ALIGN 70 + +static char *test_prefix; +static bool test_success; +static unsigned int failure_count; +static unsigned int total_count; +static unsigned int expected_errors; +static char *expected_error_str, *expected_fatal_str; +static test_fatal_callback_t *test_fatal_callback; +static void *test_fatal_context; + +void test_begin(const char *name) +{ + test_success = TRUE; + expected_errors = 0; + if (!expecting_fatal) + i_assert(test_prefix == NULL); + else + test_assert((test_success = (test_prefix == NULL))); + test_prefix = i_strdup(name); +} + +bool test_has_failed(void) +{ + return !test_success; +} + +void test_assert_failed(const char *code, const char *file, unsigned int line) +{ + printf("%s:%u: Assert failed: %s\n", file, line, code); + fflush(stdout); + test_success = FALSE; +#ifdef STATIC_CHECKER + i_unreached(); +#endif +} + +void test_assert_failed_idx(const char *code, const char *file, unsigned int line, long long i) +{ + printf("%s:%u: Assert(#%lld) failed: %s\n", file, line, i, code); + fflush(stdout); + test_success = FALSE; +#ifdef STATIC_CHECKER + i_unreached(); +#endif +} + +void test_assert_failed_strcmp_idx(const char *code, const char *file, unsigned int line, + const char * src, const char * dst, long long i) +{ + printf("%s:%u: Assert", file, line); + if (i == LLONG_MIN) + printf(" failed: %s\n", code); + else + printf("(#%lld) failed: %s\n", i, code); + if (src != NULL) + printf(" \"%s\" != ", src); + else + printf(" NULL != "); + if (dst != NULL) + printf("\"%s\"\n", dst); + else + printf("NULL\n"); + fflush(stdout); + test_success = FALSE; +#ifdef STATIC_CHECKER + i_unreached(); +#endif +} + +void test_assert_failed_cmp_intmax_idx(const char *code, const char *file, + unsigned int line, + intmax_t src, intmax_t dst, + const char *op, long long i) +{ + printf("%s:%u: Assert", file, line); + if (i == LLONG_MIN) + printf(" failed: %s\n", code); + else + printf("(#%lld) failed: %s\n", i, code); + printf(" %jd %s %jd is not true\n", src, op, dst); + fflush(stdout); + test_success = FALSE; +#ifdef STATIC_CHECKER + i_unreached(); +#endif +} + +void test_assert_failed_ucmp_intmax_idx(const char *code, const char *file, + unsigned int line, + uintmax_t src, uintmax_t dst, + const char *op, long long i) +{ + printf("%s:%u: Assert", file, line); + if (i == LLONG_MIN) + printf(" failed: %s\n", code); + else + printf("(#%lld) failed: %s\n", i, code); + printf(" %ju %s %ju is not true\n", src, op, dst); + fflush(stdout); + test_success = FALSE; +#ifdef STATIC_CHECKER + i_unreached(); +#endif +} + +#ifdef DEBUG +#include "randgen.h" +static void +test_dump_rand_state(void) +{ + static int64_t seen_seed = -1; + unsigned int seed; + if (rand_get_last_seed(&seed) < 0) { + if (seen_seed == -1) { + printf("test: random sequence not reproduceable, use DOVECOT_SRAND=kiss\n"); + seen_seed = -2; + } + return; + } + if (seed == seen_seed) + return; + seen_seed = seed; + printf("test: DOVECOT_SRAND random seed was %u\n", seed); +} +#else +static inline void test_dump_rand_state(void) { } +#endif + +void test_end(void) +{ + if (!expecting_fatal) + i_assert(test_prefix != NULL); + else + test_assert(test_prefix != NULL); + + test_out("", test_success); + if (!test_success) + test_dump_rand_state(); + i_free_and_null(test_prefix); + test_success = FALSE; +} + +void test_out(const char *name, bool success) +{ + test_out_reason(name, success, NULL); +} + +void test_out_quiet(const char *name, bool success) +{ + if (success) { + total_count++; + return; + } + test_out(name, success); +} + +void test_out_reason(const char *name, bool success, const char *reason) +{ + int i = 0; + + if (test_prefix != NULL) { + fputs(test_prefix, stdout); + i += strlen(test_prefix); + if (*name != '\0') { + putchar(':'); + i++; + } + putchar(' '); + i++; + } + if (*name != '\0') { + fputs(name, stdout); + putchar(' '); + i += strlen(name) + 1; + } + for (; i < OUT_NAME_ALIGN; i++) + putchar('.'); + fputs(" : ", stdout); + if (success) + fputs("ok", stdout); + else { + fputs("FAILED", stdout); + test_success = FALSE; + failure_count++; + } + if (reason != NULL && *reason != '\0') + printf(": %s", reason); + putchar('\n'); + fflush(stdout); + total_count++; +} + +void +test_expect_error_string_n_times(const char *substr, unsigned int times) +{ + i_assert(expected_errors == 0); + expected_errors = times; + expected_error_str = i_strdup(substr); +} +void +test_expect_error_string(const char *substr) +{ + test_expect_error_string_n_times(substr, 1); +} +void +test_expect_errors(unsigned int expected) +{ + i_assert(expected_errors == 0); + expected_errors = expected; +} +void +test_expect_no_more_errors(void) +{ + test_assert(expected_errors == 0 && expected_error_str == NULL); + i_free_and_null(expected_error_str); + expected_errors = 0; +} + +static bool ATTR_FORMAT(2, 0) +expect_error_check(char **error_strp, const char *format, va_list args) +{ + if (*error_strp == NULL) + return TRUE; + + bool suppress; + T_BEGIN { + /* test_assert() will reset test_success if need be. */ + const char *str = t_strdup_vprintf(format, args); + suppress = strstr(str, *error_strp) != NULL; + test_assert(suppress == TRUE); + i_free_and_null(*error_strp); + } T_END; + return suppress; +} + +static void ATTR_FORMAT(2, 0) +test_error_handler(const struct failure_context *ctx, + const char *format, va_list args) +{ + bool suppress = FALSE; + + if (expected_errors > 0) { + va_list args2; + VA_COPY(args2, args); + suppress = expect_error_check(&expected_error_str, format, args2); + expected_errors--; + va_end(args2); + } else { + test_success = FALSE; + } + + if (!suppress) { + test_dump_rand_state(); + default_error_handler(ctx, format, args); + } +} + +void test_expect_fatal_string(const char *substr) +{ + i_free(expected_fatal_str); + expected_fatal_str = i_strdup(substr); +} + +#undef test_fatal_set_callback +void test_fatal_set_callback(test_fatal_callback_t *callback, void *context) +{ + i_assert(test_fatal_callback == NULL); + test_fatal_callback = callback; + test_fatal_context = context; +} + +static void ATTR_FORMAT(2, 0) ATTR_NORETURN +test_fatal_handler(const struct failure_context *ctx, + const char *format, va_list args) +{ + /* Prevent recursion, we can't handle our own errors */ + i_set_fatal_handler(default_fatal_handler); + i_assert(expecting_fatal); /* if not at the right time, bail */ + + va_list args2; + VA_COPY(args2, args); + bool suppress = expect_error_check(&expected_fatal_str, format, args2); + va_end(args); + + if (suppress) { + if (test_fatal_callback != NULL) { + test_fatal_callback(test_fatal_context); + test_fatal_callback = NULL; + test_fatal_context = NULL; + } + + i_set_fatal_handler(test_fatal_handler); + longjmp(fatal_jmpbuf, 1); + } else { + default_fatal_handler(ctx, format, args); + } + i_unreached(); /* we simply can't get here */ +} + +static void test_init(void) +{ + test_prefix = NULL; + failure_count = 0; + total_count = 0; + + if (!lib_is_initialized()) { + lib_init(); + test_deinit_lib = TRUE; + } else + test_deinit_lib = FALSE; + + i_set_error_handler(test_error_handler); + /* Don't set fatal handler until actually needed for fatal testing */ +} + +static int test_deinit(void) +{ + i_assert(test_prefix == NULL); + printf("%u / %u tests failed\n", failure_count, total_count); + if (test_deinit_lib) + lib_deinit(); + return failure_count == 0 ? 0 : 1; +} + +static void test_run_funcs(void (*const test_functions[])(void)) +{ + unsigned int i; + + for (i = 0; test_functions[i] != NULL; i++) { + T_BEGIN { + test_functions[i](); + } T_END; + } +} +static void test_run_named_funcs(const struct named_test tests[], + const char *match) +{ + unsigned int i; + + for (i = 0; tests[i].func != NULL; i++) { + if (strstr(tests[i].name, match) != NULL) T_BEGIN { + tests[i].func(); + } T_END; + } +} + +static void run_one_fatal(test_fatal_func_t *fatal_function) +{ + static unsigned int index = 0; + for (;;) { + volatile int jumped = setjmp(fatal_jmpbuf); + if (jumped == 0) { + /* normal flow */ + expecting_fatal = TRUE; + enum fatal_test_state ret = fatal_function(index); + expecting_fatal = FALSE; + if (ret == FATAL_TEST_FINISHED) { + /* ran out of tests - good */ + index = 0; + break; + } else if (ret == FATAL_TEST_FAILURE) { + /* failed to fire assert - bad, but can continue */ + test_success = FALSE; + i_error("Desired assert failed to fire at step %i", index); + index++; + } else { /* FATAL_TEST_ABORT or other value */ + test_success = FALSE; + test_end(); + index = 0; + break; + } + } else { + /* assert fired, continue with next test */ + index++; + } + } +} +static void test_run_fatals(test_fatal_func_t *const fatal_functions[]) +{ + unsigned int i; + + for (i = 0; fatal_functions[i] != NULL; i++) { + T_BEGIN { + run_one_fatal(fatal_functions[i]); + } T_END; + } +} +static void test_run_named_fatals(const struct named_fatal fatals[], const char *match) +{ + unsigned int i; + + for (i = 0; fatals[i].func != NULL; i++) { + if (strstr(fatals[i].name, match) != NULL) T_BEGIN { + run_one_fatal(fatals[i].func); + } T_END; + } +} + +int test_run(void (*const test_functions[])(void)) +{ + test_init(); + test_run_funcs(test_functions); + return test_deinit(); +} +int test_run_named(const struct named_test tests[], const char *match) +{ + test_init(); + test_run_named_funcs(tests, match); + return test_deinit(); +} +int test_run_with_fatals(void (*const test_functions[])(void), + test_fatal_func_t *const fatal_functions[]) +{ + test_init(); + test_run_funcs(test_functions); + i_set_fatal_handler(test_fatal_handler); + test_run_fatals(fatal_functions); + return test_deinit(); +} +int test_run_named_with_fatals(const char *match, const struct named_test tests[], + const struct named_fatal fatals[]) +{ + test_init(); + test_run_named_funcs(tests, match); + i_set_fatal_handler(test_fatal_handler); + test_run_named_fatals(fatals, match); + return test_deinit(); +} + +void test_forked_end(void) +{ + i_set_error_handler(default_error_handler); + i_set_fatal_handler(default_fatal_handler); + + i_free_and_null(expected_error_str); + i_free_and_null(expected_fatal_str); + i_free_and_null(test_prefix); + t_pop_last_unsafe(); /* as we were within a T_BEGIN { tests[i].func(); } T_END */ +} + +void ATTR_NORETURN +test_exit(int status) +{ + i_free_and_null(expected_error_str); + i_free_and_null(expected_fatal_str); + i_free_and_null(test_prefix); + t_pop_last_unsafe(); /* as we were within a T_BEGIN { tests[i].func(); } T_END */ + lib_deinit(); + lib_exit(status); +} |