diff --git a/src/lib-sieve/cmd-discard.c b/src/lib-sieve/cmd-discard.c
index 901a3bb897469feba56e71a120c7b4aa3f33d875..feff4843a20f5affdb02d24ae5e71f26db342a71 100644
--- a/src/lib-sieve/cmd-discard.c
+++ b/src/lib-sieve/cmd-discard.c
@@ -84,7 +84,7 @@ static bool cmd_discard_operation_execute
 	const struct sieve_runtime_env *renv ATTR_UNUSED, 
 	sieve_size_t *address ATTR_UNUSED)
 {	
-	printf(">> DISCARD\n");
+	sieve_runtime_trace(renv, "DISCARD action");
 	
 	return ( sieve_result_add_action(renv, &act_discard, NULL, NULL) >= 0 );
 }
diff --git a/src/lib-sieve/cmd-keep.c b/src/lib-sieve/cmd-keep.c
index 982e49e349d26ff9e0fa401d22217618273ddbc0..c2ec8a587bd5a15e9afed9f556320bb66f5f7384 100644
--- a/src/lib-sieve/cmd-keep.c
+++ b/src/lib-sieve/cmd-keep.c
@@ -68,7 +68,7 @@ static bool cmd_keep_operation_execute
 	struct sieve_side_effects_list *slist = NULL;
 	int ret = 0;	
 
-	printf(">> KEEP\n");
+	sieve_runtime_trace(renv, "KEEP action");
 	
 	if ( !sieve_interpreter_handle_optional_operands(renv, address, &slist) )
 		return FALSE;
diff --git a/src/lib-sieve/cmd-redirect.c b/src/lib-sieve/cmd-redirect.c
index f8f9be884f14ede90cdefb332f315ac510b136f7..e3e256fcf085250600e026a67e73d0d31454b090 100644
--- a/src/lib-sieve/cmd-redirect.c
+++ b/src/lib-sieve/cmd-redirect.c
@@ -170,7 +170,7 @@ static bool cmd_redirect_operation_execute
 		return FALSE;
 	}
 
-	printf(">> REDIRECT \"%s\"\n", str_c(redirect));
+	sieve_runtime_trace(renv, "REDIRECT action (\"%s\")", str_c(redirect));
 	
 	/* Add redirect action to the result */
 	pool = sieve_result_pool(renv->result);
diff --git a/src/lib-sieve/plugins/body/tst-body.c b/src/lib-sieve/plugins/body/tst-body.c
index 90c2a2628795ba70e738e90750de7feda19902cc..fac486e9012f094ae0cbaef6e165d2ea626d4361 100644
--- a/src/lib-sieve/plugins/body/tst-body.c
+++ b/src/lib-sieve/plugins/body/tst-body.c
@@ -312,7 +312,7 @@ static bool ext_body_operation_execute
 	struct ext_body_part *body_parts;
 	bool matched;
 	
-	printf("?? BODY\n");
+	sieve_runtime_trace(renv, "BODY action");
 
 	/* Handle any optional arguments */
 	if ( sieve_operand_optional_present(renv->sbin, address) ) {
diff --git a/src/lib-sieve/plugins/imapflags/cmd-addflag.c b/src/lib-sieve/plugins/imapflags/cmd-addflag.c
index b57b2f308538d6d62dc17935cd9439beff42f8dd..8546a3ee0ab72df795d23951e071365b09593bc6 100644
--- a/src/lib-sieve/plugins/imapflags/cmd-addflag.c
+++ b/src/lib-sieve/plugins/imapflags/cmd-addflag.c
@@ -74,7 +74,7 @@ static bool cmd_addflag_operation_execute
 	struct sieve_variable_storage *storage;
 	unsigned int var_index;
 	
-	printf("ADDFLAG\n");
+	sieve_runtime_trace(renv, "ADDFLAG command");
 	
 	t_push();
 	
diff --git a/src/lib-sieve/plugins/imapflags/cmd-removeflag.c b/src/lib-sieve/plugins/imapflags/cmd-removeflag.c
index ffb363944c38894e05ab50b6a7c9367c8a5fd4cd..0990214565eb29c2876226dc8a08a3738dfc0511 100644
--- a/src/lib-sieve/plugins/imapflags/cmd-removeflag.c
+++ b/src/lib-sieve/plugins/imapflags/cmd-removeflag.c
@@ -75,7 +75,7 @@ static bool cmd_removeflag_operation_execute
 	struct sieve_variable_storage *storage;
 	unsigned int var_index;
 	
-	printf("REMOVEFLAG\n");
+	sieve_runtime_trace(renv, "REMOVEFLAG action");
 	
 	t_push();
 	
diff --git a/src/lib-sieve/plugins/imapflags/cmd-setflag.c b/src/lib-sieve/plugins/imapflags/cmd-setflag.c
index b035d59c8f5525e944c7b85a9775d36e4aa86d76..4ee5cf918eb17291ff7d34148175f423a209d53b 100644
--- a/src/lib-sieve/plugins/imapflags/cmd-setflag.c
+++ b/src/lib-sieve/plugins/imapflags/cmd-setflag.c
@@ -73,7 +73,7 @@ static bool cmd_setflag_operation_execute
 	struct sieve_variable_storage *storage;
 	unsigned int var_index;
 	
-	printf("SETFLAG\n");
+	sieve_runtime_trace(renv, "SETFLAG action");
 	
 	t_push();
 	
diff --git a/src/lib-sieve/plugins/imapflags/tst-hasflag.c b/src/lib-sieve/plugins/imapflags/tst-hasflag.c
index 6f2263a2de3511683074762cca2fb4c91c646e2d..9370424951433b5bacc942803f05c8b99ccd8c33 100644
--- a/src/lib-sieve/plugins/imapflags/tst-hasflag.c
+++ b/src/lib-sieve/plugins/imapflags/tst-hasflag.c
@@ -166,7 +166,7 @@ static bool tst_hasflag_operation_execute
 	const char *flag;
 	bool matched;
 	
-	printf("?? HASFLAG\n");
+	sieve_runtime_trace(renv, "HASFLAG test");
 
 	/* Handle any optional arguments */
 	if ( sieve_operand_optional_present(renv->sbin, address) ) {
diff --git a/src/lib-sieve/plugins/include/cmd-include.c b/src/lib-sieve/plugins/include/cmd-include.c
index ea181eac9c16358000c54daf7c954da25c4cc365..eafcf22a6e6f03a630233caf787cdc7c5c9a4b5d 100644
--- a/src/lib-sieve/plugins/include/cmd-include.c
+++ b/src/lib-sieve/plugins/include/cmd-include.c
@@ -254,10 +254,12 @@ static bool opc_include_execute
 	const struct sieve_runtime_env *renv, sieve_size_t *address)
 {
 	int block;
-	
+		
 	if ( !sieve_binary_read_offset(renv->sbin, address, &block) )
 		return FALSE;
 	
+	sieve_runtime_trace(renv, "INCLUDE command (BLOCK: %d)", block);
+	
 	return ext_include_execute_include(renv, (unsigned int) block);
 }
 
diff --git a/src/lib-sieve/plugins/include/cmd-return.c b/src/lib-sieve/plugins/include/cmd-return.c
index ea7db1a7001b41c84e0418b771c155f9bc69ba52..2df76ca03cf9fa4fe806df30bc499ddf6cef8622 100644
--- a/src/lib-sieve/plugins/include/cmd-return.c
+++ b/src/lib-sieve/plugins/include/cmd-return.c
@@ -64,6 +64,8 @@ static bool opc_return_execute
 	const struct sieve_runtime_env *renv, 
 	sieve_size_t *address ATTR_UNUSED)
 {	
+	sieve_runtime_trace(renv, "RETURN command");
+
 	ext_include_execute_return(renv);
 
 	return TRUE;
diff --git a/src/lib-sieve/plugins/vacation/cmd-vacation.c b/src/lib-sieve/plugins/vacation/cmd-vacation.c
index 0003d8479966dc59ec6ee66d18c18178f30f08d6..2255f13cfb04964a1dd3e1212090b66ff98dc1a4 100644
--- a/src/lib-sieve/plugins/vacation/cmd-vacation.c
+++ b/src/lib-sieve/plugins/vacation/cmd-vacation.c
@@ -371,6 +371,8 @@ static bool ext_vacation_operation_execute
 	struct sieve_coded_stringlist *addresses = NULL;
 	string_t *reason, *subject = NULL, *from = NULL, *handle = NULL; 
 		
+	sieve_runtime_trace(renv, "VACATION action");	
+		
 	if ( sieve_operand_optional_present(renv->sbin, address) ) {
 		while ( opt_code != 0 ) {
 			if ( !sieve_operand_optional_read(renv->sbin, address, &opt_code) ) 
@@ -412,8 +414,6 @@ static bool ext_vacation_operation_execute
 	if ( !sieve_opr_string_read(renv, address, &reason) ) 
 		return FALSE;
 	
-	printf(">> VACATION \"%s\"\n", str_c(reason));
-	
 	/* Add vacation action to the result */
 	pool = sieve_result_pool(renv->result);
 	act = p_new(pool, struct act_vacation_context, 1);
diff --git a/src/lib-sieve/plugins/variables/cmd-set.c b/src/lib-sieve/plugins/variables/cmd-set.c
index bec04cd767ecc0a3653940e39bf7168294736cf1..99ded9b366a5d4b4e6acd8d2a98262648013172a 100644
--- a/src/lib-sieve/plugins/variables/cmd-set.c
+++ b/src/lib-sieve/plugins/variables/cmd-set.c
@@ -351,7 +351,7 @@ static bool cmd_set_operation_execute
 	unsigned int var_index, mdfs, i;
 	string_t *value;
 	
-	printf(">> SET\n");
+	sieve_runtime_trace(renv, "SET action");
 	
 	/* Read the variable */
 	if ( !sieve_variable_operand_read
diff --git a/src/lib-sieve/plugins/variables/tst-string.c b/src/lib-sieve/plugins/variables/tst-string.c
index e14d9e28289bb5f1f7a3a076b776da01a8677862..97617f30977f649b7846526c8fc8a3d81421dd75 100644
--- a/src/lib-sieve/plugins/variables/tst-string.c
+++ b/src/lib-sieve/plugins/variables/tst-string.c
@@ -171,7 +171,7 @@ static bool tst_string_operation_execute
 	string_t *src_item;
 	bool matched;
 	
-	printf("?? STRING\n");
+	sieve_runtime_trace(renv, "STRING test");
 
 	/* Handle any optional arguments */
 	if ( sieve_operand_optional_present(renv->sbin, address) ) {
diff --git a/src/lib-sieve/sieve-code.c b/src/lib-sieve/sieve-code.c
index 22e1c90c7eda7df604da002609665d099ab449aa..d764432660fff7d77c3528a5e0726f29aad63ca4 100644
--- a/src/lib-sieve/sieve-code.c
+++ b/src/lib-sieve/sieve-code.c
@@ -795,7 +795,8 @@ static bool opc_jmp_execute
 (const struct sieve_operation *op ATTR_UNUSED, 
 	const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED) 
 {
-	printf("JMP\n");
+	sieve_runtime_trace(renv, "JMP");
+	
 	if ( !sieve_interpreter_program_jump(renv->interp, TRUE) )
 		return FALSE;
 	
@@ -806,11 +807,12 @@ static bool opc_jmptrue_execute
 (const struct sieve_operation *op ATTR_UNUSED, 
 	const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
 {	
-	if ( !sieve_interpreter_program_jump(renv->interp,
-		sieve_interpreter_get_test_result(renv->interp)) )
+	bool result = sieve_interpreter_get_test_result(renv->interp);
+	
+	sieve_runtime_trace(renv, "JMPTRUE (%s)", result ? "true" : "false");
+	
+	if ( !sieve_interpreter_program_jump(renv->interp, result) )
 		return FALSE;
-		
-	printf("JMPTRUE\n");
 	
 	return TRUE;
 }
@@ -819,11 +821,12 @@ static bool opc_jmpfalse_execute
 (const struct sieve_operation *op ATTR_UNUSED, 
 	const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
 {	
-	if ( !sieve_interpreter_program_jump(renv->interp,
-		!sieve_interpreter_get_test_result(renv->interp)) )
-		return FALSE;
-		
-	printf("JMPFALSE\n");
+	bool result = sieve_interpreter_get_test_result(renv->interp);
+	
+	sieve_runtime_trace(renv, "JMPFALSE (%s)", result ? "true" : "false" );
 	
+	if ( !sieve_interpreter_program_jump(renv->interp, !result) )
+		return FALSE;
+			
 	return TRUE;
 }	
diff --git a/src/lib-sieve/sieve-interpreter.c b/src/lib-sieve/sieve-interpreter.c
index 81be1b4365f3575bdc967e3a6366b4b64793712e..3b4accb5d206ffe05183c2f0e9eb1fb4561d696d 100644
--- a/src/lib-sieve/sieve-interpreter.c
+++ b/src/lib-sieve/sieve-interpreter.c
@@ -236,6 +236,19 @@ void sieve_runtime_log
 	va_end(args);
 }
 
+void _sieve_runtime_trace
+	(const struct sieve_runtime_env *runenv, const char *fmt, ...)
+{	
+	va_list args;
+	
+	va_start(args, fmt);
+	T_BEGIN {
+		printf("%08x: %s\n", runenv->interp->current_op_addr, 
+			t_strdup_vprintf(fmt, args)); 
+	} T_END;
+	va_end(args);
+}
+
 /* Extension support */
 
 void sieve_interpreter_extension_set_context
@@ -384,7 +397,6 @@ int sieve_interpreter_continue
 	
 	while ( ret >= 0 && !interp->interrupted && 
 		interp->pc < sieve_binary_get_code_size(interp->runenv.sbin) ) {
-		printf("%08x: ", interp->pc);
 		
 		if ( !sieve_interpreter_execute_operation(interp) ) {
 			printf("Execution aborted.\n");
diff --git a/src/lib-sieve/sieve-interpreter.h b/src/lib-sieve/sieve-interpreter.h
index c33020022ab1a1f3d21bfa6c840ee99228c11815..15f53e8c56d2529c3d123e22dadce9fa74202d90 100644
--- a/src/lib-sieve/sieve-interpreter.h
+++ b/src/lib-sieve/sieve-interpreter.h
@@ -75,6 +75,17 @@ void sieve_runtime_warning
 void sieve_runtime_log
 	(const struct sieve_runtime_env *runenv, const char *fmt, ...)
 		ATTR_FORMAT(2, 3);
+		
+void _sieve_runtime_trace
+	(const struct sieve_runtime_env *runenv, const char *fmt, ...)
+		ATTR_FORMAT(2, 3);
+		
+#ifdef SIEVE_TRACE
+# define sieve_runtime_trace(runenv, ...) \
+	_sieve_runtime_trace(runenv, __VA_ARGS__)
+#else
+# define sieve_runtime_trace(runenv, ...)
+#endif
 
 /* Extension support */
 
diff --git a/src/lib-sieve/tst-address.c b/src/lib-sieve/tst-address.c
index cef90e8c3b20c4bd12a60f731bfd2c4967c3653a..245974d4b78a449661ac5d034aa2eb3351dcf5a3 100644
--- a/src/lib-sieve/tst-address.c
+++ b/src/lib-sieve/tst-address.c
@@ -145,7 +145,7 @@ static bool tst_address_operation_execute
 	string_t *hdr_item;
 	bool matched;
 	
-	printf("?? ADDRESS\n");
+	sieve_runtime_trace(renv, "ADDRESS test");
 
 	if ( !sieve_addrmatch_default_get_optionals
 		(renv, address, &addrp, &mtch, &cmp) )
diff --git a/src/lib-sieve/tst-exists.c b/src/lib-sieve/tst-exists.c
index 6c44cb6f127d70d7fc4f4c6fb2bb5ddd4c7dd02c..38af7b8771922b447dab8108291511ffd1b298c4 100644
--- a/src/lib-sieve/tst-exists.c
+++ b/src/lib-sieve/tst-exists.c
@@ -100,7 +100,7 @@ static bool tst_exists_operation_execute
 	string_t *hdr_item;
 	bool matched;
 	
-	printf("?? EXISTS\n");
+	sieve_runtime_trace(renv, "EXISTS test");
 
 	t_push();
 		
diff --git a/src/lib-sieve/tst-header.c b/src/lib-sieve/tst-header.c
index 6c05ee4f7b4a21f4d5a8611076e73f9ed5679b64..0087f4f65e4c9ca98aad3adec023e162d1465bdf 100644
--- a/src/lib-sieve/tst-header.c
+++ b/src/lib-sieve/tst-header.c
@@ -168,7 +168,7 @@ static bool tst_header_operation_execute
 	string_t *hdr_item;
 	bool matched;
 	
-	printf("?? HEADER\n");
+	sieve_runtime_trace(renv, "HEADER test");
 
 	/* Handle any optional arguments */
 	if ( sieve_operand_optional_present(renv->sbin, address) ) {
@@ -213,15 +213,11 @@ static bool tst_header_operation_execute
 	while ( !matched && (result=sieve_coded_stringlist_next_item(hdr_list, &hdr_item)) 
 		&& hdr_item != NULL ) {
 		const char *const *headers;
-
-		printf("HEADER: %s\n", str_c(hdr_item));
-
 			
 		if ( mail_get_headers_utf8(renv->msgdata->mail, str_c(hdr_item), &headers) >= 0 ) {	
 			
 			int i;
 			for ( i = 0; !matched && headers[i] != NULL; i++ ) {
-				printf("HEADER: %s %s\n", str_c(hdr_item), headers[i]);
 				if ( sieve_match_value(mctx, headers[i], strlen(headers[i])) )
 					matched = TRUE;				
 			} 
diff --git a/src/lib-sieve/tst-size.c b/src/lib-sieve/tst-size.c
index f95d5135e5a603d0d20698c8b401d968048ae003..97da0b1c623164a7b0e5757b292a8a5947b8aa4a 100644
--- a/src/lib-sieve/tst-size.c
+++ b/src/lib-sieve/tst-size.c
@@ -221,7 +221,7 @@ static bool tst_size_operation_execute
 {
 	sieve_size_t mail_size, limit;
 	
-	printf("%s\n", op->mnemonic);
+	sieve_runtime_trace(renv, "%s test", op->mnemonic);
 	
 	if ( !sieve_opr_number_read(renv, address, &limit) ) 
 		return FALSE;	
diff --git a/src/testsuite/cmd-test-fail.c b/src/testsuite/cmd-test-fail.c
index 13a775356be95ef373fb6cc08db5c84840cc9a7b..6203afbd2062d955c701b3491c825227b0b9713b 100644
--- a/src/testsuite/cmd-test-fail.c
+++ b/src/testsuite/cmd-test-fail.c
@@ -135,7 +135,8 @@ static bool cmd_test_fail_operation_execute
 		return FALSE;
 	}
 
-	printf("TEST FAILED: %s\n", str_c(reason));
+	sieve_runtime_trace(renv, "TEST FAIL");
+	testsuite_test_fail(reason);
 	
 	t_pop();
 	
diff --git a/src/testsuite/cmd-test-set.c b/src/testsuite/cmd-test-set.c
index 1db6aacb68aab097a51552eb103a2adee449a251..6942bf44f5f1cf7c5cc5f901ac253736a9688ff9 100644
--- a/src/testsuite/cmd-test-set.c
+++ b/src/testsuite/cmd-test-set.c
@@ -146,7 +146,7 @@ static bool cmd_test_set_operation_execute
 		return FALSE;
 	}
 
-	printf(">> TEST SET %s = \"%s\"\n", 
+	sieve_runtime_trace(renv, "TEST SET command (%s = \"%s\")", 
 		testsuite_object_member_name(object, member_id), str_c(value));
 	
 	if ( object->set_member == NULL ) {
diff --git a/src/testsuite/cmd-test.c b/src/testsuite/cmd-test.c
index eb41afb54b0eea4aef8e059179a6fd19f62cd5ac..85b884b9bf6d5f9be8f0899cf89e1ec14e998a0f 100644
--- a/src/testsuite/cmd-test.c
+++ b/src/testsuite/cmd-test.c
@@ -2,6 +2,7 @@
 #include "sieve-commands-private.h"
 #include "sieve-validator.h"
 #include "sieve-generator.h"
+#include "sieve-interpreter.h"
 #include "sieve-code.h"
 #include "sieve-binary.h"
 #include "sieve-dump.h"
@@ -16,6 +17,9 @@ static bool cmd_test_operation_dump
 static bool cmd_test_operation_execute
 	(const struct sieve_operation *op, 
 		const struct sieve_runtime_env *renv, sieve_size_t *address);
+static bool cmd_test_finish_operation_execute
+	(const struct sieve_operation *op, 
+		const struct sieve_runtime_env *renv, sieve_size_t *address);
 
 static bool cmd_test_validate
 	(struct sieve_validator *validator, struct sieve_command_context *cmd);
@@ -47,6 +51,14 @@ const struct sieve_operation test_operation = {
 	cmd_test_operation_execute 
 };
 
+const struct sieve_operation test_finish_operation = { 
+	"TEST-FINISH",
+	&testsuite_extension, 
+	TESTSUITE_OPERATION_TEST_FINISH,
+	NULL, 
+	cmd_test_finish_operation_execute 
+};
+
 /* Validation */
 
 static bool cmd_test_validate
@@ -98,6 +110,9 @@ static bool cmd_test_generate
 	/* Test body */
 	sieve_generate_block(gentr, ctx->ast_node);
 	
+	sieve_generator_emit_operation_ext(gentr, &test_finish_operation, 
+		ext_testsuite_my_id);
+	
 	/* Resolve exit jumps to this point */
 	sieve_jumplist_resolve(genctx->exit_jumps); 
 			
@@ -135,14 +150,29 @@ static bool cmd_test_operation_execute
 		t_pop();
 		return FALSE;
 	}
+	
+	testsuite_test_start(test_name);
 
-	printf("TEST: %s\n", str_c(test_name));
+	sieve_runtime_trace(renv, "TEST \"%s\"", str_c(test_name));
 	
 	t_pop();
 	
 	return TRUE;
 }
 
+static bool cmd_test_finish_operation_execute
+(const struct sieve_operation *op ATTR_UNUSED,
+	const struct sieve_runtime_env *renv ATTR_UNUSED, 
+	sieve_size_t *address ATTR_UNUSED)
+{
+	sieve_runtime_trace(renv, "TEST FINISHED");
+	
+	testsuite_test_succeed(NULL);
+	
+	return TRUE;
+}
+
+
 
 
 
diff --git a/src/testsuite/ext-testsuite.c b/src/testsuite/ext-testsuite.c
index 514755cf8f7a9f116d4b8e24f6f1a1fae143e336..7bccc2a3380716cabf72516f17efc7db10e88b62 100644
--- a/src/testsuite/ext-testsuite.c
+++ b/src/testsuite/ext-testsuite.c
@@ -41,8 +41,12 @@ extern const struct sieve_command cmd_test_set;
 
 /* Operations */
 
-const struct sieve_operation *testsuite_operations[] =
-    { &test_operation, &test_fail_operation, &test_set_operation };
+const struct sieve_operation *testsuite_operations[] = { 
+	&test_operation, 
+	&test_finish_operation,
+	&test_fail_operation, 
+	&test_set_operation 
+};
 
 /* Operands */
 
diff --git a/src/testsuite/tests/match-types/matches.svtest b/src/testsuite/tests/match-types/matches.svtest
index ce0643317c19b57ef53fe6c5dace7db8553241b2..ea9bcb2b868e07291d264b4dbc8d35e226f1ee44 100644
--- a/src/testsuite/tests/match-types/matches.svtest
+++ b/src/testsuite/tests/match-types/matches.svtest
@@ -16,136 +16,140 @@ Het werkt!
 .
 ;
 
+# Matching tests
+
 test "MATCH-A" {
 	if not address :matches "from" "*@d*ksn*ers.com" {
-		test_fail "A case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-B" {
 	if not address :matches "from" "stephan+sieve@drunksnipers.*" {
-		test_fail "B case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-C" {
 	if not address :matches "from" "*+sieve@drunksnipers.com" {
-		test_fail "C case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-D" {
 	if not address :matches "from" "stephan+sieve?drunksnipers.com" {
-		test_fail "D case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-E" {
 	if not address :matches "from" "?tephan+sieve@drunksnipers.com" {
-		test_fail "E case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-F" {
 	if not address :matches "from" "stephan+sieve@drunksnipers.co?" {
-		test_fail "F case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-G" {
 	if not address :matches "from" "?t?phan?sieve?drunksnip?rs.co?" {
-		test_fail "G case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-H" {
 	if not header :matches "x-bullshit" "33333\\?\\?\\??" {
-		test_fail "H case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 
 test "MATCH-I" {
 	if not header :matches "x-bullshit" "33333*\\?\\??" {
-		test_fail "I case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-J" {
 	if not header :matches "X-Bullshit" "*\\?\\?\\?a" {
-		test_fail "J case should have matched";
+		test_fail "should have matched";
 	}
 }
 
 test "MATCH-K" {
 	if not header :matches "x-bullshit" "*3333\\?\\?\\?a" {
-		test_fail "K case should have matched";
+		test_fail "should have matched";
 	}
 }
 
+# Non-matching tests
+
 test "NO-MATCH-A" {
 	if address :matches "from" "*@d*kn*ers.com" {
-		test_fail "A case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-B" {
 	if address :matches "from" "stepan+sieve@drunksnipers.*" {
-		test_fail "B case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-C" {
 	if address :matches "from" "*+sieve@drunksnipers.om" {
-		test_fail "C case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-D" {
 	if address :matches "from" "stephan+sieve?drunksipers.com" {
-		test_fail "D case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-E" {
 	if address :matches "from" "?tephan+sievedrunksnipers.com" {
-		test_fail "E case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-F" {
 	if address :matches "from" "sephan+sieve@drunksnipers.co?" {
-		test_fail "F case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-G" {
 	if address :matches "from" "?t?phan?sieve?dunksnip?rs.co?" {
-		test_fail "G case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-H" {
 	if header :matches "x-bullshit" "33333\\?\\?\\?" {
-		test_fail "H case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-I" {
 	if header :matches "x-bullshit" "33333\\?\\?\\?aa" {
-		test_fail "I case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-J" {
 	if header :matches "x-bullshit" "\\*3333\\?\\?\\?a" {
-		test_fail "J case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
 test "NO-MATCH-K" {
 	if header :matches "x-bullshit" "\\f3333\\?\\?\\?a" {
-		test_fail "K case should not have matched";
+		test_fail "should not have matched";
 	}
 }
 
diff --git a/src/testsuite/testsuite-common.c b/src/testsuite/testsuite-common.c
index fab9752b17721865e1c5d49d32729243f8fecace..e61a35e3b7c124eb18c27b61607483db8f2ebcad 100644
--- a/src/testsuite/testsuite-common.c
+++ b/src/testsuite/testsuite-common.c
@@ -1,4 +1,5 @@
 #include "lib.h"
+#include "str.h"
 #include "string.h"
 #include "ostream.h"
 #include "hash.h"
@@ -25,6 +26,11 @@
 
 struct sieve_message_data testsuite_msgdata;
 
+/* Test context */
+
+static string_t *test_name;
+unsigned int test_index;
+
 /* 
  * Testsuite message environment 
  */
@@ -156,3 +162,62 @@ bool testsuite_generator_context_initialize(struct sieve_generator *gentr)
 
 	return TRUE;
 }
+
+/*
+ * Test context
+ */
+ 
+void testsuite_test_context_init(void)
+{
+	test_name = str_new(default_pool, 128);
+	test_index = 0;	
+}
+
+void testsuite_test_start(string_t *name)
+{
+	str_truncate(test_name, 0);
+	str_append_str(test_name, name);
+
+	test_index++;
+}
+
+void testsuite_test_fail(string_t *reason)
+{	
+	if ( str_len(test_name) == 0 ) {
+		if ( reason == NULL || str_len(reason) == 0 )
+			printf("%d: Test FAILED\n", test_index);
+		else
+			printf("%d: Test FAILED: %s\n", test_index, str_c(reason));
+	} else {
+		if ( reason == NULL || str_len(reason) == 0 )
+			printf("%d: Test '%s' FAILED\n", test_index, str_c(test_name));
+		else
+			printf("%d: Test '%s' FAILED: %s\n", test_index, 
+				str_c(test_name), str_c(reason));
+	}
+
+	str_truncate(test_name, 0);
+}
+
+void testsuite_test_succeed(string_t *reason)
+{
+	if ( str_len(test_name) == 0 ) {
+		if ( reason == NULL || str_len(reason) == 0 )
+			printf("%d: Test SUCCEEDED\n", test_index);
+		else
+			printf("%d: Test SUCCEEDED: %s\n", test_index, str_c(reason));
+	} else {
+		if ( reason == NULL || str_len(reason) == 0 )
+			printf("%d: Test '%s' SUCCEEDED\n", test_index, str_c(test_name));
+		else
+			printf("%d: Test '%s' SUCCEEDED: %s\n", test_index, 
+				str_c(test_name), str_c(reason));
+	}
+	str_truncate(test_name, 0);
+}
+
+void testsuite_test_context_deinit(void)
+{
+	//str_free(test_name);
+}
+
diff --git a/src/testsuite/testsuite-common.h b/src/testsuite/testsuite-common.h
index b5bb806e38767a55a1ba3592098932b70edbc008..2edbea9f2ce61923f9803bd0a914129d374825f6 100644
--- a/src/testsuite/testsuite-common.h
+++ b/src/testsuite/testsuite-common.h
@@ -38,11 +38,13 @@ bool testsuite_generator_context_initialize(struct sieve_generator *gentr);
 
 enum testsuite_operation_code {
 	TESTSUITE_OPERATION_TEST,
+	TESTSUITE_OPERATION_TEST_FINISH,
 	TESTSUITE_OPERATION_TEST_FAIL,
 	TESTSUITE_OPERATION_TEST_SET
 };
 
 extern const struct sieve_operation test_operation;
+extern const struct sieve_operation test_finish_operation;
 extern const struct sieve_operation test_fail_operation;
 extern const struct sieve_operation test_set_operation;
 
@@ -54,4 +56,12 @@ enum testsuite_operand_code {
 	TESTSUITE_OPERAND_OBJECT
 };
 
+/* Test context */
+
+void testsuite_test_context_init(void);
+void testsuite_test_start(string_t *name);
+void testsuite_test_fail(string_t *reason);
+void testsuite_test_succeed(string_t *reason);
+void testsuite_test_context_deinit(void);
+
 #endif /* __TESTSUITE_COMMON_H */
diff --git a/src/testsuite/testsuite.c b/src/testsuite/testsuite.c
index 1d9c2e9181bbd9b03c34e7a89b46ffd05297ab79..51b5e01164f8df6eb38c1f5e25baca7db0266832 100644
--- a/src/testsuite/testsuite.c
+++ b/src/testsuite/testsuite.c
@@ -52,10 +52,14 @@ static void testsuite_init(void)
 		i_fatal("Failed to initialize sieve implementation\n");
 
 	(void) sieve_extension_register(&testsuite_extension);
+	
+	testsuite_test_context_init();
 }
 
 static void testsuite_deinit(void)
 {
+	testsuite_test_context_deinit();
+	
 	sieve_deinit();
 	
 	lib_signals_deinit();