1/* Unit test for sieve */
2/* Heavily based on the old sieve/test.c which bore this message:
3 *
4 * - * test.c -- tester for libcyrus_sieve
5 * - * Larry Greenfield
6 *
7 */
8#include "cunit/cyrunit.h"
9#include <malloc.h>
10#include "sieve/bytecode.h"
11#include "sieve/comparator.h"
12#include "sieve/message.h"
13#include "sieve/sieve_interface.h"
14#include "sieve/variables.h"
15#include "imap/message.h"
16#include "prot.h"
17#include "retry.h"
18#include "imap/spool.h"
19#include "map.h"
20#include "util.h"
21#include "cyrusdb.h"
22#include "libcyr_cfg.h"
23#include "libconfig.h"
24#include "xstrlcat.h"
25#include "xstrlcpy.h"
26#include "xmalloc.h"
27
28#define DBDIR       "test-sieve-dbdir"
29#define PARTITION   "default"
30
31typedef struct {
32    sieve_interp_t *interp;
33    sieve_execute_t *exe;
34    struct {
35        unsigned int actions;
36        unsigned int errors;
37        unsigned int redirects;
38        unsigned int discards;
39        unsigned int rejects;
40        unsigned int fileintos;
41        unsigned int keeps;
42        unsigned int notifies;
43        unsigned int vaction_responses;
44    } stats;
45    char *redirected_to;
46    char *reject_message;
47    char *filed_mailbox;
48    char *filed_flags;
49    char *notify_method;
50    char *notify_priority;
51    char *notify_options;
52    char *vacation_message;
53    char *vacation_subject;
54    char *vacation_to;
55    char *vacation_from;
56    strarray_t *compile_errors;
57    strarray_t *run_errors;
58    strarray_t *flags;
59} sieve_test_context_t;
60
61typedef struct {
62    const char *text;
63    int length;
64    struct message_content content;
65    hdrcache_t headers;
66    char *filename;
67} sieve_test_message_t;
68
69/* set to SIEVE_DONE if you want to test "already responded */
70int autorespond_response = SIEVE_OK;
71
72extern int verbose;
73
74static void context_setup(sieve_test_context_t *ctx,
75                          const char *script);
76static void context_cleanup(sieve_test_context_t *ctx);
77
78static void test_variable_modifiers(void)
79{
80    /*
81   Examples:
82      # The value assigned to the variable is printed after the arrow
83      set "a" "juMBlEd lETteRS";             => "juMBlEd lETteRS"
84      set :length "b" "${a}";                => "15"
85      set :lower "b" "${a}";                 => "jumbled letters"
86      set :upperfirst "b" "${a}";            => "JuMBlEd lETteRS"
87      set :upperfirst :lower "b" "${a}";     => "Jumbled letters"
88      set :quotewildcard "b" "^Rock*";       => "^Rock\*"
89      set :quoteregex "b" "^Rock*";          => "\^Rock\*"
90     */
91    const char *a = "juMBlEd lETteRS";
92    char *b;
93    CU_ASSERT_STRING_EQUAL(a, "juMBlEd lETteRS");
94    b = variables_modify_string(a, BFV_LENGTH);
95    CU_ASSERT_STRING_EQUAL(b, "15");
96    free(b);
97    b = variables_modify_string(a, BFV_LOWER);
98    CU_ASSERT_STRING_EQUAL(b, "jumbled letters");
99    free(b);
100    b = variables_modify_string(a, BFV_UPPERFIRST);
101    CU_ASSERT_STRING_EQUAL(b, "JuMBlEd lETteRS");
102    free(b);
103    b = variables_modify_string(a, BFV_UPPERFIRST | BFV_LOWER);
104    CU_ASSERT_STRING_EQUAL(b, "Jumbled letters");
105    free(b);
106    b = variables_modify_string("^Rock*", BFV_QUOTEWILDCARD);
107    CU_ASSERT_STRING_EQUAL(b, "^Rock\\*");
108    free(b);
109    b = variables_modify_string("^Rock*", BFV_QUOTEREGEX);
110    CU_ASSERT_STRING_EQUAL(b, "\\^Rock\\*");
111    free(b);
112    b = variables_modify_string("~Rock\\*", BFV_QUOTEWILDCARD | BFV_ENCODEURL);
113    CU_ASSERT_STRING_EQUAL(b, "~Rock%5C%5C%5C%2A");
114    free(b);
115}
116
117#define TESTCASE(_comp, _mode, _pat, _text, _result)            \
118    comprock = NULL;                                            \
119    c = lookup_comp(ctx.interp, _comp, _mode, -1, &comprock);   \
120    CU_ASSERT_PTR_NOT_NULL(c);                                  \
121    if (c) {                                                    \
122        res = c(_text, strlen(_text), _pat, NULL, comprock);    \
123        CU_ASSERT_EQUAL(res, _result);                          \
124    }
125
126static void test_comparator(void)
127{
128    static const char SCRIPT[] =
129    "require [\"comparator-i;ascii-numeric\"];\n"
130    ;
131    sieve_test_context_t ctx;
132    void *comprock;
133    comparator_t *c;
134    int res;
135
136    context_setup(&ctx, SCRIPT);
137
138    TESTCASE( B_OCTET, B_IS, "", "", 1 );
139    TESTCASE( B_OCTET, B_IS, "a", "", 0 );
140    TESTCASE( B_OCTET, B_IS, "", "a", 0 );
141    TESTCASE( B_OCTET, B_IS, "a", "a", 1 );
142    TESTCASE( B_OCTET, B_IS, "a", "A", 0 );
143
144    TESTCASE( B_ASCIICASEMAP, B_IS, "", "", 1 );
145    TESTCASE( B_ASCIICASEMAP, B_IS, "a", "", 0 );
146    TESTCASE( B_ASCIICASEMAP, B_IS, "", "a", 0 );
147    TESTCASE( B_ASCIICASEMAP, B_IS, "a", "a", 1 );
148    TESTCASE( B_ASCIICASEMAP, B_IS, "a", "A", 1 );
149
150    TESTCASE( B_ASCIINUMERIC, B_IS, "123", "123", 1 );
151    TESTCASE( B_ASCIINUMERIC, B_IS, "123", "-123", 0 );
152    TESTCASE( B_ASCIINUMERIC, B_IS, "abc", "123", 0 );
153    TESTCASE( B_ASCIINUMERIC, B_IS, "abc", "abc", 1 );
154    TESTCASE( B_ASCIINUMERIC, B_IS, "12345678900", "3755744308", 0 );    /* test for 32bit overflow */
155    TESTCASE( B_ASCIINUMERIC, B_IS, "1567", "1567pounds", 1 );
156    TESTCASE( B_ASCIINUMERIC, B_IS, "", "", 1 );
157    TESTCASE( B_ASCIINUMERIC, B_IS, "123456789", "567", 0 );
158    TESTCASE( B_ASCIINUMERIC, B_IS, "567", "123456789", 0 );
159    TESTCASE( B_ASCIINUMERIC, B_IS, "123456789", "00000123456789", 1 );
160    TESTCASE( B_ASCIINUMERIC, B_IS, "102", "1024", 0 );
161    TESTCASE( B_ASCIINUMERIC, B_IS, "1567M", "1567 arg", 1 );
162
163    TESTCASE( B_OCTET, B_CONTAINS, "", "", 1 );
164    TESTCASE( B_OCTET, B_CONTAINS, "", "a", 1 );
165    TESTCASE( B_OCTET, B_CONTAINS, "a", "", 0 );
166    TESTCASE( B_OCTET, B_CONTAINS, "a", "a", 1 );
167    TESTCASE( B_OCTET, B_CONTAINS, "a", "ab", 1 );
168    TESTCASE( B_OCTET, B_CONTAINS, "a", "ba", 1 );
169    TESTCASE( B_OCTET, B_CONTAINS, "a", "aba", 1 );
170    TESTCASE( B_OCTET, B_CONTAINS, "a", "bab", 1 );
171    TESTCASE( B_OCTET, B_CONTAINS, "a", "bb", 0 );
172    TESTCASE( B_OCTET, B_CONTAINS, "a", "bbb", 0 );
173
174    TESTCASE( B_OCTET, B_MATCHES, "", "", 1 );
175    TESTCASE( B_OCTET, B_MATCHES, "", "a", 0 );
176    TESTCASE( B_OCTET, B_MATCHES, "a", "", 0 );
177    TESTCASE( B_OCTET, B_MATCHES, "a", "a", 1 );
178    TESTCASE( B_OCTET, B_MATCHES, "a", "ab", 0 );
179    TESTCASE( B_OCTET, B_MATCHES, "a", "ba", 0 );
180    TESTCASE( B_OCTET, B_MATCHES, "a", "aba", 0 );
181    TESTCASE( B_OCTET, B_MATCHES, "a", "bab", 0 );
182    TESTCASE( B_OCTET, B_MATCHES, "a", "bb", 0 );
183    TESTCASE( B_OCTET, B_MATCHES, "a", "bbb", 0 );
184
185    TESTCASE( B_OCTET, B_MATCHES, "*", "", 1 );
186    TESTCASE( B_OCTET, B_MATCHES, "*", "a", 1 );
187    TESTCASE( B_OCTET, B_MATCHES, "*a*", "", 0 );
188    TESTCASE( B_OCTET, B_MATCHES, "*a*", "a", 1 );
189    TESTCASE( B_OCTET, B_MATCHES, "*a*", "ab", 1 );
190    TESTCASE( B_OCTET, B_MATCHES, "*a*", "ba", 1 );
191    TESTCASE( B_OCTET, B_MATCHES, "*a*", "aba", 1 );
192    TESTCASE( B_OCTET, B_MATCHES, "*a*", "bab", 1 );
193    TESTCASE( B_OCTET, B_MATCHES, "*a*", "bb", 0 );
194    TESTCASE( B_OCTET, B_MATCHES, "*a*", "bbb", 0 );
195
196    TESTCASE( B_OCTET, B_MATCHES, "*a", "", 0 );
197    TESTCASE( B_OCTET, B_MATCHES, "*a", "a", 1 );
198    TESTCASE( B_OCTET, B_MATCHES, "*a", "ab", 0 );
199    TESTCASE( B_OCTET, B_MATCHES, "*a", "ba", 1 );
200    TESTCASE( B_OCTET, B_MATCHES, "*a", "aba", 1 );
201    TESTCASE( B_OCTET, B_MATCHES, "*a", "bab", 0 );
202    TESTCASE( B_OCTET, B_MATCHES, "*a", "bb", 0 );
203    TESTCASE( B_OCTET, B_MATCHES, "*a", "bbb", 0 );
204
205    TESTCASE( B_OCTET, B_MATCHES, "a*", "", 0 );
206    TESTCASE( B_OCTET, B_MATCHES, "a*", "a", 1 );
207    TESTCASE( B_OCTET, B_MATCHES, "a*", "ab", 1 );
208    TESTCASE( B_OCTET, B_MATCHES, "a*", "ba", 0 );
209    TESTCASE( B_OCTET, B_MATCHES, "a*", "aba", 1 );
210    TESTCASE( B_OCTET, B_MATCHES, "a*", "bab", 0 );
211    TESTCASE( B_OCTET, B_MATCHES, "a*", "bb", 0 );
212    TESTCASE( B_OCTET, B_MATCHES, "a*", "bbb", 0 );
213
214    TESTCASE( B_OCTET, B_MATCHES, "a*b", "", 0 );
215    TESTCASE( B_OCTET, B_MATCHES, "a*b", "a", 0 );
216    TESTCASE( B_OCTET, B_MATCHES, "a*b", "ab", 1 );
217    TESTCASE( B_OCTET, B_MATCHES, "a*b", "ba", 0 );
218    TESTCASE( B_OCTET, B_MATCHES, "a*b", "aba", 0 );
219    TESTCASE( B_OCTET, B_MATCHES, "a*b", "bab", 0 );
220    TESTCASE( B_OCTET, B_MATCHES, "a*b", "bb", 0 );
221    TESTCASE( B_OCTET, B_MATCHES, "a*b", "bbb", 0 );
222    TESTCASE( B_OCTET, B_MATCHES, "a*b", "abbb", 1 );
223    TESTCASE( B_OCTET, B_MATCHES, "a*b", "acb", 1 );
224    TESTCASE( B_OCTET, B_MATCHES, "a*b", "acbc", 0 );
225
226    TESTCASE( B_OCTET, B_MATCHES, "a?b", "", 0 );
227    TESTCASE( B_OCTET, B_MATCHES, "a?b", "a", 0 );
228    TESTCASE( B_OCTET, B_MATCHES, "a?b", "ab", 0 );
229    TESTCASE( B_OCTET, B_MATCHES, "a?b", "ba", 0 );
230    TESTCASE( B_OCTET, B_MATCHES, "a?b", "aba", 0 );
231    TESTCASE( B_OCTET, B_MATCHES, "a?b", "bab", 0 );
232    TESTCASE( B_OCTET, B_MATCHES, "a?b", "bb", 0 );
233    TESTCASE( B_OCTET, B_MATCHES, "a?b", "bbb", 0 );
234    TESTCASE( B_OCTET, B_MATCHES, "a?b", "abbb", 0 );
235    TESTCASE( B_OCTET, B_MATCHES, "a?b", "acb", 1 );
236    TESTCASE( B_OCTET, B_MATCHES, "a?b", "acbc", 0 );
237
238    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "", 0 );
239    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "a", 0 );
240    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "ab", 0 );
241    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "ba", 0 );
242    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "aba", 0 );
243    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "bab", 0 );
244    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "bb", 0 );
245    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "bbb", 0 );
246    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "abbb", 1 );
247    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "acb", 1 );
248    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "acbc", 0 );
249
250    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "", 0 );
251    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "a", 0 );
252    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "ab", 0 );
253    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "ba", 0 );
254    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "aba", 0 );
255    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "bab", 0 );
256    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "bb", 0 );
257    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "bbb", 0 );
258    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "abbb", 1 );
259    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "acb", 1 );
260    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "acbc", 0 );
261
262    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "", 0 );
263    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "a", 0 );
264    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "ab", 0 );
265    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "ba", 0 );
266    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "aba", 0 );
267    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "bab", 0 );
268    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "bb", 0 );
269    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "bbb", 0 );
270    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "abbb", 1 );
271    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "acb", 1 );
272    TESTCASE( B_OCTET, B_MATCHES, "a*?*b?", "acbc", 1 );
273
274    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "", 0 );
275    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "a", 0 );
276    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ab", 1 );
277    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ba", 0 );
278    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "aba", 0 );
279    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "bab", 0 );
280    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "bb", 0 );
281    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "bbb", 0 );
282    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "abbb", 1 );
283    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "acb", 1 );
284    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "acbc", 0 );
285
286    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "", 0 );
287    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "A", 0 );
288    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "Ab", 1 );
289    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BA", 0 );
290    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ABA", 0 );
291    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BAb", 0 );
292    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BB", 0 );
293    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BBB", 0 );
294    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "aBBB", 1 );
295    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ACB", 1 );
296    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ACBC", 0 );
297
298    context_cleanup(&ctx);
299}
300
301
302/* gets the header "head" from msg. */
303static int getheader(void *mc, const char *name, const char ***body)
304{
305    sieve_test_message_t *msg = (sieve_test_message_t *)mc;
306
307    *body = spool_getheader(msg->headers, name);
308    if (!*body)
309        return SIEVE_FAIL;
310    return SIEVE_OK;
311}
312
313static int getsize(void *mc, int *size)
314{
315    sieve_test_message_t *msg = (sieve_test_message_t *)mc;
316
317    *size = msg->length;
318    return SIEVE_OK;
319}
320
321static int getbody(void *mc, const char **content_types, sieve_bodypart_t ***parts)
322{
323    sieve_test_message_t *msg = (sieve_test_message_t *)mc;
324    int r = 0;
325
326    if (!msg->content.body) {
327        /* parse the message body if we haven't already */
328        FILE *fp = fopen(msg->filename, "r");
329        CU_ASSERT_PTR_NOT_NULL(fp);
330        r = message_parse_file_buf(fp,
331                               &msg->content.map,
332                               &msg->content.body,
333                               msg->filename);
334        CU_ASSERT_EQUAL(r, 0);
335        fclose(fp);
336    }
337
338    /* XXX currently struct bodypart as defined in message.h is the same as
339       sieve_bodypart_t as defined in sieve_interface.h, so we can typecast */
340    if (!r)
341        message_fetch_part(&msg->content, content_types,
342                           (struct bodypart ***) parts);
343
344    if (r)
345        return SIEVE_FAIL;
346    return SIEVE_OK;
347}
348
349static int getinclude(void *sc __attribute__((unused)),
350                      const char *script,
351                      int isglobal __attribute__((unused)),
352                      char *fpath, size_t size)
353{
354    strlcpy(fpath, script, size);
355    strlcat(fpath, ".bc", size);
356    return SIEVE_OK;
357}
358
359static int redirect(void *ac, void *ic, void *sc __attribute__((unused)),
360                    void *mc __attribute__((unused)),
361                    const char **errmsg __attribute__((unused)))
362{
363    sieve_redirect_context_t *rc = (sieve_redirect_context_t *)ac;
364    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
365
366    ctx->stats.actions++;
367    ctx->stats.redirects++;
368    free(ctx->redirected_to);
369    ctx->redirected_to = xstrdup(rc->addr);
370
371    /* TODO: test returning SIEVE_FAIL */
372    return SIEVE_OK;
373}
374
375static int discard(void *ac __attribute__((unused)),
376                   void *ic, void *sc __attribute__((unused)),
377                   void *mc __attribute__((unused)),
378                   const char **errmsg __attribute__((unused)))
379{
380    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
381
382    ctx->stats.actions++;
383    ctx->stats.discards++;
384
385    /* TODO: test returning SIEVE_FAIL */
386    return SIEVE_OK;
387}
388
389static int reject(void *ac, void *ic, void *sc __attribute__((unused)),
390                  void *mc __attribute__((unused)),
391                  const char **errmsg __attribute__((unused)))
392{
393    sieve_reject_context_t *rc = (sieve_reject_context_t *)ac;
394    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
395
396    ctx->stats.actions++;
397    ctx->stats.rejects++;
398    free(ctx->reject_message);
399    ctx->reject_message = xstrdup(rc->msg);
400
401    /* TODO: test returning SIEVE_FAIL */
402    return SIEVE_OK;
403}
404
405static int fileinto(void *ac, void *ic, void *sc __attribute__((unused)),
406                    void *mc,
407                    const char **errmsg __attribute__((unused)))
408{
409    sieve_fileinto_context_t *fc = (sieve_fileinto_context_t *)ac;
410    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
411
412    if (!mc) {
413        /* just doing destination mailbox resolution */
414        return SIEVE_OK;
415    }
416
417    ctx->stats.actions++;
418    ctx->stats.fileintos++;
419    free(ctx->filed_mailbox);
420    ctx->filed_mailbox = xstrdup(fc->mailbox);
421    free(ctx->filed_flags);
422    ctx->filed_flags = strarray_join(fc->imapflags, " ");
423    strarray_free(ctx->flags);
424    ctx->flags = strarray_dup(fc->imapflags);
425
426    /* TODO: test returning SIEVE_FAIL */
427    return SIEVE_OK;
428}
429
430static int keep(void *ac, void *ic, void *sc __attribute__((unused)),
431                void *mc,
432                const char **errmsg __attribute__((unused)))
433{
434    sieve_keep_context_t *kc = (sieve_keep_context_t *)ac;
435    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
436
437    if (!mc) {
438        /* just doing destination mailbox resolution */
439        return SIEVE_OK;
440    }
441
442    ctx->stats.actions++;
443    ctx->stats.keeps++;
444    free(ctx->filed_flags);
445    ctx->filed_flags = strarray_join(kc->imapflags, " ");
446    strarray_free(ctx->flags);
447    ctx->flags = strarray_dup(kc->imapflags);
448
449    /* TODO: test returning SIEVE_FAIL */
450    return SIEVE_OK;
451}
452
453static int notify(void *ac, void *ic, void *sc __attribute__((unused)),
454                  void *mc __attribute__((unused)),
455                  const char **errmsg __attribute__((unused)))
456{
457    sieve_notify_context_t *nc = (sieve_notify_context_t *)ac;
458    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
459    struct buf opts = BUF_INITIALIZER;
460    int i;
461
462    for (i = 0; i < strarray_size(nc->options); i++) {
463        if (opts.len)
464            buf_putc(&opts, ' ');
465        buf_appendcstr(&opts, strarray_nth(nc->options, i));
466    }
467
468    ctx->stats.actions++;
469    ctx->stats.notifies++;
470    free(ctx->notify_options);
471    ctx->notify_options = buf_release(&opts);
472    free(ctx->notify_method);
473    ctx->notify_method = xstrdup(nc->method);
474    free(ctx->notify_priority);
475    ctx->notify_method = xstrdup(nc->priority);
476
477    /* TODO: test returning SIEVE_FAIL */
478    return SIEVE_OK;
479}
480
481static int mysieve_error(int lineno, const char *msg,
482                         void *ic,
483                         void *sc __attribute__((unused)))
484{
485    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
486    struct buf buf = BUF_INITIALIZER;
487
488    ctx->stats.errors++;
489    buf_printf(&buf, "line %d: %s", lineno, msg);
490    strarray_appendm(ctx->compile_errors, buf_release(&buf));
491
492    return SIEVE_OK;
493}
494
495static int mysieve_execute_error(const char *msg,
496                                 void *ic,
497                                 void *sc __attribute__((unused)),
498                                 void *mc __attribute__((unused)))
499{
500    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
501
502    ctx->stats.errors++;
503    strarray_append(ctx->run_errors, msg);
504
505    return SIEVE_OK;
506}
507
508static int autorespond(void *ac __attribute__((unused)),
509                       void *ic __attribute__((unused)),
510                       void *sc __attribute__((unused)),
511                       void *mc __attribute__((unused)),
512                       const char **errmsg __attribute__((unused)))
513{
514    return autorespond_response;
515}
516
517static int send_response(void *ac, void *ic, void *sc __attribute__((unused)),
518                         void *mc __attribute__((unused)),
519                         const char **errmsg __attribute__((unused)))
520{
521    sieve_send_response_context_t *src = (sieve_send_response_context_t *)ac;
522    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
523
524    ctx->stats.actions++;
525    ctx->stats.vaction_responses++;
526    free(ctx->vacation_message);
527    ctx->vacation_message = xstrdup(src->msg);
528    free(ctx->vacation_subject);
529    ctx->vacation_subject = xstrdup(src->subj);
530    free(ctx->vacation_to);
531    ctx->vacation_to = xstrdup(src->addr);
532    free(ctx->vacation_from);
533    ctx->vacation_from = xstrdup(src->fromaddr);
534
535    /* TODO: test returning SIEVE_FAIL */
536    return SIEVE_OK;
537}
538
539#ifndef HAVE_FMEMOPEN
540static FILE *fmemopen(const void *buf, size_t len, const char *mode)
541{
542    FILE *fp;
543
544    fp = fopen("/dev/null", mode);
545    if (!fp)
546        return NULL;
547    setbuffer(fp, buf, len);
548    return fp;
549}
550#endif
551
552static int set_up(void)
553{
554    libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, DBDIR);
555    config_read_string(
556        "configdirectory: "DBDIR"/conf\n"
557        "defaultpartition: "PARTITION"\n"
558        "partition-"PARTITION": "DBDIR"/data\n"
559        "sievenotifier: mailto\n"
560        "sieve_extensions: fileinto reject vacation imapflags notify" \
561	    " envelope body relational regex subaddress copy date index" \
562	    " imap4flags variables\n"
563    );
564    libcyrus_init();
565    return 0;
566}
567
568static int tear_down(void)
569{
570    int r;
571
572    libcyrus_done();
573    config_reset();
574
575    r = system("rm -rf " DBDIR);
576
577    return r;
578}
579
580static void context_setup(sieve_test_context_t *ctx,
581                          const char *script)
582{
583    int r;
584    static sieve_vacation_t vacation = {
585        0,                      /* min response */
586        0,                      /* max response */
587        &autorespond,           /* autorespond() */
588        &send_response          /* send_response() */
589    };
590    char *errors = NULL;
591    int fd;
592    sieve_script_t *scr = NULL;
593    bytecode_info_t *bytecode = NULL;
594    char tempfile[32];
595
596    memset(ctx, 0, sizeof(*ctx));
597
598    ctx->compile_errors = strarray_new();
599    ctx->run_errors = strarray_new();
600
601    ctx->interp = sieve_interp_alloc(ctx);
602    sieve_register_redirect(ctx->interp, redirect);
603    sieve_register_discard(ctx->interp, discard);
604    sieve_register_reject(ctx->interp, reject);
605    sieve_register_fileinto(ctx->interp, fileinto);
606    sieve_register_keep(ctx->interp, keep);
607    sieve_register_size(ctx->interp, getsize);
608    sieve_register_header(ctx->interp, getheader);
609    sieve_register_envelope(ctx->interp, getheader);
610    sieve_register_body(ctx->interp, getbody);
611    sieve_register_include(ctx->interp, getinclude);
612    sieve_register_vacation(ctx->interp, &vacation);
613    sieve_register_notify(ctx->interp, notify, NULL);
614    sieve_register_parse_error(ctx->interp, mysieve_error);
615    sieve_register_execute_error(ctx->interp, mysieve_execute_error);
616
617    r = sieve_script_parse_string(ctx->interp, script, &errors, &scr);
618    CU_ASSERT_EQUAL(r, SIEVE_OK);
619    free(errors);
620
621    r = sieve_generate_bytecode(&bytecode, scr);
622    CU_ASSERT(r > 0);
623    strcpy(tempfile, "/tmp/sievetest-BC-XXXXXX");
624    fd = mkstemp(tempfile);
625    CU_ASSERT(fd >= 0);
626    r = sieve_emit_bytecode(fd, bytecode);
627    CU_ASSERT(r > 0);
628    sieve_free_bytecode(&bytecode);
629    sieve_script_free(&scr);
630
631    /* Now load the compiled bytecode */
632    r = sieve_script_load(tempfile, &ctx->exe);
633    CU_ASSERT_EQUAL(r, SIEVE_OK);
634    unlink(tempfile);
635}
636
637static void context_cleanup(sieve_test_context_t *ctx)
638{
639    int r;
640
641    if (verbose > 1) {
642        int i;
643
644        fprintf(stderr, "sieve test context\n");
645        fprintf(stderr, "    actions: %u\n", ctx->stats.actions);
646        fprintf(stderr, "    errors: %u\n", ctx->stats.errors);
647        fprintf(stderr, "    redirects: %u\n", ctx->stats.redirects);
648        fprintf(stderr, "    discards: %u\n", ctx->stats.discards);
649        fprintf(stderr, "    rejects: %u\n", ctx->stats.rejects);
650        fprintf(stderr, "    fileintos: %u\n", ctx->stats.fileintos);
651        fprintf(stderr, "    keeps: %u\n", ctx->stats.keeps);
652        fprintf(stderr, "    notifies: %u\n", ctx->stats.notifies);
653        fprintf(stderr, "    vaction_responses: %u\n", ctx->stats.vaction_responses);
654        if (ctx->redirected_to)
655            fprintf(stderr, "    redirected_to: %s\n", ctx->redirected_to);
656        if (ctx->reject_message)
657            fprintf(stderr, "    reject_message: %s\n", ctx->reject_message);
658        if (ctx->filed_mailbox)
659            fprintf(stderr, "    filed_mailbox: %s\n", ctx->filed_mailbox);
660        if (ctx->filed_flags)
661            fprintf(stderr, "    filed_flags: %s\n", ctx->filed_flags);
662        if (ctx->notify_method)
663            fprintf(stderr, "    notify_method: %s\n", ctx->notify_method);
664        if (ctx->notify_priority)
665            fprintf(stderr, "    notify_priority: %s\n", ctx->notify_priority);
666        if (ctx->notify_options)
667            fprintf(stderr, "    notify_options: %s\n", ctx->notify_options);
668        if (ctx->vacation_message)
669            fprintf(stderr, "    vacation_message: %s\n", ctx->vacation_message);
670        if (ctx->vacation_subject)
671            fprintf(stderr, "    vacation_subject: %s\n", ctx->vacation_subject);
672        if (ctx->vacation_to)
673            fprintf(stderr, "    vacation_to: %s\n", ctx->vacation_to);
674        if (ctx->vacation_from)
675            fprintf(stderr, "    vacation_from: %s\n", ctx->vacation_from);
676        if (ctx->compile_errors->count) {
677            fprintf(stderr, "    compile_errors:\n");
678            for (i = 0 ; i < ctx->compile_errors->count ; i++)
679                fprintf(stderr, "\t[%d] %s\n", i, ctx->compile_errors->data[i]);
680        }
681        if (ctx->run_errors->count) {
682            fprintf(stderr, "    run_errors:\n");
683            for (i = 0 ; i < ctx->run_errors->count ; i++)
684                fprintf(stderr, "\t[%d] %s\n", i, ctx->run_errors->data[i]);
685        }
686    }
687
688    /*used to be sieve_script_free*/
689    r = sieve_script_unload(&ctx->exe);
690    CU_ASSERT_EQUAL(r, SIEVE_OK);
691    CU_ASSERT_PTR_NULL(ctx->exe);
692
693    r = sieve_interp_free(&ctx->interp);
694    CU_ASSERT_EQUAL(r, SIEVE_OK);
695
696    free(ctx->redirected_to);
697    free(ctx->reject_message);
698    free(ctx->filed_mailbox);
699    free(ctx->filed_flags);
700    free(ctx->notify_method);
701    free(ctx->notify_priority);
702    free(ctx->notify_options);
703    free(ctx->vacation_message);
704    free(ctx->vacation_subject);
705    free(ctx->vacation_to);
706    free(ctx->vacation_from);
707    strarray_free(ctx->compile_errors);
708    strarray_free(ctx->run_errors);
709    strarray_free(ctx->flags);
710}
711
712
713static sieve_test_message_t *sieve_test_message_new(const char *text, int len)
714{
715    sieve_test_message_t *msg;
716    struct protstream *pin;
717    FILE *fout;
718    int fd;
719    int r;
720    char tempfile[32];
721
722    msg = xzmalloc(sizeof(sieve_test_message_t));
723    msg->text = text;
724    msg->length = len;
725    msg->headers = spool_new_hdrcache();
726
727    strcpy(tempfile, "/tmp/sievetest-MS-XXXXXX");
728    fd = mkstemp(tempfile);
729    CU_ASSERT(fd >= 0);
730    msg->filename = xstrdup(tempfile);
731    r = retry_write(fd, text, len);
732    CU_ASSERT_EQUAL(r, len);
733    lseek(fd, SEEK_SET, 0);
734
735    pin = prot_new(fd, /*read*/0);
736    CU_ASSERT_PTR_NOT_NULL(pin);
737
738    fout = fopen("/dev/null", "w");
739    CU_ASSERT_PTR_NOT_NULL(fout);
740
741    r = spool_fill_hdrcache(pin, fout, msg->headers, NULL);
742    CU_ASSERT_EQUAL(r, 0);
743
744    fclose(fout);
745    prot_free(pin);
746
747    return msg;
748}
749
750static void message_free(sieve_test_message_t *msg)
751{
752    spool_free_hdrcache(msg->headers);
753    if (msg->content.body)
754        message_free_body(msg->content.body);
755    buf_free(&msg->content.map);
756    unlink(msg->filename);
757    free(msg->filename);
758    free(msg);
759}
760
761
762static void run_message(sieve_test_context_t *ctx,
763                        const char *text)
764{
765    sieve_test_message_t *msg;
766    int r;
767
768    msg = sieve_test_message_new(text, strlen(text));
769    CU_ASSERT_PTR_NOT_NULL(msg);
770
771    r = sieve_execute_bytecode(ctx->exe, ctx->interp, ctx, msg);
772    CU_ASSERT_EQUAL(r, SIEVE_OK);
773
774    message_free(msg);
775}
776
777static void test_address_all(void)
778{
779    static const char SCRIPT_IS[] =
780    "if address :all :is \"from\" \"zme@true.com\"\n"
781    "{redirect \"me@blah.com\";}\n"
782    ;
783    static const char SCRIPT_CONTAINS[] =
784    "if address :all :contains \"from\" \"true.com\"\n"
785    "{redirect \"me@blah.com\";}\n"
786    ;
787    static const char SCRIPT_MATCHES[] =
788    "if address :all :matches \"from\" \"*true.com\"\n"
789    "{redirect \"me@blah.com\";}\n"
790    ;
791
792    static const char MSG_TRUE[] =
793    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
794    "From: zme@true.com\r\n"
795    "To: you\r\n"
796    "Subject: simple address test\r\n"
797    "\r\n"
798    "blah\n"
799    ;
800    static const char MSG_FALSE[] =
801    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
802    "From: zme@false.com\r\n"
803    "To: you\r\n"
804    "Subject: simple address test\r\n"
805    "\r\n"
806    "blah\n"
807    ;
808    sieve_test_context_t ctx;
809
810    /* Test :is */
811    context_setup(&ctx, SCRIPT_IS);
812    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
813
814    run_message(&ctx, MSG_TRUE);
815    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
816    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
817    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
818    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
819    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
820
821    run_message(&ctx, MSG_FALSE);
822    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
823    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
824    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
825    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
826    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
827
828    context_cleanup(&ctx);
829
830    /* Test :contains */
831    context_setup(&ctx, SCRIPT_CONTAINS);
832    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
833
834    run_message(&ctx, MSG_TRUE);
835    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
836    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
837    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
838    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
839    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
840
841    run_message(&ctx, MSG_FALSE);
842    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
843    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
844    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
845    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
846    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
847
848    context_cleanup(&ctx);
849
850    /* Test :matches */
851    context_setup(&ctx, SCRIPT_MATCHES);
852    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
853
854    run_message(&ctx, MSG_TRUE);
855    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
856    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
857    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
858    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
859    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
860
861    run_message(&ctx, MSG_FALSE);
862    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
863    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
864    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
865    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
866    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
867
868    context_cleanup(&ctx);
869}
870
871static void test_exists(void)
872{
873    static const char SCRIPT[] =
874    "if exists \"flooglewart\"\n"
875    "{redirect \"me@blah.com\";}\n"
876    ;
877    static const char MSG_TRUE[] =
878    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
879    "From: zme@true.com\r\n"
880    "To: you\r\n"
881    "Subject: simple address test\r\n"
882    "Flooglewart: fnarp fmeh oogedyboogedy\r\n"
883    "\r\n"
884    "blah\n"
885    ;
886    static const char MSG_FALSE[] =
887    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
888    "From: yme@false.com\r\n"
889    "To: you\r\n"
890    "Subject: simple address test\r\n"
891    "\r\n"
892    "blah\n"
893    ;
894    sieve_test_context_t ctx;
895
896    context_setup(&ctx, SCRIPT);
897    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
898
899    run_message(&ctx, MSG_TRUE);
900    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
901    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
902    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
903    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
904    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
905
906    run_message(&ctx, MSG_FALSE);
907    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
908    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
909    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
910    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
911    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
912
913    context_cleanup(&ctx);
914}
915
916static void test_address_domain(void)
917{
918    static const char SCRIPT[] =
919    "if address :domain :is \"from\" \"true.com\"\n"
920    "{redirect \"me@blah.com\";}\n"
921    ;
922    static const char MSG_TRUE[] =
923    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
924    "From: zme@true.com\r\n"
925    "To: you\r\n"
926    "Subject: simple address test\r\n"
927    "\r\n"
928    "blah\n"
929    ;
930    static const char MSG_FALSE[] =
931    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
932    "From: zme@false.com\r\n"
933    "To: you\r\n"
934    "Subject: simple address test\r\n"
935    "\r\n"
936    "blah\n"
937    ;
938    sieve_test_context_t ctx;
939
940    context_setup(&ctx, SCRIPT);
941    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
942
943    run_message(&ctx, MSG_TRUE);
944    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
945    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
946    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
947    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
948    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
949
950    run_message(&ctx, MSG_FALSE);
951    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
952    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
953    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
954    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
955    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
956
957    context_cleanup(&ctx);
958}
959
960static void test_address_localpart(void)
961{
962    static const char SCRIPT[] =
963    "if address :localpart :is \"from\" \"zme\"\n"
964    "{redirect \"me@blah.com\";}\n"
965    ;
966    static const char MSG_TRUE[] =
967    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
968    "From: zme@true.com\r\n"
969    "To: you\r\n"
970    "Subject: simple address test\r\n"
971    "\r\n"
972    "blah\n"
973    ;
974    static const char MSG_FALSE[] =
975    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
976    "From: yme@false.com\r\n"
977    "To: you\r\n"
978    "Subject: simple address test\r\n"
979    "\r\n"
980    "blah\n"
981    ;
982    sieve_test_context_t ctx;
983
984    context_setup(&ctx, SCRIPT);
985    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
986
987    run_message(&ctx, MSG_TRUE);
988    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
989    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
990    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
991    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
992    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
993
994    run_message(&ctx, MSG_FALSE);
995    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
996    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
997    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
998    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
999    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1000
1001    context_cleanup(&ctx);
1002}
1003
1004static void test_address_index(void)
1005{
1006    static const char SCRIPT[] =
1007    "require [\"index\"];\n"
1008    "if address :index 2 :is \"from\" \"zme@true.com\"\n"
1009    "{redirect \"me@blah.com\";}\n"
1010    ;
1011
1012    static const char SCRIPT_LAST[] =
1013    "require [\"index\"];\n"
1014    "if address :index 3 :last :is \"from\" \"zme@true.com\"\n"
1015    "{redirect \"me@blah.com\";}\n"
1016    ;
1017
1018    static const char MSG_TRUE[] =
1019    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1020    "From: zme@false.com\r\n"
1021    "From: zme@true.com\r\n"
1022    "From: zme@false.com\r\n"
1023    "From: zme@false.com\r\n"
1024    "To: you\r\n"
1025    "Subject: simple address test\r\n"
1026    "\r\n"
1027    "blah\n"
1028    ;
1029    static const char MSG_FALSE[] =
1030    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1031    "From: zme@true.com\r\n"
1032    "From: zme@false.com\r\n"
1033    "From: zme@true.com\r\n"
1034    "From: zme@true.com\r\n"
1035    "To: you\r\n"
1036    "Subject: simple address test\r\n"
1037    "\r\n"
1038    "blah\n"
1039    ;
1040    sieve_test_context_t ctx;
1041
1042    /* Test :index */
1043    context_setup(&ctx, SCRIPT);
1044    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1045
1046    run_message(&ctx, MSG_TRUE);
1047    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1048    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1049    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1050    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1051    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1052
1053    run_message(&ctx, MSG_FALSE);
1054    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1055    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1056    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1057    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1058    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1059
1060    context_cleanup(&ctx);
1061
1062    /* Test :index :last */
1063    context_setup(&ctx, SCRIPT_LAST);
1064    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1065
1066    run_message(&ctx, MSG_TRUE);
1067    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1068    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1069    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1070    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1071    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1072
1073    run_message(&ctx, MSG_FALSE);
1074    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1075    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1076    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1077    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1078    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1079
1080    context_cleanup(&ctx);
1081}
1082
1083static void test_header_index(void)
1084{
1085    static const char SCRIPT[] =
1086    "require [\"index\"];\n"
1087    "if header :index 3 :is \"X-Virus-Status\" \"Clean\"\n"
1088    "{redirect \"me@blah.com\";}\n"
1089    ;
1090
1091    static const char SCRIPT_LAST[] =
1092    "require [\"index\"];\n"
1093    "if header :index 2 :last :is \"X-Virus-Status\" \"Clean\"\n"
1094    "{redirect \"me@blah.com\";}\n"
1095    ;
1096
1097    static const char MSG_TRUE[] =
1098    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1099    "From: zme@true.com\r\n"
1100    "To: you\r\n"
1101    "Subject: simple address test\r\n"
1102    "X-Virus-Status: *****\r\n"
1103    "X-Virus-Status: *****\r\n"
1104    "X-Virus-Status: Clean\r\n"
1105    "X-Virus-Status: *****\r\n"
1106    "\r\n"
1107    "blah\n"
1108    ;
1109    static const char MSG_FALSE[] =
1110    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1111    "From: zme@false.com\r\n"
1112    "To: you\r\n"
1113    "Subject: simple address test\r\n"
1114    "X-Virus-Status: Clean\r\n"
1115    "X-Virus-Status: Clean\r\n"
1116    "X-Virus-Status: *****\r\n"
1117    "X-Virus-Status: Clean\r\n"
1118    "\r\n"
1119    "blah\n"
1120    ;
1121    sieve_test_context_t ctx;
1122
1123    /* Test :index */
1124    context_setup(&ctx, SCRIPT);
1125    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1126
1127    run_message(&ctx, MSG_TRUE);
1128    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1129    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1130    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1131    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1132    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1133
1134    run_message(&ctx, MSG_FALSE);
1135    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1136    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1137    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1138    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1139    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1140
1141    context_cleanup(&ctx);
1142
1143    /* Test :index :last */
1144    context_setup(&ctx, SCRIPT_LAST);
1145    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1146
1147    run_message(&ctx, MSG_TRUE);
1148    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1149    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1150    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1151    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1152    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1153
1154    run_message(&ctx, MSG_FALSE);
1155    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1156    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1157    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1158    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1159    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1160
1161    context_cleanup(&ctx);
1162}
1163
1164static void test_date_year(void)
1165{
1166    static const char SCRIPT[] =
1167    "require [\"date\", \"index\"];\n"
1168    "if date :index 1 :last :is \"received\" \"year\" [ \"1983\", \"1993\", \"2003\", \"2013\" ]\n"
1169    "{redirect \"me@blah.com\";}\n"
1170    ;
1171    static const char MSG_TRUE[] =
1172    "Date: Sat, 16 Nov 2013 12:46:49 +1100\r\n"
1173    "Received: from localhost (localhost [127.0.0.1])\r\n"
1174    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1175    "           Tue, 16 Nov 2010 12:50:12 +1100\r\n"
1176    "Received: from localhost (localhost [127.0.0.1])\r\n"
1177    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1178    "           Tue, 16 Nov 2013 12:50:12 +1100\r\n"
1179    "From: zme@true.com\r\n"
1180    "To: you\r\n"
1181    "Subject: simple address test\r\n"
1182    "\r\n"
1183    "blah\n"
1184    ;
1185    static const char MSG_FALSE[] =
1186    "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n"
1187    "Received: from localhost (localhost [127.0.0.1])\r\n"
1188    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1189    "           Tue, 16 Nov 2013 12:50:12 +1100\r\n"
1190    "Received: from localhost (localhost [127.0.0.1])\r\n"
1191    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1192    "           Tue, 16 Nov 2010 12:50:12 +1100\r\n"
1193    "From: yme@false.com\r\n"
1194    "To: you\r\n"
1195    "Subject: simple address test\r\n"
1196    "\r\n"
1197    "blah\n"
1198    ;
1199    sieve_test_context_t ctx;
1200
1201    context_setup(&ctx, SCRIPT);
1202    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1203
1204    run_message(&ctx, MSG_TRUE);
1205    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1206    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1207    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1208    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1209    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1210
1211    run_message(&ctx, MSG_FALSE);
1212    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1213    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1214    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1215    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1216    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1217
1218    context_cleanup(&ctx);
1219}
1220
1221static void test_date_zone_month(void)
1222{
1223    static const char SCRIPT[] =
1224    "require [\"date\"];\n"
1225    "if date :is :zone \"-0800\" \"date\" \"month\" \"11\"\n"
1226    "{redirect \"me@blah.com\";}\n"
1227    ;
1228    static const char MSG_TRUE[] =
1229    "Date: Fri, 1 Nov 2013 19:46:49 +1100\r\n"
1230    "From: zme@true.com\r\n"
1231    "To: you\r\n"
1232    "Subject: simple address test\r\n"
1233    "\r\n"
1234    "blah\n"
1235    ;
1236    static const char MSG_FALSE[] =
1237    "Date: Fri, 1 Nov 2013 11:46:49 +1100\r\n"
1238    "From: yme@false.com\r\n"
1239    "To: you\r\n"
1240    "Subject: simple address test\r\n"
1241    "\r\n"
1242    "blah\n"
1243    ;
1244    sieve_test_context_t ctx;
1245
1246    context_setup(&ctx, SCRIPT);
1247    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1248
1249    run_message(&ctx, MSG_TRUE);
1250    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1251    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1252    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1253    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1254    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1255
1256    run_message(&ctx, MSG_FALSE);
1257    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1258    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1259    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1260    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1261    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1262
1263    context_cleanup(&ctx);
1264}
1265
1266static void test_date_date(void)
1267{
1268    static const char SCRIPT[] =
1269    "require [\"date\"];\n"
1270    "if date :is :originalzone \"date\" \"date\" \"2013-11-02\"\n"
1271    "{redirect \"me@blah.com\";}\n"
1272    ;
1273    static const char MSG_TRUE[] =
1274    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1275    "From: zme@true.com\r\n"
1276    "To: you\r\n"
1277    "Subject: simple address test\r\n"
1278    "\r\n"
1279    "blah\n"
1280    ;
1281    static const char MSG_FALSE[] =
1282    "Date: Fri, 1 Nov 2013 19:45:49 +1100\r\n"
1283    "From: yme@false.com\r\n"
1284    "To: you\r\n"
1285    "Subject: simple address test\r\n"
1286    "\r\n"
1287    "blah\n"
1288    ;
1289    sieve_test_context_t ctx;
1290
1291    context_setup(&ctx, SCRIPT);
1292    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1293
1294    run_message(&ctx, MSG_TRUE);
1295    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1296    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1297    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1298    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1299    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1300
1301    run_message(&ctx, MSG_FALSE);
1302    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1303    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1304    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1305    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1306    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1307
1308    context_cleanup(&ctx);
1309}
1310
1311static void test_date_time(void)
1312{
1313    static const char SCRIPT[] =
1314    "require [\"date\"];\n"
1315    "if date :is :originalzone \"date\" \"time\" \"19:46:49\"\n"
1316    "{redirect \"me@blah.com\";}\n"
1317    ;
1318    static const char MSG_TRUE[] =
1319    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1320    "From: zme@true.com\r\n"
1321    "To: you\r\n"
1322    "Subject: simple address test\r\n"
1323    "\r\n"
1324    "blah\n"
1325    ;
1326    static const char MSG_FALSE[] =
1327    "Date: Sat, 2 Nov 2013 19:45:49 +1100\r\n"
1328    "From: yme@false.com\r\n"
1329    "To: you\r\n"
1330    "Subject: simple address test\r\n"
1331    "\r\n"
1332    "blah\n"
1333    ;
1334    sieve_test_context_t ctx;
1335
1336    context_setup(&ctx, SCRIPT);
1337    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1338
1339    run_message(&ctx, MSG_TRUE);
1340    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1341    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1342    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1343    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1344    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1345
1346    run_message(&ctx, MSG_FALSE);
1347    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1348    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1349    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1350    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1351    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1352
1353    context_cleanup(&ctx);
1354}
1355
1356static void test_date_originalzone_day(void)
1357{
1358    static const char SCRIPT[] =
1359    "require [\"date\"];\n"
1360    "if date :is :originalzone \"date\" \"day\" \"16\"\n"
1361    "{redirect \"me@blah.com\";}\n"
1362    ;
1363    static const char MSG_TRUE[] =
1364    "Date: Sat, 16 Nov 2013 12:46:49 +1100\r\n"
1365    "From: zme@true.com\r\n"
1366    "To: you\r\n"
1367    "Subject: simple address test\r\n"
1368    "\r\n"
1369    "blah\n"
1370    ;
1371    static const char MSG_FALSE[] =
1372    "Date: Fri, 15 Nov 2013 12:46:49 +1100\r\n"
1373    "From: yme@false.com\r\n"
1374    "To: you\r\n"
1375    "Subject: simple address test\r\n"
1376    "\r\n"
1377    "blah\n"
1378    ;
1379    sieve_test_context_t ctx;
1380
1381    context_setup(&ctx, SCRIPT);
1382    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1383
1384    run_message(&ctx, MSG_TRUE);
1385    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1386    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1387    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1388    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1389    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1390
1391    run_message(&ctx, MSG_FALSE);
1392    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1393    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1394    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1395    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1396    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1397
1398    context_cleanup(&ctx);
1399}
1400
1401static void test_date_weekend_weekday(void)
1402{
1403    static const char SCRIPT[] =
1404    "require [\"date\"];\n"
1405    "if anyof(date :is :zone \"-0800\" \"date\" \"weekday\" \"0\",\n"
1406    "         date :is :zone \"-0800\" \"date\" \"weekday\" \"6\")\n"
1407    "{redirect \"me@blah.com\";}\n"
1408    ;
1409    static const char MSG_TRUE[] =
1410    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1411    "From: zme@true.com\r\n"
1412    "To: you\r\n"
1413    "Subject: simple address test\r\n"
1414    "\r\n"
1415    "blah\n"
1416    ;
1417    static const char MSG_FALSE[] =
1418    "Date: Fri, 1 Nov 2013 11:46:49 +1100\r\n"
1419    "From: yme@false.com\r\n"
1420    "To: you\r\n"
1421    "Subject: simple address test\r\n"
1422    "\r\n"
1423    "blah\n"
1424    ;
1425    sieve_test_context_t ctx;
1426
1427    context_setup(&ctx, SCRIPT);
1428    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1429
1430    run_message(&ctx, MSG_TRUE);
1431    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1432    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1433    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1434    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1435    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1436
1437    run_message(&ctx, MSG_FALSE);
1438    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1439    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1440    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1441    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1442    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1443
1444    context_cleanup(&ctx);
1445}
1446
1447static void test_date_zone(void)
1448{
1449    static const char SCRIPT[] =
1450    "require [\"date\"];\n"
1451    "if date :is :originalzone \"date\" \"zone\" \"+1100\"\n"
1452    "{redirect \"me@blah.com\";}\n"
1453    ;
1454    static const char MSG_TRUE[] =
1455    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1456    "From: zme@true.com\r\n"
1457    "To: you\r\n"
1458    "Subject: simple address test\r\n"
1459    "\r\n"
1460    "blah\n"
1461    ;
1462    static const char MSG_FALSE[] =
1463    "Date: Fri, 1 Nov 2013 11:46:49 -0700\r\n"
1464    "From: yme@false.com\r\n"
1465    "To: you\r\n"
1466    "Subject: simple address test\r\n"
1467    "\r\n"
1468    "blah\n"
1469    ;
1470    sieve_test_context_t ctx;
1471
1472    context_setup(&ctx, SCRIPT);
1473    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1474
1475    run_message(&ctx, MSG_TRUE);
1476    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1477    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1478    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1479    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1480    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1481
1482    run_message(&ctx, MSG_FALSE);
1483    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1484    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1485    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1486    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1487    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1488
1489    context_cleanup(&ctx);
1490}
1491
1492static void test_currentdate_true(void)
1493{
1494    static const char SCRIPT[] =
1495    "require [\"date\", \"relational\"];\n"
1496    "if allof(currentdate :zone \"+0000\" :value \"ge\" \"date\" \"2014-01-01\",\n"
1497    "         currentdate :zone \"+0000\" :value \"le\" \"date\" \"2114-01-01\")\n"
1498    "{redirect \"me@blah.com\";}\n"
1499    ;
1500    static const char MSG_TRUE[] =
1501    "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n"
1502    "From: yme@true.com\r\n"
1503    "To: you\r\n"
1504    "Subject: simple address test\r\n"
1505    "\r\n"
1506    "blah\n"
1507    ;
1508    sieve_test_context_t ctx;
1509
1510    context_setup(&ctx, SCRIPT);
1511    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1512
1513    run_message(&ctx, MSG_TRUE);
1514    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1515    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1516    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1517    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1518    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1519
1520    context_cleanup(&ctx);
1521}
1522
1523static void test_currentdate_false(void)
1524{
1525    static const char SCRIPT[] =
1526    "require [\"date\", \"relational\"];\n"
1527    "if allof(currentdate :zone \"+0000\" :value \"ge\" \"date\" \"1970-01-01\",\n"
1528    "         currentdate :zone \"+0000\" :value \"le\" \"date\" \"2014-01-01\")\n"
1529    "{redirect \"me@blah.com\";}\n"
1530    ;
1531    static const char MSG_FALSE[] =
1532    "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n"
1533    "From: yme@false.com\r\n"
1534    "To: you\r\n"
1535    "Subject: simple address test\r\n"
1536    "\r\n"
1537    "blah\n"
1538    ;
1539    sieve_test_context_t ctx;
1540
1541    context_setup(&ctx, SCRIPT);
1542    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1543
1544    run_message(&ctx, MSG_FALSE);
1545    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1546    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1547    CU_ASSERT_EQUAL(ctx.stats.redirects, 0);
1548    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1549    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, NULL);
1550
1551    context_cleanup(&ctx);
1552}
1553
1554static void test_imap4flags_setflag(void)
1555{
1556    static const char SCRIPT_INTERNAL[] =
1557    "require [\"imap4flags\", \"variables\"];\n"
1558    "setflag \"myvar\" [\"myval1\", \"myval2\"];\n"
1559    "keep :flags \"${myvar}\";\n"
1560    ;
1561
1562    static const char MSG_TRUE[] =
1563    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1564    "From: zme@true.com\r\n"
1565    "To: you\r\n"
1566    "Subject: simple flag test\r\n"
1567    "\r\n"
1568    "blah\n"
1569    ;
1570    sieve_test_context_t ctx;
1571
1572    /* Test setflag "variablename" "flags" */
1573    context_setup(&ctx, SCRIPT_INTERNAL);
1574    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1575
1576    run_message(&ctx, MSG_TRUE);
1577    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1578    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1579    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1580    CU_ASSERT_NOT_EQUAL(strarray_find_case(ctx.flags,"myval1",0),-1);
1581    CU_ASSERT_NOT_EQUAL(strarray_find_case(ctx.flags,"myval2",0),-1);
1582
1583    context_cleanup(&ctx);
1584
1585    /* Test #2 */
1586}
1587
1588// TODO: test
1589// if size :over 10K { redirect "me@blah.com"; }
1590// TODO: test
1591// if true {...}
1592//
1593// if false {...}
1594//
1595// if not false {...}
1596//
1597// if true {...} else {...}
1598//
1599// if false {...} elsif true {...} else {...}
1600//
1601// if false {...} elsif false {...} else {...}
1602//
1603// if false {} else {...}
1604//
1605// if true { if true { if true { ... } } }
1606//
1607// if allof(false, false) {...} else {...}
1608//
1609// if allof(false,true) {...} else {...}
1610//
1611// if allof(true,false) {...} else {...}
1612//
1613// if allof(true,true) {...} else {...}
1614//
1615// if anyof(false, false) {...} else {...}
1616//
1617// if anyof(false,true) {...} else {...}
1618//
1619// if anyof(true,false) {...} else {...}
1620//
1621// if anyof(true,true) {...} else {...}
1622/* vim: set ft=c: */
1623