]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-test: permit tests of fatal conditions
authorPhil Carmody <phil@dovecot.fi>
Wed, 30 Jul 2014 12:01:29 +0000 (15:01 +0300)
committerPhil Carmody <phil@dovecot.fi>
Wed, 30 Jul 2014 12:01:29 +0000 (15:01 +0300)
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 <phil@dovecot.fi>
src/lib-test/test-common.c
src/lib-test/test-common.h

index 4d6ba064b93a804565bd91c481fbf8bbd7074dad..af055a8401d94e002a8766d70b1090f92b510cb4 100644 (file)
@@ -7,6 +7,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include <setjmp.h> /* 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();
+}
index 8ebe8f37a936ebf7d607658b0872523dff4c21ce..66da66a2b58dac1b6dfe7774a82ff605d0adf4f6 100644 (file)
@@ -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