From: Phil Carmody Date: Wed, 30 Jul 2014 12:01:29 +0000 (+0300) Subject: lib-test: permit tests of fatal conditions X-Git-Tag: 2.2.14.rc1~201 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a4e0dc5b1c4ff4caccfb928e3be0542905b0ec9a;p=thirdparty%2Fdovecot%2Fcore.git lib-test: permit tests of fatal conditions Some functions have no mechanism of reporting an error, and mustn't continue, so fatality is the only way out. (E.g. memory allocation failures.) This addition is for those situations. Semantics of failure tests are very different from normal tests: - The test function must have the following prototype: enum fatal_test_state test_fatal_things(int index); - The index it will be called with starts at 0, and increments each time. - It must call test_start() at the start of its first call. - Apart from its final call, it must call a function it expects to trap the fatal error handler. If that fails to trap, it must return FATAL_TEST_FAILURE - After returning FATAL_TEST_FAILURE, it will continue to be called as normal. - When there are no more tests to perform, it must clean up, call test_end() and return FATAL_TEST_FINISHED. It will not be called again. - If it detects errors in this protocol, it must not i_assert(), as that will be treated as an expected fatal, it must return FATAL_TEST_ABORT. It will then not be called again. It must not call test_end() in this case. Signed-off-by: Phil Carmody --- diff --git a/src/lib-test/test-common.c b/src/lib-test/test-common.c index 4d6ba064b9..af055a8401 100644 --- a/src/lib-test/test-common.c +++ b/src/lib-test/test-common.c @@ -7,6 +7,8 @@ #include #include +#include /* for fatal tests */ + #define OUT_NAME_ALIGN 70 static char *test_prefix; @@ -257,6 +259,23 @@ test_error_handler(const struct failure_context *ctx, test_success = FALSE; } +/* 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; + +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 */ + i_set_fatal_handler(test_fatal_handler); + longjmp(fatal_jmpbuf, 1); + /* we simply can't get here - will the compiler complain? */ + default_fatal_handler(ctx, format, args); +} + static void test_init(void) { test_prefix = NULL; @@ -265,6 +284,7 @@ static void test_init(void) lib_init(); i_set_error_handler(test_error_handler); + /* Don't set fatal handler until actually needed for fatal testing */ } static int test_deinit(void) @@ -286,9 +306,60 @@ static void test_run_funcs(void (*test_functions[])(void)) } } +static void run_one_fatal(enum fatal_test_state (*fatal_function)(int)) +{ + static 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(enum fatal_test_state (*fatal_functions[])(int index)) +{ + unsigned int i; + + for (i = 0; fatal_functions[i] != NULL; i++) { + T_BEGIN { + run_one_fatal(fatal_functions[i]); + } T_END; + } +} + int test_run(void (*test_functions[])(void)) { test_init(); test_run_funcs(test_functions); return test_deinit(); } +int test_run_with_fatals(void (*test_functions[])(void), + enum fatal_test_state (*fatal_functions[])(int)) +{ + test_init(); + test_run_funcs(test_functions); + i_set_fatal_handler(test_fatal_handler); + test_run_fatals(fatal_functions); + return test_deinit(); +} diff --git a/src/lib-test/test-common.h b/src/lib-test/test-common.h index 8ebe8f37a9..66da66a2b5 100644 --- a/src/lib-test/test-common.h +++ b/src/lib-test/test-common.h @@ -29,4 +29,12 @@ void test_out_reason(const char *name, bool success, const char *reason) int test_run(void (*test_functions[])(void)); +enum fatal_test_state { + FATAL_TEST_FINISHED, /* no more test stages, don't call again */ + FATAL_TEST_FAILURE, /* single stage has failed, continue */ + FATAL_TEST_ABORT, /* something's gone horrifically wrong */ +}; +int test_run_with_fatals(void (*test_functions[])(void), + enum fatal_test_state (*fatal_functions[])(int)); + #endif