dovecot-2.2: lib-test: permit tests of fatal conditions

dovecot at dovecot.org dovecot at dovecot.org
Wed Jul 30 12:04:01 UTC 2014


details:   http://hg.dovecot.org/dovecot-2.2/rev/aeb6520c0ae9
changeset: 17669:aeb6520c0ae9
user:      Phil Carmody <phil at dovecot.fi>
date:      Wed Jul 30 15:01:29 2014 +0300
description:
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 <phil at dovecot.fi>

diffstat:

 src/lib-test/test-common.c |  71 ++++++++++++++++++++++++++++++++++++++++++++++
 src/lib-test/test-common.h |   8 +++++
 2 files changed, 79 insertions(+), 0 deletions(-)

diffs (121 lines):

diff -r b5b243739b67 -r aeb6520c0ae9 src/lib-test/test-common.c
--- a/src/lib-test/test-common.c	Tue Jul 29 17:27:24 2014 +0300
+++ b/src/lib-test/test-common.c	Wed Jul 30 15:01:29 2014 +0300
@@ -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_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 @@
 
 	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 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 -r b5b243739b67 -r aeb6520c0ae9 src/lib-test/test-common.h
--- a/src/lib-test/test-common.h	Tue Jul 29 17:27:24 2014 +0300
+++ b/src/lib-test/test-common.h	Wed Jul 30 15:01:29 2014 +0300
@@ -29,4 +29,12 @@
 
 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


More information about the dovecot-cvs mailing list