summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/testsuite
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /pigeonhole/src/testsuite
parentInitial commit. (diff)
downloaddovecot-upstream.tar.xz
dovecot-upstream.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pigeonhole/src/testsuite')
-rw-r--r--pigeonhole/src/testsuite/Makefile.am74
-rw-r--r--pigeonhole/src/testsuite/Makefile.in869
-rw-r--r--pigeonhole/src/testsuite/cmd-test-binary.c208
-rw-r--r--pigeonhole/src/testsuite/cmd-test-config.c504
-rw-r--r--pigeonhole/src/testsuite/cmd-test-fail.c129
-rw-r--r--pigeonhole/src/testsuite/cmd-test-imap-metadata.c284
-rw-r--r--pigeonhole/src/testsuite/cmd-test-mailbox.c261
-rw-r--r--pigeonhole/src/testsuite/cmd-test-message.c569
-rw-r--r--pigeonhole/src/testsuite/cmd-test-result.c138
-rw-r--r--pigeonhole/src/testsuite/cmd-test-set.c161
-rw-r--r--pigeonhole/src/testsuite/cmd-test.c208
-rw-r--r--pigeonhole/src/testsuite/ext-testsuite.c175
-rw-r--r--pigeonhole/src/testsuite/testsuite-arguments.c190
-rw-r--r--pigeonhole/src/testsuite/testsuite-arguments.h6
-rw-r--r--pigeonhole/src/testsuite/testsuite-binary.c82
-rw-r--r--pigeonhole/src/testsuite/testsuite-binary.h17
-rw-r--r--pigeonhole/src/testsuite/testsuite-common.c374
-rw-r--r--pigeonhole/src/testsuite/testsuite-common.h193
-rw-r--r--pigeonhole/src/testsuite/testsuite-log.c319
-rw-r--r--pigeonhole/src/testsuite/testsuite-log.h26
-rw-r--r--pigeonhole/src/testsuite/testsuite-mailstore.c349
-rw-r--r--pigeonhole/src/testsuite/testsuite-mailstore.h40
-rw-r--r--pigeonhole/src/testsuite/testsuite-message.c339
-rw-r--r--pigeonhole/src/testsuite/testsuite-message.h44
-rw-r--r--pigeonhole/src/testsuite/testsuite-objects.c369
-rw-r--r--pigeonhole/src/testsuite/testsuite-objects.h83
-rw-r--r--pigeonhole/src/testsuite/testsuite-result.c206
-rw-r--r--pigeonhole/src/testsuite/testsuite-result.h25
-rw-r--r--pigeonhole/src/testsuite/testsuite-script.c256
-rw-r--r--pigeonhole/src/testsuite/testsuite-script.h22
-rw-r--r--pigeonhole/src/testsuite/testsuite-settings.c96
-rw-r--r--pigeonhole/src/testsuite/testsuite-settings.h12
-rw-r--r--pigeonhole/src/testsuite/testsuite-smtp.c176
-rw-r--r--pigeonhole/src/testsuite/testsuite-smtp.h35
-rw-r--r--pigeonhole/src/testsuite/testsuite-substitutions.c253
-rw-r--r--pigeonhole/src/testsuite/testsuite-substitutions.h23
-rw-r--r--pigeonhole/src/testsuite/testsuite-variables.c183
-rw-r--r--pigeonhole/src/testsuite/testsuite-variables.h11
-rw-r--r--pigeonhole/src/testsuite/testsuite.c256
-rw-r--r--pigeonhole/src/testsuite/tst-test-error.c273
-rw-r--r--pigeonhole/src/testsuite/tst-test-multiscript.c155
-rw-r--r--pigeonhole/src/testsuite/tst-test-result-action.c268
-rw-r--r--pigeonhole/src/testsuite/tst-test-result-execute.c96
-rw-r--r--pigeonhole/src/testsuite/tst-test-script-compile.c144
-rw-r--r--pigeonhole/src/testsuite/tst-test-script-run.c198
45 files changed, 8699 insertions, 0 deletions
diff --git a/pigeonhole/src/testsuite/Makefile.am b/pigeonhole/src/testsuite/Makefile.am
new file mode 100644
index 0000000..f7b85dc
--- /dev/null
+++ b/pigeonhole/src/testsuite/Makefile.am
@@ -0,0 +1,74 @@
+noinst_PROGRAMS = testsuite
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib-sieve \
+ -I$(top_srcdir)/src/lib-sieve/util \
+ -I$(top_srcdir)/src/lib-sieve/plugins/variables \
+ -I$(top_srcdir)/src/lib-sieve-tool \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE)
+
+testsuite_LDFLAGS = -export-dynamic
+
+libs = \
+ $(top_builddir)/src/lib-sieve/libdovecot-sieve.la \
+ $(top_builddir)/src/lib-sieve-tool/libsieve-tool.la
+
+testsuite_LDADD = $(libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT_LDA) $(LIBDOVECOT)
+testsuite_DEPENDENCIES = $(libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_LDA_DEPS) $(LIBDOVECOT_DEPS)
+
+commands = \
+ cmd-test.c \
+ cmd-test-fail.c \
+ cmd-test-config.c \
+ cmd-test-set.c \
+ cmd-test-result.c \
+ cmd-test-message.c \
+ cmd-test-mailbox.c \
+ cmd-test-binary.c \
+ cmd-test-imap-metadata.c
+
+tests = \
+ tst-test-script-compile.c \
+ tst-test-script-run.c \
+ tst-test-multiscript.c \
+ tst-test-error.c \
+ tst-test-result-action.c \
+ tst-test-result-execute.c
+
+testsuite_SOURCES = \
+ testsuite-common.c \
+ testsuite-settings.c \
+ testsuite-objects.c \
+ testsuite-substitutions.c \
+ testsuite-variables.c \
+ testsuite-arguments.c \
+ testsuite-message.c \
+ testsuite-log.c \
+ testsuite-script.c \
+ testsuite-result.c \
+ testsuite-smtp.c \
+ testsuite-mailstore.c \
+ testsuite-binary.c \
+ $(commands) \
+ $(tests) \
+ ext-testsuite.c \
+ testsuite.c
+
+noinst_HEADERS = \
+ testsuite-common.h \
+ testsuite-settings.h \
+ testsuite-objects.h \
+ testsuite-substitutions.h \
+ testsuite-variables.h \
+ testsuite-arguments.h \
+ testsuite-message.h \
+ testsuite-log.h \
+ testsuite-script.h \
+ testsuite-result.h \
+ testsuite-smtp.h \
+ testsuite-mailstore.h \
+ testsuite-binary.h
+
+clean-local:
+ -rm -rf test.out.*
diff --git a/pigeonhole/src/testsuite/Makefile.in b/pigeonhole/src/testsuite/Makefile.in
new file mode 100644
index 0000000..3706d3c
--- /dev/null
+++ b/pigeonhole/src/testsuite/Makefile.in
@@ -0,0 +1,869 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = testsuite$(EXEEXT)
+subdir = src/testsuite
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/dummy-config.h \
+ $(top_builddir)/pigeonhole-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am__objects_1 = cmd-test.$(OBJEXT) cmd-test-fail.$(OBJEXT) \
+ cmd-test-config.$(OBJEXT) cmd-test-set.$(OBJEXT) \
+ cmd-test-result.$(OBJEXT) cmd-test-message.$(OBJEXT) \
+ cmd-test-mailbox.$(OBJEXT) cmd-test-binary.$(OBJEXT) \
+ cmd-test-imap-metadata.$(OBJEXT)
+am__objects_2 = tst-test-script-compile.$(OBJEXT) \
+ tst-test-script-run.$(OBJEXT) tst-test-multiscript.$(OBJEXT) \
+ tst-test-error.$(OBJEXT) tst-test-result-action.$(OBJEXT) \
+ tst-test-result-execute.$(OBJEXT)
+am_testsuite_OBJECTS = testsuite-common.$(OBJEXT) \
+ testsuite-settings.$(OBJEXT) testsuite-objects.$(OBJEXT) \
+ testsuite-substitutions.$(OBJEXT) \
+ testsuite-variables.$(OBJEXT) testsuite-arguments.$(OBJEXT) \
+ testsuite-message.$(OBJEXT) testsuite-log.$(OBJEXT) \
+ testsuite-script.$(OBJEXT) testsuite-result.$(OBJEXT) \
+ testsuite-smtp.$(OBJEXT) testsuite-mailstore.$(OBJEXT) \
+ testsuite-binary.$(OBJEXT) $(am__objects_1) $(am__objects_2) \
+ ext-testsuite.$(OBJEXT) testsuite.$(OBJEXT)
+testsuite_OBJECTS = $(am_testsuite_OBJECTS)
+am__DEPENDENCIES_1 =
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+testsuite_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(testsuite_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/cmd-test-binary.Po \
+ ./$(DEPDIR)/cmd-test-config.Po ./$(DEPDIR)/cmd-test-fail.Po \
+ ./$(DEPDIR)/cmd-test-imap-metadata.Po \
+ ./$(DEPDIR)/cmd-test-mailbox.Po \
+ ./$(DEPDIR)/cmd-test-message.Po ./$(DEPDIR)/cmd-test-result.Po \
+ ./$(DEPDIR)/cmd-test-set.Po ./$(DEPDIR)/cmd-test.Po \
+ ./$(DEPDIR)/ext-testsuite.Po \
+ ./$(DEPDIR)/testsuite-arguments.Po \
+ ./$(DEPDIR)/testsuite-binary.Po \
+ ./$(DEPDIR)/testsuite-common.Po ./$(DEPDIR)/testsuite-log.Po \
+ ./$(DEPDIR)/testsuite-mailstore.Po \
+ ./$(DEPDIR)/testsuite-message.Po \
+ ./$(DEPDIR)/testsuite-objects.Po \
+ ./$(DEPDIR)/testsuite-result.Po \
+ ./$(DEPDIR)/testsuite-script.Po \
+ ./$(DEPDIR)/testsuite-settings.Po \
+ ./$(DEPDIR)/testsuite-smtp.Po \
+ ./$(DEPDIR)/testsuite-substitutions.Po \
+ ./$(DEPDIR)/testsuite-variables.Po ./$(DEPDIR)/testsuite.Po \
+ ./$(DEPDIR)/tst-test-error.Po \
+ ./$(DEPDIR)/tst-test-multiscript.Po \
+ ./$(DEPDIR)/tst-test-result-action.Po \
+ ./$(DEPDIR)/tst-test-result-execute.Po \
+ ./$(DEPDIR)/tst-test-script-compile.Po \
+ ./$(DEPDIR)/tst-test-script-run.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(testsuite_SOURCES)
+DIST_SOURCES = $(testsuite_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(noinst_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOVECOT_BINARY_CFLAGS = @DOVECOT_BINARY_CFLAGS@
+DOVECOT_BINARY_LDFLAGS = @DOVECOT_BINARY_LDFLAGS@
+DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
+DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
+DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
+DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
+DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
+DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_ACL_INCLUDE = @LIBDOVECOT_ACL_INCLUDE@
+LIBDOVECOT_AUTH_INCLUDE = @LIBDOVECOT_AUTH_INCLUDE@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_COMPRESS_DEPS = @LIBDOVECOT_COMPRESS_DEPS@
+LIBDOVECOT_CONFIG_INCLUDE = @LIBDOVECOT_CONFIG_INCLUDE@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DOVEADM_INCLUDE = @LIBDOVECOT_DOVEADM_INCLUDE@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_DSYNC_DEPS = @LIBDOVECOT_DSYNC_DEPS@
+LIBDOVECOT_DSYNC_INCLUDE = @LIBDOVECOT_DSYNC_INCLUDE@
+LIBDOVECOT_FTS_INCLUDE = @LIBDOVECOT_FTS_INCLUDE@
+LIBDOVECOT_IMAPC_INCLUDE = @LIBDOVECOT_IMAPC_INCLUDE@
+LIBDOVECOT_IMAP_INCLUDE = @LIBDOVECOT_IMAP_INCLUDE@
+LIBDOVECOT_IMAP_LOGIN_INCLUDE = @LIBDOVECOT_IMAP_LOGIN_INCLUDE@
+LIBDOVECOT_INCLUDE = @LIBDOVECOT_INCLUDE@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDA_DEPS = @LIBDOVECOT_LDA_DEPS@
+LIBDOVECOT_LDA_INCLUDE = @LIBDOVECOT_LDA_INCLUDE@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LIBFTS_INCLUDE = @LIBDOVECOT_LIBFTS_INCLUDE@
+LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LMTP_INCLUDE@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
+LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
+LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
+LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
+LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
+LIBDOVECOT_SERVICE_INCLUDE = @LIBDOVECOT_SERVICE_INCLUDE@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_SQL_DEPS = @LIBDOVECOT_SQL_DEPS@
+LIBDOVECOT_SQL_INCLUDE = @LIBDOVECOT_SQL_INCLUDE@
+LIBDOVECOT_SSL = @LIBDOVECOT_SSL@
+LIBDOVECOT_SSL_DEPS = @LIBDOVECOT_SSL_DEPS@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBDOVECOT_STORAGE_INCLUDE = @LIBDOVECOT_STORAGE_INCLUDE@
+LIBDOVECOT_SUBMISSION_INCLUDE = @LIBDOVECOT_SUBMISSION_INCLUDE@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dovecot_docdir = @dovecot_docdir@
+dovecot_installed_moduledir = @dovecot_installed_moduledir@
+dovecot_moduledir = @dovecot_moduledir@
+dovecot_pkgincludedir = @dovecot_pkgincludedir@
+dovecot_pkglibdir = @dovecot_pkglibdir@
+dovecot_pkglibexecdir = @dovecot_pkglibexecdir@
+dovecot_statedir = @dovecot_statedir@
+dovecotdir = @dovecotdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sieve_docdir = @sieve_docdir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib-sieve \
+ -I$(top_srcdir)/src/lib-sieve/util \
+ -I$(top_srcdir)/src/lib-sieve/plugins/variables \
+ -I$(top_srcdir)/src/lib-sieve-tool \
+ $(LIBDOVECOT_INCLUDE) \
+ $(LIBDOVECOT_SERVICE_INCLUDE)
+
+testsuite_LDFLAGS = -export-dynamic
+libs = \
+ $(top_builddir)/src/lib-sieve/libdovecot-sieve.la \
+ $(top_builddir)/src/lib-sieve-tool/libsieve-tool.la
+
+testsuite_LDADD = $(libs) $(LIBDOVECOT_STORAGE) $(LIBDOVECOT_LDA) $(LIBDOVECOT)
+testsuite_DEPENDENCIES = $(libs) $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_LDA_DEPS) $(LIBDOVECOT_DEPS)
+commands = \
+ cmd-test.c \
+ cmd-test-fail.c \
+ cmd-test-config.c \
+ cmd-test-set.c \
+ cmd-test-result.c \
+ cmd-test-message.c \
+ cmd-test-mailbox.c \
+ cmd-test-binary.c \
+ cmd-test-imap-metadata.c
+
+tests = \
+ tst-test-script-compile.c \
+ tst-test-script-run.c \
+ tst-test-multiscript.c \
+ tst-test-error.c \
+ tst-test-result-action.c \
+ tst-test-result-execute.c
+
+testsuite_SOURCES = \
+ testsuite-common.c \
+ testsuite-settings.c \
+ testsuite-objects.c \
+ testsuite-substitutions.c \
+ testsuite-variables.c \
+ testsuite-arguments.c \
+ testsuite-message.c \
+ testsuite-log.c \
+ testsuite-script.c \
+ testsuite-result.c \
+ testsuite-smtp.c \
+ testsuite-mailstore.c \
+ testsuite-binary.c \
+ $(commands) \
+ $(tests) \
+ ext-testsuite.c \
+ testsuite.c
+
+noinst_HEADERS = \
+ testsuite-common.h \
+ testsuite-settings.h \
+ testsuite-objects.h \
+ testsuite-substitutions.h \
+ testsuite-variables.h \
+ testsuite-arguments.h \
+ testsuite-message.h \
+ testsuite-log.h \
+ testsuite-script.h \
+ testsuite-result.h \
+ testsuite-smtp.h \
+ testsuite-mailstore.h \
+ testsuite-binary.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/testsuite/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/testsuite/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+testsuite$(EXEEXT): $(testsuite_OBJECTS) $(testsuite_DEPENDENCIES) $(EXTRA_testsuite_DEPENDENCIES)
+ @rm -f testsuite$(EXEEXT)
+ $(AM_V_CCLD)$(testsuite_LINK) $(testsuite_OBJECTS) $(testsuite_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-binary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-config.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-fail.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-imap-metadata.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-mailbox.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-message.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-result.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test-set.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd-test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext-testsuite.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-arguments.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-binary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-common.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-log.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-mailstore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-message.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-objects.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-result.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-script.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-smtp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-substitutions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite-variables.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/testsuite.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-test-error.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-test-multiscript.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-test-result-action.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-test-result-execute.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-test-script-compile.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tst-test-script-run.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-local clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cmd-test-binary.Po
+ -rm -f ./$(DEPDIR)/cmd-test-config.Po
+ -rm -f ./$(DEPDIR)/cmd-test-fail.Po
+ -rm -f ./$(DEPDIR)/cmd-test-imap-metadata.Po
+ -rm -f ./$(DEPDIR)/cmd-test-mailbox.Po
+ -rm -f ./$(DEPDIR)/cmd-test-message.Po
+ -rm -f ./$(DEPDIR)/cmd-test-result.Po
+ -rm -f ./$(DEPDIR)/cmd-test-set.Po
+ -rm -f ./$(DEPDIR)/cmd-test.Po
+ -rm -f ./$(DEPDIR)/ext-testsuite.Po
+ -rm -f ./$(DEPDIR)/testsuite-arguments.Po
+ -rm -f ./$(DEPDIR)/testsuite-binary.Po
+ -rm -f ./$(DEPDIR)/testsuite-common.Po
+ -rm -f ./$(DEPDIR)/testsuite-log.Po
+ -rm -f ./$(DEPDIR)/testsuite-mailstore.Po
+ -rm -f ./$(DEPDIR)/testsuite-message.Po
+ -rm -f ./$(DEPDIR)/testsuite-objects.Po
+ -rm -f ./$(DEPDIR)/testsuite-result.Po
+ -rm -f ./$(DEPDIR)/testsuite-script.Po
+ -rm -f ./$(DEPDIR)/testsuite-settings.Po
+ -rm -f ./$(DEPDIR)/testsuite-smtp.Po
+ -rm -f ./$(DEPDIR)/testsuite-substitutions.Po
+ -rm -f ./$(DEPDIR)/testsuite-variables.Po
+ -rm -f ./$(DEPDIR)/testsuite.Po
+ -rm -f ./$(DEPDIR)/tst-test-error.Po
+ -rm -f ./$(DEPDIR)/tst-test-multiscript.Po
+ -rm -f ./$(DEPDIR)/tst-test-result-action.Po
+ -rm -f ./$(DEPDIR)/tst-test-result-execute.Po
+ -rm -f ./$(DEPDIR)/tst-test-script-compile.Po
+ -rm -f ./$(DEPDIR)/tst-test-script-run.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/cmd-test-binary.Po
+ -rm -f ./$(DEPDIR)/cmd-test-config.Po
+ -rm -f ./$(DEPDIR)/cmd-test-fail.Po
+ -rm -f ./$(DEPDIR)/cmd-test-imap-metadata.Po
+ -rm -f ./$(DEPDIR)/cmd-test-mailbox.Po
+ -rm -f ./$(DEPDIR)/cmd-test-message.Po
+ -rm -f ./$(DEPDIR)/cmd-test-result.Po
+ -rm -f ./$(DEPDIR)/cmd-test-set.Po
+ -rm -f ./$(DEPDIR)/cmd-test.Po
+ -rm -f ./$(DEPDIR)/ext-testsuite.Po
+ -rm -f ./$(DEPDIR)/testsuite-arguments.Po
+ -rm -f ./$(DEPDIR)/testsuite-binary.Po
+ -rm -f ./$(DEPDIR)/testsuite-common.Po
+ -rm -f ./$(DEPDIR)/testsuite-log.Po
+ -rm -f ./$(DEPDIR)/testsuite-mailstore.Po
+ -rm -f ./$(DEPDIR)/testsuite-message.Po
+ -rm -f ./$(DEPDIR)/testsuite-objects.Po
+ -rm -f ./$(DEPDIR)/testsuite-result.Po
+ -rm -f ./$(DEPDIR)/testsuite-script.Po
+ -rm -f ./$(DEPDIR)/testsuite-settings.Po
+ -rm -f ./$(DEPDIR)/testsuite-smtp.Po
+ -rm -f ./$(DEPDIR)/testsuite-substitutions.Po
+ -rm -f ./$(DEPDIR)/testsuite-variables.Po
+ -rm -f ./$(DEPDIR)/testsuite.Po
+ -rm -f ./$(DEPDIR)/tst-test-error.Po
+ -rm -f ./$(DEPDIR)/tst-test-multiscript.Po
+ -rm -f ./$(DEPDIR)/tst-test-result-action.Po
+ -rm -f ./$(DEPDIR)/tst-test-result-execute.Po
+ -rm -f ./$(DEPDIR)/tst-test-script-compile.Po
+ -rm -f ./$(DEPDIR)/tst-test-script-run.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-local clean-noinstPROGRAMS \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+clean-local:
+ -rm -rf test.out.*
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/pigeonhole/src/testsuite/cmd-test-binary.c b/pigeonhole/src/testsuite/cmd-test-binary.c
new file mode 100644
index 0000000..6f810bd
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-binary.c
@@ -0,0 +1,208 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.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"
+
+#include "testsuite-common.h"
+#include "testsuite-binary.h"
+#include "testsuite-script.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_binary_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_binary_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Test_binary_load command
+ *
+ * Syntax:
+ * test_binary_load <binary-name: string>
+ */
+
+const struct sieve_command_def cmd_test_binary_load = {
+ .identifier = "test_binary_load",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_binary_validate,
+ .generate = cmd_test_binary_generate
+};
+
+/* Test_binary_save command
+ *
+ * Syntax:
+ * test_binary_save <binary-name: string>
+ */
+
+const struct sieve_command_def cmd_test_binary_save = {
+ .identifier = "test_binary_save",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_binary_validate,
+ .generate = cmd_test_binary_generate,
+};
+
+/*
+ * Operations
+ */
+
+static bool cmd_test_binary_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_binary_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* test_binary_create operation */
+
+const struct sieve_operation_def test_binary_load_operation = {
+ .mnemonic = "TEST_BINARY_LOAD",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_BINARY_LOAD,
+ .dump = cmd_test_binary_operation_dump,
+ .execute = cmd_test_binary_operation_execute
+};
+
+/* test_binary_delete operation */
+
+const struct sieve_operation_def test_binary_save_operation = {
+ .mnemonic = "TEST_BINARY_SAVE",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_BINARY_SAVE,
+ .dump = cmd_test_binary_operation_dump,
+ .execute = cmd_test_binary_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_binary_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "binary-name", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_binary_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ /* Emit operation */
+ if ( sieve_command_is(cmd, cmd_test_binary_load) )
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &test_binary_load_operation);
+ else if ( sieve_command_is(cmd, cmd_test_binary_save) )
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &test_binary_save_operation);
+ else
+ i_unreached();
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_binary_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "%s:", sieve_operation_mnemonic(denv->oprtn));
+
+ sieve_code_descend(denv);
+
+ return sieve_opr_string_dump(denv, address, "binary-name");
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_binary_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_operation *oprtn = renv->oprtn;
+ string_t *binary_name = NULL;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Binary Name */
+
+ if ( (ret=sieve_opr_string_read(renv, address, "binary-name", &binary_name))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if ( sieve_operation_is(oprtn, test_binary_load_operation) ) {
+ struct sieve_binary *sbin = testsuite_binary_load(str_c(binary_name));
+
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ sieve_runtime_trace(renv, 0, "testsuite: test_binary_load command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "load binary `%s'", str_c(binary_name));
+ }
+
+ if ( sbin != NULL ) {
+ testsuite_script_set_binary(renv, sbin);
+
+ sieve_binary_unref(&sbin);
+ } else {
+ e_error(testsuite_sieve_instance->event,
+ "failed to load binary %s", str_c(binary_name));
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ } else if ( sieve_operation_is(oprtn, test_binary_save_operation) ) {
+ struct sieve_binary *sbin = testsuite_script_get_binary(renv);
+
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ sieve_runtime_trace(renv, 0, "testsuite: test_binary_save command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "save binary `%s'", str_c(binary_name));
+ }
+
+ if ( sbin != NULL )
+ testsuite_binary_save(sbin, str_c(binary_name));
+ else {
+ e_error(testsuite_sieve_instance->event,
+ "no compiled binary to save as %s",
+ str_c(binary_name));
+ return SIEVE_EXEC_FAILURE;
+ }
+ } else {
+ i_unreached();
+ }
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-config.c b/pigeonhole/src/testsuite/cmd-test-config.c
new file mode 100644
index 0000000..c1399b0
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-config.c
@@ -0,0 +1,504 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-settings.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.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"
+
+#include "testsuite-common.h"
+#include "testsuite-settings.h"
+
+/*
+ * Commands
+ */
+
+static bool
+cmd_test_config_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+/* Test_config_set command
+ *
+ * Syntax:
+ * test_config_set <setting: string> <value: string>
+ */
+
+static bool
+cmd_test_config_set_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_config_set = {
+ .identifier = "test_config_set",
+ .type = SCT_COMMAND,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_config_set_validate,
+ .generate = cmd_test_config_generate
+};
+
+/* Test_config_unset command
+ *
+ * Syntax:
+ * test_config_unset <setting: string>
+ */
+
+static bool
+cmd_test_config_unset_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_config_unset = {
+ .identifier = "test_config_unset",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_config_unset_validate,
+ .generate = cmd_test_config_generate,
+};
+
+/* Test_config_reload command
+ *
+ * Syntax:
+ * test_config_reload [:extension <extension: string>]
+ */
+
+static bool
+cmd_test_config_reload_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+
+const struct sieve_command_def cmd_test_config_reload = {
+ .identifier = "test_config_reload",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_test_config_reload_registered,
+ .generate = cmd_test_config_generate
+};
+
+/*
+ * Command tags
+ */
+
+/* Forward declarations */
+
+static bool
+cmd_test_config_reload_validate_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def test_config_reload_extension_tag = {
+ .identifier = "extension",
+ .validate = cmd_test_config_reload_validate_tag,
+};
+
+/* Codes for optional arguments */
+
+enum cmd_test_config_optional {
+ OPT_END,
+ OPT_EXTENSION
+};
+
+/*
+ * Operations
+ */
+
+/* Test_config_set operation */
+
+static bool
+cmd_test_config_set_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_config_set_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_config_set_operation = {
+ .mnemonic = "TEST_CONFIG_SET",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_CONFIG_SET,
+ .dump = cmd_test_config_set_operation_dump,
+ .execute = cmd_test_config_set_operation_execute
+};
+
+/* Test_config_unset operation */
+
+static bool
+cmd_test_config_unset_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_config_unset_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_config_unset_operation = {
+ .mnemonic = "TEST_CONFIG_UNSET",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_CONFIG_UNSET,
+ .dump = cmd_test_config_unset_operation_dump,
+ .execute = cmd_test_config_unset_operation_execute
+};
+
+/* Test_config_read operation */
+
+static bool
+cmd_test_config_reload_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_config_reload_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_config_reload_operation = {
+ .mnemonic = "TEST_CONFIG_RELOAD",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_CONFIG_RELOAD,
+ .dump = cmd_test_config_reload_operation_dump,
+ .execute = cmd_test_config_reload_operation_execute
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+cmd_test_config_reload_validate_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :extension <extension: string>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, TRUE))
+ return FALSE;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool
+cmd_test_config_reload_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &test_config_reload_extension_tag,
+ OPT_EXTENSION);
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool
+cmd_test_config_set_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* Check syntax:
+ * <setting: string> <value: string>
+ */
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "setting",
+ 1, SAAT_STRING))
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "value", 2,
+ SAAT_STRING))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+static bool
+cmd_test_config_unset_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* Check syntax:
+ * <setting: string>
+ */
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "setting", 1,
+ SAAT_STRING))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_test_config_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ if (sieve_command_is(cmd, cmd_test_config_set)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_config_set_operation);
+ } else if (sieve_command_is(cmd, cmd_test_config_unset)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_config_unset_operation);
+ } else if (sieve_command_is(cmd, cmd_test_config_reload)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_config_reload_operation);
+ } else {
+ i_unreached();
+ }
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_test_config_set_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "TEST_CONFIG_SET:");
+
+ sieve_code_descend(denv);
+
+ return (sieve_opr_string_dump(denv, address, "setting") &&
+ sieve_opr_string_dump(denv, address, "value"));
+}
+
+static bool
+cmd_test_config_unset_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "TEST_CONFIG_UNSET:");
+
+ sieve_code_descend(denv);
+
+ return sieve_opr_string_dump(denv, address, "setting");
+}
+
+static bool
+cmd_test_config_reload_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "TEST_CONFIG_RELOAD:");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_EXTENSION:
+ opok = sieve_opr_string_dump(denv, address,
+ "extensions");
+ break;
+ default:
+ opok = FALSE;
+ break;
+ }
+
+ if (!opok)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Interpretation
+ */
+
+static int
+cmd_test_config_set_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ string_t *setting;
+ string_t *value;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Setting */
+ if ((ret = sieve_opr_string_read(renv, address, "setting",
+ &setting)) <= 0)
+ return ret;
+
+ /* Value */
+ if ((ret = sieve_opr_string_read(renv, address, "value",
+ &value)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_config_set command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "set config `%s' = `%s'",
+ str_c(setting), str_c(value));
+ }
+
+ testsuite_setting_set(str_c(setting), str_c(value));
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+cmd_test_config_unset_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ string_t *setting;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Setting */
+ if ((ret = sieve_opr_string_read(renv, address, "setting",
+ &setting)) <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_config_unset command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "unset config `%s'", str_c(setting));
+ }
+
+ testsuite_setting_unset(str_c(setting));
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+cmd_test_config_reload_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_extension *ext;
+ int opt_code = 0;
+ string_t *extension = NULL;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_EXTENSION:
+ ret = sieve_opr_string_read(renv, address, "extension",
+ &extension);
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ret <= 0)
+ return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_config_reload command");
+ sieve_runtime_trace_descend(renv);
+ }
+
+ if (extension == NULL) {
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0,
+ "reload configuration for sieve engine");
+ }
+
+ sieve_settings_load(eenv->svinst);
+ } else {
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0,
+ "reload configuration for extension `%s'",
+ str_c(extension));
+ }
+
+ ext = sieve_extension_get_by_name(eenv->svinst,
+ str_c(extension));
+ if (ext == NULL) {
+ return testsuite_test_failf(
+ renv, "test_config_reload: "
+ "unknown extension '%s'", str_c(extension));
+ }
+ sieve_extension_reload(ext);
+ }
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-fail.c b/pigeonhole/src/testsuite/cmd-test-fail.c
new file mode 100644
index 0000000..346c187
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-fail.c
@@ -0,0 +1,129 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.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"
+
+#include "testsuite-common.h"
+
+/*
+ * Test_fail command
+ *
+ * Syntax:
+ * test_fail <reason: string>
+ */
+
+static bool
+cmd_test_fail_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_test_fail_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_test_fail = {
+ .identifier = "test_fail",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_fail_validate,
+ .generate = cmd_test_fail_generate,
+};
+
+/*
+ * Test operation
+ */
+
+static bool
+cmd_test_fail_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_fail_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_fail_operation = {
+ .mnemonic = "TEST_FAIL",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_FAIL,
+ .dump = cmd_test_fail_operation_dump,
+ .execute = cmd_test_fail_operation_execute,
+};
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_test_fail_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "reason", 1,
+ SAAT_STRING))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_test_fail_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &test_fail_operation);
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_test_fail_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "TEST_FAIL:");
+ sieve_code_descend(denv);
+
+ if (!sieve_opr_string_dump(denv, address, "reason"))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int
+cmd_test_fail_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ string_t *reason;
+ int ret;
+
+ ret = sieve_opr_string_read(renv, address, "reason", &reason);
+ if (ret <= 0)
+ return ret;
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "testsuite: "
+ "test_fail command; FAIL current test");
+
+ return testsuite_test_fail(renv, reason);
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-imap-metadata.c b/pigeonhole/src/testsuite/cmd-test-imap-metadata.c
new file mode 100644
index 0000000..1c5e2a9
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-imap-metadata.c
@@ -0,0 +1,284 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.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"
+
+#include "testsuite-common.h"
+#include "testsuite-mailstore.h"
+
+/*
+ * Commands
+ */
+
+static bool cmd_test_imap_metadata_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool cmd_test_imap_metadata_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_imap_metadata_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+/* Test_mailbox_create command
+ *
+ * Syntax:
+ * test_imap_metadata_set
+ * <mailbox: string> <annotation: string> <value:string>
+ */
+
+const struct sieve_command_def cmd_test_imap_metadata_set = {
+ .identifier = "test_imap_metadata_set",
+ .type = SCT_COMMAND,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_test_imap_metadata_registered,
+ .validate = cmd_test_imap_metadata_validate,
+ .generate = cmd_test_imap_metadata_generate,
+};
+
+/*
+ * Command tags
+ */
+
+static bool cmd_test_imap_metadata_validate_mailbox_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_imap_metadata_mailbox_tag = {
+ .identifier = "mailbox",
+ .validate = cmd_test_imap_metadata_validate_mailbox_tag
+};
+
+/*
+ * Operations
+ */
+
+static bool cmd_test_imap_metadata_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_imap_metadata_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+/* Test_mailbox_create operation */
+
+const struct sieve_operation_def test_imap_metadata_set_operation = {
+ .mnemonic = "TEST_IMAP_METADATA_SET",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_IMAP_METADATA_SET,
+ .dump = cmd_test_imap_metadata_operation_dump,
+ .execute = cmd_test_imap_metadata_operation_execute
+};
+
+/* Codes for optional arguments */
+
+enum cmd_vacation_optional {
+ OPT_END,
+ OPT_MAILBOX
+};
+
+/*
+ * Tag validation
+ */
+
+static bool cmd_test_imap_metadata_validate_mailbox_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+
+ /* Delete this tag */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Check syntax:
+ * :mailbox string
+ */
+ if ( !sieve_validate_tag_parameter
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE) ) {
+ return FALSE;
+ }
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool cmd_test_imap_metadata_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &test_imap_metadata_mailbox_tag, OPT_MAILBOX);
+ return TRUE;
+}
+
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_imap_metadata_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "annotation", 2, SAAT_STRING) )
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "value", 3, SAAT_STRING) )
+ return FALSE;
+
+ if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_imap_metadata_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ /* Emit operation */
+ if ( sieve_command_is(cmd, cmd_test_imap_metadata_set) )
+ sieve_operation_emit
+ (cgenv->sblock, cmd->ext, &test_imap_metadata_set_operation);
+ else
+ i_unreached();
+
+ /* Generate arguments */
+ if ( !sieve_generate_arguments(cgenv, cmd, NULL) )
+ return FALSE;
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_imap_metadata_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "%s:", sieve_operation_mnemonic(denv->oprtn));
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_MAILBOX:
+ opok = sieve_opr_string_dump(denv, address, "mailbox");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if ( !opok ) return FALSE;
+ }
+
+ return
+ ( sieve_opr_string_dump(denv, address, "annotation") &&
+ sieve_opr_string_dump(denv, address, "value") );
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_imap_metadata_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ const struct sieve_operation *oprtn = renv->oprtn;
+ int opt_code = 0;
+ string_t *mailbox = NULL, *annotation = NULL, *value = NULL;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_MAILBOX:
+ ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
+ break;
+ default:
+ sieve_runtime_trace_error(renv, "unknown optional operand");
+ ret = SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( ret <= 0 ) return ret;
+ }
+
+ /* Fixed operands */
+
+ if ( (ret=sieve_opr_string_read
+ (renv, address, "annotation", &annotation)) <= 0 )
+ return ret;
+ if ( (ret=sieve_opr_string_read
+ (renv, address, "value", &value)) <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if ( sieve_operation_is(oprtn, test_imap_metadata_set_operation) ) {
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ sieve_runtime_trace(renv, 0, "testsuite/test_imap_metadata_set command");
+ sieve_runtime_trace_descend(renv);
+ if (mailbox == NULL) {
+ sieve_runtime_trace(renv, 0,
+ "set server annotation `%s'", str_c(annotation));
+ } else {
+ sieve_runtime_trace(renv, 0,
+ "set annotation `%s' for mailbox `%s'",
+ str_c(annotation), str_c(mailbox));
+ }
+ }
+
+ if (testsuite_mailstore_set_imap_metadata
+ (( mailbox == NULL ? NULL : str_c(mailbox) ),
+ str_c(annotation), str_c(value)) < 0)
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-mailbox.c b/pigeonhole/src/testsuite/cmd-test-mailbox.c
new file mode 100644
index 0000000..b5d946c
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-mailbox.c
@@ -0,0 +1,261 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.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"
+#include "sieve-actions.h"
+
+#include "testsuite-common.h"
+#include "testsuite-mailstore.h"
+
+/*
+ * Commands
+ */
+
+static bool
+cmd_test_mailbox_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_test_mailbox_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+/* Test_mailbox_create command
+ *
+ * Syntax:
+ * test_mailbox_create <mailbox: string>
+ */
+
+const struct sieve_command_def cmd_test_mailbox_create = {
+ .identifier = "test_mailbox_create",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_mailbox_validate,
+ .generate = cmd_test_mailbox_generate,
+};
+
+/* Test_mailbox_delete command
+ *
+ * Syntax:
+ * test_mailbox_create <mailbox: string>
+ */
+
+const struct sieve_command_def cmd_test_mailbox_delete = {
+ .identifier = "test_mailbox_delete",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_mailbox_validate,
+ .generate = cmd_test_mailbox_generate,
+};
+
+/*
+ * Operations
+ */
+
+static bool
+cmd_test_mailbox_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_mailbox_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+/* Test_mailbox_create operation */
+
+const struct sieve_operation_def test_mailbox_create_operation = {
+ .mnemonic = "TEST_MAILBOX_CREATE",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_MAILBOX_CREATE,
+ .dump = cmd_test_mailbox_operation_dump,
+ .execute = cmd_test_mailbox_operation_execute,
+};
+
+/* Test_mailbox_delete operation */
+
+const struct sieve_operation_def test_mailbox_delete_operation = {
+ .mnemonic = "TEST_MAILBOX_DELETE",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_MAILBOX_DELETE,
+ .dump = cmd_test_mailbox_operation_dump,
+ .execute = cmd_test_mailbox_operation_execute,
+};
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_test_mailbox_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "mailbox", 1,
+ SAAT_STRING))
+ return FALSE;
+
+ if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
+ return FALSE;
+
+ /* Check name validity when folder argument is not a variable */
+ if ( sieve_argument_is_string_literal(arg) ) {
+ const char *folder = sieve_ast_argument_strc(arg), *error;
+
+ if ( !sieve_mailbox_check_name(folder, &error) ) {
+ sieve_command_validate_error(
+ valdtr, cmd, "%s command: "
+ "invalid mailbox `%s' specified: %s",
+ sieve_command_identifier(cmd),
+ str_sanitize(folder, 256), error);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_test_mailbox_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ /* Emit operation */
+ if (sieve_command_is(cmd, cmd_test_mailbox_create)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_mailbox_create_operation);
+ } else if (sieve_command_is(cmd, cmd_test_mailbox_delete)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_mailbox_delete_operation);
+ } else {
+ i_unreached();
+ }
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_test_mailbox_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "%s:", sieve_operation_mnemonic(denv->oprtn));
+
+ sieve_code_descend(denv);
+
+ return sieve_opr_string_dump(denv, address, "mailbox");
+}
+
+/*
+ * Intepretation
+ */
+
+static const char *
+cmd_test_mailbox_get_command_name(const struct sieve_operation *oprtn)
+{
+ if (sieve_operation_is(oprtn, test_mailbox_create_operation))
+ return "test_mailbox_create";
+ if (sieve_operation_is(oprtn, test_mailbox_delete_operation))
+ return "test_mailbox_delete";
+
+ i_unreached();
+}
+
+static int
+cmd_test_mailbox_create_execute(const struct sieve_runtime_env *renv,
+ const char *mailbox)
+{
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0,
+ "testsuite/test_mailbox_create command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "create mailbox `%s'", mailbox);
+ }
+
+ testsuite_mailstore_mailbox_create(renv, mailbox);
+ return SIEVE_EXEC_OK;
+}
+
+static int
+cmd_test_mailbox_delete_execute(const struct sieve_runtime_env *renv,
+ const char *mailbox)
+{
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0,
+ "testsuite/test_mailbox_delete command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "delete mailbox `%s'", mailbox);
+ }
+
+ /* FIXME: implement */
+ return testsuite_test_failf(
+ renv, "test_mailbox_delete: NOT IMPLEMENTED");
+}
+
+static int
+cmd_test_mailbox_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_operation *oprtn = renv->oprtn;
+ string_t *mailbox = NULL;
+ const char *error;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Mailbox */
+
+ ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
+ if (ret <= 0)
+ return ret;
+
+ if (!sieve_mailbox_check_name(str_c(mailbox), &error)) {
+ sieve_runtime_error(
+ renv, NULL, "%s command: "
+ "invalid mailbox `%s' specified: %s",
+ cmd_test_mailbox_get_command_name(oprtn),
+ str_c(mailbox), error);
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ if (sieve_operation_is(oprtn, test_mailbox_create_operation))
+ ret = cmd_test_mailbox_create_execute(renv, str_c(mailbox));
+ else if (sieve_operation_is(oprtn, test_mailbox_delete_operation))
+ ret = cmd_test_mailbox_delete_execute(renv, str_c(mailbox));
+ else
+ i_unreached();
+
+ return ret;
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-message.c b/pigeonhole/src/testsuite/cmd-test-message.c
new file mode 100644
index 0000000..ff36704
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-message.c
@@ -0,0 +1,569 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "mail-storage.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-message.h"
+#include "sieve-actions.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"
+
+#include "testsuite-common.h"
+#include "testsuite-smtp.h"
+#include "testsuite-mailstore.h"
+
+/*
+ * Commands
+ */
+
+/* Test_message command
+ *
+ * Syntax:
+ * test_message ( :smtp / :mailbox <mailbox: string> ) <index: number>
+ */
+
+static bool
+cmd_test_message_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+cmd_test_message_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_test_message_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_test_message = {
+ .identifier = "test_message",
+ .type = SCT_HYBRID,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_test_message_registered,
+ .validate = cmd_test_message_validate,
+ .generate = cmd_test_message_generate,
+};
+
+/* Test_message_print command
+ *
+ * Syntax:
+ * test_message_print
+ */
+
+static bool
+cmd_test_message_print_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_message_print = {
+ .identifier = "test_message_print",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = cmd_test_message_print_generate,
+};
+
+/*
+ * Operations
+ */
+
+/* Test_message_smtp operation */
+
+static bool
+cmd_test_message_smtp_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_message_smtp_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_message_smtp_operation = {
+ .mnemonic = "TEST_MESSAGE_SMTP",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_MESSAGE_SMTP,
+ .dump = cmd_test_message_smtp_operation_dump,
+ .execute = cmd_test_message_smtp_operation_execute,
+};
+
+/* Test_message_mailbox operation */
+
+static bool
+cmd_test_message_mailbox_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_message_mailbox_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_message_mailbox_operation = {
+ .mnemonic = "TEST_MESSAGE_MAILBOX",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_MESSAGE_MAILBOX,
+ .dump = cmd_test_message_mailbox_operation_dump,
+ .execute = cmd_test_message_mailbox_operation_execute,
+};
+
+/* Test_message_print operation */
+
+static bool
+cmd_test_message_print_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_message_print_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_message_print_operation = {
+ .mnemonic = "TEST_MESSAGE_PRINT",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_MESSAGE_PRINT,
+ .dump = cmd_test_message_print_operation_dump,
+ .execute = cmd_test_message_print_operation_execute,
+};
+
+/*
+ * Compiler context data
+ */
+
+enum test_message_source {
+ MSG_SOURCE_SMTP,
+ MSG_SOURCE_MAILBOX,
+ MSG_SOURCE_LAST,
+};
+
+const struct sieve_operation_def *test_message_operations[] = {
+ &test_message_smtp_operation,
+ &test_message_mailbox_operation,
+};
+
+struct cmd_test_message_context_data {
+ enum test_message_source msg_source;
+ const char *folder;
+};
+
+#define CMD_TEST_MESSAGE_ERROR_DUP_TAG \
+ "exactly one of the ':smtp' or ':folder' tags must be specified " \
+ "for the test_message command, but more were found"
+
+/*
+ * Command tags
+ */
+
+static bool
+cmd_test_message_validate_smtp_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_test_message_validate_folder_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_message_smtp_tag = {
+ .identifier = "smtp",
+ .validate = cmd_test_message_validate_smtp_tag,
+};
+
+static const struct sieve_argument_def test_message_folder_tag = {
+ .identifier = "folder",
+ .validate = cmd_test_message_validate_folder_tag,
+};
+
+static bool
+cmd_test_message_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* Register our tags */
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &test_message_folder_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &test_message_smtp_tag, 0);
+ return TRUE;
+}
+
+static struct cmd_test_message_context_data *
+cmd_test_message_validate_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_test_message_context_data *ctx_data =
+ (struct cmd_test_message_context_data *)cmd->data;
+
+ if (ctx_data != NULL) {
+ sieve_argument_validate_error(valdtr, *arg,
+ CMD_TEST_MESSAGE_ERROR_DUP_TAG);
+ return NULL;
+ }
+
+ ctx_data = p_new(sieve_command_pool(cmd),
+ struct cmd_test_message_context_data, 1);
+ cmd->data = ctx_data;
+
+ /* Delete this tag */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ return ctx_data;
+}
+
+static bool
+cmd_test_message_validate_smtp_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct cmd_test_message_context_data *ctx_data =
+ cmd_test_message_validate_tag(valdtr, arg, cmd);
+
+ /* Return value is NULL on error */
+ if (ctx_data == NULL)
+ return FALSE;
+
+ /* Assign chosen message source */
+ ctx_data->msg_source = MSG_SOURCE_SMTP;
+
+ return TRUE;
+}
+
+static bool
+cmd_test_message_validate_folder_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_test_message_context_data *ctx_data =
+ cmd_test_message_validate_tag(valdtr, arg, cmd);
+
+ /* Return value is NULL on error */
+ if (ctx_data == NULL)
+ return FALSE;
+
+ /* Assign chose message source */
+ ctx_data->msg_source = MSG_SOURCE_MAILBOX;
+
+ /* Check syntax:
+ * :folder string
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE)) {
+ return FALSE;
+ }
+
+ /* Check name validity when folder argument is not a variable */
+ if ( sieve_argument_is_string_literal(*arg) ) {
+ const char *folder = sieve_ast_argument_strc(*arg), *error;
+
+ if ( !sieve_mailbox_check_name(folder, &error) ) {
+ sieve_command_validate_error(
+ valdtr, cmd, "%s command: "
+ "invalid mailbox `%s' specified: %s",
+ sieve_command_identifier(cmd),
+ str_sanitize(folder, 256), error);
+ return FALSE;
+ }
+ }
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_test_message_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ if (cmd->data == NULL) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the test_message command requires either "
+ "the :smtp or the :mailbox tag to be specified");
+ return FALSE;
+ }
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "index", 1,
+ SAAT_NUMBER))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_test_message_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ struct cmd_test_message_context_data *ctx_data =
+ (struct cmd_test_message_context_data *)cmd->data;
+
+ i_assert(ctx_data->msg_source < MSG_SOURCE_LAST);
+
+ /* Emit operation */
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ test_message_operations[ctx_data->msg_source]);
+
+ /* Emit is_test flag */
+ sieve_binary_emit_byte(cgenv->sblock,
+ (cmd->ast_node->type == SAT_TEST ? 1 : 0));
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+static bool
+cmd_test_message_print_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ /* Emit operation */
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_message_print_operation);
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_test_message_smtp_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ unsigned int is_test;
+
+ if (!sieve_binary_read_byte(denv->sblock, address, &is_test))
+ return FALSE;
+
+ sieve_code_dumpf(denv, "TEST_MESSAGE_SMTP (%s):",
+ (is_test > 0 ? "TEST" : "COMMAND"));
+
+ sieve_code_descend(denv);
+
+ return sieve_opr_number_dump(denv, address, "index");
+}
+
+static bool
+cmd_test_message_mailbox_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ unsigned int is_test;
+
+ if (!sieve_binary_read_byte(denv->sblock, address, &is_test))
+ return FALSE;
+
+ sieve_code_dumpf(denv, "TEST_MESSAGE_MAILBOX (%s):",
+ (is_test > 0 ? "TEST" : "COMMAND"));
+
+ sieve_code_descend(denv);
+
+ return (sieve_opr_string_dump(denv, address, "folder") &&
+ sieve_opr_number_dump(denv, address, "index"));
+}
+
+static bool
+cmd_test_message_print_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_code_dumpf(denv, "TEST_MESSAGE_PRINT");
+
+ return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int
+cmd_test_message_smtp_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ sieve_number_t msg_index;
+ unsigned int is_test = 0;
+ bool result;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Is test */
+
+ if (!sieve_binary_read_byte(renv->sblock, address, &is_test)) {
+ sieve_runtime_trace_error(renv, "invalid is_test flag");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Index */
+
+ ret = sieve_opr_number_read(renv, address, "index", &msg_index);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if (is_test > 0) {
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_message test");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0,
+ "check and retrieve smtp message [index=%llu]",
+ (unsigned long long)msg_index);
+ }
+ } else {
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_message command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0,
+ "retrieve smtp message [index=%llu]",
+ (unsigned long long)msg_index);
+ }
+ }
+
+ result = testsuite_smtp_get(renv, msg_index);
+
+ if (is_test > 0) {
+ sieve_interpreter_set_test_result(renv->interp, result);
+ return SIEVE_EXEC_OK;
+ }
+
+ if (!result) {
+ return testsuite_test_failf(
+ renv, "no outgoing SMTP message with index %llu",
+ (unsigned long long)msg_index);
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+cmd_test_message_mailbox_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ string_t *folder;
+ sieve_number_t msg_index;
+ unsigned int is_test = 0;
+ bool result;
+ const char *error;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Is test */
+ if (!sieve_binary_read_byte(renv->sblock, address, &is_test)) {
+ sieve_runtime_trace_error(renv, "invalid is_test flag");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ /* Folder */
+ ret = sieve_opr_string_read(renv, address, "folder", &folder);
+ if (ret <= 0)
+ return ret;
+
+ /* Index */
+ ret = sieve_opr_number_read(renv, address, "index", &msg_index);
+ if (ret <= 0)
+ return ret;
+
+ if (!sieve_mailbox_check_name(str_c(folder), &error)) {
+ return testsuite_test_failf(
+ renv, "invalid mailbox `%s' specified: %s",
+ str_c(folder), error);
+ }
+
+ /*
+ * Perform operation
+ */
+
+ if (is_test > 0) {
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_message test");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "check and retrieve mailbox message "
+ "[mailbox=`%s' index=%llu]",
+ str_c(folder), (unsigned long long)msg_index);
+ }
+ } else {
+ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
+ sieve_runtime_trace(
+ renv, 0, "testsuite: test_message command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(
+ renv, 0, "retrieve mailbox message "
+ "[mailbox=`%s' index=%llu]",
+ str_c(folder), (unsigned long long)msg_index);
+ }
+ }
+
+ result = testsuite_mailstore_mail_index(renv, str_c(folder), msg_index);
+
+ if (is_test > 0) {
+ sieve_interpreter_set_test_result(renv->interp, result);
+ return SIEVE_EXEC_OK;
+ }
+
+ if (!result) {
+ return testsuite_test_failf(
+ renv, "no message in folder '%s' with index %llu",
+ str_c(folder), (unsigned long long)msg_index);
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+cmd_test_message_print_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ struct mail *mail = sieve_message_get_mail(renv->msgctx);
+ struct istream *input;
+ const unsigned char *data;
+ size_t size;
+
+ if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
+ sieve_runtime_error(renv, NULL, "test_message_print: "
+ "failed to read current message");
+ return SIEVE_EXEC_OK;
+ }
+
+ printf("\n--MESSAGE: \n");
+
+ /* Pipe the message to the outgoing SMTP transport */
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ ssize_t wret;
+
+ wret = write(1, data, size);
+ if (wret <= 0)
+ break;
+ i_stream_skip(input, wret);
+ }
+ printf("\n--MESSAGE--\n");
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-result.c b/pigeonhole/src/testsuite/cmd-test-result.c
new file mode 100644
index 0000000..230aa62
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-result.c
@@ -0,0 +1,138 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.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"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+#include "testsuite-message.h"
+#include "testsuite-smtp.h"
+
+/*
+ * Commands
+ */
+
+static bool
+cmd_test_result_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd);
+
+/* Test_result_reset command
+ *
+ * Syntax:
+ * test_result_reset
+ */
+
+const struct sieve_command_def cmd_test_result_reset = {
+ .identifier = "test_result_reset",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = cmd_test_result_generate,
+};
+
+/* Test_result_print command
+ *
+ * Syntax:
+ * test_result_print
+ */
+
+const struct sieve_command_def cmd_test_result_print = {
+ .identifier = "test_result_print",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = cmd_test_result_generate,
+};
+
+/*
+ * Operations
+ */
+
+/* test_result_reset */
+
+static int
+cmd_test_result_reset_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_result_reset_operation = {
+ .mnemonic = "TEST_RESULT_RESET",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_RESULT_RESET,
+ .execute = cmd_test_result_reset_operation_execute,
+};
+
+/* test_result_print */
+
+static int
+cmd_test_result_print_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_result_print_operation = {
+ .mnemonic = "TEST_RESULT_PRINT",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_RESULT_PRINT,
+ .execute = cmd_test_result_print_operation_execute,
+};
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_test_result_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ if (sieve_command_is(cmd, cmd_test_result_reset)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_result_reset_operation);
+ } else if (sieve_command_is(cmd, cmd_test_result_print)) {
+ sieve_operation_emit(cgenv->sblock, cmd->ext,
+ &test_result_print_operation);
+ } else {
+ i_unreached();
+ }
+
+ return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int
+cmd_test_result_reset_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "testsuite: "
+ "test_result_reset command; reset script result");
+
+ testsuite_result_reset(renv);
+ testsuite_smtp_reset();
+
+ return SIEVE_EXEC_OK;
+}
+
+static int
+cmd_test_result_print_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "testsuite: "
+ "test_result_print command; print script result ");
+
+ testsuite_result_print(renv);
+
+ return SIEVE_EXEC_OK;
+}
diff --git a/pigeonhole/src/testsuite/cmd-test-set.c b/pigeonhole/src/testsuite/cmd-test-set.c
new file mode 100644
index 0000000..22b51d2
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test-set.c
@@ -0,0 +1,161 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "istream.h"
+#include "istream-header-filter.h"
+
+#include "sieve-common.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-code-dumper.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-objects.h"
+
+#include <stdio.h>
+
+/*
+ * Test_set command
+ *
+ * Syntax
+ * test_set <testsuite object (member): string> <value: string>
+ */
+
+static bool cmd_test_set_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool cmd_test_set_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def cmd_test_set = {
+ .identifier = "test_set",
+ .type = SCT_COMMAND,
+ .positional_args = 2,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = cmd_test_set_validate,
+ .generate = cmd_test_set_generate
+};
+
+/*
+ * Test_set operation
+ */
+
+static bool cmd_test_set_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int cmd_test_set_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_set_operation = {
+ .mnemonic = "TEST_SET",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_SET,
+ .dump = cmd_test_set_operation_dump,
+ .execute = cmd_test_set_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool cmd_test_set_validate
+(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* Check arguments */
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "object", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ if ( !testsuite_object_argument_activate(valdtr, arg, cmd) )
+ return FALSE;
+
+ arg = sieve_ast_argument_next(arg);
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, cmd, arg, "value", 2, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool cmd_test_set_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &test_set_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool cmd_test_set_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "TEST SET:");
+ sieve_code_descend(denv);
+
+ return
+ testsuite_object_dump(denv, address) &&
+ sieve_opr_string_dump(denv, address, "value");
+}
+
+/*
+ * Intepretation
+ */
+
+static int cmd_test_set_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct testsuite_object tobj;
+ string_t *value;
+ int member_id;
+ int ret;
+
+ if ( !testsuite_object_read_member
+ (renv->sblock, address, &tobj, &member_id) ) {
+ sieve_runtime_trace_error(renv, "invalid testsuite object member");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( (ret=sieve_opr_string_read(renv, address, "string", &value)) <= 0 )
+ return ret;
+
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS) ) {
+ sieve_runtime_trace(renv, 0, "testsuite: test_set command");
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0,
+ "set test parameter '%s' = \"%s\"",
+ testsuite_object_member_name(&tobj, member_id), str_c(value));
+ }
+
+ if ( tobj.def == NULL || tobj.def->set_member == NULL ) {
+ sieve_runtime_trace_error(renv, "unimplemented testsuite object");
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ tobj.def->set_member(renv, member_id, value);
+ return SIEVE_EXEC_OK;
+}
+
+
+
diff --git a/pigeonhole/src/testsuite/cmd-test.c b/pigeonhole/src/testsuite/cmd-test.c
new file mode 100644
index 0000000..c817aed
--- /dev/null
+++ b/pigeonhole/src/testsuite/cmd-test.c
@@ -0,0 +1,208 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-commands.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"
+
+#include "testsuite-common.h"
+
+/*
+ * Test command
+ *
+ * Syntax:
+ * test <test-name: string> <block>
+ */
+
+static bool
+cmd_test_validate(struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool
+cmd_test_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *md);
+
+const struct sieve_command_def cmd_test = {
+ .identifier = "test",
+ .type = SCT_COMMAND,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = TRUE,
+ .block_required = TRUE,
+ .validate = cmd_test_validate,
+ .generate = cmd_test_generate,
+};
+
+/*
+ * Test operations
+ */
+
+/* Test operation */
+
+static bool
+cmd_test_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_test_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_operation = {
+ .mnemonic = "TEST",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST,
+ .dump = cmd_test_operation_dump,
+ .execute = cmd_test_operation_execute,
+};
+
+/* Test_finish operation */
+
+static int
+cmd_test_finish_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def test_finish_operation = {
+ .mnemonic = "TEST-FINISH",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_FINISH,
+ .execute = cmd_test_finish_operation_execute,
+};
+
+/*
+ * Validation
+ */
+
+static bool
+cmd_test_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *arg = cmd->first_positional;
+
+ /* Check valid command placement */
+ if (!sieve_command_is_toplevel(cmd)) {
+ sieve_command_validate_error(
+ valdtr, cmd, "tests cannot be nested: test "
+ "command must be issued at top-level");
+ return FALSE;
+ }
+
+ if (!sieve_validate_positional_argument(valdtr, cmd, arg, "test-name",
+ 1, SAAT_STRING))
+ return FALSE;
+
+ return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
+}
+
+/*
+ * 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_ext);
+}
+
+static bool
+cmd_test_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ struct testsuite_generator_context *genctx =
+ _get_generator_context(cgenv->gentr);
+
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &test_operation);
+
+ /* Generate arguments */
+ if (!sieve_generate_arguments(cgenv, cmd, NULL))
+ return FALSE;
+
+ /* Prepare jumplist */
+ sieve_jumplist_reset(genctx->exit_jumps);
+ sieve_jumplist_add(genctx->exit_jumps,
+ sieve_binary_emit_offset(cgenv->sblock, 0));
+
+ /* Test body */
+ if (!sieve_generate_block(cgenv, cmd->ast_node))
+ return FALSE;
+
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &test_finish_operation);
+
+ /* Resolve exit jumps to this point */
+ sieve_jumplist_resolve(genctx->exit_jumps);
+
+ return TRUE;
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_test_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ sieve_size_t tst_begin;
+ sieve_offset_t tst_end_offset;
+
+ sieve_code_dumpf(denv, "TEST:");
+ sieve_code_descend(denv);
+
+ if (!sieve_opr_string_dump(denv, address, "test name"))
+ return FALSE;
+
+ sieve_code_mark(denv);
+ tst_begin = *address;
+ if (!sieve_binary_read_offset(denv->sblock, address, &tst_end_offset))
+ return FALSE;
+ sieve_code_dumpf(denv, "end: %d [%08llx]",
+ tst_end_offset,
+ (unsigned long long)tst_begin + tst_end_offset);
+
+ return TRUE;
+}
+
+/*
+ * Interpretation
+ */
+
+static int
+cmd_test_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ sieve_size_t tst_begin, tst_end;
+ sieve_offset_t tst_end_offset;
+ string_t *test_name;
+ int ret;
+
+ ret = sieve_opr_string_read(renv, address, "test name", &test_name);
+ if (ret <= 0)
+ return ret;
+
+ tst_begin = *address;
+ if (!sieve_binary_read_offset(renv->sblock, address, &tst_end_offset)) {
+ sieve_runtime_trace_error(renv, "invalid end offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ tst_end = tst_begin + tst_end_offset;
+
+ sieve_runtime_trace_sep(renv);
+ sieve_runtime_trace(renv, SIEVE_TRLVL_NONE,
+ "** Testsuite test start: \"%s\" (end: %08llx)",
+ str_c(test_name),
+ (unsigned long long)tst_end);
+
+ return testsuite_test_start(renv, test_name, tst_end);
+}
+
+static int
+cmd_test_finish_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ sieve_runtime_trace(renv, SIEVE_TRLVL_NONE, "** Testsuite test end");
+ sieve_runtime_trace_sep(renv);
+
+ return testsuite_test_succeed(renv, address, NULL);
+}
diff --git a/pigeonhole/src/testsuite/ext-testsuite.c b/pigeonhole/src/testsuite/ext-testsuite.c
new file mode 100644
index 0000000..cb9c92f
--- /dev/null
+++ b/pigeonhole/src/testsuite/ext-testsuite.c
@@ -0,0 +1,175 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+/* Extension testsuite
+ * -------------------
+ *
+ * Authors: Stephan Bosch
+ * Specification: vendor-specific
+ * (FIXME: provide specification for test authors)
+ *
+ */
+
+/*
+ * Purpose: This custom extension is used to add sieve commands and tests that
+ * act the Sieve engine and on the test suite itself. This practically
+ * provides the means to completely control and thereby test the Sieve
+ * compiler and interpreter. This extension transforms the basic Sieve
+ * language into something much more powerful and suitable to perform
+ * complex self-test operations. Of course, this extension is only
+ * available (as vnd.dovecot.testsuite) when the sieve engine is used
+ * from within the testsuite commandline tool. Test scripts have the
+ * extension .svtest by convention to distinguish them from any normal
+ * sieve scripts that may reside in the same directory.
+ *
+ * WARNING: Although this code can serve as an example on how to write
+ * extensions to the Sieve interpreter, it is generally _NOT_ to be
+ * used as a source for ideas on new Sieve extensions. Many of the
+ * commands and tests that this extension introduces conflict with the
+ * goal and the implied restrictions of the Sieve language. These
+ * restrictions were put in place with good reason. Therefore, do
+ * _NOT_ export functionality provided by this testsuite extension to
+ * your custom extensions that are to be put to general use.
+ */
+
+#include <stdio.h>
+
+#include "sieve-common.h"
+
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-variables.h"
+#include "testsuite-arguments.h"
+
+/*
+ * Operations
+ */
+
+const struct sieve_operation_def *testsuite_operations[] = {
+ &test_operation,
+ &test_finish_operation,
+ &test_fail_operation,
+ &test_config_set_operation,
+ &test_config_unset_operation,
+ &test_config_reload_operation,
+ &test_set_operation,
+ &test_script_compile_operation,
+ &test_script_run_operation,
+ &test_multiscript_operation,
+ &test_error_operation,
+ &test_result_action_operation,
+ &test_result_execute_operation,
+ &test_result_reset_operation,
+ &test_result_print_operation,
+ &test_message_smtp_operation,
+ &test_message_mailbox_operation,
+ &test_message_print_operation,
+ &test_mailbox_create_operation,
+ &test_mailbox_delete_operation,
+ &test_binary_load_operation,
+ &test_binary_save_operation,
+ &test_imap_metadata_set_operation
+};
+
+/*
+ * Operands
+ */
+
+const struct sieve_operand_def *testsuite_operands[] = {
+ &testsuite_object_operand,
+ &testsuite_substitution_operand,
+ &testsuite_namespace_operand
+};
+
+/*
+ * Extension
+ */
+
+/* Forward declarations */
+
+static bool ext_testsuite_validator_load
+ (const struct sieve_extension *ext, struct sieve_validator *valdtr);
+static bool ext_testsuite_generator_load
+ (const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv);
+static bool ext_testsuite_interpreter_load
+ (const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+static bool ext_testsuite_binary_load
+ (const struct sieve_extension *ext, struct sieve_binary *sbin);
+
+/* Extension object */
+
+const struct sieve_extension_def testsuite_extension = {
+ .name = "vnd.dovecot.testsuite",
+ .validator_load = ext_testsuite_validator_load,
+ .generator_load = ext_testsuite_generator_load,
+ .interpreter_load = ext_testsuite_interpreter_load,
+ .binary_load = ext_testsuite_binary_load,
+ SIEVE_EXT_DEFINE_OPERATIONS(testsuite_operations),
+ SIEVE_EXT_DEFINE_OPERANDS(testsuite_operands)
+};
+
+/* Extension implementation */
+
+static bool ext_testsuite_validator_load
+(const struct sieve_extension *ext, struct sieve_validator *valdtr)
+{
+ sieve_validator_register_command(valdtr, ext, &cmd_test);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_fail);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_config_set);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_config_unset);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_config_reload);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_set);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_result_print);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_result_reset);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_message);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_message_print);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_mailbox_create);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_mailbox_delete);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_binary_load);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_binary_save);
+ sieve_validator_register_command(valdtr, ext, &cmd_test_imap_metadata_set);
+
+ sieve_validator_register_command(valdtr, ext, &tst_test_script_compile);
+ sieve_validator_register_command(valdtr, ext, &tst_test_script_run);
+ sieve_validator_register_command(valdtr, ext, &tst_test_multiscript);
+ sieve_validator_register_command(valdtr, ext, &tst_test_error);
+ sieve_validator_register_command(valdtr, ext, &tst_test_result_action);
+ sieve_validator_register_command(valdtr, ext, &tst_test_result_execute);
+
+/* sieve_validator_argument_override(valdtr, SAT_VAR_STRING, ext,
+ &testsuite_string_argument);*/
+
+ testsuite_variables_init(ext, valdtr);
+
+ return testsuite_validator_context_initialize(valdtr);
+}
+
+static bool ext_testsuite_generator_load
+(const struct sieve_extension *ext, const struct sieve_codegen_env *cgenv)
+{
+ return testsuite_generator_context_initialize(cgenv->gentr, ext);
+}
+
+static bool ext_testsuite_interpreter_load
+(const struct sieve_extension *ext, const struct sieve_runtime_env *renv,
+ sieve_size_t *address ATTR_UNUSED)
+{
+ return testsuite_interpreter_context_initialize(renv->interp, ext);
+}
+
+static bool ext_testsuite_binary_load
+(const struct sieve_extension *ext ATTR_UNUSED, struct sieve_binary *sbin ATTR_UNUSED)
+{
+ return TRUE;
+}
+
+
+
diff --git a/pigeonhole/src/testsuite/testsuite-arguments.c b/pigeonhole/src/testsuite/testsuite-arguments.c
new file mode 100644
index 0000000..f1e17f4
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-arguments.c
@@ -0,0 +1,190 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-commands.h"
+#include "sieve-code.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-substitutions.h"
+#include "testsuite-arguments.h"
+
+#include <ctype.h>
+
+/*
+ * Testsuite string argument
+ */
+
+static bool arg_testsuite_string_validate
+ (struct sieve_validator *validator, struct sieve_ast_argument **arg,
+ struct sieve_command *context);
+
+const struct sieve_argument_def testsuite_string_argument = {
+ .identifier = "@testsuite-string",
+ .validate = arg_testsuite_string_validate,
+ .generate = sieve_arg_catenated_string_generate,
+};
+
+static bool arg_testsuite_string_validate
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ enum { ST_NONE, ST_OPEN, ST_SUBSTITUTION, ST_PARAM, ST_CLOSE } state =
+ ST_NONE;
+ pool_t pool = sieve_ast_pool((*arg)->ast);
+ struct sieve_arg_catenated_string *catstr = NULL;
+ string_t *str = sieve_ast_argument_str(*arg);
+ const char *p, *strstart, *substart = NULL;
+ const char *strval = (const char *) str_data(str);
+ const char *strend = strval + str_len(str);
+ bool result = TRUE;
+ string_t *subs_name = t_str_new(256);
+ string_t *subs_param = t_str_new(256);
+
+ T_BEGIN {
+ /* Initialize substitution structure */
+
+ p = strval;
+ strstart = p;
+ while ( result && p < strend ) {
+ switch ( state ) {
+
+ /* Nothing found yet */
+ case ST_NONE:
+ if ( *p == '%' ) {
+ substart = p;
+ state = ST_OPEN;
+ str_truncate(subs_name, 0);
+ str_truncate(subs_param, 0);
+ }
+ p++;
+ break;
+
+ /* Got '%' */
+ case ST_OPEN:
+ if ( *p == '{' ) {
+ state = ST_SUBSTITUTION;
+ p++;
+ } else
+ state = ST_NONE;
+ break;
+
+ /* Got '%{' */
+ case ST_SUBSTITUTION:
+ state = ST_PARAM;
+
+ while ( *p != '}' && *p != ':' ) {
+ if ( !i_isalnum(*p) ) {
+ state = ST_NONE;
+ break;
+ }
+ str_append_c(subs_name, *p);
+ p++;
+ }
+ break;
+
+ /* Got '%{name' */
+ case ST_PARAM:
+ if ( *p == ':' ) {
+ p++;
+ while ( *p != '}' ) {
+ str_append_c(subs_param, *p);
+ p++;
+ }
+ }
+ state = ST_CLOSE;
+ break;
+
+ /* Finished parsing param, expecting '}' */
+ case ST_CLOSE:
+ if ( *p == '}' ) {
+ struct sieve_ast_argument *strarg;
+
+ /* We now know that the substitution is valid */
+
+ if ( catstr == NULL ) {
+ catstr = sieve_arg_catenated_string_create(*arg);
+ }
+
+ /* Add the substring that is before the substitution to the
+ * variable-string AST.
+ */
+ if ( substart > strstart ) {
+ string_t *newstr = str_new(pool, substart - strstart);
+ str_append_data(newstr, strstart, substart - strstart);
+
+ strarg = sieve_ast_argument_string_create_raw
+ ((*arg)->ast, newstr, (*arg)->source_line);
+ sieve_arg_catenated_string_add_element(catstr, strarg);
+
+ /* Give other substitution extensions a chance to do their work */
+ if ( !sieve_validator_argument_activate_super
+ (valdtr, cmd, strarg, FALSE) ) {
+ result = FALSE;
+ break;
+ }
+ }
+
+ strarg = testsuite_substitution_argument_create
+ (valdtr, (*arg)->ast, (*arg)->source_line, str_c(subs_name),
+ str_c(subs_param));
+
+ if ( strarg != NULL )
+ sieve_arg_catenated_string_add_element(catstr, strarg);
+ else {
+ sieve_argument_validate_error(valdtr, *arg,
+ "unknown testsuite substitution type '%s'", str_c(subs_name));
+ }
+
+ strstart = p + 1;
+ substart = strstart;
+
+ p++;
+ }
+
+ /* Finished, reset for the next substitution */
+ state = ST_NONE;
+ }
+ }
+ } T_END;
+
+ /* Bail out early if substitution is invalid */
+ if ( !result ) return FALSE;
+
+ /* Check whether any substitutions were found */
+ if ( catstr == NULL ) {
+ /* No substitutions in this string, pass it on to any other substution
+ * extension.
+ */
+ return sieve_validator_argument_activate_super(valdtr, cmd, *arg, TRUE);
+ }
+
+ /* Add the final substring that comes after the last substitution to the
+ * variable-string AST.
+ */
+ if ( strend > strstart ) {
+ struct sieve_ast_argument *strarg;
+ string_t *newstr = str_new(pool, strend - strstart);
+ str_append_data(newstr, strstart, strend - strstart);
+
+ strarg = sieve_ast_argument_string_create_raw
+ ((*arg)->ast, newstr, (*arg)->source_line);
+ sieve_arg_catenated_string_add_element(catstr, strarg);
+
+ /* Give other substitution extensions a chance to do their work */
+ if ( !sieve_validator_argument_activate_super
+ (valdtr, cmd, strarg, FALSE) )
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-arguments.h b/pigeonhole/src/testsuite/testsuite-arguments.h
new file mode 100644
index 0000000..22ab82b
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-arguments.h
@@ -0,0 +1,6 @@
+#ifndef TESTSUITE_ARGUMENTS_H
+#define TESTSUITE_ARGUMENTS_H
+
+extern const struct sieve_argument_def testsuite_string_argument;
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-binary.c b/pigeonhole/src/testsuite/testsuite-binary.c
new file mode 100644
index 0000000..6c875a7
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-binary.c
@@ -0,0 +1,82 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "imem.h"
+#include "array.h"
+#include "strfuncs.h"
+#include "unlink-directory.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-error.h"
+
+#include "testsuite-common.h"
+#include "testsuite-binary.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/*
+ * State
+ */
+
+static char *testsuite_binary_tmp = NULL;
+
+/*
+ * Initialization
+ */
+
+void testsuite_binary_init(void)
+{
+ testsuite_binary_tmp = i_strconcat
+ (testsuite_tmp_dir_get(), "/binaries", NULL);
+
+ if ( mkdir(testsuite_binary_tmp, 0700) < 0 ) {
+ i_fatal("failed to create temporary directory '%s': %m.",
+ testsuite_binary_tmp);
+ }
+}
+
+void testsuite_binary_deinit(void)
+{
+ const char *error;
+
+ if ( unlink_directory(testsuite_binary_tmp, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0 ) {
+ i_warning("failed to remove temporary directory '%s': %s.",
+ testsuite_binary_tmp, error);
+ }
+
+ i_free(testsuite_binary_tmp);
+}
+
+void testsuite_binary_reset(void)
+{
+ testsuite_binary_init();
+ testsuite_binary_deinit();
+}
+
+/*
+ * Binary Access
+ */
+
+bool testsuite_binary_save(struct sieve_binary *sbin, const char *name)
+{
+ return ( sieve_save_as(sbin, t_strdup_printf
+ ("%s/%s", testsuite_binary_tmp, sieve_binfile_from_name(name)), TRUE,
+ 0600, NULL) > 0 );
+}
+
+struct sieve_binary *testsuite_binary_load(const char *name)
+{
+ struct sieve_instance *svinst = testsuite_sieve_instance;
+
+ return sieve_load(svinst, t_strdup_printf
+ ("%s/%s", testsuite_binary_tmp, sieve_binfile_from_name(name)), NULL);
+}
+
+
+
diff --git a/pigeonhole/src/testsuite/testsuite-binary.h b/pigeonhole/src/testsuite/testsuite-binary.h
new file mode 100644
index 0000000..0a09d2b
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-binary.h
@@ -0,0 +1,17 @@
+#ifndef TESTSUITE_BINARY_H
+#define TESTSUITE_BINARY_H
+
+#include "sieve-common.h"
+
+void testsuite_binary_init(void);
+void testsuite_binary_deinit(void);
+void testsuite_binary_reset(void);
+
+/*
+ * Binary Access
+ */
+
+bool testsuite_binary_save(struct sieve_binary *sbin, const char *name);
+struct sieve_binary *testsuite_binary_load(const char *name);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-common.c b/pigeonhole/src/testsuite/testsuite-common.c
new file mode 100644
index 0000000..269deee
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-common.c
@@ -0,0 +1,374 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "string.h"
+#include "ostream.h"
+#include "hash.h"
+#include "mail-storage.h"
+#include "env-util.h"
+#include "unlink-directory.h"
+
+#include "mail-raw.h"
+
+#include "sieve-common.h"
+#include "sieve-code.h"
+#include "sieve-message.h"
+#include "sieve-commands.h"
+#include "sieve-extensions.h"
+#include "sieve-binary.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-settings.h"
+#include "testsuite-objects.h"
+#include "testsuite-log.h"
+#include "testsuite-script.h"
+#include "testsuite-binary.h"
+#include "testsuite-result.h"
+#include "testsuite-smtp.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+/*
+ * Global data
+ */
+
+struct sieve_instance *testsuite_sieve_instance = NULL;
+char *testsuite_test_path = NULL;
+
+/* Test context */
+
+static string_t *test_name;
+static sieve_size_t test_block_end;
+static unsigned int test_index;
+static unsigned int test_failures;
+
+/* Extension */
+
+const struct sieve_extension *testsuite_ext;
+
+/*
+ * Validator context
+ */
+
+bool testsuite_validator_context_initialize(struct sieve_validator *valdtr)
+{
+ pool_t pool = sieve_validator_pool(valdtr);
+ struct testsuite_validator_context *ctx =
+ p_new(pool, struct testsuite_validator_context, 1);
+
+ /* Setup object registry */
+ ctx->object_registrations =
+ sieve_validator_object_registry_create(valdtr);
+ testsuite_register_core_objects(ctx);
+
+ sieve_validator_extension_set_context(valdtr, testsuite_ext, ctx);
+
+ return TRUE;
+}
+
+struct testsuite_validator_context *
+testsuite_validator_context_get(struct sieve_validator *valdtr)
+{
+ return (struct testsuite_validator_context *)
+ sieve_validator_extension_get_context(valdtr, testsuite_ext);
+}
+
+/*
+ * Generator context
+ */
+
+bool testsuite_generator_context_initialize(
+ struct sieve_generator *gentr, const struct sieve_extension *this_ext)
+{
+ pool_t pool = sieve_generator_pool(gentr);
+ struct sieve_binary_block *sblock = sieve_generator_get_block(gentr);
+ struct testsuite_generator_context *ctx =
+ p_new(pool, struct testsuite_generator_context, 1);
+
+ /* Setup exit jumplist */
+ ctx->exit_jumps = sieve_jumplist_create(pool, sblock);
+
+ sieve_generator_extension_set_context(gentr, this_ext, ctx);
+
+ return TRUE;
+}
+
+/*
+ * Interpreter context
+ */
+
+static void
+testsuite_interpreter_free(const struct sieve_extension *ext ATTR_UNUSED,
+ struct sieve_interpreter *interp ATTR_UNUSED,
+ void *context)
+{
+ struct testsuite_interpreter_context *ctx =
+ (struct testsuite_interpreter_context *)context;
+
+ sieve_binary_unref(&ctx->compiled_script);
+}
+
+const struct sieve_interpreter_extension testsuite_interpreter_ext = {
+ .ext_def = &testsuite_extension,
+ .free = testsuite_interpreter_free,
+};
+
+bool testsuite_interpreter_context_initialize(
+ struct sieve_interpreter *interp, const struct sieve_extension *this_ext)
+{
+ pool_t pool = sieve_interpreter_pool(interp);
+ struct testsuite_interpreter_context *ctx =
+ p_new(pool, struct testsuite_interpreter_context, 1);
+
+ sieve_interpreter_extension_register(interp, this_ext,
+ &testsuite_interpreter_ext, ctx);
+ return TRUE;
+}
+
+struct testsuite_interpreter_context *
+testsuite_interpreter_context_get(struct sieve_interpreter *interp,
+ const struct sieve_extension *this_ext)
+{
+ struct testsuite_interpreter_context *ctx =
+ sieve_interpreter_extension_get_context(interp, this_ext);
+
+ return ctx;
+}
+
+/*
+ * Test context
+ */
+
+static void testsuite_test_context_init(void)
+{
+ test_name = str_new(default_pool, 128);
+ test_block_end = 0;
+ test_index = 0;
+ test_failures = 0;
+}
+
+int testsuite_test_start(const struct sieve_runtime_env *renv,
+ string_t *name, sieve_size_t block_end)
+{
+ if (test_block_end != 0) {
+ sieve_runtime_trace_error(renv, "already inside test block");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ str_truncate(test_name, 0);
+ str_append_str(test_name, name);
+
+ test_block_end = block_end;
+ test_index++;
+
+ return SIEVE_EXEC_OK;
+}
+
+int testsuite_test_fail(const struct sieve_runtime_env *renv,
+ string_t *reason)
+{
+ return testsuite_test_fail_cstr(renv, str_c(reason));
+}
+
+int testsuite_test_failf(const struct sieve_runtime_env *renv,
+ const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = testsuite_test_fail_cstr(renv, t_strdup_vprintf(fmt, args));
+ va_end(args);
+
+ return ret;
+}
+
+int testsuite_test_fail_cstr(const struct sieve_runtime_env *renv,
+ const char *reason)
+{
+ sieve_size_t end = test_block_end;
+
+ if (str_len(test_name) == 0) {
+ if (reason == NULL || *reason == '\0')
+ printf("%2d: Test FAILED\n", test_index);
+ else
+ printf("%2d: Test FAILED: %s\n", test_index, reason);
+ } else {
+ if (reason == NULL || *reason == '\0') {
+ printf("%2d: Test '%s' FAILED\n",
+ test_index, str_c(test_name));
+ } else {
+ printf("%2d: Test '%s' FAILED: %s\n",
+ test_index, str_c(test_name), reason);
+ }
+ }
+
+ str_truncate(test_name, 0);
+ test_block_end = 0;
+
+ test_failures++;
+
+ return sieve_interpreter_program_jump_to(renv->interp, end, FALSE);
+}
+
+void testsuite_testcase_fail(const char *reason)
+{
+ if (reason == NULL || *reason == '\0')
+ printf("XX: Test CASE FAILED\n");
+ else
+ printf("XX: Test CASE FAILED: %s\n", reason);
+
+ test_failures++;
+}
+
+int testsuite_test_succeed(const struct sieve_runtime_env *renv,
+ sieve_size_t *address, string_t *reason)
+{
+ sieve_size_t end = test_block_end;
+ int ret;
+
+ if (str_len(test_name) == 0) {
+ if (reason == NULL || str_len(reason) == 0)
+ printf("%2d: Test SUCCEEDED\n", test_index);
+ else {
+ printf("%2d: Test SUCCEEDED: %s\n",
+ test_index, str_c(reason));
+ }
+ } else {
+ if (reason == NULL || str_len(reason) == 0) {
+ printf("%2d: Test '%s' SUCCEEDED\n",
+ test_index, str_c(test_name));
+ } else {
+ printf("%2d: Test '%s' SUCCEEDED: %s\n", test_index,
+ str_c(test_name), str_c(reason));
+ }
+ }
+
+ str_truncate(test_name, 0);
+ test_block_end = 0;
+
+ if (*address > end) {
+ sieve_runtime_trace_error(
+ renv, "invalid test block end offset");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ } else if (*address < end) {
+ ret = sieve_interpreter_program_jump_to(
+ renv->interp, end, FALSE);
+ if (ret <= 0)
+ return ret;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+static void testsuite_test_context_deinit(void)
+{
+ str_free(&test_name);
+}
+
+bool testsuite_testcase_result(bool expect_failure)
+{
+ if (expect_failure) {
+ if (test_failures < test_index) {
+ printf("\nFAIL: Only %d of %d tests failed "
+ "(all expected to fail).\n\n",
+ test_failures, test_index);
+ return FALSE;
+ }
+
+ printf("\nPASS: %d tests failed (expected to fail).\n\n",
+ (test_index == 0 ? 1 : test_index));
+ return TRUE;
+ }
+
+ if (test_failures > 0) {
+ printf("\nFAIL: %d of %d tests failed.\n\n",
+ test_failures, test_index);
+ return FALSE;
+ }
+
+ printf("\nPASS: %d tests succeeded.\n\n", test_index);
+ return TRUE;
+}
+
+/*
+ * Testsuite temporary directory
+ */
+
+static char *testsuite_tmp_dir;
+
+static void testsuite_tmp_dir_init(void)
+{
+ testsuite_tmp_dir = i_strdup_printf("/tmp/dsieve-testsuite.%s.%s",
+ dec2str(time(NULL)),
+ dec2str(getpid()));
+
+ if (mkdir(testsuite_tmp_dir, 0700) < 0) {
+ i_fatal("failed to create temporary directory '%s': %m.",
+ testsuite_tmp_dir);
+ }
+}
+
+static void testsuite_tmp_dir_deinit(void)
+{
+ const char *error;
+
+ if (unlink_directory(testsuite_tmp_dir,
+ UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
+ i_warning("failed to remove temporary directory '%s': %s.",
+ testsuite_tmp_dir, error);
+
+ i_free(testsuite_tmp_dir);
+}
+
+const char *testsuite_tmp_dir_get(void)
+{
+ return testsuite_tmp_dir;
+}
+
+/*
+ * Main testsuite init/deinit
+ */
+
+void testsuite_init(struct sieve_instance *svinst, const char *test_path,
+ bool log_stdout)
+{
+ testsuite_sieve_instance = svinst;
+
+ testsuite_test_context_init();
+ testsuite_log_init(log_stdout);
+ testsuite_tmp_dir_init();
+
+ testsuite_script_init();
+ testsuite_binary_init();
+ testsuite_smtp_init();
+
+ testsuite_ext =
+ sieve_extension_register(svinst, &testsuite_extension, TRUE);
+
+ testsuite_test_path = i_strdup(test_path);
+}
+
+void testsuite_deinit(void)
+{
+ i_free(testsuite_test_path);
+
+ testsuite_smtp_deinit();
+ testsuite_binary_deinit();
+ testsuite_script_deinit();
+
+ testsuite_tmp_dir_deinit();
+ testsuite_log_deinit();
+ testsuite_test_context_deinit();
+}
diff --git a/pigeonhole/src/testsuite/testsuite-common.h b/pigeonhole/src/testsuite/testsuite-common.h
new file mode 100644
index 0000000..9a40cdd
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-common.h
@@ -0,0 +1,193 @@
+#ifndef TESTSUITE_COMMON_H
+#define TESTSUITE_COMMON_H
+
+#include "sieve-common.h"
+
+#include "sieve-tool.h"
+
+/*
+ * Global data
+ */
+
+extern struct sieve_instance *testsuite_sieve_instance;
+
+extern const struct sieve_extension_def testsuite_extension;
+
+extern const struct sieve_extension *testsuite_ext;
+
+extern const struct sieve_script_env *testsuite_scriptenv;
+
+extern char *testsuite_test_path;
+
+
+/*
+ * Validator context
+ */
+
+struct testsuite_validator_context {
+ struct sieve_validator_object_registry *object_registrations;
+};
+
+bool testsuite_validator_context_initialize(struct sieve_validator *valdtr);
+struct testsuite_validator_context *
+testsuite_validator_context_get(struct sieve_validator *valdtr);
+
+/*
+ * Generator context
+ */
+
+struct testsuite_generator_context {
+ struct sieve_jumplist *exit_jumps;
+};
+
+bool testsuite_generator_context_initialize(
+ struct sieve_generator *gentr, const struct sieve_extension *this_ext);
+
+/*
+ * Interpreter context
+ */
+
+struct testsuite_interpreter_context {
+ struct sieve_binary *compiled_script;
+};
+
+bool testsuite_interpreter_context_initialize(
+ struct sieve_interpreter *interp,
+ const struct sieve_extension *this_ext);
+struct testsuite_interpreter_context *
+testsuite_interpreter_context_get(struct sieve_interpreter *interp,
+ const struct sieve_extension *this_ext);
+
+/*
+ * Commands
+ */
+
+extern const struct sieve_command_def cmd_test;
+extern const struct sieve_command_def cmd_test_fail;
+extern const struct sieve_command_def cmd_test_config_set;
+extern const struct sieve_command_def cmd_test_config_unset;
+extern const struct sieve_command_def cmd_test_config_reload;
+extern const struct sieve_command_def cmd_test_set;
+extern const struct sieve_command_def cmd_test_result_reset;
+extern const struct sieve_command_def cmd_test_result_print;
+extern const struct sieve_command_def cmd_test_message;
+extern const struct sieve_command_def cmd_test_message_print;
+extern const struct sieve_command_def cmd_test_mailbox;
+extern const struct sieve_command_def cmd_test_mailbox_create;
+extern const struct sieve_command_def cmd_test_mailbox_delete;
+extern const struct sieve_command_def cmd_test_binary_load;
+extern const struct sieve_command_def cmd_test_binary_save;
+extern const struct sieve_command_def cmd_test_imap_metadata_set;
+
+/*
+ * Tests
+ */
+
+extern const struct sieve_command_def tst_test_script_compile;
+extern const struct sieve_command_def tst_test_script_run;
+extern const struct sieve_command_def tst_test_multiscript;
+extern const struct sieve_command_def tst_test_error;
+extern const struct sieve_command_def tst_test_result_action;
+extern const struct sieve_command_def tst_test_result_execute;
+
+/*
+ * Operations
+ */
+
+enum testsuite_operation_code {
+ TESTSUITE_OPERATION_TEST,
+ TESTSUITE_OPERATION_TEST_FINISH,
+ TESTSUITE_OPERATION_TEST_FAIL,
+ TESTSUITE_OPERATION_TEST_CONFIG_SET,
+ TESTSUITE_OPERATION_TEST_CONFIG_UNSET,
+ TESTSUITE_OPERATION_TEST_CONFIG_RELOAD,
+ TESTSUITE_OPERATION_TEST_SET,
+ TESTSUITE_OPERATION_TEST_SCRIPT_COMPILE,
+ TESTSUITE_OPERATION_TEST_SCRIPT_RUN,
+ TESTSUITE_OPERATION_TEST_MULTISCRIPT,
+ TESTSUITE_OPERATION_TEST_ERROR,
+ TESTSUITE_OPERATION_TEST_RESULT_ACTION,
+ TESTSUITE_OPERATION_TEST_RESULT_EXECUTE,
+ TESTSUITE_OPERATION_TEST_RESULT_RESET,
+ TESTSUITE_OPERATION_TEST_RESULT_PRINT,
+ TESTSUITE_OPERATION_TEST_MESSAGE_SMTP,
+ TESTSUITE_OPERATION_TEST_MESSAGE_MAILBOX,
+ TESTSUITE_OPERATION_TEST_MESSAGE_PRINT,
+ TESTSUITE_OPERATION_TEST_MAILBOX_CREATE,
+ TESTSUITE_OPERATION_TEST_MAILBOX_DELETE,
+ TESTSUITE_OPERATION_TEST_BINARY_LOAD,
+ TESTSUITE_OPERATION_TEST_BINARY_SAVE,
+ TESTSUITE_OPERATION_TEST_IMAP_METADATA_SET
+};
+
+extern const struct sieve_operation_def test_operation;
+extern const struct sieve_operation_def test_finish_operation;
+extern const struct sieve_operation_def test_fail_operation;
+extern const struct sieve_operation_def test_config_set_operation;
+extern const struct sieve_operation_def test_config_unset_operation;
+extern const struct sieve_operation_def test_config_reload_operation;
+extern const struct sieve_operation_def test_set_operation;
+extern const struct sieve_operation_def test_script_compile_operation;
+extern const struct sieve_operation_def test_script_run_operation;
+extern const struct sieve_operation_def test_multiscript_operation;
+extern const struct sieve_operation_def test_error_operation;
+extern const struct sieve_operation_def test_result_action_operation;
+extern const struct sieve_operation_def test_result_execute_operation;
+extern const struct sieve_operation_def test_result_reset_operation;
+extern const struct sieve_operation_def test_result_print_operation;
+extern const struct sieve_operation_def test_message_smtp_operation;
+extern const struct sieve_operation_def test_message_mailbox_operation;
+extern const struct sieve_operation_def test_message_print_operation;
+extern const struct sieve_operation_def test_mailbox_create_operation;
+extern const struct sieve_operation_def test_mailbox_delete_operation;
+extern const struct sieve_operation_def test_binary_load_operation;
+extern const struct sieve_operation_def test_binary_save_operation;
+extern const struct sieve_operation_def test_imap_metadata_set_operation;
+
+/*
+ * Operands
+ */
+
+extern const struct sieve_operand_def testsuite_object_operand;
+extern const struct sieve_operand_def testsuite_substitution_operand;
+
+enum testsuite_operand_code {
+ TESTSUITE_OPERAND_OBJECT,
+ TESTSUITE_OPERAND_SUBSTITUTION,
+ TESTSUITE_OPERAND_NAMESPACE
+};
+
+/*
+ * Test context
+ */
+
+int testsuite_test_start(const struct sieve_runtime_env *renv,
+ string_t *name, sieve_size_t block_end);
+int testsuite_test_fail(const struct sieve_runtime_env *renv,
+ string_t *reason);
+int testsuite_test_failf(const struct sieve_runtime_env *renv,
+ const char *fmt, ...) ATTR_FORMAT(2, 3);
+int testsuite_test_fail_cstr(const struct sieve_runtime_env *renv,
+ const char *reason);
+
+int testsuite_test_succeed(const struct sieve_runtime_env *renv,
+ sieve_size_t *address, string_t *reason);
+
+void testsuite_testcase_fail(const char *reason);
+bool testsuite_testcase_result(bool expect_failure);
+
+/*
+ * Testsuite temporary directory
+ */
+
+const char *testsuite_tmp_dir_get(void);
+
+/*
+ * Testsuite init/deinit
+ */
+
+void testsuite_init(struct sieve_instance *svinst, const char *test_path,
+ bool log_stdout);
+void testsuite_deinit(void);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-log.c b/pigeonhole/src/testsuite/testsuite-log.c
new file mode 100644
index 0000000..d06b744
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-log.c
@@ -0,0 +1,319 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-error-private.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+
+/*
+ * Configuration
+ */
+
+bool _testsuite_log_stdout = FALSE;
+
+/*
+ * Testsuite log error handlers
+ */
+
+struct sieve_error_handler *testsuite_log_ehandler = NULL;
+struct sieve_error_handler *testsuite_log_main_ehandler = NULL;
+
+struct _testsuite_log_message {
+ const char *location;
+ const char *message;
+};
+
+static pool_t _testsuite_logmsg_pool = NULL;
+ARRAY(struct _testsuite_log_message) _testsuite_log_errors;
+ARRAY(struct _testsuite_log_message) _testsuite_log_warnings;
+ARRAY(struct _testsuite_log_message) _testsuite_log_messages;
+
+static inline void
+_testsuite_stdout_log(const struct sieve_error_params *params,
+ const char *prefix, const char *message)
+{
+ if (_testsuite_log_stdout) {
+ if (params->location == NULL || *params->location == '\0') {
+ fprintf(stdout, "LOG: %s: %s\n",
+ prefix, message);
+ } else {
+ fprintf(stdout, "LOG: %s: %s: %s\n",
+ params->location, prefix, message);
+ }
+ }
+}
+
+static void
+_testsuite_log(struct sieve_error_handler *ehandler ATTR_UNUSED,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags ATTR_UNUSED, const char *message)
+{
+ pool_t pool = _testsuite_logmsg_pool;
+ struct _testsuite_log_message msg;
+ const char *prefix;
+
+ switch (params->log_type) {
+ case LOG_TYPE_ERROR:
+ prefix = "error";
+ break;
+ case LOG_TYPE_WARNING:
+ prefix = "warning";
+ break;
+ case LOG_TYPE_INFO:
+ prefix = "info";
+ break;
+ case LOG_TYPE_DEBUG:
+ prefix = "debug";
+ break;
+ default:
+ i_unreached();
+ }
+
+ _testsuite_stdout_log(params, prefix, message);
+
+ msg.location = p_strdup(pool, params->location);
+ msg.message = p_strdup(pool, message);
+
+ switch (params->log_type) {
+ case LOG_TYPE_ERROR:
+ array_append(&_testsuite_log_errors, &msg, 1);
+ break;
+ case LOG_TYPE_WARNING:
+ array_append(&_testsuite_log_warnings, &msg, 1);
+ break;
+ case LOG_TYPE_INFO:
+ array_append(&_testsuite_log_messages, &msg, 1);
+ break;
+ case LOG_TYPE_DEBUG:
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void
+_testsuite_main_log(struct sieve_error_handler *ehandler,
+ const struct sieve_error_params *params,
+ enum sieve_error_flags flags, const char *message)
+{
+ if (params->log_type != LOG_TYPE_ERROR)
+ return _testsuite_log(ehandler, params, flags, message);
+
+ if (params->location == NULL || *params->location == '\0')
+ fprintf(stderr, "error: %s\n", message);
+ else
+ fprintf(stderr, "%s: error: %s\n", params->location, message);
+}
+
+static struct sieve_error_handler *_testsuite_log_ehandler_create(void)
+{
+ pool_t pool;
+ struct sieve_error_handler *ehandler;
+
+ pool = pool_alloconly_create("testsuite_log_ehandler",
+ sizeof(struct sieve_error_handler));
+ ehandler = p_new(pool, struct sieve_error_handler, 1);
+ sieve_error_handler_init(ehandler, testsuite_sieve_instance, pool, 0);
+
+ ehandler->log = _testsuite_log;
+
+ return ehandler;
+}
+
+static struct sieve_error_handler *_testsuite_log_main_ehandler_create(void)
+{
+ pool_t pool;
+ struct sieve_error_handler *ehandler;
+
+ pool = pool_alloconly_create("testsuite_log_main_ehandler",
+ sizeof(struct sieve_error_handler));
+ ehandler = p_new(pool, struct sieve_error_handler, 1);
+ sieve_error_handler_init(ehandler, testsuite_sieve_instance, pool, 0);
+
+ ehandler->log = _testsuite_main_log;
+
+ return ehandler;
+}
+
+static void ATTR_FORMAT(2, 0)
+testsuite_error_handler(const struct failure_context *ctx, const char *fmt,
+ va_list args)
+{
+ struct sieve_error_params params = {
+ .location = NULL,
+ };
+ pool_t pool = _testsuite_logmsg_pool;
+ struct _testsuite_log_message msg;
+
+ i_zero(&msg);
+ switch (ctx->type) {
+ case LOG_TYPE_DEBUG:
+ T_BEGIN {
+ _testsuite_stdout_log(&params, "debug",
+ t_strdup_vprintf(fmt, args));
+ } T_END;
+ break;
+ case LOG_TYPE_INFO:
+ msg.message = p_strdup_vprintf(pool, fmt, args);
+ array_append(&_testsuite_log_messages, &msg, 1);
+
+ _testsuite_stdout_log(&params, "info", msg.message);
+ break;
+ case LOG_TYPE_WARNING:
+ msg.message = p_strdup_vprintf(pool, fmt, args);
+ array_append(&_testsuite_log_warnings, &msg, 1);
+
+ _testsuite_stdout_log(&params, "warning", msg.message);
+ break;
+ case LOG_TYPE_ERROR:
+ msg.message = p_strdup_vprintf(pool, fmt, args);
+ array_append(&_testsuite_log_errors, &msg, 1);
+
+ _testsuite_stdout_log(&params, "error", msg.message);
+ break;
+ default:
+ default_error_handler(ctx, fmt, args);
+ break;
+ }
+}
+
+/*
+ *
+ */
+
+void testsuite_log_clear_messages(void)
+{
+ if (_testsuite_logmsg_pool != NULL) {
+ if (array_count(&_testsuite_log_errors) == 0)
+ return;
+ pool_unref(&_testsuite_logmsg_pool);
+ }
+
+ _testsuite_logmsg_pool =
+ pool_alloconly_create("testsuite_log_messages", 8192);
+
+ p_array_init(&_testsuite_log_errors, _testsuite_logmsg_pool, 128);
+ p_array_init(&_testsuite_log_warnings, _testsuite_logmsg_pool, 128);
+ p_array_init(&_testsuite_log_messages, _testsuite_logmsg_pool, 128);
+
+ sieve_error_handler_reset(testsuite_log_ehandler);
+}
+
+/*
+ *
+ */
+
+void testsuite_log_init(bool log_stdout)
+{
+ _testsuite_log_stdout = log_stdout;
+
+ testsuite_log_ehandler = _testsuite_log_ehandler_create();
+ sieve_error_handler_accept_infolog(testsuite_log_ehandler, TRUE);
+ sieve_error_handler_accept_debuglog(testsuite_log_ehandler, TRUE);
+
+ testsuite_log_main_ehandler = _testsuite_log_main_ehandler_create();
+ sieve_error_handler_accept_infolog(testsuite_log_main_ehandler, TRUE);
+ sieve_error_handler_accept_debuglog(testsuite_log_main_ehandler, TRUE);
+
+ i_set_error_handler(testsuite_error_handler);
+ i_set_info_handler(testsuite_error_handler);
+ i_set_debug_handler(testsuite_error_handler);
+
+ testsuite_log_clear_messages();
+}
+
+void testsuite_log_deinit(void)
+{
+ sieve_error_handler_unref(&testsuite_log_ehandler);
+ sieve_error_handler_unref(&testsuite_log_main_ehandler);
+
+ i_set_error_handler(default_error_handler);
+ i_set_info_handler(default_error_handler);
+ i_set_debug_handler(default_error_handler);
+
+ pool_unref(&_testsuite_logmsg_pool);
+}
+
+/*
+ * Log stringlist
+ */
+
+/* Forward declarations */
+
+static int
+testsuite_log_stringlist_next_item(struct sieve_stringlist *_strlist,
+ string_t **str_r);
+static void testsuite_log_stringlist_reset(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct testsuite_log_stringlist {
+ struct sieve_stringlist strlist;
+
+ int pos, index;
+};
+
+struct sieve_stringlist *
+testsuite_log_stringlist_create(const struct sieve_runtime_env *renv,
+ int index)
+{
+ struct testsuite_log_stringlist *strlist;
+
+ strlist = t_new(struct testsuite_log_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = testsuite_log_stringlist_next_item;
+ strlist->strlist.reset = testsuite_log_stringlist_reset;
+
+ strlist->index = index;
+ strlist->pos = 0;
+
+ return &strlist->strlist;
+}
+
+static int
+testsuite_log_stringlist_next_item(struct sieve_stringlist *_strlist,
+ string_t **str_r)
+{
+ struct testsuite_log_stringlist *strlist =
+ (struct testsuite_log_stringlist *) _strlist;
+ const struct _testsuite_log_message *msg;
+ int pos;
+
+ *str_r = NULL;
+
+ if (strlist->pos < 0)
+ return 0;
+
+ if (strlist->index > 0) {
+ pos = strlist->index - 1;
+ strlist->pos = -1;
+ } else {
+ pos = strlist->pos++;
+ }
+
+ if (pos >= (int) array_count(&_testsuite_log_errors)) {
+ strlist->pos = -1;
+ return 0;
+ }
+
+ msg = array_idx(&_testsuite_log_errors, (unsigned int) pos);
+
+ *str_r = t_str_new_const(msg->message, strlen(msg->message));
+ return 1;
+}
+
+static void testsuite_log_stringlist_reset(struct sieve_stringlist *_strlist)
+{
+ struct testsuite_log_stringlist *strlist =
+ (struct testsuite_log_stringlist *) _strlist;
+
+ strlist->pos = 0;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-log.h b/pigeonhole/src/testsuite/testsuite-log.h
new file mode 100644
index 0000000..335d044
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-log.h
@@ -0,0 +1,26 @@
+#ifndef TESTSUITE_LOG_H
+#define TESTSUITE_LOG_H
+
+#include "sieve-common.h"
+
+extern struct sieve_error_handler *testsuite_log_ehandler;
+extern struct sieve_error_handler *testsuite_log_main_ehandler;
+
+/*
+ * Initialization
+ */
+
+void testsuite_log_init(bool log_stdout);
+void testsuite_log_deinit(void);
+
+/*
+ * Access
+ */
+
+void testsuite_log_clear_messages(void);
+
+struct sieve_stringlist *
+testsuite_log_stringlist_create(const struct sieve_runtime_env *renv,
+ int index);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-mailstore.c b/pigeonhole/src/testsuite/testsuite-mailstore.c
new file mode 100644
index 0000000..7762d50
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-mailstore.c
@@ -0,0 +1,349 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "mempool.h"
+#include "imem.h"
+#include "array.h"
+#include "strfuncs.h"
+#include "str-sanitize.h"
+#include "path-util.h"
+#include "unlink-directory.h"
+#include "env-util.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "imap-metadata.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-actions.h"
+#include "sieve-interpreter.h"
+
+#include "testsuite-message.h"
+#include "testsuite-common.h"
+#include "testsuite-smtp.h"
+
+#include "testsuite-mailstore.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+struct testsuite_mailstore_mail {
+ struct testsuite_mailstore_mail *next;
+
+ char *folder;
+ struct mailbox *box;
+ struct mailbox_transaction_context *trans;
+ struct mail *mail;
+};
+
+/*
+ * Forward declarations
+ */
+
+static void testsuite_mailstore_free(bool all);
+
+/*
+ * State
+ */
+
+static struct mail_user *testsuite_mailstore_user = NULL;
+
+static struct testsuite_mailstore_mail *testsuite_mailstore_mail = NULL;
+
+static char *testsuite_mailstore_location = NULL;
+static char *testsuite_mailstore_attrs = NULL;
+
+/*
+ * Initialization
+ */
+
+void testsuite_mailstore_init(void)
+{
+ struct mail_user *mail_user_dovecot, *mail_user;
+ struct mail_namespace *ns;
+ struct mail_namespace_settings *ns_set;
+ struct mail_storage_settings *mail_set;
+ const char *tmpdir, *error, *cwd;
+
+ tmpdir = testsuite_tmp_dir_get();
+ testsuite_mailstore_location =
+ i_strconcat(tmpdir, "/mailstore", NULL);
+ testsuite_mailstore_attrs =
+ i_strconcat(tmpdir, "/mail-attrs.dict", NULL);
+
+ if (mkdir(testsuite_mailstore_location, 0700) < 0) {
+ i_fatal("failed to create temporary directory '%s': %m.",
+ testsuite_mailstore_location);
+ }
+
+ mail_user_dovecot = sieve_tool_get_mail_user(sieve_tool);
+ mail_user = mail_user_alloc(NULL, "testsuite-mail-user@example.org",
+ mail_user_dovecot->set_info,
+ mail_user_dovecot->unexpanded_set);
+ mail_user->autocreated = TRUE;
+ if (t_get_working_dir(&cwd, &error) < 0)
+ i_fatal("Failed to get working directory: %s", error);
+ mail_user_set_home(mail_user, cwd);
+ if (mail_user_init(mail_user, &error) < 0)
+ i_fatal("Testsuite user initialization failed: %s", error);
+
+ ns_set = p_new(mail_user->pool, struct mail_namespace_settings, 1);
+ ns_set->location = testsuite_mailstore_location;
+ ns_set->separator = ".";
+
+ ns = mail_namespaces_init_empty(mail_user);
+ ns->flags |= NAMESPACE_FLAG_INBOX_USER;
+ ns->set = ns_set;
+ /* absolute paths are ok with raw storage */
+ mail_set = p_new(mail_user->pool, struct mail_storage_settings, 1);
+ *mail_set = *ns->mail_set;
+ mail_set->mail_location = p_strconcat(
+ mail_user->pool, "maildir:",
+ testsuite_mailstore_location, NULL);
+ mail_set->mail_attribute_dict = p_strconcat(
+ mail_user->pool, "file:",
+ testsuite_mailstore_attrs, NULL);
+ ns->mail_set = mail_set;
+
+ if (mail_storage_create(ns, "maildir", 0, &error) < 0)
+ i_fatal("Couldn't create testsuite storage: %s", error);
+ if (mail_namespaces_init_finish(ns, &error) < 0)
+ i_fatal("Couldn't create testsuite namespace: %s", error);
+
+ testsuite_mailstore_user = mail_user;
+}
+
+void testsuite_mailstore_deinit(void)
+{
+ const char *error;
+
+ testsuite_mailstore_free(TRUE);
+
+ if (unlink_directory(testsuite_mailstore_location,
+ UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) {
+ i_warning("failed to remove temporary directory '%s': %s.",
+ testsuite_mailstore_location, error);
+ }
+
+ i_free(testsuite_mailstore_location);
+ i_free(testsuite_mailstore_attrs);
+ mail_user_unref(&testsuite_mailstore_user);
+}
+
+/*
+ * Mail user
+ */
+
+struct mail_user *testsuite_mailstore_get_user(void)
+{
+ if (testsuite_mailstore_user == NULL)
+ return sieve_tool_get_mail_user(sieve_tool);
+ return testsuite_mailstore_user;
+}
+
+/*
+ * Mailbox Access
+ */
+
+bool testsuite_mailstore_mailbox_create(
+ const struct sieve_runtime_env *renv ATTR_UNUSED, const char *folder)
+{
+ struct mail_user *mail_user = testsuite_mailstore_user;
+ struct mail_namespace *ns = mail_user->namespaces;
+ struct mailbox *box;
+
+ box = mailbox_alloc(ns->list, folder, 0);
+
+ if (mailbox_create(box, NULL, FALSE) < 0) {
+ mailbox_free(&box);
+ return FALSE;
+ }
+
+ mailbox_free(&box);
+
+ return TRUE;
+}
+
+static struct testsuite_mailstore_mail *
+testsuite_mailstore_open(const char *folder)
+{
+ enum mailbox_flags flags =
+ MAILBOX_FLAG_SAVEONLY | MAILBOX_FLAG_POST_SESSION;
+ struct mail_user *mail_user = testsuite_mailstore_user;
+ struct mail_namespace *ns = mail_user->namespaces;
+ struct mailbox *box;
+ struct mailbox_transaction_context *t;
+ struct testsuite_mailstore_mail *tmail, *tmail_prev;
+ const char *error;
+
+ if (!sieve_mailbox_check_name(folder, &error)) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: invalid mailbox name `%s' specified: %s",
+ folder, error);
+ return NULL;
+ }
+
+ tmail = testsuite_mailstore_mail;
+ tmail_prev = NULL;
+ while (tmail != NULL) {
+ if (strcmp(tmail->folder, folder) == 0) {
+ if (tmail_prev != NULL) {
+ /* Remove it from list if it is not first. */
+ tmail_prev->next = tmail->next;
+ }
+ break;
+ }
+ tmail_prev = tmail;
+ tmail = tmail->next;
+ }
+ if (tmail != NULL) {
+ if (tmail != testsuite_mailstore_mail) {
+ /* Bring it to front */
+ tmail->next = testsuite_mailstore_mail;
+ testsuite_mailstore_mail = tmail;
+ }
+ return tmail;
+ }
+
+ box = mailbox_alloc(ns->list, folder, flags);
+ if (mailbox_open(box) < 0) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: failed to open mailbox '%s'", folder);
+ mailbox_free(&box);
+ return NULL;
+ }
+
+ /* Sync mailbox */
+
+ if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: failed to sync mailbox '%s'", folder);
+ mailbox_free(&box);
+ return NULL;
+ }
+
+ /* Start transaction */
+
+ t = mailbox_transaction_begin(box, 0, __func__);
+
+ tmail = i_new(struct testsuite_mailstore_mail, 1);
+ tmail->next = testsuite_mailstore_mail;
+ testsuite_mailstore_mail = tmail;
+
+ tmail->folder = i_strdup(folder);
+ tmail->box = box;
+ tmail->trans = t;
+ tmail->mail = mail_alloc(t, 0, NULL);
+
+ return tmail;
+}
+
+static void testsuite_mailstore_free(bool all)
+{
+ struct testsuite_mailstore_mail *tmail;
+
+ if (testsuite_mailstore_mail == NULL)
+ return;
+
+ tmail = (all ?
+ testsuite_mailstore_mail : testsuite_mailstore_mail->next);
+ while (tmail != NULL) {
+ struct testsuite_mailstore_mail *tmail_next = tmail->next;
+
+ mail_free(&tmail->mail);
+ mailbox_transaction_rollback(&tmail->trans);
+ mailbox_free(&tmail->box);
+ i_free(tmail->folder);
+ i_free(tmail);
+
+ tmail = tmail_next;
+ }
+ if (all)
+ testsuite_mailstore_mail = NULL;
+ else
+ testsuite_mailstore_mail->next = NULL;
+}
+
+void testsuite_mailstore_flush(void)
+{
+ testsuite_mailstore_free(FALSE);
+}
+
+bool testsuite_mailstore_mail_index(const struct sieve_runtime_env *renv,
+ const char *folder, unsigned int index)
+{
+ struct testsuite_mailstore_mail *tmail;
+ struct mailbox_status status;
+
+ tmail = testsuite_mailstore_open(folder);
+ if (tmail == NULL)
+ return FALSE;
+
+ mailbox_get_open_status(tmail->box, STATUS_MESSAGES, &status);
+ if (index >= status.messages)
+ return FALSE;
+
+ mail_set_seq(tmail->mail, index+1);
+ testsuite_message_set_mail(renv, tmail->mail);
+
+ return TRUE;
+}
+
+/*
+ * IMAP metadata
+ */
+
+int testsuite_mailstore_set_imap_metadata(const char *mailbox,
+ const char *annotation,
+ const char *value)
+{
+ struct imap_metadata_transaction *imtrans;
+ struct mail_attribute_value avalue;
+ struct mailbox *box;
+ enum mail_error error_code;
+ const char *error;
+ int ret;
+
+ if (!imap_metadata_verify_entry_name(annotation, &error)) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: imap metadata: "
+ "specified annotation name `%s' is invalid: %s",
+ str_sanitize(annotation, 256), error);
+ return -1;
+ }
+
+ if (mailbox != NULL) {
+ struct mail_namespace *ns;
+ ns = mail_namespace_find(testsuite_mailstore_user->namespaces,
+ mailbox);
+ box = mailbox_alloc(ns->list, mailbox, 0);
+ imtrans = imap_metadata_transaction_begin(box);
+ } else {
+ box = NULL;
+ imtrans = imap_metadata_transaction_begin_server(
+ testsuite_mailstore_user);
+ }
+
+ i_zero(&avalue);
+ avalue.value = value;
+ if ((ret = imap_metadata_set(imtrans, annotation, &avalue)) < 0) {
+ error = imap_metadata_transaction_get_last_error(
+ imtrans, &error_code);
+ imap_metadata_transaction_rollback(&imtrans);
+ } else {
+ ret = imap_metadata_transaction_commit(&imtrans,
+ &error_code, &error);
+ }
+ if (box != NULL)
+ mailbox_free(&box);
+
+ if (ret < 0) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: imap metadata: "
+ "failed to assign annotation `%s': %s",
+ str_sanitize(annotation, 256), error);
+ return -1;
+ }
+ return 0;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-mailstore.h b/pigeonhole/src/testsuite/testsuite-mailstore.h
new file mode 100644
index 0000000..07b0ab0
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-mailstore.h
@@ -0,0 +1,40 @@
+#ifndef TESTSUITE_MAILSTORE_H
+#define TESTSUITE_MAILSTORE_H
+
+#include "lib.h"
+
+#include "sieve-common.h"
+
+/*
+ * Initialization
+ */
+
+void testsuite_mailstore_init(void);
+void testsuite_mailstore_deinit(void);
+void testsuite_mailstore_flush(void);
+
+/*
+ * Mail user
+ */
+
+struct mail_user *testsuite_mailstore_get_user(void);
+
+/*
+ * Mailbox Access
+ */
+
+bool testsuite_mailstore_mailbox_create(
+ const struct sieve_runtime_env *renv ATTR_UNUSED, const char *folder);
+
+bool testsuite_mailstore_mail_index(const struct sieve_runtime_env *renv,
+ const char *folder, unsigned int index);
+
+/*
+ * IMAP metadata
+ */
+
+int testsuite_mailstore_set_imap_metadata(const char *mailbox,
+ const char *annotation,
+ const char *value);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-message.c b/pigeonhole/src/testsuite/testsuite-message.c
new file mode 100644
index 0000000..68e20eb
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-message.c
@@ -0,0 +1,339 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "smtp-params.h"
+#include "message-address.h"
+#include "mail-storage.h"
+#include "master-service.h"
+#include "mail-raw.h"
+
+#include "sieve-common.h"
+#include "sieve-address.h"
+#include "sieve-error.h"
+#include "sieve-message.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-tool.h"
+
+#include "testsuite-common.h"
+#include "testsuite-message.h"
+
+/*
+ * Testsuite message environment
+ */
+
+struct testsuite_message {
+ struct testsuite_message *next;
+
+ struct mail_raw *mail_raw;
+};
+
+struct sieve_message_data testsuite_msgdata;
+static struct smtp_params_rcpt testsuite_rcpt_params;
+
+static struct testsuite_message *testsuite_msg;
+
+static const char *_default_message_data =
+"From: sender@example.com\n"
+"To: recipient@example.org\n"
+"Subject: Frop!\n"
+"\n"
+"Friep!\n";
+
+static struct smtp_address *testsuite_env_mail_from = NULL;
+static struct smtp_address *testsuite_env_rcpt_to = NULL;
+static struct smtp_address *testsuite_env_orig_rcpt_to = NULL;
+static char *testsuite_env_auth = NULL;
+
+static pool_t testsuite_msg_pool;
+static char *testsuite_msg_id = NULL;
+
+static const struct smtp_address *
+testsuite_message_get_address(struct mail *mail, const char *header)
+{
+ struct message_address *addr;
+ struct smtp_address *smtp_addr;
+ const char *str;
+
+ if (mail_get_first_header(mail, header, &str) <= 0)
+ return NULL;
+ addr = message_address_parse(pool_datastack_create(),
+ (const unsigned char *)str,
+ strlen(str), 1, 0);
+ if (addr == NULL || addr->mailbox == NULL || *addr->mailbox == '\0')
+ return NULL;
+ if (smtp_address_create_from_msg_temp(addr, &smtp_addr) < 0)
+ return NULL;
+ return smtp_addr;
+}
+
+static void testsuite_message_set_data(struct mail *mail)
+{
+ const struct smtp_address *recipient = NULL, *sender = NULL;
+ const char *msg_id;
+
+ static const struct smtp_address default_recipient = {
+ .localpart = "recipient",
+ .domain = "example.com",
+ };
+ static const struct smtp_address default_sender = {
+ .localpart = "sender",
+ .domain = "example.com",
+ };
+
+ i_free(testsuite_env_mail_from);
+ i_free(testsuite_env_rcpt_to);
+ i_free(testsuite_env_orig_rcpt_to);
+ i_free(testsuite_env_auth);
+ i_free(testsuite_msg_id);
+
+ /*
+ * Collect necessary message data
+ */
+
+ /* Get recipient address */
+ recipient = testsuite_message_get_address(mail, "Envelope-To");
+ if (recipient == NULL)
+ recipient = testsuite_message_get_address(mail, "To");
+ if (recipient == NULL)
+ recipient = &default_recipient;
+
+ /* Get sender address */
+ sender = testsuite_message_get_address(mail, "Return-path");
+ if (sender == NULL)
+ sender = testsuite_message_get_address(mail, "Sender");
+ if (sender == NULL)
+ sender = testsuite_message_get_address(mail, "From");
+ if (sender == NULL)
+ sender = &default_sender;
+
+ testsuite_env_mail_from = smtp_address_clone(default_pool, sender);
+ testsuite_env_rcpt_to = smtp_address_clone(default_pool, recipient);
+ testsuite_env_orig_rcpt_to = smtp_address_clone(default_pool, recipient);
+
+ (void)mail_get_message_id(mail, &msg_id);
+ testsuite_msg_id = i_strdup(msg_id);
+
+ i_zero(&testsuite_msgdata);
+ testsuite_msgdata.mail = mail;
+ testsuite_msgdata.auth_user = sieve_tool_get_username(sieve_tool);
+ testsuite_msgdata.envelope.mail_from = testsuite_env_mail_from;
+ testsuite_msgdata.envelope.rcpt_to = testsuite_env_rcpt_to;
+ testsuite_msgdata.id = testsuite_msg_id;
+
+ i_zero(&testsuite_rcpt_params);
+ testsuite_rcpt_params.orcpt.addr = testsuite_env_orig_rcpt_to;
+
+ testsuite_msgdata.envelope.rcpt_params = &testsuite_rcpt_params;
+}
+
+static struct testsuite_message *testsuite_message_new(void)
+{
+ struct testsuite_message *msg;
+
+ msg = i_new(struct testsuite_message, 1);
+ msg->next = testsuite_msg;
+ testsuite_msg = msg;
+
+ return msg;
+}
+
+static void testsuite_message_new_string(string_t *mail_str)
+{
+ struct mail_user *mail_raw_user =
+ sieve_tool_get_mail_raw_user(sieve_tool);
+ struct testsuite_message *msg;
+
+ msg = testsuite_message_new();
+ msg->mail_raw = mail_raw_open_data(mail_raw_user, mail_str);
+
+ testsuite_message_set_data(msg->mail_raw->mail);
+}
+
+static void testsuite_message_new_file(const char *mail_path)
+{
+ struct mail_user *mail_raw_user =
+ sieve_tool_get_mail_raw_user(sieve_tool);
+ struct testsuite_message *msg;
+
+ msg = testsuite_message_new();
+ msg->mail_raw = mail_raw_open_file(mail_raw_user, mail_path);
+
+ testsuite_message_set_data(msg->mail_raw->mail);
+}
+
+static void testsuite_message_free(bool all)
+{
+ struct testsuite_message *msg;
+
+ if (testsuite_msg == NULL)
+ return;
+
+ msg = (all ? testsuite_msg : testsuite_msg->next);
+ while (msg != NULL) {
+ struct testsuite_message *msg_next = msg->next;
+
+ mail_raw_close(&msg->mail_raw);
+ i_free(msg);
+
+ msg = msg_next;
+ }
+ if (all)
+ testsuite_msg = NULL;
+ else
+ testsuite_msg->next = NULL;
+}
+
+void testsuite_message_flush(void)
+{
+ testsuite_message_free(FALSE);
+}
+
+void testsuite_message_init(void)
+{
+ testsuite_msg_pool = pool_alloconly_create("testsuite_message", 6096);
+
+ string_t *default_message = str_new(testsuite_msg_pool, 1024);
+ str_append(default_message, _default_message_data);
+
+ testsuite_message_new_string(default_message);
+}
+
+void testsuite_message_set_string(const struct sieve_runtime_env *renv,
+ string_t *message)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ testsuite_message_new_string(message);
+}
+
+void testsuite_message_set_file(const struct sieve_runtime_env *renv,
+ const char *file_path)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ testsuite_message_new_file(file_path);
+}
+
+void testsuite_message_set_mail(const struct sieve_runtime_env *renv,
+ struct mail *mail)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ testsuite_message_set_data(mail);
+}
+
+void testsuite_message_deinit(void)
+{
+ testsuite_message_free(TRUE);
+
+ i_free(testsuite_env_mail_from);
+ i_free(testsuite_env_rcpt_to);
+ i_free(testsuite_env_orig_rcpt_to);
+ i_free(testsuite_env_auth);
+ pool_unref(&testsuite_msg_pool);
+ i_free(testsuite_msg_id);
+}
+
+void testsuite_envelope_set_sender_address(const struct sieve_runtime_env *renv,
+ const struct smtp_address *address)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ i_free(testsuite_env_mail_from);
+
+ testsuite_env_mail_from = smtp_address_clone(default_pool, address);
+ testsuite_msgdata.envelope.mail_from = testsuite_env_mail_from;
+}
+
+void testsuite_envelope_set_sender(const struct sieve_runtime_env *renv,
+ const char *value)
+{
+ struct smtp_address *address = NULL;
+ const char *error;
+
+ if (smtp_address_parse_path(pool_datastack_create(), value,
+ (SMTP_ADDRESS_PARSE_FLAG_ALLOW_EMPTY |
+ SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL),
+ &address, &error) < 0) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: envelope sender address "
+ "`%s' is invalid: %s", value, error);
+ }
+ testsuite_envelope_set_sender_address(renv, address);
+}
+
+void testsuite_envelope_set_recipient_address(
+ const struct sieve_runtime_env *renv,
+ const struct smtp_address *address)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ i_free(testsuite_env_rcpt_to);
+ i_free(testsuite_env_orig_rcpt_to);
+
+ testsuite_env_rcpt_to = smtp_address_clone(default_pool, address);
+ testsuite_env_orig_rcpt_to = smtp_address_clone(default_pool, address);
+ testsuite_msgdata.envelope.rcpt_to = testsuite_env_rcpt_to;
+ testsuite_rcpt_params.orcpt.addr = testsuite_env_orig_rcpt_to;
+}
+
+void testsuite_envelope_set_recipient(const struct sieve_runtime_env *renv,
+ const char *value)
+{
+ struct smtp_address *address = NULL;
+ const char *error;
+
+ if (smtp_address_parse_path(pool_datastack_create(), value,
+ (SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+ SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL),
+ &address, &error) < 0) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: envelope recipient address "
+ "`%s' is invalid: %s", value, error);
+ }
+ testsuite_envelope_set_recipient_address(renv, address);
+}
+
+void testsuite_envelope_set_orig_recipient_address(
+ const struct sieve_runtime_env *renv,
+ const struct smtp_address *address)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ i_free(testsuite_env_orig_rcpt_to);
+
+ testsuite_env_orig_rcpt_to = smtp_address_clone(default_pool, address);
+ testsuite_rcpt_params.orcpt.addr = testsuite_env_orig_rcpt_to;
+}
+
+void testsuite_envelope_set_orig_recipient(const struct sieve_runtime_env *renv,
+ const char *value)
+{
+ struct smtp_address *address = NULL;
+ const char *error;
+
+ if (smtp_address_parse_path(pool_datastack_create(), value,
+ (SMTP_ADDRESS_PARSE_FLAG_ALLOW_LOCALPART |
+ SMTP_ADDRESS_PARSE_FLAG_BRACKETS_OPTIONAL),
+ &address, &error) < 0) {
+ e_error(testsuite_sieve_instance->event,
+ "testsuite: envelope recipient address "
+ "`%s' is invalid: %s", value, error);
+ }
+ testsuite_envelope_set_orig_recipient_address(renv, address);
+}
+
+void testsuite_envelope_set_auth_user(const struct sieve_runtime_env *renv,
+ const char *value)
+{
+ sieve_message_context_reset(renv->msgctx);
+
+ i_free(testsuite_env_auth);
+
+ testsuite_env_auth = i_strdup(value);
+ testsuite_msgdata.auth_user = testsuite_env_auth;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-message.h b/pigeonhole/src/testsuite/testsuite-message.h
new file mode 100644
index 0000000..2a4d3b4
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-message.h
@@ -0,0 +1,44 @@
+#ifndef TESTSUITE_MESSAGE_H
+#define TESTSUITE_MESSAGE_H
+
+#include "lib.h"
+#include "master-service.h"
+
+#include "sieve-common.h"
+#include "sieve-tool.h"
+
+extern struct sieve_message_data testsuite_msgdata;
+
+void testsuite_message_init(void);
+void testsuite_message_deinit(void);
+
+void testsuite_message_flush(void);
+
+void testsuite_message_set_string(const struct sieve_runtime_env *renv,
+ string_t *message);
+void testsuite_message_set_file(const struct sieve_runtime_env *renv,
+ const char *file_path);
+void testsuite_message_set_mail(const struct sieve_runtime_env *renv,
+ struct mail *mail);
+
+void testsuite_envelope_set_sender_address(const struct sieve_runtime_env *renv,
+ const struct smtp_address *address);
+void testsuite_envelope_set_sender(const struct sieve_runtime_env *renv,
+ const char *value);
+
+void testsuite_envelope_set_recipient_address(
+ const struct sieve_runtime_env *renv,
+ const struct smtp_address *address);
+void testsuite_envelope_set_recipient(const struct sieve_runtime_env *renv,
+ const char *value);
+
+void testsuite_envelope_set_orig_recipient_address(
+ const struct sieve_runtime_env *renv,
+ const struct smtp_address *address);
+void testsuite_envelope_set_orig_recipient(const struct sieve_runtime_env *renv,
+ const char *value);
+
+void testsuite_envelope_set_auth_user(const struct sieve_runtime_env *renv,
+ const char *value);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-objects.c b/pigeonhole/src/testsuite/testsuite-objects.c
new file mode 100644
index 0000000..4c09c85
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-objects.c
@@ -0,0 +1,369 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "string.h"
+#include "ostream.h"
+#include "hash.h"
+#include "mail-storage.h"
+
+#include "sieve.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-extensions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-objects.h"
+#include "testsuite-message.h"
+
+/*
+ * Testsuite core objects
+ */
+
+enum testsuite_object_code {
+ TESTSUITE_OBJECT_MESSAGE,
+ TESTSUITE_OBJECT_ENVELOPE
+};
+
+const struct testsuite_object_def *testsuite_core_objects[] = {
+ &message_testsuite_object, &envelope_testsuite_object
+};
+
+const unsigned int testsuite_core_objects_count =
+ N_ELEMENTS(testsuite_core_objects);
+
+/*
+ * Testsuite object registry
+ */
+
+static inline struct sieve_validator_object_registry *_get_object_registry
+(struct sieve_validator *valdtr)
+{
+ struct testsuite_validator_context *ctx =
+ testsuite_validator_context_get(valdtr);
+
+ return ctx->object_registrations;
+}
+
+void testsuite_object_register
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct testsuite_object_def *tobj_def)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+
+ sieve_validator_object_registry_add(regs, ext, &tobj_def->obj_def);
+}
+
+static const struct testsuite_object *testsuite_object_create
+(struct sieve_validator *valdtr, struct sieve_command *cmd,
+ const char *identifier)
+{
+ struct sieve_validator_object_registry *regs = _get_object_registry(valdtr);
+ struct sieve_object object;
+ struct testsuite_object *tobj;
+
+ if ( !sieve_validator_object_registry_find(regs, identifier, &object) )
+ return NULL;
+
+ tobj = p_new(sieve_command_pool(cmd), struct testsuite_object, 1);
+ tobj->object = object;
+ tobj->def = (const struct testsuite_object_def *) object.def;
+
+ return tobj;
+}
+
+void testsuite_register_core_objects
+(struct testsuite_validator_context *ctx)
+{
+ struct sieve_validator_object_registry *regs = ctx->object_registrations;
+ unsigned int i;
+
+ /* Register core testsuite objects */
+ for ( i = 0; i < testsuite_core_objects_count; i++ ) {
+ const struct testsuite_object_def *tobj_def = testsuite_core_objects[i];
+
+ sieve_validator_object_registry_add
+ (regs, testsuite_ext, &tobj_def->obj_def);
+ }
+}
+
+/*
+ * Testsuite object code
+ */
+
+const struct sieve_operand_class sieve_testsuite_object_operand_class =
+ { "testsuite object" };
+
+static const struct sieve_extension_objects core_testsuite_objects =
+ SIEVE_EXT_DEFINE_OBJECTS(testsuite_core_objects);
+
+const struct sieve_operand_def testsuite_object_operand = {
+ .name = "testsuite-object",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERAND_OBJECT,
+ .class = &sieve_testsuite_object_operand_class,
+ .interface = &core_testsuite_objects
+};
+
+static void testsuite_object_emit
+(struct sieve_binary_block *sblock, const struct testsuite_object *tobj,
+ int member_id)
+{
+ sieve_opr_object_emit(sblock, tobj->object.ext, tobj->object.def);
+
+ if ( tobj->def != NULL && tobj->def->get_member_id != NULL ) {
+ (void) sieve_binary_emit_byte(sblock, (unsigned char) member_id);
+ }
+}
+
+bool testsuite_object_read
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+ struct testsuite_object *tobj)
+{
+ struct sieve_operand oprnd;
+
+ if ( !sieve_operand_read(sblock, address, NULL, &oprnd) )
+ return FALSE;
+
+ if ( !sieve_opr_object_read_data
+ (sblock, &oprnd, &sieve_testsuite_object_operand_class, address,
+ &tobj->object) )
+ return FALSE;
+
+ tobj->def = (const struct testsuite_object_def *) tobj->object.def;
+ i_assert(tobj->def != NULL);
+ return TRUE;
+}
+
+bool testsuite_object_read_member
+(struct sieve_binary_block *sblock, sieve_size_t *address,
+ struct testsuite_object *tobj, int *member_id_r)
+{
+ if ( !testsuite_object_read(sblock, address, tobj) )
+ return FALSE;
+
+ *member_id_r = -1;
+ if ( tobj->def->get_member_id != NULL ) {
+ if ( !sieve_binary_read_code(sblock, address, member_id_r) )
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+const char *testsuite_object_member_name
+(const struct testsuite_object *object, int member_id)
+{
+ const struct testsuite_object_def *obj_def = object->def;
+ const char *member = NULL;
+
+ if ( obj_def->get_member_id != NULL ) {
+ if ( obj_def->get_member_name != NULL )
+ member = obj_def->get_member_name(member_id);
+ } else
+ return obj_def->obj_def.identifier;
+
+ if ( member == NULL )
+ return t_strdup_printf("%s.%d", obj_def->obj_def.identifier, member_id);
+
+ return t_strdup_printf("%s.%s", obj_def->obj_def.identifier, member);
+}
+
+bool testsuite_object_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ struct testsuite_object object;
+ int member_id;
+
+ sieve_code_mark(denv);
+
+ if ( !testsuite_object_read_member
+ (denv->sblock, address, &object, &member_id) )
+ return FALSE;
+
+ sieve_code_dumpf(denv, "%s: %s",
+ sieve_testsuite_object_operand_class.name,
+ testsuite_object_member_name(&object, member_id));
+
+ return TRUE;
+}
+
+/*
+ * Testsuite object argument
+ */
+
+static bool arg_testsuite_object_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+const struct sieve_argument_def testsuite_object_argument = {
+ .identifier = "testsuite-object",
+ .generate = arg_testsuite_object_generate
+};
+
+struct testsuite_object_argctx {
+ const struct testsuite_object *object;
+ int member;
+};
+
+bool testsuite_object_argument_activate
+(struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd)
+{
+ const char *objname = sieve_ast_argument_strc(arg);
+ const struct testsuite_object *tobj;
+ int member_id;
+ const char *member;
+ struct testsuite_object_argctx *ctx;
+
+ /* Parse the object specifier */
+
+ member = strchr(objname, '.');
+ if ( member != NULL ) {
+ objname = t_strdup_until(objname, member);
+ member++;
+ }
+
+ /* Find the object */
+
+ tobj = testsuite_object_create(valdtr, cmd, objname);
+ if ( tobj == NULL ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "unknown testsuite object '%s'", objname);
+ return FALSE;
+ }
+
+ /* Find the object member */
+
+ member_id = -1;
+ if ( member != NULL ) {
+ if ( tobj->def == NULL || tobj->def->get_member_id == NULL ||
+ (member_id=tobj->def->get_member_id(member)) == -1 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "member '%s' does not exist for testsuite object '%s'", member, objname);
+ return FALSE;
+ }
+ }
+
+ /* Assign argument context */
+
+ ctx = p_new(sieve_command_pool(cmd), struct testsuite_object_argctx, 1);
+ ctx->object = tobj;
+ ctx->member = member_id;
+
+ arg->argument = sieve_argument_create
+ (arg->ast, &testsuite_object_argument, testsuite_ext, 0);
+ arg->argument->data = (void *) ctx;
+
+ return TRUE;
+}
+
+static bool arg_testsuite_object_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd ATTR_UNUSED)
+{
+ struct testsuite_object_argctx *ctx =
+ (struct testsuite_object_argctx *) arg->argument->data;
+
+ testsuite_object_emit(cgenv->sblock, ctx->object, ctx->member);
+
+ return TRUE;
+}
+
+/*
+ * Testsuite core object implementation
+ */
+
+static bool tsto_message_set_member
+ (const struct sieve_runtime_env *renv, int id, string_t *value);
+
+static int tsto_envelope_get_member_id(const char *identifier);
+static const char *tsto_envelope_get_member_name(int id);
+static bool tsto_envelope_set_member
+ (const struct sieve_runtime_env *renv, int id, string_t *value);
+
+const struct testsuite_object_def message_testsuite_object = {
+ SIEVE_OBJECT("message",
+ &testsuite_object_operand, TESTSUITE_OBJECT_MESSAGE),
+ .set_member = tsto_message_set_member
+};
+
+const struct testsuite_object_def envelope_testsuite_object = {
+ SIEVE_OBJECT("envelope",
+ &testsuite_object_operand, TESTSUITE_OBJECT_ENVELOPE),
+ .get_member_id = tsto_envelope_get_member_id,
+ .get_member_name = tsto_envelope_get_member_name,
+ .set_member = tsto_envelope_set_member
+};
+
+enum testsuite_object_envelope_field {
+ TESTSUITE_OBJECT_ENVELOPE_FROM,
+ TESTSUITE_OBJECT_ENVELOPE_TO,
+ TESTSUITE_OBJECT_ENVELOPE_ORIG_TO,
+ TESTSUITE_OBJECT_ENVELOPE_AUTH_USER
+};
+
+static bool tsto_message_set_member
+(const struct sieve_runtime_env *renv, int id, string_t *value)
+{
+ if ( id != -1 ) return FALSE;
+
+ testsuite_message_set_string(renv, value);
+
+ return TRUE;
+}
+
+static int tsto_envelope_get_member_id(const char *identifier)
+{
+ if ( strcasecmp(identifier, "from") == 0 )
+ return TESTSUITE_OBJECT_ENVELOPE_FROM;
+ if ( strcasecmp(identifier, "to") == 0 )
+ return TESTSUITE_OBJECT_ENVELOPE_TO;
+ if ( strcasecmp(identifier, "orig_to") == 0 )
+ return TESTSUITE_OBJECT_ENVELOPE_ORIG_TO;
+ if ( strcasecmp(identifier, "auth") == 0 )
+ return TESTSUITE_OBJECT_ENVELOPE_AUTH_USER;
+
+ return -1;
+}
+
+static const char *tsto_envelope_get_member_name(int id)
+{
+ switch ( id ) {
+ case TESTSUITE_OBJECT_ENVELOPE_FROM:
+ return "from";
+ case TESTSUITE_OBJECT_ENVELOPE_TO:
+ return "to";
+ case TESTSUITE_OBJECT_ENVELOPE_ORIG_TO:
+ return "orig_to";
+ case TESTSUITE_OBJECT_ENVELOPE_AUTH_USER:
+ return "auth";
+ }
+
+ return NULL;
+}
+
+static bool tsto_envelope_set_member
+(const struct sieve_runtime_env *renv, int id, string_t *value)
+{
+ switch ( id ) {
+ case TESTSUITE_OBJECT_ENVELOPE_FROM:
+ testsuite_envelope_set_sender(renv, str_c(value));
+ return TRUE;
+ case TESTSUITE_OBJECT_ENVELOPE_TO:
+ testsuite_envelope_set_recipient(renv, str_c(value));
+ return TRUE;
+ case TESTSUITE_OBJECT_ENVELOPE_ORIG_TO:
+ testsuite_envelope_set_orig_recipient(renv, str_c(value));
+ return TRUE;
+ case TESTSUITE_OBJECT_ENVELOPE_AUTH_USER:
+ testsuite_envelope_set_auth_user(renv, str_c(value));
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-objects.h b/pigeonhole/src/testsuite/testsuite-objects.h
new file mode 100644
index 0000000..0aa6f4f
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-objects.h
@@ -0,0 +1,83 @@
+#ifndef TESTSUITE_OBJECTS_H
+#define TESTSUITE_OBJECTS_H
+
+#include "sieve-common.h"
+#include "sieve-objects.h"
+
+#include "testsuite-common.h"
+
+/*
+ * Testsuite object operand
+ */
+
+struct testsuite_object_operand_interface {
+ struct sieve_extension_objects testsuite_objects;
+};
+
+extern const struct sieve_operand_class testsuite_object_oprclass;
+
+/*
+ * Testsuite object access
+ */
+
+struct testsuite_object_def {
+ struct sieve_object_def obj_def;
+
+ int (*get_member_id)(const char *identifier);
+ const char *(*get_member_name)(int id);
+
+ bool (*set_member)
+ (const struct sieve_runtime_env *renv, int id, string_t *value);
+ string_t *(*get_member)
+ (const struct sieve_runtime_env *renv, int id);
+};
+
+struct testsuite_object {
+ struct sieve_object object;
+
+ const struct testsuite_object_def *def;
+};
+
+/*
+ * Testsuite object registration
+ */
+
+void testsuite_register_core_objects
+ (struct testsuite_validator_context *ctx);
+void testsuite_object_register
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ const struct testsuite_object_def *tobj_def);
+
+/*
+ * Testsuite object argument
+ */
+
+bool testsuite_object_argument_activate
+ (struct sieve_validator *valdtr, struct sieve_ast_argument *arg,
+ struct sieve_command *cmd);
+
+/*
+ * Testsuite object code
+ */
+
+bool testsuite_object_read
+ (struct sieve_binary_block *sblock, sieve_size_t *address,
+ struct testsuite_object *tobj);
+bool testsuite_object_read_member
+ (struct sieve_binary_block *sblock, sieve_size_t *address,
+ struct testsuite_object *tobj, int *member_id_r);
+
+bool testsuite_object_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+
+const char *testsuite_object_member_name
+ (const struct testsuite_object *object, int member_id);
+
+/*
+ * Testsuite core objects
+ */
+
+extern const struct testsuite_object_def message_testsuite_object;
+extern const struct testsuite_object_def envelope_testsuite_object;
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-result.c b/pigeonhole/src/testsuite/testsuite-result.c
new file mode 100644
index 0000000..51be3ff
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-result.c
@@ -0,0 +1,206 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "ostream.h"
+#include "str.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-stringlist.h"
+#include "sieve-actions.h"
+#include "sieve-interpreter.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+#include "testsuite-message.h"
+#include "testsuite-mailstore.h"
+
+#include "testsuite-result.h"
+
+struct sieve_execute_env testsuite_execute_env;
+
+static pool_t testsuite_execute_pool = NULL;
+static struct sieve_result *_testsuite_result = NULL;
+static struct sieve_result_execution *_testsuite_rexec = NULL;
+
+void testsuite_result_init(void)
+{
+ struct sieve_instance *svinst = testsuite_sieve_instance;
+
+ testsuite_execute_pool = pool_alloconly_create("sieve execution", 4096);
+
+ sieve_execute_init(&testsuite_execute_env, testsuite_sieve_instance,
+ testsuite_execute_pool, &testsuite_msgdata,
+ testsuite_scriptenv, 0);
+
+ _testsuite_result = sieve_result_create(svinst, testsuite_execute_pool,
+ &testsuite_execute_env);
+}
+
+void testsuite_result_deinit(void)
+{
+ sieve_result_execution_destroy(&_testsuite_rexec);
+ if (_testsuite_result != NULL)
+ sieve_result_unref(&_testsuite_result);
+ sieve_execute_deinit(&testsuite_execute_env);
+ pool_unref(&testsuite_execute_pool);
+}
+
+void testsuite_result_reset(const struct sieve_runtime_env *renv)
+{
+ struct sieve_instance *svinst = testsuite_sieve_instance;
+
+ if (_testsuite_result != NULL) {
+ sieve_result_execution_destroy(&_testsuite_rexec);
+ sieve_result_unref(&_testsuite_result);
+ pool_unref(&testsuite_execute_pool);
+ }
+
+ testsuite_message_flush();
+ testsuite_mailstore_flush();
+ i_zero(testsuite_execute_env.exec_status);
+
+ testsuite_execute_pool = pool_alloconly_create("sieve execution", 4096);
+ _testsuite_result = sieve_result_create(svinst, testsuite_execute_pool,
+ &testsuite_execute_env);
+ sieve_interpreter_set_result(renv->interp, _testsuite_result);
+}
+
+struct sieve_result *testsuite_result_get(void)
+{
+ return _testsuite_result;
+}
+
+struct sieve_result_iterate_context *testsuite_result_iterate_init(void)
+{
+ if (_testsuite_result == NULL)
+ return NULL;
+
+ return sieve_result_iterate_init(_testsuite_result);
+}
+
+bool testsuite_result_execute(const struct sieve_runtime_env *renv)
+{
+ int ret;
+
+ if (_testsuite_result == NULL) {
+ sieve_runtime_error(renv, NULL, "testsuite: "
+ "trying to execute result, "
+ "but no result evaluated yet");
+ return FALSE;
+ }
+
+ testsuite_log_clear_messages();
+
+ if (_testsuite_rexec == NULL) {
+ _testsuite_rexec = sieve_result_execution_create(
+ _testsuite_result, testsuite_execute_pool);
+ }
+
+ /* Execute the result */
+ ret = sieve_result_execute(_testsuite_rexec, SIEVE_EXEC_OK, TRUE,
+ testsuite_log_ehandler, NULL);
+
+ return (ret > 0);
+}
+
+void testsuite_result_print(const struct sieve_runtime_env *renv)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ struct ostream *out;
+
+ out = o_stream_create_fd(1, 0);
+ o_stream_set_no_error_handling(out, TRUE);
+
+ o_stream_nsend_str(out, "\n--");
+ sieve_result_print(_testsuite_result, eenv->scriptenv, out, NULL);
+ o_stream_nsend_str(out, "--\n\n");
+
+ o_stream_destroy(&out);
+}
+
+/*
+ * Result stringlist
+ */
+
+/* Forward declarations */
+
+static int
+testsuite_result_stringlist_next_item(struct sieve_stringlist *_strlist,
+ string_t **str_r);
+static void
+testsuite_result_stringlist_reset(struct sieve_stringlist *_strlist);
+
+/* Stringlist object */
+
+struct testsuite_result_stringlist {
+ struct sieve_stringlist strlist;
+
+ struct sieve_result_iterate_context *result_iter;
+ int pos, index;
+};
+
+struct sieve_stringlist *
+testsuite_result_stringlist_create(const struct sieve_runtime_env *renv,
+ int index)
+{
+ struct testsuite_result_stringlist *strlist;
+
+ strlist = t_new(struct testsuite_result_stringlist, 1);
+ strlist->strlist.runenv = renv;
+ strlist->strlist.exec_status = SIEVE_EXEC_OK;
+ strlist->strlist.next_item = testsuite_result_stringlist_next_item;
+ strlist->strlist.reset = testsuite_result_stringlist_reset;
+
+ strlist->result_iter = testsuite_result_iterate_init();
+ strlist->index = index;
+ strlist->pos = 0;
+
+ return &strlist->strlist;
+}
+
+static int
+testsuite_result_stringlist_next_item(struct sieve_stringlist *_strlist,
+ string_t **str_r)
+{
+ struct testsuite_result_stringlist *strlist =
+ (struct testsuite_result_stringlist *)_strlist;
+ const struct sieve_action *action;
+ const char *act_name;
+ bool keep;
+
+ *str_r = NULL;
+
+ if (strlist->index > 0 && strlist->pos > 0)
+ return 0;
+
+ do {
+ if ((action = sieve_result_iterate_next(strlist->result_iter,
+ &keep)) == NULL)
+ return 0;
+
+ strlist->pos++;
+ } while (strlist->pos < strlist->index);
+
+ if (keep)
+ act_name = "keep";
+ else {
+ act_name = ((action == NULL || action->def == NULL ||
+ action->def->name == NULL) ?
+ "" : action->def->name);
+ }
+
+ *str_r = t_str_new_const(act_name, strlen(act_name));
+ return 1;
+}
+
+static void testsuite_result_stringlist_reset(struct sieve_stringlist *_strlist)
+{
+ struct testsuite_result_stringlist *strlist =
+ (struct testsuite_result_stringlist *)_strlist;
+
+ strlist->result_iter = testsuite_result_iterate_init();
+ strlist->pos = 0;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-result.h b/pigeonhole/src/testsuite/testsuite-result.h
new file mode 100644
index 0000000..3e232d2
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-result.h
@@ -0,0 +1,25 @@
+#ifndef TESTSUITE_RESULT_H
+#define TESTSUITE_RESULT_H
+
+#include "sieve-execute.h"
+
+extern struct sieve_execute_env testsuite_execute_env;
+
+void testsuite_result_init(void);
+void testsuite_result_deinit(void);
+
+void testsuite_result_reset(const struct sieve_runtime_env *renv);
+
+struct sieve_result *testsuite_result_get(void);
+
+struct sieve_result_iterate_context *testsuite_result_iterate_init(void);
+
+bool testsuite_result_execute(const struct sieve_runtime_env *renv);
+
+void testsuite_result_print(const struct sieve_runtime_env *renv ATTR_UNUSED);
+
+struct sieve_stringlist *
+testsuite_result_stringlist_create(const struct sieve_runtime_env *renv,
+ int index);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-script.c b/pigeonhole/src/testsuite/testsuite-script.c
new file mode 100644
index 0000000..b6edd9b
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-script.c
@@ -0,0 +1,256 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve.h"
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-interpreter.h"
+#include "sieve-runtime-trace.h"
+#include "sieve-result.h"
+
+#include "testsuite-common.h"
+#include "testsuite-settings.h"
+#include "testsuite-log.h"
+#include "testsuite-smtp.h"
+#include "testsuite-result.h"
+
+#include "testsuite-script.h"
+
+/*
+ * Tested script environment
+ */
+
+void testsuite_script_init(void)
+{
+}
+
+void testsuite_script_deinit(void)
+{
+}
+
+static struct sieve_binary *
+_testsuite_script_compile(const struct sieve_runtime_env *renv,
+ const char *script)
+{
+ struct sieve_instance *svinst = testsuite_sieve_instance;
+ struct sieve_binary *sbin;
+ const char *script_path;
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "compile script `%s'", script);
+
+ script_path = sieve_file_script_get_dirpath(renv->script);
+ if (script_path == NULL)
+ return NULL;
+
+ script_path = t_strconcat(script_path, "/", script, NULL);
+ if ((sbin = sieve_compile(svinst, script_path, NULL,
+ testsuite_log_ehandler, 0, NULL)) == NULL)
+ return NULL;
+
+ return sbin;
+}
+
+bool testsuite_script_compile(const struct sieve_runtime_env *renv,
+ const char *script)
+{
+ struct testsuite_interpreter_context *ictx =
+ testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+ struct sieve_binary *sbin;
+
+ i_assert(ictx != NULL);
+ testsuite_log_clear_messages();
+
+ if ((sbin = _testsuite_script_compile(renv, script)) == NULL)
+ return FALSE;
+
+ sieve_binary_unref(&ictx->compiled_script);
+
+ ictx->compiled_script = sbin;
+ return TRUE;
+}
+
+bool testsuite_script_is_subtest(const struct sieve_runtime_env *renv)
+{
+ struct testsuite_interpreter_context *ictx =
+ testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+
+ i_assert(ictx != NULL);
+ if (ictx->compiled_script == NULL)
+ return FALSE;
+
+ return (sieve_binary_extension_get_index(ictx->compiled_script,
+ testsuite_ext) >= 0);
+}
+
+bool testsuite_script_run(const struct sieve_runtime_env *renv)
+{
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ struct testsuite_interpreter_context *ictx =
+ testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+ struct sieve_script_env scriptenv;
+ struct sieve_exec_status exec_status;
+ struct sieve_result *result;
+ struct sieve_interpreter *interp;
+ pool_t pool;
+ struct sieve_execute_env exec_env;
+ const char *error;
+ int ret;
+
+ i_assert(ictx != NULL);
+
+ if (ictx->compiled_script == NULL) {
+ sieve_runtime_error(renv, NULL, "testsuite: "
+ "trying to run script, but no script compiled yet");
+ return FALSE;
+ }
+
+ testsuite_log_clear_messages();
+
+ i_zero(&exec_status);
+
+ /* Compose script execution environment */
+ if (sieve_script_env_init(&scriptenv, senv->user, &error) < 0) {
+ sieve_runtime_error(renv, NULL, "testsuite: "
+ "failed to initialize script execution: %s", error);
+ return FALSE;
+ }
+ scriptenv.default_mailbox = "INBOX";
+ scriptenv.smtp_start = testsuite_smtp_start;
+ scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
+ scriptenv.smtp_send = testsuite_smtp_send;
+ scriptenv.smtp_abort = testsuite_smtp_abort;
+ scriptenv.smtp_finish = testsuite_smtp_finish;
+ scriptenv.duplicate_mark = NULL;
+ scriptenv.duplicate_check = NULL;
+ scriptenv.trace_log = eenv->scriptenv->trace_log;
+ scriptenv.trace_config = eenv->scriptenv->trace_config;
+
+ result = testsuite_result_get();
+
+ pool = pool_alloconly_create("sieve execution", 4096);
+ sieve_execute_init(&exec_env, eenv->svinst, pool, eenv->msgdata,
+ &scriptenv, eenv->flags);
+ pool_unref(&pool);
+
+ /* Execute the script */
+ interp = sieve_interpreter_create(ictx->compiled_script, NULL,
+ &exec_env, testsuite_log_ehandler);
+
+ if (interp == NULL) {
+ sieve_execute_deinit(&exec_env);
+ return FALSE;
+ }
+
+ ret = sieve_interpreter_run(interp, result);
+ sieve_interpreter_free(&interp);
+
+ sieve_execute_finish(&exec_env, ret);
+ sieve_execute_deinit(&exec_env);
+
+ return (ret > 0 ||
+ sieve_binary_extension_get_index(ictx->compiled_script,
+ testsuite_ext) >= 0);
+}
+
+struct sieve_binary *
+testsuite_script_get_binary(const struct sieve_runtime_env *renv)
+{
+ struct testsuite_interpreter_context *ictx =
+ testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+
+ i_assert(ictx != NULL);
+ return ictx->compiled_script;
+}
+
+void testsuite_script_set_binary(const struct sieve_runtime_env *renv,
+ struct sieve_binary *sbin)
+{
+ struct testsuite_interpreter_context *ictx =
+ testsuite_interpreter_context_get(renv->interp, testsuite_ext);
+
+ i_assert(ictx != NULL);
+
+ sieve_binary_unref(&ictx->compiled_script);
+
+ ictx->compiled_script = sbin;
+ sieve_binary_ref(sbin);
+}
+
+/*
+ * Multiscript
+ */
+
+bool testsuite_script_multiscript(const struct sieve_runtime_env *renv,
+ ARRAY_TYPE (const_string) *scriptfiles)
+{
+ struct sieve_instance *svinst = testsuite_sieve_instance;
+ const struct sieve_execute_env *eenv = renv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ struct sieve_script_env scriptenv;
+ struct sieve_exec_status exec_status;
+ struct sieve_multiscript *mscript;
+ const char *const *scripts;
+ const char *error;
+ unsigned int count, i;
+ bool more = TRUE;
+ bool result = TRUE;
+
+ testsuite_log_clear_messages();
+
+ /* Compose script execution environment */
+ if (sieve_script_env_init(&scriptenv, senv->user, &error) < 0) {
+ sieve_runtime_error(renv, NULL,
+ "testsuite: failed to initialize script execution: %s",
+ error);
+ return FALSE;
+ }
+ scriptenv.default_mailbox = "INBOX";
+ scriptenv.smtp_start = testsuite_smtp_start;
+ scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
+ scriptenv.smtp_send = testsuite_smtp_send;
+ scriptenv.smtp_abort = testsuite_smtp_abort;
+ scriptenv.smtp_finish = testsuite_smtp_finish;
+ scriptenv.duplicate_mark = NULL;
+ scriptenv.duplicate_check = NULL;
+ scriptenv.trace_log = eenv->scriptenv->trace_log;
+ scriptenv.trace_config = eenv->scriptenv->trace_config;
+ scriptenv.exec_status = &exec_status;
+
+ /* Start execution */
+
+ mscript = sieve_multiscript_start_execute(svinst, eenv->msgdata,
+ &scriptenv);
+
+ /* Execute scripts before main script */
+
+ scripts = array_get(scriptfiles, &count);
+ for (i = 0; i < count && more; i++) {
+ struct sieve_binary *sbin = NULL;
+ const char *script = scripts[i];
+
+ /* Open */
+ if ((sbin = _testsuite_script_compile(renv, script)) == NULL) {
+ result = FALSE;
+ break;
+ }
+
+ /* Execute */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "run script `%s'", script);
+
+ more = sieve_multiscript_run(mscript, sbin,
+ testsuite_log_ehandler,
+ testsuite_log_ehandler, 0);
+
+ sieve_close(&sbin);
+ }
+
+ return (sieve_multiscript_finish(&mscript, testsuite_log_ehandler,
+ 0, SIEVE_EXEC_OK) > 0 && result);
+}
diff --git a/pigeonhole/src/testsuite/testsuite-script.h b/pigeonhole/src/testsuite/testsuite-script.h
new file mode 100644
index 0000000..547fdfc
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-script.h
@@ -0,0 +1,22 @@
+#ifndef TESTSUITE_SCRIPT_H
+#define TESTSUITE_SCRIPT_H
+
+#include "sieve-common.h"
+
+void testsuite_script_init(void);
+void testsuite_script_deinit(void);
+
+bool testsuite_script_is_subtest(const struct sieve_runtime_env *renv);
+
+bool testsuite_script_compile(const struct sieve_runtime_env *renv,
+ const char *script);
+bool testsuite_script_run(const struct sieve_runtime_env *renv);
+bool testsuite_script_multiscript(const struct sieve_runtime_env *renv,
+ ARRAY_TYPE (const_string) *scriptfiles);
+
+struct sieve_binary *
+testsuite_script_get_binary(const struct sieve_runtime_env *renv);
+void testsuite_script_set_binary(const struct sieve_runtime_env *renv,
+ struct sieve_binary *sbin);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-settings.c b/pigeonhole/src/testsuite/testsuite-settings.c
new file mode 100644
index 0000000..9d3c872
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-settings.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "hash.h"
+#include "imem.h"
+#include "strfuncs.h"
+#include "mail-user.h"
+
+#include "sieve-common.h"
+
+#include "testsuite-common.h"
+#include "testsuite-mailstore.h"
+#include "testsuite-settings.h"
+
+struct testsuite_setting {
+ char *identifier;
+ char *value;
+};
+
+static HASH_TABLE(const char *, struct testsuite_setting *) settings;
+
+static const char *testsuite_setting_get
+ (void *context, const char *identifier);
+
+void testsuite_settings_init(void)
+{
+ hash_table_create(&settings, default_pool, 0, str_hash, strcmp);
+
+ sieve_tool_set_setting_callback(sieve_tool, testsuite_setting_get, NULL);
+}
+
+void testsuite_settings_deinit(void)
+{
+ struct hash_iterate_context *itx =
+ hash_table_iterate_init(settings);
+ const char *key;
+ struct testsuite_setting *setting;
+
+ while ( hash_table_iterate(itx, settings, &key, &setting) ) {
+ i_free(setting->identifier);
+ i_free(setting->value);
+ i_free(setting);
+ }
+
+ hash_table_iterate_deinit(&itx);
+
+ hash_table_destroy(&settings);
+}
+
+static const char *testsuite_setting_get
+(void *context ATTR_UNUSED, const char *identifier)
+{
+ struct testsuite_setting *setting;
+ struct mail_user *user;
+
+ setting = hash_table_lookup(settings, identifier);
+ if ( setting != NULL )
+ return setting->value;
+
+ user = testsuite_mailstore_get_user();
+ if ( user == NULL )
+ return NULL;
+ return mail_user_plugin_getenv(user, identifier);
+}
+
+void testsuite_setting_set(const char *identifier, const char *value)
+{
+ struct testsuite_setting *setting =
+ hash_table_lookup(settings, identifier);
+
+ if ( setting != NULL ) {
+ i_free(setting->value);
+ setting->value = i_strdup(value);
+ } else {
+ setting = i_new(struct testsuite_setting, 1);
+ setting->identifier = i_strdup(identifier);
+ setting->value = i_strdup(value);
+
+ hash_table_insert(settings, identifier, setting);
+ }
+}
+
+void testsuite_setting_unset(const char *identifier)
+{
+ struct testsuite_setting *setting =
+ hash_table_lookup(settings, identifier);
+
+ if ( setting != NULL ) {
+ i_free(setting->identifier);
+ i_free(setting->value);
+ i_free(setting);
+
+ hash_table_remove(settings, identifier);
+ }
+}
diff --git a/pigeonhole/src/testsuite/testsuite-settings.h b/pigeonhole/src/testsuite/testsuite-settings.h
new file mode 100644
index 0000000..b84e620
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-settings.h
@@ -0,0 +1,12 @@
+#ifndef TESTSUITE_SETTINGS_H
+#define TESTSUITE_SETTINGS_H
+
+#include "sieve-common.h"
+
+void testsuite_settings_init(void);
+void testsuite_settings_deinit(void);
+
+void testsuite_setting_set(const char *identifier, const char *value);
+void testsuite_setting_unset(const char *identifier);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-smtp.c b/pigeonhole/src/testsuite/testsuite-smtp.c
new file mode 100644
index 0000000..0eb40ac
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-smtp.c
@@ -0,0 +1,176 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "ostream.h"
+#include "unlink-directory.h"
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-interpreter.h"
+
+#include "testsuite-message.h"
+#include "testsuite-common.h"
+#include "testsuite-smtp.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+struct testsuite_smtp_message {
+ const struct smtp_address *envelope_from, *envelope_to;
+ const char *file;
+};
+
+static pool_t testsuite_smtp_pool;
+static const char *testsuite_smtp_tmp;
+static ARRAY(struct testsuite_smtp_message) testsuite_smtp_messages;
+
+/*
+ * Initialize
+ */
+
+void testsuite_smtp_init(void)
+{
+ pool_t pool;
+
+ testsuite_smtp_pool = pool = pool_alloconly_create("testsuite_smtp", 8192);
+
+ testsuite_smtp_tmp = p_strconcat
+ (pool, testsuite_tmp_dir_get(), "/smtp", NULL);
+
+ if ( mkdir(testsuite_smtp_tmp, 0700) < 0 ) {
+ i_fatal("failed to create temporary directory '%s': %m.",
+ testsuite_smtp_tmp);
+ }
+
+ p_array_init(&testsuite_smtp_messages, pool, 16);
+}
+
+void testsuite_smtp_deinit(void)
+{
+ const char *error;
+
+ if ( unlink_directory(testsuite_smtp_tmp, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0 )
+ i_warning("failed to remove temporary directory '%s': %s.",
+ testsuite_smtp_tmp, error);
+
+ pool_unref(&testsuite_smtp_pool);
+}
+
+void testsuite_smtp_reset(void)
+{
+ testsuite_smtp_deinit();
+ testsuite_smtp_init();
+}
+
+/*
+ * Simulated SMTP out
+ */
+
+struct testsuite_smtp {
+ char *msg_file;
+ struct smtp_address *mail_from;
+ struct ostream *output;
+};
+
+void *testsuite_smtp_start
+(const struct sieve_script_env *senv ATTR_UNUSED,
+ const struct smtp_address *mail_from)
+{
+ struct testsuite_smtp *smtp;
+ unsigned int smtp_count = array_count(&testsuite_smtp_messages);
+ int fd;
+
+ smtp = i_new(struct testsuite_smtp, 1);
+
+ smtp->msg_file = i_strdup_printf("%s/%d.eml", testsuite_smtp_tmp, smtp_count);
+ smtp->mail_from = smtp_address_clone(default_pool, mail_from);
+
+ if ( (fd=open(smtp->msg_file, O_WRONLY | O_CREAT, 0600)) < 0 ) {
+ i_fatal("failed create tmp file for SMTP simulation: open(%s) failed: %m",
+ smtp->msg_file);
+ }
+
+ smtp->output = o_stream_create_fd_autoclose(&fd, (size_t)-1);
+
+ return (void *) smtp;
+}
+
+void testsuite_smtp_add_rcpt
+(const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle, const struct smtp_address *rcpt_to)
+{
+ struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+ struct testsuite_smtp_message *msg;
+
+ msg = array_append_space(&testsuite_smtp_messages);
+
+ msg->file = p_strdup(testsuite_smtp_pool, smtp->msg_file);
+ msg->envelope_from = smtp_address_clone(testsuite_smtp_pool, smtp->mail_from);
+ msg->envelope_to = smtp_address_clone(testsuite_smtp_pool, rcpt_to);
+}
+
+struct ostream *testsuite_smtp_send
+(const struct sieve_script_env *senv ATTR_UNUSED, void *handle)
+{
+ struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+
+ return smtp->output;
+}
+
+void testsuite_smtp_abort
+(const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle)
+{
+ struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+
+ o_stream_ignore_last_errors(smtp->output);
+ o_stream_unref(&smtp->output);
+ i_unlink(smtp->msg_file);
+ i_free(smtp->msg_file);
+ i_free(smtp->mail_from);
+ i_free(smtp);
+}
+
+int testsuite_smtp_finish
+(const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle, const char **error_r ATTR_UNUSED)
+{
+ struct testsuite_smtp *smtp = (struct testsuite_smtp *) handle;
+ int ret = 1;
+
+ if (o_stream_finish(smtp->output) < 0) {
+ i_error("write(%s) failed: %s", smtp->msg_file,
+ o_stream_get_error(smtp->output));
+ ret = -1;
+ }
+ o_stream_unref(&smtp->output);
+ i_free(smtp->msg_file);
+ i_free(smtp->mail_from);
+ i_free(smtp);
+ return ret;
+}
+
+/*
+ * Access
+ */
+
+bool testsuite_smtp_get
+(const struct sieve_runtime_env *renv, unsigned int index)
+{
+ const struct testsuite_smtp_message *smtp_msg;
+
+ if ( index >= array_count(&testsuite_smtp_messages) )
+ return FALSE;
+
+ smtp_msg = array_idx(&testsuite_smtp_messages, index);
+
+ testsuite_message_set_file(renv, smtp_msg->file);
+ testsuite_envelope_set_sender_address(renv, smtp_msg->envelope_from);
+ testsuite_envelope_set_recipient_address(renv, smtp_msg->envelope_to);
+
+ return TRUE;
+}
diff --git a/pigeonhole/src/testsuite/testsuite-smtp.h b/pigeonhole/src/testsuite/testsuite-smtp.h
new file mode 100644
index 0000000..0b120b2
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-smtp.h
@@ -0,0 +1,35 @@
+#ifndef TESTSUITE_SMTP_H
+#define TESTSUITE_SMTP_H
+
+void testsuite_smtp_init(void);
+void testsuite_smtp_deinit(void);
+void testsuite_smtp_reset(void);
+
+/*
+ * Simulated SMTP out
+ */
+
+void *testsuite_smtp_start
+ (const struct sieve_script_env *senv ATTR_UNUSED,
+ const struct smtp_address *mail_from);
+void testsuite_smtp_add_rcpt
+ (const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle, const struct smtp_address *rcpt_to);
+struct ostream *testsuite_smtp_send
+ (const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle);
+void testsuite_smtp_abort
+ (const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle);
+int testsuite_smtp_finish
+ (const struct sieve_script_env *senv ATTR_UNUSED,
+ void *handle, const char **error_r);
+
+/*
+ * Access
+ */
+
+bool testsuite_smtp_get
+ (const struct sieve_runtime_env *renv, unsigned int index);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-substitutions.c b/pigeonhole/src/testsuite/testsuite-substitutions.c
new file mode 100644
index 0000000..b165587
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-substitutions.c
@@ -0,0 +1,253 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-binary.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "testsuite-common.h"
+#include "testsuite-substitutions.h"
+
+/*
+ * Forward declarations
+ */
+
+void testsuite_opr_substitution_emit
+ (struct sieve_binary_block *sblock, const struct testsuite_substitution *tsub,
+ const char *param);
+
+/*
+ * Testsuite substitutions
+ */
+
+/* FIXME: make this extendible */
+
+enum {
+ TESTSUITE_SUBSTITUTION_FILE,
+};
+
+static const struct testsuite_substitution_def testsuite_file_substitution;
+
+static const struct testsuite_substitution_def *substitutions[] = {
+ &testsuite_file_substitution,
+};
+
+static const unsigned int substitutions_count = N_ELEMENTS(substitutions);
+
+static inline const struct testsuite_substitution_def *
+testsuite_substitution_get
+(unsigned int code)
+{
+ if ( code >= substitutions_count )
+ return NULL;
+
+ return substitutions[code];
+}
+
+static const struct testsuite_substitution *testsuite_substitution_create
+(struct sieve_ast *ast, const char *identifier)
+{
+ unsigned int i;
+
+ for ( i = 0; i < substitutions_count; i++ ) {
+ if ( strcasecmp(substitutions[i]->obj_def.identifier, identifier) == 0 ) {
+ const struct testsuite_substitution_def *tsub_def = substitutions[i];
+ struct testsuite_substitution *tsub;
+
+ tsub = p_new(sieve_ast_pool(ast), struct testsuite_substitution, 1);
+ tsub->object.def = &tsub_def->obj_def;
+ tsub->object.ext = testsuite_ext;
+ tsub->def = tsub_def;
+
+ return tsub;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Substitution argument
+ */
+
+static bool arg_testsuite_substitution_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context);
+
+struct _testsuite_substitution_context {
+ const struct testsuite_substitution *tsub;
+ const char *param;
+};
+
+const struct sieve_argument_def testsuite_substitution_argument = {
+ .identifier = "@testsuite-substitution",
+ .generate = arg_testsuite_substitution_generate
+};
+
+struct sieve_ast_argument *testsuite_substitution_argument_create
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast *ast,
+ unsigned int source_line, const char *substitution, const char *param)
+{
+ const struct testsuite_substitution *tsub;
+ struct _testsuite_substitution_context *tsctx;
+ struct sieve_ast_argument *arg;
+ pool_t pool;
+
+ tsub = testsuite_substitution_create(ast, substitution);
+ if ( tsub == NULL )
+ return NULL;
+
+ arg = sieve_ast_argument_create(ast, source_line);
+ arg->type = SAAT_STRING;
+
+ pool = sieve_ast_pool(ast);
+ tsctx = p_new(pool, struct _testsuite_substitution_context, 1);
+ tsctx->tsub = tsub;
+ tsctx->param = p_strdup(pool, param);
+
+ arg->argument = sieve_argument_create
+ (ast, &testsuite_substitution_argument, testsuite_ext, 0);
+ arg->argument->data = (void *) tsctx;
+
+ return arg;
+}
+
+static bool arg_testsuite_substitution_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
+ struct sieve_command *context ATTR_UNUSED)
+{
+ struct _testsuite_substitution_context *tsctx =
+ (struct _testsuite_substitution_context *) arg->argument->data;
+
+ testsuite_opr_substitution_emit(cgenv->sblock, tsctx->tsub, tsctx->param);
+
+ return TRUE;
+}
+
+/*
+ * Substitution operand
+ */
+
+static bool opr_substitution_dump
+ (const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address);
+static int opr_substitution_read_value
+ (const struct sieve_runtime_env *renv, const struct sieve_operand *oprnd,
+ sieve_size_t *address, string_t **str);
+
+const struct sieve_opr_string_interface testsuite_substitution_interface = {
+ opr_substitution_dump,
+ opr_substitution_read_value
+};
+
+const struct sieve_operand_def testsuite_substitution_operand = {
+ .name = "test-substitution",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERAND_SUBSTITUTION,
+ .class = &string_class,
+ .interface = &testsuite_substitution_interface
+};
+
+void testsuite_opr_substitution_emit
+(struct sieve_binary_block *sblock, const struct testsuite_substitution *tsub,
+ const char *param)
+{
+ /* Default variable storage */
+ (void) sieve_operand_emit
+ (sblock, testsuite_ext, &testsuite_substitution_operand);
+ (void) sieve_binary_emit_unsigned(sblock, tsub->object.def->code);
+ (void) sieve_binary_emit_cstring(sblock, param);
+}
+
+static bool opr_substitution_dump
+(const struct sieve_dumptime_env *denv, const struct sieve_operand *oprnd,
+ sieve_size_t *address)
+{
+ unsigned int code = 0;
+ const struct testsuite_substitution_def *tsub;
+ string_t *param;
+
+ if ( !sieve_binary_read_unsigned(denv->sblock, address, &code) )
+ return FALSE;
+
+ tsub = testsuite_substitution_get(code);
+ if ( tsub == NULL )
+ return FALSE;
+
+ if ( !sieve_binary_read_string(denv->sblock, address, &param) )
+ return FALSE;
+
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf(denv, "%s: TEST_SUBS %%{%s:%s}",
+ oprnd->field_name, tsub->obj_def.identifier, str_c(param));
+ else
+ sieve_code_dumpf(denv, "TEST_SUBS %%{%s:%s}",
+ tsub->obj_def.identifier, str_c(param));
+ return TRUE;
+}
+
+static int opr_substitution_read_value
+(const struct sieve_runtime_env *renv,
+ const struct sieve_operand *oprnd ATTR_UNUSED, sieve_size_t *address,
+ string_t **str_r)
+{
+ const struct testsuite_substitution_def *tsub;
+ unsigned int code = 0;
+ string_t *param;
+
+ if ( !sieve_binary_read_unsigned(renv->sblock, address, &code) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ tsub = testsuite_substitution_get(code);
+ if ( tsub == NULL )
+ return SIEVE_EXEC_FAILURE;
+
+ /* Parameter str can be NULL if we are requested to only skip and not
+ * actually read the argument.
+ */
+ if ( str_r == NULL ) {
+ if ( !sieve_binary_read_string(renv->sblock, address, NULL) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ return SIEVE_EXEC_OK;
+ }
+
+ if ( !sieve_binary_read_string(renv->sblock, address, &param) )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if ( !tsub->get_value(str_c(param), str_r) )
+ return SIEVE_EXEC_FAILURE;
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Testsuite substitution definitions
+ */
+
+static bool testsuite_file_substitution_get_value
+ (const char *param, string_t **result);
+
+static const struct testsuite_substitution_def
+testsuite_file_substitution = {
+ SIEVE_OBJECT("file",
+ &testsuite_substitution_operand,
+ TESTSUITE_SUBSTITUTION_FILE),
+ .get_value = testsuite_file_substitution_get_value
+};
+
+static bool testsuite_file_substitution_get_value
+(const char *param, string_t **result)
+{
+ *result = t_str_new(256);
+
+ str_printfa(*result, "[FILE: %s]", param);
+ return TRUE;
+}
+
diff --git a/pigeonhole/src/testsuite/testsuite-substitutions.h b/pigeonhole/src/testsuite/testsuite-substitutions.h
new file mode 100644
index 0000000..8b256d0
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-substitutions.h
@@ -0,0 +1,23 @@
+#ifndef TESTSUITE_SUBSTITUTIONS_H
+#define TESTSUITE_SUBSTITUTIONS_H
+
+#include "sieve-common.h"
+#include "sieve-objects.h"
+
+struct testsuite_substitution_def {
+ struct sieve_object_def obj_def;
+
+ bool (*get_value)(const char *param, string_t **result);
+};
+
+struct testsuite_substitution {
+ struct sieve_object object;
+
+ const struct testsuite_substitution_def *def;
+};
+
+struct sieve_ast_argument *testsuite_substitution_argument_create
+ (struct sieve_validator *valdtr, struct sieve_ast *ast,
+ unsigned int source_line, const char *substitution, const char *param);
+
+#endif
diff --git a/pigeonhole/src/testsuite/testsuite-variables.c b/pigeonhole/src/testsuite/testsuite-variables.c
new file mode 100644
index 0000000..f644393
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-variables.c
@@ -0,0 +1,183 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-ast.h"
+#include "sieve-binary.h"
+#include "sieve-code.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+
+#include "sieve-ext-variables.h"
+
+#include "testsuite-common.h"
+#include "testsuite-variables.h"
+
+/*
+ *
+ */
+
+static const struct sieve_extension *testsuite_ext_variables = NULL;
+
+/*
+ *
+ */
+
+bool testsuite_varnamespace_validate
+ (struct sieve_validator *valdtr, const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment);
+bool testsuite_varnamespace_generate
+ (const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd, void *var_data);
+bool testsuite_varnamespace_dump_variable
+ (const struct sieve_dumptime_env *denv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd, sieve_size_t *address);
+int testsuite_varnamespace_read_variable
+ (const struct sieve_runtime_env *renv,
+ const struct sieve_variables_namespace *nspc,
+ const struct sieve_operand *oprnd, sieve_size_t *address, string_t **str_r);
+
+static const struct sieve_variables_namespace_def testsuite_namespace = {
+ SIEVE_OBJECT("tst", &testsuite_namespace_operand, 0),
+ testsuite_varnamespace_validate,
+ testsuite_varnamespace_generate,
+ testsuite_varnamespace_dump_variable,
+ testsuite_varnamespace_read_variable
+};
+
+bool testsuite_varnamespace_validate
+(struct sieve_validator *valdtr,
+ const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+ struct sieve_ast_argument *arg, struct sieve_command *cmd ATTR_UNUSED,
+ ARRAY_TYPE(sieve_variable_name) *var_name, void **var_data,
+ bool assignment)
+{
+ struct sieve_ast *ast = arg->ast;
+ const struct sieve_variable_name *name_element;
+ const char *variable;
+
+ /* Check variable name */
+
+ if ( array_count(var_name) != 2 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "testsuite: invalid variable name within testsuite namespace: "
+ "encountered sub-namespace");
+ return FALSE;
+ }
+
+ name_element = array_idx(var_name, 1);
+ if ( name_element->num_variable >= 0 ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "testsuite: invalid variable name within testsuite namespace 'tst.%d': "
+ "encountered numeric variable name", name_element->num_variable);
+ return FALSE;
+ }
+
+ variable = str_c(name_element->identifier);
+
+ if ( assignment ) {
+ sieve_argument_validate_error(valdtr, arg,
+ "testsuite: cannot assign to testsuite variable 'tst.%s'", variable);
+ return FALSE;
+ }
+
+ *var_data = (void *) p_strdup(sieve_ast_pool(ast), variable);
+
+ return TRUE;
+}
+
+bool testsuite_varnamespace_generate
+(const struct sieve_codegen_env *cgenv,
+ const struct sieve_variables_namespace *nspc,
+ struct sieve_ast_argument *arg ATTR_UNUSED,
+ struct sieve_command *cmd ATTR_UNUSED, void *var_data)
+{
+ const struct sieve_extension *this_ext = SIEVE_OBJECT_EXTENSION(nspc);
+ const char *variable = (const char *) var_data;
+
+ if ( this_ext == NULL )
+ return FALSE;
+
+ sieve_variables_opr_namespace_variable_emit
+ (cgenv->sblock, testsuite_ext_variables, this_ext, &testsuite_namespace);
+ sieve_binary_emit_cstring(cgenv->sblock, variable);
+
+ return TRUE;
+}
+
+bool testsuite_varnamespace_dump_variable
+(const struct sieve_dumptime_env *denv,
+ const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+ const struct sieve_operand *oprnd, sieve_size_t *address)
+{
+ string_t *var_name;
+
+ if ( !sieve_binary_read_string(denv->sblock, address, &var_name) )
+ return FALSE;
+
+ if ( oprnd->field_name != NULL )
+ sieve_code_dumpf(denv, "%s: VAR ${tst.%s}",
+ oprnd->field_name, str_c(var_name));
+ else
+ sieve_code_dumpf(denv, "VAR ${tst.%s}",
+ str_c(var_name));
+
+ return TRUE;
+}
+
+int testsuite_varnamespace_read_variable
+(const struct sieve_runtime_env *renv,
+ const struct sieve_variables_namespace *nspc ATTR_UNUSED,
+ const struct sieve_operand *oprnd, sieve_size_t *address,
+ string_t **str_r)
+{
+ string_t *var_name;
+
+ if ( !sieve_binary_read_string(renv->sblock, address, &var_name) ) {
+ sieve_runtime_trace_operand_error(renv, oprnd,
+ "testsuite variable operand corrupt: invalid name");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if ( str_r != NULL ) {
+ if ( strcmp(str_c(var_name), "path") == 0 )
+ *str_r = t_str_new_const(testsuite_test_path, strlen(testsuite_test_path));
+ else
+ *str_r = NULL;
+ }
+ return SIEVE_EXEC_OK;
+}
+
+
+/*
+ * Namespace registration
+ */
+
+static const struct sieve_extension_objects testsuite_namespaces =
+ SIEVE_VARIABLES_DEFINE_NAMESPACE(testsuite_namespace);
+
+const struct sieve_operand_def testsuite_namespace_operand = {
+ .name = "testsuite-namespace",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERAND_NAMESPACE,
+ .class = &sieve_variables_namespace_operand_class,
+ .interface = &testsuite_namespaces
+};
+
+void testsuite_variables_init
+(const struct sieve_extension *this_ext, struct sieve_validator *valdtr)
+{
+ testsuite_ext_variables = sieve_ext_variables_get_extension(this_ext->svinst);
+
+ sieve_variables_namespace_register
+ (testsuite_ext_variables, valdtr, this_ext, &testsuite_namespace);
+}
diff --git a/pigeonhole/src/testsuite/testsuite-variables.h b/pigeonhole/src/testsuite/testsuite-variables.h
new file mode 100644
index 0000000..60f6b18
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite-variables.h
@@ -0,0 +1,11 @@
+#ifndef TESTSUITE_VARIABLES_H
+#define TESTSUITE_VARIABLES_H
+
+extern const struct sieve_operand_def testsuite_namespace_operand;
+
+void testsuite_variables_init
+ (const struct sieve_extension *this_ext, struct sieve_validator *valdtr);
+
+#endif
+
+
diff --git a/pigeonhole/src/testsuite/testsuite.c b/pigeonhole/src/testsuite/testsuite.c
new file mode 100644
index 0000000..2a6b82e
--- /dev/null
+++ b/pigeonhole/src/testsuite/testsuite.c
@@ -0,0 +1,256 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "ioloop.h"
+#include "env-util.h"
+#include "ostream.h"
+#include "hostpid.h"
+#include "path-util.h"
+
+#include "sieve.h"
+#include "sieve-extensions.h"
+#include "sieve-script.h"
+#include "sieve-binary.h"
+#include "sieve-result.h"
+#include "sieve-interpreter.h"
+
+#include "sieve-tool.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+#include "testsuite-settings.h"
+#include "testsuite-result.h"
+#include "testsuite-message.h"
+#include "testsuite-smtp.h"
+#include "testsuite-mailstore.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sysexits.h>
+
+const struct sieve_script_env *testsuite_scriptenv;
+
+/*
+ * Configuration
+ */
+
+#define DEFAULT_SENDMAIL_PATH "/usr/lib/sendmail"
+
+/*
+ * Testsuite execution
+ */
+
+static void print_help(void)
+{
+ printf(
+"Usage: testsuite [-D] [-E] [-F] [-d <dump-filename>]\n"
+" [-t <trace-filename>] [-T <trace-option>]\n"
+" [-P <plugin>] [-x <extensions>]\n"
+" <scriptfile>\n"
+ );
+}
+
+static int
+testsuite_run(struct sieve_binary *sbin, struct sieve_error_handler *ehandler)
+{
+ struct sieve_interpreter *interp;
+ struct sieve_result *result;
+ int ret = 0;
+
+ /* Create the interpreter */
+ interp = sieve_interpreter_create(sbin, NULL, &testsuite_execute_env,
+ ehandler);
+ if (interp == NULL)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ /* Run the interpreter */
+ result = testsuite_result_get();
+ ret = sieve_interpreter_run(interp, result);
+
+ /* Free the interpreter */
+ sieve_interpreter_free(&interp);
+
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ struct sieve_instance *svinst;
+ const char *scriptfile, *dumpfile, *tracefile;
+ struct sieve_trace_config trace_config;
+ struct sieve_binary *sbin;
+ const char *sieve_dir, *cwd, *error;
+ bool log_stdout = FALSE, expect_failure = FALSE;
+ int ret, c;
+
+ sieve_tool = sieve_tool_init("testsuite", &argc, &argv,
+ "d:t:T:EFDP:", TRUE);
+
+ /* Parse arguments */
+ dumpfile = tracefile = NULL;
+ i_zero(&trace_config);
+ trace_config.level = SIEVE_TRLVL_ACTIONS;
+ while ((c = sieve_tool_getopt(sieve_tool)) > 0) {
+ switch (c) {
+ case 'd':
+ /* destination address */
+ dumpfile = optarg;
+ break;
+ case 't':
+ /* trace file */
+ tracefile = optarg;
+ break;
+ case 'T':
+ sieve_tool_parse_trace_option(&trace_config, optarg);
+ break;
+ case 'E':
+ log_stdout = TRUE;
+ break;
+ case 'F':
+ expect_failure = TRUE;
+ break;
+ default:
+ print_help();
+ i_fatal_status(EX_USAGE, "Unknown argument: %c", c);
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ scriptfile = t_strdup(argv[optind++]);
+ } else {
+ print_help();
+ i_fatal_status(EX_USAGE, "Missing <scriptfile> argument");
+ }
+
+ if (optind != argc) {
+ print_help();
+ i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
+ }
+
+ // FIXME: very very ugly
+ master_service_parse_option(
+ master_service, 'o',
+ "postmaster_address=postmaster@example.com");
+
+ /* Initialize mail user */
+ if (t_get_working_dir(&cwd, &error) < 0)
+ i_fatal("Failed to get working directory: %s", error);
+ sieve_tool_set_homedir(sieve_tool, cwd);
+
+ /* Initialize settings environment */
+ testsuite_settings_init();
+
+ /* Currently needed for include (FIXME) */
+ sieve_dir = strrchr(scriptfile, '/');
+ if (sieve_dir == NULL)
+ sieve_dir = "./";
+ else
+ sieve_dir = t_strdup_until(scriptfile, sieve_dir+1);
+
+ testsuite_setting_set("sieve_dir",
+ t_strconcat(sieve_dir, "included", NULL));
+ testsuite_setting_set("sieve_global_dir",
+ t_strconcat(sieve_dir, "included-global", NULL));
+
+ /* Finish testsuite initialization */
+ svinst = sieve_tool_init_finish(sieve_tool, FALSE, FALSE);
+ testsuite_init(svinst, sieve_dir, log_stdout);
+
+ printf("Test case: %s:\n\n", scriptfile);
+
+ /* Compile sieve script */
+ if ((sbin = sieve_compile(svinst, scriptfile, NULL,
+ testsuite_log_main_ehandler,
+ 0, NULL)) != NULL) {
+ struct sieve_trace_log *trace_log = NULL;
+ struct sieve_exec_status exec_status;
+ struct sieve_script_env scriptenv;
+
+ /* Dump script */
+ sieve_tool_dump_binary_to(sbin, dumpfile, FALSE);
+
+ if (tracefile != NULL) {
+ (void)sieve_trace_log_create(
+ svinst, (strcmp(tracefile, "-") == 0 ?
+ NULL : tracefile), &trace_log);
+ }
+
+ testsuite_mailstore_init();
+ testsuite_message_init();
+
+ if (sieve_script_env_init(&scriptenv,
+ testsuite_mailstore_get_user(),
+ &error) < 0) {
+ i_fatal("Failed to initialize script execution: %s",
+ error);
+ }
+
+ i_zero(&exec_status);
+
+ scriptenv.default_mailbox = "INBOX";
+ scriptenv.smtp_start = testsuite_smtp_start;
+ scriptenv.smtp_add_rcpt = testsuite_smtp_add_rcpt;
+ scriptenv.smtp_send = testsuite_smtp_send;
+ scriptenv.smtp_abort = testsuite_smtp_abort;
+ scriptenv.smtp_finish = testsuite_smtp_finish;
+ scriptenv.trace_log = trace_log;
+ scriptenv.trace_config = trace_config;
+ scriptenv.exec_status = &exec_status;
+
+ testsuite_scriptenv = &scriptenv;
+
+ testsuite_result_init();
+
+ /* Run the test */
+ ret = testsuite_run(sbin, testsuite_log_main_ehandler);
+
+ switch (ret) {
+ case SIEVE_EXEC_OK:
+ break;
+ case SIEVE_EXEC_FAILURE:
+ case SIEVE_EXEC_KEEP_FAILED:
+ case SIEVE_EXEC_TEMP_FAILURE:
+ testsuite_testcase_fail(
+ "test script execution aborted due to error");
+ break;
+ case SIEVE_EXEC_BIN_CORRUPT:
+ testsuite_testcase_fail(
+ "compiled test script binary is corrupt");
+ break;
+ case SIEVE_EXEC_RESOURCE_LIMIT:
+ testsuite_testcase_fail(
+ "resource limit exceeded");
+ break;
+ }
+
+ sieve_close(&sbin);
+
+ /* De-initialize message environment */
+ testsuite_result_deinit();
+ testsuite_message_deinit();
+ testsuite_mailstore_deinit();
+
+ if (trace_log != NULL)
+ sieve_trace_log_free(&trace_log);
+
+ testsuite_scriptenv = NULL;
+ } else {
+ testsuite_testcase_fail("failed to compile testcase script");
+ }
+
+ /* De-initialize testsuite */
+ testsuite_deinit();
+ testsuite_settings_deinit();
+
+ sieve_tool_deinit(&sieve_tool);
+
+ if (!testsuite_testcase_result(expect_failure))
+ return EXIT_FAILURE;
+
+ return EXIT_SUCCESS;
+}
diff --git a/pigeonhole/src/testsuite/tst-test-error.c b/pigeonhole/src/testsuite/tst-test-error.c
new file mode 100644
index 0000000..f2f2eb9
--- /dev/null
+++ b/pigeonhole/src/testsuite/tst-test-error.c
@@ -0,0 +1,273 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-error.h"
+#include "sieve-script.h"
+#include "sieve-commands.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-dump.h"
+#include "sieve-match.h"
+
+#include "testsuite-common.h"
+#include "testsuite-log.h"
+
+/*
+ * Test_error command
+ *
+ * Syntax:
+ * test [MATCH-TYPE] [COMPARATOR] [:index number] <key-list: string-list>
+ */
+
+static bool tst_test_error_registered
+ (struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_test_error_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool tst_test_error_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_test_error = {
+ .identifier = "test_error",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_test_error_registered,
+ .validate = tst_test_error_validate,
+ .generate = tst_test_error_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_error_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_error_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_error_operation = {
+ .mnemonic = "TEST_ERROR",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_ERROR,
+ .dump = tst_test_error_operation_dump,
+ .execute = tst_test_error_operation_execute
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* NOTE: This will be merged with the date-index extension when it is
+ * implemented.
+ */
+
+static bool tst_test_error_validate_index_tag
+ (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_error_index_tag = {
+ .identifier = "index",
+ .validate = tst_test_error_validate_index_tag
+};
+
+enum tst_test_error_optional {
+ OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+};
+
+
+/*
+ * Argument implementation
+ */
+
+static bool tst_test_error_validate_index_tag
+(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
+ struct sieve_command *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
+ (valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+ return FALSE;
+ }
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ return TRUE;
+}
+
+
+/*
+ * Command registration
+ */
+
+static bool tst_test_error_registered
+(struct sieve_validator *valdtr, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ /* The order of these is not significant */
+ sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
+ sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
+
+ sieve_validator_register_tag
+ (valdtr, cmd_reg, ext, &test_error_index_tag, OPT_INDEX);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_test_error_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+ struct sieve_match_type mcht_default =
+ SIEVE_COMPARATOR_DEFAULT(is_match_type);
+
+ 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, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_error_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &test_error_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_error_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "TEST_ERROR:");
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+ < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ if ( opt_code == OPT_INDEX ) {
+ if ( !sieve_opr_number_dump(denv, address, "index") )
+ return FALSE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_error_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ int opt_code = 0;
+ struct sieve_comparator cmp = SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+ struct sieve_match_type mcht = SIEVE_COMPARATOR_DEFAULT(is_match_type);
+ struct sieve_stringlist *value_list, *key_list;
+ int index = -1;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read optional operands */
+ for (;;) {
+ sieve_number_t number;
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_read
+ (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ if ( opt_code == OPT_INDEX ) {
+ if ( (ret=sieve_opr_number_read(renv, address, "index", &number)) <= 0 )
+ return ret;
+ index = (int) number;
+ } else {
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key_list", &key_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if ( index > 0 )
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "testsuite: test_error test; match error message [index=%d]", index);
+ else
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "testsuite: test_error test; match error messages");
+
+ /* Create value stringlist */
+ value_list = testsuite_log_stringlist_create(renv, index);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
+
+
+
+
diff --git a/pigeonhole/src/testsuite/tst-test-multiscript.c b/pigeonhole/src/testsuite/tst-test-multiscript.c
new file mode 100644
index 0000000..b4b2a0e
--- /dev/null
+++ b/pigeonhole/src/testsuite/tst-test-multiscript.c
@@ -0,0 +1,155 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-binary.h"
+#include "sieve-dump.h"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-script.h"
+
+/*
+ * Test_multiscript command
+ *
+ * Syntax:
+ * test_multiscript <scripts: string-list>
+ */
+
+static bool tst_test_multiscript_validate
+ (struct sieve_validator *validator, struct sieve_command *cmd);
+static bool tst_test_multiscript_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *tst);
+
+const struct sieve_command_def tst_test_multiscript = {
+ .identifier = "test_multiscript",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_test_multiscript_validate,
+ .generate = tst_test_multiscript_generate,
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_multiscript_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_multiscript_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_multiscript_operation = {
+ .mnemonic = "TEST_MULTISCRIPT",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_MULTISCRIPT,
+ .dump = tst_test_multiscript_operation_dump,
+ .execute = tst_test_multiscript_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool tst_test_multiscript_validate
+(struct sieve_validator *valdtr, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "scripts", 1, SAAT_STRING_LIST) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_multiscript_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &test_multiscript_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_multiscript_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "TEST_MULTISCRIPT:");
+ sieve_code_descend(denv);
+
+ if ( !sieve_opr_stringlist_dump(denv, address, "scripts") )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_multiscript_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ struct sieve_stringlist *scripts_list;
+ string_t *script_name;
+ ARRAY_TYPE (const_string) scriptfiles;
+ bool result = TRUE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "scripts", &scripts_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "testsuite: test_multiscript test");
+ sieve_runtime_trace_descend(renv);
+
+ t_array_init(&scriptfiles, 16);
+
+ script_name = NULL;
+ while ( result &&
+ (ret=sieve_stringlist_next_item(scripts_list, &script_name)) > 0 ) {
+ const char *script = t_strdup(str_c(script_name));
+
+ array_append(&scriptfiles, &script, 1);
+ }
+
+ result = result && (ret >= 0) &&
+ testsuite_script_multiscript(renv, &scriptfiles);
+
+ /* Set result */
+ sieve_interpreter_set_test_result(renv->interp, result);
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
+
diff --git a/pigeonhole/src/testsuite/tst-test-result-action.c b/pigeonhole/src/testsuite/tst-test-result-action.c
new file mode 100644
index 0000000..75d4a2f
--- /dev/null
+++ b/pigeonhole/src/testsuite/tst-test-result-action.c
@@ -0,0 +1,268 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#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_action command
+ *
+ * Syntax:
+ * test_result_action [MATCH-TYPE] [COMPARATOR] [:index number]
+ * <key-list: string-list>
+ */
+
+static bool tst_test_result_action_registered
+ (struct sieve_validator *validator, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_test_result_action_validate
+ (struct sieve_validator *validator, struct sieve_command *cmd);
+static bool tst_test_result_action_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
+
+const struct sieve_command_def tst_test_result_action = {
+ .identifier = "test_result_action",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_test_result_action_registered,
+ .validate = tst_test_result_action_validate,
+ .generate = tst_test_result_action_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_result_action_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_result_action_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_result_action_operation = {
+ .mnemonic = "TEST_RESULT_ACTION",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_RESULT_ACTION,
+ .dump = tst_test_result_action_operation_dump,
+ .execute = tst_test_result_action_operation_execute
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* FIXME: merge this with the test_error version of this tag */
+
+static bool tst_test_result_action_validate_index_tag
+ (struct sieve_validator *validator, struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+static const struct sieve_argument_def test_result_action_index_tag = {
+ .identifier = "index",
+ .validate = tst_test_result_action_validate_index_tag
+};
+
+enum tst_test_result_action_optional {
+ OPT_INDEX = SIEVE_MATCH_OPT_LAST,
+};
+
+/*
+ * Argument implementation
+ */
+
+static bool tst_test_result_action_validate_index_tag
+(struct sieve_validator *validator, struct sieve_ast_argument **arg,
+ struct sieve_command *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, NULL, 0, SAAT_NUMBER, FALSE) ) {
+ return FALSE;
+ }
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ return TRUE;
+}
+
+
+/*
+ * Command registration
+ */
+
+static bool tst_test_result_action_registered
+(struct sieve_validator *validator, const struct sieve_extension *ext,
+ 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, ext, &test_result_action_index_tag, OPT_INDEX);
+
+ return TRUE;
+}
+
+/*
+ * Validation
+ */
+
+static bool tst_test_result_action_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+ struct sieve_comparator cmp_default =
+ SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+ struct sieve_match_type mcht_default =
+ SIEVE_COMPARATOR_DEFAULT(is_match_type);
+
+ 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, &mcht_default, &cmp_default);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_result_action_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &test_result_action_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_result_action_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "TEST_RESULT_ACTION:");
+ sieve_code_descend(denv);
+
+ /* Handle any optional arguments */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code))
+ < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ if ( opt_code == OPT_INDEX ) {
+ if ( !sieve_opr_number_dump(denv, address, "index") )
+ return FALSE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return sieve_opr_stringlist_dump(denv, address, "key list");
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_result_action_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ int opt_code = 0;
+ struct sieve_comparator cmp = SIEVE_COMPARATOR_DEFAULT(i_octet_comparator);
+ struct sieve_match_type mcht = SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
+ struct sieve_stringlist *value_list, *key_list;
+ int index = 0;
+ int match, ret;
+
+ /*
+ * Read operands
+ */
+
+ /* Read optional operands */
+ for (;;) {
+ sieve_number_t number;
+ int opt;
+
+ if ( (opt=sieve_match_opr_optional_read
+ (renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
+ return ret;
+
+ if ( opt == 0 ) break;
+
+ if ( opt_code == OPT_INDEX ) {
+ if ( (ret=sieve_opr_number_read(renv, address, "index", &number)) <= 0 )
+ return ret;
+ index = (int) number;
+ } else {
+ sieve_runtime_trace_error(renv, "invalid optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /* Read key-list */
+ if ( (ret=sieve_opr_stringlist_read(renv, address, "key-list", &key_list))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "testsuite: test_result_action test; match result name (index: %d)", index);
+
+ /* Create value stringlist */
+ value_list = testsuite_result_stringlist_create(renv, index);
+
+ /* Perform match */
+ if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
+ return ret;
+
+ /* Set test result for subsequent conditional jump */
+ sieve_interpreter_set_test_result(renv->interp, match > 0);
+ return SIEVE_EXEC_OK;
+}
+
+
+
diff --git a/pigeonhole/src/testsuite/tst-test-result-execute.c b/pigeonhole/src/testsuite/tst-test-result-execute.c
new file mode 100644
index 0000000..1b9a7ee
--- /dev/null
+++ b/pigeonhole/src/testsuite/tst-test-result-execute.c
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.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"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-result.h"
+
+/*
+ * Test_result_execute command
+ *
+ * Syntax:
+ * test_result_execute
+ */
+
+static bool tst_test_result_execute_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_test_result_execute = {
+ .identifier = "test_result_execute",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .generate = tst_test_result_execute_generate
+};
+
+/*
+ * Operation
+ */
+
+static int tst_test_result_execute_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_result_execute_operation = {
+ .mnemonic = "TEST_RESULT_EXECUTE",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_RESULT_EXECUTE,
+ .execute = tst_test_result_execute_operation_execute
+};
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_result_execute_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &test_result_execute_operation);
+
+ return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_result_execute_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{
+ bool result = TRUE;
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "testsuite: test_result_execute test");
+
+ result = testsuite_result_execute(renv);
+
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "execution of result %s",
+ ( result ? "succeeded" : "failed" ));
+ }
+
+ /* Set result */
+ sieve_interpreter_set_test_result(renv->interp, result);
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
+
diff --git a/pigeonhole/src/testsuite/tst-test-script-compile.c b/pigeonhole/src/testsuite/tst-test-script-compile.c
new file mode 100644
index 0000000..c053486
--- /dev/null
+++ b/pigeonhole/src/testsuite/tst-test-script-compile.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.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"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-script.h"
+
+/*
+ * Test_script_compile command
+ *
+ * Syntax:
+ * test_script_compile <scriptpath: string>
+ */
+
+static bool tst_test_script_compile_validate
+ (struct sieve_validator *valdtr, struct sieve_command *cmd);
+static bool tst_test_script_compile_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_test_script_compile = {
+ .identifier = "test_script_compile",
+ .type = SCT_TEST,
+ .positional_args = 1,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .validate = tst_test_script_compile_validate,
+ .generate = tst_test_script_compile_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_script_compile_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_script_compile_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_script_compile_operation = {
+ .mnemonic = "TEST_SCRIPT_COMPILE",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_SCRIPT_COMPILE,
+ .dump = tst_test_script_compile_operation_dump,
+ .execute = tst_test_script_compile_operation_execute
+};
+
+/*
+ * Validation
+ */
+
+static bool tst_test_script_compile_validate
+(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *tst)
+{
+ struct sieve_ast_argument *arg = tst->first_positional;
+
+ if ( !sieve_validate_positional_argument
+ (valdtr, tst, arg, "script", 1, SAAT_STRING) ) {
+ return FALSE;
+ }
+
+ return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
+}
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_script_compile_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &test_script_compile_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_script_compile_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ sieve_code_dumpf(denv, "TEST_SCRIPT_COMPILE:");
+ sieve_code_descend(denv);
+
+ if ( !sieve_opr_string_dump(denv, address, "script-name") )
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_script_compile_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ string_t *script_name;
+ bool result = TRUE;
+ int ret;
+
+ /*
+ * Read operands
+ */
+
+ if ( (ret=sieve_opr_string_read(renv, address, "script-name", &script_name))
+ <= 0 )
+ return ret;
+
+ /*
+ * Perform operation
+ */
+
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+ sieve_runtime_trace(renv, 0, "testsuite: test_script_compile test");
+ sieve_runtime_trace_descend(renv);
+ }
+
+ /* Attempt script compile */
+
+ result = testsuite_script_compile(renv, str_c(script_name));
+
+ /* Set result */
+ sieve_interpreter_set_test_result(renv->interp, result);
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
+
diff --git a/pigeonhole/src/testsuite/tst-test-script-run.c b/pigeonhole/src/testsuite/tst-test-script-run.c
new file mode 100644
index 0000000..214a5c6
--- /dev/null
+++ b/pigeonhole/src/testsuite/tst-test-script-run.c
@@ -0,0 +1,198 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "sieve-common.h"
+#include "sieve-script.h"
+#include "sieve-commands.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"
+#include "sieve.h"
+
+#include "testsuite-common.h"
+#include "testsuite-script.h"
+#include "testsuite-result.h"
+
+/*
+ * Test_script_run command
+ *
+ * Syntax:
+ * test_script_run
+ */
+
+static bool tst_test_script_run_registered
+(struct sieve_validator *validator, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool tst_test_script_run_generate
+ (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd);
+
+const struct sieve_command_def tst_test_script_run = {
+ .identifier = "test_script_run",
+ .type = SCT_TEST,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = tst_test_script_run_registered,
+ .generate = tst_test_script_run_generate
+};
+
+/*
+ * Operation
+ */
+
+static bool tst_test_script_run_operation_dump
+ (const struct sieve_dumptime_env *denv, sieve_size_t *address);
+static int tst_test_script_run_operation_execute
+ (const struct sieve_runtime_env *renv, sieve_size_t *address);
+
+const struct sieve_operation_def test_script_run_operation = {
+ .mnemonic = "TEST_SCRIPT_RUN",
+ .ext_def = &testsuite_extension,
+ .code = TESTSUITE_OPERATION_TEST_SCRIPT_RUN,
+ .dump = tst_test_script_run_operation_dump,
+ .execute = tst_test_script_run_operation_execute
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Codes for optional arguments */
+
+enum cmd_vacation_optional {
+ OPT_END,
+ OPT_APPEND_RESULT
+};
+
+/* Tags */
+
+static const struct sieve_argument_def append_result_tag = {
+ .identifier = "append_result"
+};
+
+static bool tst_test_script_run_registered
+(struct sieve_validator *validator, const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag
+ (validator, cmd_reg, ext, &append_result_tag, OPT_APPEND_RESULT);
+
+ return TRUE;
+}
+
+
+/*
+ * Code generation
+ */
+
+static bool tst_test_script_run_generate
+(const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
+{
+ sieve_operation_emit(cgenv->sblock, tst->ext, &test_script_run_operation);
+
+ return sieve_generate_arguments(cgenv, tst, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool tst_test_script_run_operation_dump
+(const struct sieve_dumptime_env *denv, sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "TEST_SCRIPT_RUN");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_dump(denv, address, &opt_code)) < 0 )
+ return FALSE;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_APPEND_RESULT:
+ sieve_code_dumpf(denv, "append_result");
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Intepretation
+ */
+
+static int tst_test_script_run_operation_execute
+(const struct sieve_runtime_env *renv, sieve_size_t *address)
+{
+ bool append_result = FALSE;
+ int opt_code = 0;
+ bool result = TRUE;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+ for (;;) {
+ int opt;
+
+ if ( (opt=sieve_opr_optional_read(renv, address, &opt_code)) < 0 )
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if ( opt == 0 ) break;
+
+ switch ( opt_code ) {
+ case OPT_APPEND_RESULT:
+ append_result = TRUE;
+ break;
+ default:
+ sieve_runtime_trace_error(renv,
+ "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+ }
+
+ /*
+ * Perform operation
+ */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
+ "testsuite: run compiled script [append_result=%s]",
+ ( append_result ? "yes" : "no" ));
+
+ /* Reset result object */
+ if ( !append_result )
+ testsuite_result_reset(renv);
+
+ /* Run script */
+ result = testsuite_script_run(renv);
+
+ if ( sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS) ) {
+ sieve_runtime_trace_descend(renv);
+ sieve_runtime_trace(renv, 0, "execution of script %s",
+ ( result ? "succeeded" : "failed" ));
+ }
+
+ /* Indicate test status */
+ sieve_interpreter_set_test_result(renv->interp, result);
+
+ return SIEVE_EXEC_OK;
+}
+
+
+
+