diff --git a/TODO b/TODO
index f1c6674e188d67beb22331e3cea2b29370a4a74c..0a2b73888f629d4bbccf0022655c2af822810741 100644
--- a/TODO
+++ b/TODO
@@ -1,8 +1,5 @@
 Next (in order of descending priority/precedence):
 
-* Finish the test suite for the base functionality
-	- Add capability to test the result.
-
 * ## MAKE A FIRST RELEASE (0.1.x) ##
 
 * Fix remaining RFC deviations:
diff --git a/src/lib-sieve/sieve-result.c b/src/lib-sieve/sieve-result.c
index a8c13ae5e83d72dcc44812c3e83524183d541914..ba406a21046628c7752875b81ee9fee8a31583f3 100644
--- a/src/lib-sieve/sieve-result.c
+++ b/src/lib-sieve/sieve-result.c
@@ -760,6 +760,48 @@ int sieve_result_execute
 	return SIEVE_EXEC_OK;
 }
 
+/*
+ * Result evaluation
+ */
+
+struct sieve_result_iterate_context {
+	struct sieve_result *result;
+	struct sieve_result_action *action;
+};
+
+struct sieve_result_iterate_context *sieve_result_iterate_init
+(struct sieve_result *result)
+{
+	struct sieve_result_iterate_context *rictx = 
+		t_new(struct sieve_result_iterate_context, 1);
+	
+	rictx->result = result;
+	rictx->action = result->first_action;
+
+	return rictx;
+}
+
+const struct sieve_action *sieve_result_iterate_next
+	(struct sieve_result_iterate_context *rictx, void **context)
+{
+	struct sieve_result_action *act;
+
+	if ( rictx == NULL )
+		return  NULL;
+
+	act = rictx->action;
+	if ( act != NULL ) {
+		rictx->action = act->next;
+
+		if ( context != NULL )
+			*context = act->context;
+	
+		return act->action;
+	}
+
+	return NULL;
+}
+
 /*
  * Side effects list
  */
diff --git a/src/lib-sieve/sieve-result.h b/src/lib-sieve/sieve-result.h
index c0f8d0f8ddae95bbeb40457ba66fab2043a892c2..025a3eee3ebdeb756e05045b62181b10a11a4ece 100644
--- a/src/lib-sieve/sieve-result.h
+++ b/src/lib-sieve/sieve-result.h
@@ -90,7 +90,18 @@ bool sieve_result_implicit_keep
 int sieve_result_execute
 	(struct sieve_result *result, const struct sieve_message_data *msgdata,
 		const struct sieve_script_env *senv, struct sieve_exec_status *estatus);
-		
+
+/*
+ * Result evaluation
+ */
+
+struct sieve_result_iterate_context;
+
+struct sieve_result_iterate_context *sieve_result_iterate_init
+	(struct sieve_result *result);
+const struct sieve_action *sieve_result_iterate_next
+	(struct sieve_result_iterate_context *rictx, void **context);
+	
 /*
  * Side effects list
  */
diff --git a/src/testsuite/Makefile.am b/src/testsuite/Makefile.am
index d320a32844c6334e1d11793ad2fa6da3075d18d4..d8e7f508c3e29bd8db371d94725a1716e14cd40a 100644
--- a/src/testsuite/Makefile.am
+++ b/src/testsuite/Makefile.am
@@ -20,19 +20,19 @@ libs = \
 	$(dovecot_incdir)/src/lib-storage/index/raw/libstorage_raw.a \
 	$(dovecot_incdir)/src/lib-storage/index/maildir/libstorage_maildir.a \
 	$(dovecot_incdir)/src/lib-storage/index/mbox/libstorage_mbox.a \
-    $(dovecot_incdir)/src/lib-storage/index/libstorage_index.a \
-    $(dovecot_incdir)/src/lib-storage/libstorage.a \
-    $(dovecot_incdir)/src/lib-index/libindex.a \
-    $(dovecot_incdir)/src/lib-imap/libimap.a \
-    $(dovecot_incdir)/src/lib-mail/libmail.a \
-    $(dovecot_incdir)/src/lib-charset/libcharset.a \
+	$(dovecot_incdir)/src/lib-storage/index/libstorage_index.a \
+	$(dovecot_incdir)/src/lib-storage/libstorage.a \
+	$(dovecot_incdir)/src/lib-index/libindex.a \
+	$(dovecot_incdir)/src/lib-imap/libimap.a \
+	$(dovecot_incdir)/src/lib-mail/libmail.a \
+	$(dovecot_incdir)/src/lib-charset/libcharset.a \
 	$(dovecot_incdir)/src/lib/liblib.a
 
 ldadd = \
 	$(libs) \
  	$(LIBICONV) \
-    $(RAND_LIBS) \
-    $(MODULE_LIBS)
+	$(RAND_LIBS) \
+	$(MODULE_LIBS)
 
 testsuite_LDADD = $(ldadd)
 testsuite_DEPENDENCIES = $(libs)
@@ -45,13 +45,15 @@ commands = \
 tests = \
 	tst-test-compile.c \
 	tst-test-execute.c \
-	tst-test-error.c
+	tst-test-error.c \
+	tst-test-result.c
 
 testsuite_SOURCES = \
 	namespaces.c \
 	mail-raw.c \
 	testsuite-common.c \
 	testsuite-objects.c \
+	testsuite-result.c \
 	$(commands) \
 	$(tests) \
 	ext-testsuite.c \
@@ -60,6 +62,7 @@ testsuite_SOURCES = \
 noinst_HEADERS = \
 	testsuite-common.h \
 	testsuite-objects.h \
+	testsuite-result.c \
 	namespaces.h \
 	mail-raw.h
 
diff --git a/src/testsuite/ext-testsuite.c b/src/testsuite/ext-testsuite.c
index e648fa29ec91eb193b5832b9a0e3b4b93a8e9260..b01c50cbb639bc08b6f94cfe8659ed18fd70dafd 100644
--- a/src/testsuite/ext-testsuite.c
+++ b/src/testsuite/ext-testsuite.c
@@ -57,7 +57,8 @@ const struct sieve_operation *testsuite_operations[] = {
 	&test_set_operation,
 	&test_compile_operation,
 	&test_execute_operation,
-	&test_error_operation
+	&test_error_operation,
+	&test_result_operation
 };
 
 /* 
@@ -113,7 +114,8 @@ static bool ext_testsuite_validator_load(struct sieve_validator *valdtr)
 	sieve_validator_register_command(valdtr, &tst_test_compile);
 	sieve_validator_register_command(valdtr, &tst_test_execute);
 	sieve_validator_register_command(valdtr, &tst_test_error);
-	
+	sieve_validator_register_command(valdtr, &tst_test_result);	
+
 	return testsuite_validator_context_initialize(valdtr);
 }
 
diff --git a/src/testsuite/testsuite-common.c b/src/testsuite/testsuite-common.c
index a395c06ee74e1518f96f485aacf7e3f0642d6273..64e3e23d58e8748db81918bc83fc693f212db152 100644
--- a/src/testsuite/testsuite-common.c
+++ b/src/testsuite/testsuite-common.c
@@ -23,8 +23,9 @@
 #include "sieve-result.h"
 #include "sieve-dump.h"
 
-#include "testsuite-objects.h"
 #include "testsuite-common.h"
+#include "testsuite-objects.h"
+#include "testsuite-result.h"
 
 /*
  * Global data
@@ -96,7 +97,8 @@ static void _testsuite_message_set(string_t *message)
 	if ( sender == NULL ) 
 		sender = "sender@example.com";
 
-	memset(&testsuite_msgdata, 0, sizeof(testsuite_msgdata));	testsuite_msgdata.mail = mail;
+	memset(&testsuite_msgdata, 0, sizeof(testsuite_msgdata));	
+	testsuite_msgdata.mail = mail;
 	testsuite_msgdata.auth_user = testsuite_user;
 	testsuite_msgdata.return_path = sender;
 	testsuite_msgdata.to_address = recipient;
@@ -321,20 +323,20 @@ static void _testsuite_script_verror
 
 static struct sieve_error_handler *_testsuite_script_ehandler_create(void)
 {
-    pool_t pool;
-    struct sieve_error_handler *ehandler;
+	pool_t pool;
+	struct sieve_error_handler *ehandler;
 
-    /* Pool is not strictly necessary, but other handler types will need a pool,
-     * so this one will have one too.
-     */
-    pool = pool_alloconly_create
-        ("testsuite_script_error_handler", sizeof(struct sieve_error_handler));
-    ehandler = p_new(pool, struct sieve_error_handler, 1);
-    sieve_error_handler_init(ehandler, pool, 0);
+	/* Pool is not strictly necessary, but other handler types will need a pool,
+	 * so this one will have one too.
+	 */
+	pool = pool_alloconly_create
+		("testsuite_script_error_handler", sizeof(struct sieve_error_handler));
+	ehandler = p_new(pool, struct sieve_error_handler, 1);
+	sieve_error_handler_init(ehandler, pool, 0);
 
-    ehandler->verror = _testsuite_script_verror;
+	ehandler->verror = _testsuite_script_verror;
 
-    return ehandler;
+	return ehandler;
 }
 
 static void testsuite_script_clear_messages(void)
@@ -345,8 +347,8 @@ static void testsuite_script_clear_messages(void)
 		pool_unref(&_testsuite_scriptmsg_pool);
 	}
 
-	 _testsuite_scriptmsg_pool = pool_alloconly_create
-        ("testsuite_script_messages", 8192);
+	_testsuite_scriptmsg_pool = pool_alloconly_create
+		("testsuite_script_messages", 8192);
 	
 	p_array_init(&_testsuite_script_errors, _testsuite_scriptmsg_pool, 128);	
 
@@ -376,7 +378,7 @@ const char *testsuite_script_get_error_next(bool location)
 static void testsuite_script_init(void)
 {
 	test_script_ehandler = _testsuite_script_ehandler_create(); 	
-    sieve_error_handler_accept_infolog(test_script_ehandler, TRUE);
+	sieve_error_handler_accept_infolog(test_script_ehandler, TRUE);
 
 	testsuite_script_clear_messages();
 
@@ -390,22 +392,22 @@ bool testsuite_script_compile(const char *script_path)
 
 	testsuite_script_clear_messages();
 
-	    /* Initialize environment */
-    sieve_dir = strrchr(script_path, '/');
-    if ( sieve_dir == NULL )
-        sieve_dir= "./";
-    else
-        sieve_dir = t_strdup_until(script_path, sieve_dir+1);
+	/* Initialize environment */
+	sieve_dir = strrchr(script_path, '/');
+	if ( sieve_dir == NULL )
+		sieve_dir= "./";
+	else
+		sieve_dir = t_strdup_until(script_path, sieve_dir+1);
 
-    /* Currently needed for include (FIXME) */
-    env_put(t_strconcat("SIEVE_DIR=", sieve_dir, "included", NULL));
-    env_put(t_strconcat("SIEVE_GLOBAL_DIR=", sieve_dir, "included-global", NULL));
+	/* Currently needed for include (FIXME) */
+	env_put(t_strconcat("SIEVE_DIR=", sieve_dir, "included", NULL));
+	env_put(t_strconcat("SIEVE_GLOBAL_DIR=", sieve_dir, "included-global", NULL));
 
-    if ( (sbin = sieve_compile(script_path, test_script_ehandler)) == NULL )
-        return FALSE;
+	if ( (sbin = sieve_compile(script_path, test_script_ehandler)) == NULL )
+		return FALSE;
 
 	if ( _testsuite_compiled_script != NULL ) {
-	    sieve_close(&_testsuite_compiled_script);
+		sieve_close(&_testsuite_compiled_script);
 	}
 
 	_testsuite_compiled_script = sbin;
@@ -452,6 +454,8 @@ bool testsuite_script_execute(const struct sieve_runtime_env *renv)
 	ret = sieve_interpreter_run(interp, renv->msgdata, &scriptenv, &result, &estatus);
 
 	sieve_interpreter_free(&interp);
+
+	testsuite_result_assign(result);
 	
 	return ( ret > 0 );
 }
@@ -461,8 +465,8 @@ static void testsuite_script_deinit(void)
 	sieve_error_handler_unref(&test_script_ehandler);
 
 	if ( _testsuite_compiled_script != NULL ) {
-        sieve_close(&_testsuite_compiled_script);
-    }
+		sieve_close(&_testsuite_compiled_script);
+	}
 
 	pool_unref(&_testsuite_scriptmsg_pool);
 	//str_free(test_script_error_buf);
@@ -476,10 +480,12 @@ void testsuite_init(void)
 {
 	testsuite_test_context_init();
 	testsuite_script_init();
+	testsuite_result_init();
 }
 
 void testsuite_deinit(void)
 {
+	testsuite_result_deinit();
 	testsuite_script_deinit();
 	testsuite_test_context_deinit();
 }
diff --git a/src/testsuite/testsuite-common.h b/src/testsuite/testsuite-common.h
index 4cb1d5a981ae6db558f1b946a057b476532fad75..7e6300d75ed0979b93e5ff5def1109eb419225c6 100644
--- a/src/testsuite/testsuite-common.h
+++ b/src/testsuite/testsuite-common.h
@@ -63,6 +63,7 @@ extern const struct sieve_command cmd_test_set;
 extern const struct sieve_command tst_test_compile;
 extern const struct sieve_command tst_test_execute;
 extern const struct sieve_command tst_test_error;
+extern const struct sieve_command tst_test_result;
 
 /* 
  * Operations 
@@ -76,6 +77,7 @@ enum testsuite_operation_code {
 	TESTSUITE_OPERATION_TEST_COMPILE,
 	TESTSUITE_OPERATION_TEST_EXECUTE,
 	TESTSUITE_OPERATION_TEST_ERROR,
+	TESTSUITE_OPERATION_TEST_RESULT
 };
 
 extern const struct sieve_operation test_operation;
@@ -85,6 +87,7 @@ extern const struct sieve_operation test_set_operation;
 extern const struct sieve_operation test_compile_operation;
 extern const struct sieve_operation test_execute_operation;
 extern const struct sieve_operation test_error_operation;
+extern const struct sieve_operation test_result_operation;
 
 /* 
  * Operands 
diff --git a/src/testsuite/testsuite-result.c b/src/testsuite/testsuite-result.c
new file mode 100644
index 0000000000000000000000000000000000000000..fc48aa68a6c6c74b2f047d8b440b685e374a7247
--- /dev/null
+++ b/src/testsuite/testsuite-result.c
@@ -0,0 +1,43 @@
+/* Copyright (c) 2002-2008 Dovecot Sieve authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-actions.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+
+unsigned int _testsuite_result_index; /* Yuck */
+static struct sieve_result *_testsuite_result;
+
+void testsuite_result_init(void)
+{
+	_testsuite_result = NULL;
+	_testsuite_result_index = 0;
+}
+
+void testsuite_result_deinit(void)
+{
+	if ( _testsuite_result != NULL ) {
+		sieve_result_unref(&_testsuite_result);
+	}
+}
+
+void testsuite_result_assign(struct sieve_result *result)
+{
+	if ( _testsuite_result != NULL ) {
+		sieve_result_unref(&_testsuite_result);
+	}
+
+	_testsuite_result = result;
+}
+
+struct sieve_result_iterate_context *testsuite_result_iterate_init(void)
+{
+	if ( _testsuite_result == NULL )
+		return NULL;
+
+	return sieve_result_iterate_init(_testsuite_result);
+}
+
diff --git a/src/testsuite/testsuite-result.h b/src/testsuite/testsuite-result.h
new file mode 100644
index 0000000000000000000000000000000000000000..404b40e1a7dec8a10be00b94357fbacf9743600a
--- /dev/null
+++ b/src/testsuite/testsuite-result.h
@@ -0,0 +1,14 @@
+/* Copyright (c) 2002-2008 Dovecot Sieve authors, see the included COPYING file
+ */
+
+#ifndef __TESTSUITE_RESULT_H
+#define __TESTSUITE_RESULT_H
+
+void testsuite_result_init(void);
+void testsuite_result_deinit(void);
+
+void testsuite_result_assign(struct sieve_result *result);
+
+struct sieve_result_iterate_context *testsuite_result_iterate_init(void);
+
+#endif /* __TESTSUITE_RESULT_H */
diff --git a/src/testsuite/tst-test-error.c b/src/testsuite/tst-test-error.c
index e7d23afe1ca380ac4f4746b8bb0e1c8ef782879e..39eda3429a43739ec79b8ed985dc72c1c687caa2 100644
--- a/src/testsuite/tst-test-error.c
+++ b/src/testsuite/tst-test-error.c
@@ -74,10 +74,10 @@ static bool tst_test_error_validate_index_tag
 		struct sieve_command_context *cmd);
 
 static const struct sieve_argument test_error_index_tag = {
-    "index",
-    NULL, NULL,
-    tst_test_error_validate_index_tag,
-    NULL, NULL
+	"index",
+	NULL, NULL,
+	tst_test_error_validate_index_tag,
+	NULL, NULL
 };
 
 enum tst_test_error_optional {
@@ -106,9 +106,9 @@ static bool tst_test_error_validate_index_tag
 		return FALSE;
 	}
 
-    /* Skip parameter */
-    *arg = sieve_ast_argument_next(*arg);
-    return TRUE;
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
 }
 
 
@@ -123,8 +123,8 @@ static bool tst_test_error_registered
 	sieve_comparators_link_tag(validator, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
 	sieve_match_types_link_tags(validator, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
 
-	 sieve_validator_register_tag
-        (validator, cmd_reg, &test_error_index_tag, OPT_INDEX);
+	sieve_validator_register_tag
+		(validator, cmd_reg, &test_error_index_tag, OPT_INDEX);
 
 	return TRUE;
 }
@@ -139,15 +139,15 @@ static bool tst_test_error_validate
 	struct sieve_ast_argument *arg = tst->first_positional;
 	
 	if ( !sieve_validate_positional_argument
-        (valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
-        return FALSE;
-    }
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
 
-    if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
-        return FALSE;
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
 
-    /* Validate the key argument to a specified match type */
-    return sieve_match_type_validate
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
 		(valdtr, tst, arg, &is_match_type, &i_octet_comparator);
 }
 
@@ -265,10 +265,10 @@ static int tst_test_error_operation_execute
 
 	testsuite_script_get_error_init();
 
-    /* Initialize match */
-    mctx = sieve_match_begin(renv->interp, mtch, cmp, NULL, key_list);
+	/* Initialize match */
+	mctx = sieve_match_begin(renv->interp, mtch, cmp, NULL, key_list);
 
-    /* Iterate through all errors to match */
+	/* Iterate through all errors to match */
 	error = NULL;
 	matched = FALSE;
 	cur_index = 1;
@@ -285,22 +285,22 @@ static int tst_test_error_operation_execute
 
 		matched = ret > 0;
 		cur_index++;
-    }
+	}
 
-    /* Finish match */
-    if ( (ret=sieve_match_end(mctx)) < 0 )
-        result = FALSE;
-    else
-        matched = ( ret > 0 || matched );
+	/* Finish match */
+	if ( (ret=sieve_match_end(mctx)) < 0 )
+		result = FALSE;
+	else
+		matched = ( ret > 0 || matched );
 
 	/* Set test result for subsequent conditional jump */
-    if ( result ) {
-        sieve_interpreter_set_test_result(renv->interp, matched);
-        return SIEVE_EXEC_OK;
-    }
+	if ( result ) {
+		sieve_interpreter_set_test_result(renv->interp, matched);
+		return SIEVE_EXEC_OK;
+	}
 
-    sieve_runtime_trace_error(renv, "invalid string-list item");
-    return SIEVE_EXEC_BIN_CORRUPT;
+	sieve_runtime_trace_error(renv, "invalid string-list item");
+	return SIEVE_EXEC_BIN_CORRUPT;
 }
 
 
diff --git a/src/testsuite/tst-test-result.c b/src/testsuite/tst-test-result.c
new file mode 100644
index 0000000000000000000000000000000000000000..c417789cb3c75ba826b27d46fabcfab8c566ff25
--- /dev/null
+++ b/src/testsuite/tst-test-result.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 2002-2008 Dovecot Sieve authors, see the included COPYING file
+ */
+
+/* FIXME: this file is very similar to tst-test-error.c. Maybe it is best to 
+ * implement errors and actions as testsuite-objects and implement a common
+ * interface to test these.
+ */
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-comparators.h"
+#include "sieve-match-types.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-dump.h"
+#include "sieve-match.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+
+/*
+ * test_result command
+ *
+ * Syntax:   
+ *   test [MATCH-TYPE] [COMPARATOR] [:index number] <key-list: string-list>
+ */
+
+static bool tst_test_result_registered
+    (struct sieve_validator *validator, struct sieve_command_registration *cmd_reg);
+static bool tst_test_result_validate
+	(struct sieve_validator *validator, struct sieve_command_context *cmd);
+static bool tst_test_result_generate
+	(const struct sieve_codegen_env *cgenv, struct sieve_command_context *ctx);
+
+const struct sieve_command tst_test_result = { 
+	"test_result", 
+	SCT_TEST, 
+	1, 0, FALSE, FALSE,
+	tst_test_result_registered, 
+	NULL,
+	tst_test_result_validate, 
+	tst_test_result_generate, 
+	NULL 
+};
+
+/* 
+ * Operation 
+ */
+
+static bool tst_test_result_operation_dump
+	(const struct sieve_operation *op,
+		const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_result_operation_execute
+	(const struct sieve_operation *op, 
+		const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation test_result_operation = { 
+	"test_result",
+	&testsuite_extension, 
+	TESTSUITE_OPERATION_TEST_RESULT,
+	tst_test_result_operation_dump, 
+	tst_test_result_operation_execute 
+};
+
+/*
+ * Tagged arguments
+ */ 
+
+/* NOTE: This will be merged with the date-index extension when it is 
+ * implemented.
+ */
+
+/* FIXME: at least merge this with the test_error version of this tag */
+
+static bool tst_test_result_validate_index_tag
+	(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+		struct sieve_command_context *cmd);
+
+static const struct sieve_argument test_result_index_tag = {
+    "index",
+    NULL, NULL,
+    tst_test_result_validate_index_tag,
+    NULL, NULL
+};
+
+enum tst_test_result_optional {
+	OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+};
+
+/*
+ * Argument implementation
+ */
+
+static bool tst_test_result_validate_index_tag
+(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+	struct sieve_command_context *cmd)
+{
+	struct sieve_ast_argument *tag = *arg;
+
+	/* Detach the tag itself */
+	*arg = sieve_ast_arguments_detach(*arg,1);
+
+	/* Check syntax:
+	 *   :index number
+	 */
+	if ( !sieve_validate_tag_parameter
+		(validator, cmd, tag, *arg, SAAT_NUMBER) ) {
+		return FALSE;
+	}
+
+	/* Skip parameter */
+	*arg = sieve_ast_argument_next(*arg);
+	return TRUE;
+}
+
+
+/*
+ * Command registration
+ */
+
+static bool tst_test_result_registered
+(struct sieve_validator *validator, struct sieve_command_registration *cmd_reg)
+{
+	/* The order of these is not significant */
+	sieve_comparators_link_tag(validator, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+	sieve_match_types_link_tags(validator, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+	sieve_validator_register_tag
+		(validator, cmd_reg, &test_result_index_tag, OPT_INDEX);
+
+	return TRUE;
+}
+
+/* 
+ * Validation 
+ */
+
+static bool tst_test_result_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command_context *tst) 
+{
+	struct sieve_ast_argument *arg = tst->first_positional;
+	
+	if ( !sieve_validate_positional_argument
+		(valdtr, tst, arg, "key list", 2, SAAT_STRING_LIST) ) {
+		return FALSE;
+	}
+
+	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
+		return FALSE;
+
+	/* Validate the key argument to a specified match type */
+	return sieve_match_type_validate
+		(valdtr, tst, arg, &is_match_type, &i_octet_comparator);
+}
+
+/* 
+ * Code generation 
+ */
+
+static inline struct testsuite_generator_context *
+_get_generator_context(struct sieve_generator *gentr)
+{
+	return (struct testsuite_generator_context *) 
+		sieve_generator_extension_get_context(gentr, &testsuite_extension);
+}
+
+static bool tst_test_result_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command_context *tst)
+{
+	sieve_operation_emit_code(cgenv->sbin, &test_result_operation);
+
+	/* Generate arguments */
+	return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/* 
+ * Code dump
+ */
+ 
+static bool tst_test_result_operation_dump
+(const struct sieve_operation *op ATTR_UNUSED,
+	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+	int opt_code = 0;
+
+	sieve_code_dumpf(denv, "TEST_RESULT:");
+	sieve_code_descend(denv);
+
+	/* Handle any optional arguments */
+	do {
+		if ( !sieve_match_dump_optional_operands(denv, address, &opt_code) )
+			return FALSE;
+
+		switch ( opt_code ) {
+		case SIEVE_MATCH_OPT_END:
+			break;
+		case OPT_INDEX:
+			if ( !sieve_opr_number_dump(denv, address, "index") )
+				return FALSE;
+			break;
+		default:
+			return FALSE;
+		}
+	} while ( opt_code != SIEVE_MATCH_OPT_END );
+
+	return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_result_operation_execute
+(const struct sieve_operation *op ATTR_UNUSED,
+	const struct sieve_runtime_env *renv, sieve_size_t *address)
+{	
+	int opt_code = 0;
+	bool result = TRUE;
+	const struct sieve_comparator *cmp = &i_octet_comparator;
+	const struct sieve_match_type *mtch = &is_match_type;
+	struct sieve_match_context *mctx;
+	struct sieve_coded_stringlist *key_list;
+	bool matched;
+	struct sieve_result_iterate_context *rictx;
+	const struct sieve_action *action;
+	int cur_index = 0, index = 0;
+	int ret;
+
+	/*
+	 * Read operands
+	 */
+
+	/* Handle optional operands */
+	do {
+		sieve_number_t number; 
+
+		if ( (ret=sieve_match_read_optional_operands
+			(renv, address, &opt_code, &cmp, &mtch)) <= 0 )
+ 			return ret;
+
+		switch ( opt_code ) {
+		case SIEVE_MATCH_OPT_END:
+			break;
+		case OPT_INDEX:
+			if ( !sieve_opr_number_read(renv, address, &number) ) {
+				sieve_runtime_trace_error(renv, "invalid index operand");
+				return SIEVE_EXEC_BIN_CORRUPT;
+			}
+			index = (int) number;
+			break;
+		default:
+			sieve_runtime_trace_error(renv, "invalid optional operand");
+			return SIEVE_EXEC_BIN_CORRUPT;
+		}	
+	} while ( opt_code != SIEVE_MATCH_OPT_END);
+
+	/* Read key-list */
+	if ( (key_list=sieve_opr_stringlist_read(renv, address)) == NULL ) {
+		sieve_runtime_trace_error(renv, "invalid key-list operand");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+
+	/*
+	 * Perform operation
+	 */
+	
+	sieve_runtime_trace(renv, "TEST_RESULT test (index: %d)", index);
+
+	rictx = testsuite_result_iterate_init();
+
+  /* Initialize match */
+  mctx = sieve_match_begin(renv->interp, mtch, cmp, NULL, key_list);
+
+  /* Iterate through all errors to match */
+	matched = FALSE;
+	cur_index = 1;
+	ret = 0;
+	while ( result && !matched &&
+		(action=sieve_result_iterate_next(rictx, NULL)) != NULL ) {
+		const char *act_name = action->name;
+
+		if ( index == 0 || index == cur_index ) {
+			if ( (ret=sieve_match_value(mctx, act_name, strlen(act_name))) < 0 ) {
+				result = FALSE;
+				break;
+			}
+		}
+
+		matched = ret > 0;
+		cur_index++;
+	}
+
+	/* Finish match */
+	if ( (ret=sieve_match_end(mctx)) < 0 )
+		result = FALSE;
+	else
+		matched = ( ret > 0 || matched );
+
+	/* Set test result for subsequent conditional jump */
+	if ( result ) {
+		sieve_interpreter_set_test_result(renv->interp, matched);
+		return SIEVE_EXEC_OK;
+	}
+
+	sieve_runtime_trace_error(renv, "invalid string-list item");
+	return SIEVE_EXEC_BIN_CORRUPT;
+}
+
+
+
+
diff --git a/tests/execute/actions.svtest b/tests/execute/actions.svtest
index 0660167283f9cd8a237eedf80c3b1e49c8522da7..d7c2b1d1f4fcac8a4d2829c78a1ae4ba3dc26167 100644
--- a/tests/execute/actions.svtest
+++ b/tests/execute/actions.svtest
@@ -1,4 +1,6 @@
 require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
 
 test_set "message" text:
 To: nico@vestingbar.nl
@@ -17,6 +19,22 @@ test "Fileinto" {
 	if not test_execute {
 		test_fail "execute failed";
 	}
+
+	if not test_result :count "eq" :comparator "i;ascii-numeric" "3" {
+		test_fail "wrong number of actions in result";
+	} 
+
+	if not test_result :index 1 "store" {
+		test_fail "first action is not 'store'";
+	} 
+
+	if not test_result :index 2 "store" {
+		test_fail "second action is not 'store'";
+	} 
+
+	if not test_result :index 3 "store" {
+		test_fail "third action is not 'store'";
+	} 
 }
 
 test "Redirect" {
@@ -27,5 +45,25 @@ test "Redirect" {
 	if not test_execute {
 		test_fail "execute failed";
 	}
+
+	if not test_result :count "eq" :comparator "i;ascii-numeric" "4" {
+		test_fail "wrong number of actions in result";
+	} 
+
+	if not test_result :index 1 "redirect" {
+		test_fail "first action is not 'redirect'";
+	} 
+
+	if not test_result :index 2 "store" {
+		test_fail "second action is not 'store'";
+	} 
+
+	if not test_result :index 3 "redirect" {
+		test_fail "third action is not 'redirect'";
+	} 
+
+	if not test_result :index 4 "redirect" {
+		test_fail "fourth action is not 'redirect'";
+	} 
 }
 
diff --git a/tests/execute/actions/fileinto.sieve b/tests/execute/actions/fileinto.sieve
index 41ab6b6a8d727e858b2a2accd0b205b9b63df3ce..c58af8691c2a834ecef7ffd87926e6d1069468af 100644
--- a/tests/execute/actions/fileinto.sieve
+++ b/tests/execute/actions/fileinto.sieve
@@ -1,8 +1,17 @@
 require "fileinto";
 
+/* Three store actions */
+
 if address :contains "to" "vestingbar" {
+	/* #1 */
 	fileinto "INBOX.VB";
-	stop;
 }
 
+/* #2 */
+fileinto "INBOX.backup";
+
+/* #3 */
 keep;
+
+/* Duplicate of keep */
+fileinto "INBOX";
diff --git a/tests/execute/actions/redirect.sieve b/tests/execute/actions/redirect.sieve
index 6d02c6edca32384f62c8f60792277d505b289735..601faf986b78da75ed471f863d028ab66ae58f66 100644
--- a/tests/execute/actions/redirect.sieve
+++ b/tests/execute/actions/redirect.sieve
@@ -1,10 +1,17 @@
 if address :contains "to" "vestingbar" {
+	/* #1 */
 	redirect "stephan@example.com";
+	
+	/* #2 */
 	keep;
 }
 
+/* #3 */
 redirect "stephan@rename-it.nl";
+
+/* #4 */
 redirect "nico@example.nl";
-redirect "stephan@example.com";
 
+/* Duplicates */
+redirect "Stephan Bosch <stephan@example.com>";
 keep;
diff --git a/tests/extensions/reject/execute.svtest b/tests/extensions/reject/execute.svtest
index 7400de8bd3ac56916025f1f2523713ff2073b2ae..840ee27157ae381d3c422fe8c14b95232dd3d674 100644
--- a/tests/extensions/reject/execute.svtest
+++ b/tests/extensions/reject/execute.svtest
@@ -1,4 +1,6 @@
 require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
 
 test_set "message" text:
 To: nico@vestingbar.nl
@@ -17,4 +19,12 @@ test "Execute" {
 	if not test_execute {
 		test_fail "execute failed";
 	}
+
+	if not test_result :count "eq" :comparator "i;ascii-numeric" "1" {
+		test_fail "invalid number of actions in result";
+	}
+
+	if not test_result :index 1 "reject" {
+		test_fail "reject action missing from result";
+	}
 }
diff --git a/tests/extensions/vacation/execute.svtest b/tests/extensions/vacation/execute.svtest
index 6ad15b86b0d00101e70388e690d6cb957da3af0d..a8cdd6b8893d71c2b097c8ea6286adfe0dbc8945 100644
--- a/tests/extensions/vacation/execute.svtest
+++ b/tests/extensions/vacation/execute.svtest
@@ -1,4 +1,28 @@
 require "vnd.dovecot.testsuite";
+require "relational";
+require "comparator-i;ascii-numeric";
+
+test "Action" {
+	if not test_compile "execute/action.sieve" {
+		test_fail "script compile failed";
+	}
+
+	if not test_execute {
+		test_fail "script execute failed";
+	}
+
+	if not test_result :count "eq" :comparator "i;ascii-numeric" "2" {
+		test_fail "invalid number of actions in result";
+	}
+
+	if not test_result :index 1 "vacation" {
+		test_fail "vacation action is not present as first item in result";
+	}
+	
+	if not test_result :index 2 "store" {
+		test_fail "store action is missing in result";
+	}
+}
 
 test "No :handle specified" {
 	if not test_compile "execute/no-handle.sieve" {
diff --git a/tests/extensions/vacation/execute/action.sieve b/tests/extensions/vacation/execute/action.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..7fb6d78fd844d95fe41b346d076d9ee4e424a49f
--- /dev/null
+++ b/tests/extensions/vacation/execute/action.sieve
@@ -0,0 +1,4 @@
+require "vacation";
+
+vacation "I am not at home today";
+keep;