Coverage Report

Created: 2026-07-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/testsuite/testsuite-common.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "string.h"
6
#include "ostream.h"
7
#include "hash.h"
8
#include "safe-mkstemp.h"
9
#include "mail-storage.h"
10
#include "env-util.h"
11
#include "unlink-directory.h"
12
13
#include "mail-raw.h"
14
15
#include "sieve-common.h"
16
#include "sieve-code.h"
17
#include "sieve-message.h"
18
#include "sieve-commands.h"
19
#include "sieve-extensions.h"
20
#include "sieve-binary.h"
21
#include "sieve-validator.h"
22
#include "sieve-generator.h"
23
#include "sieve-interpreter.h"
24
#include "sieve-result.h"
25
#include "sieve-dump.h"
26
27
#include "testsuite-common.h"
28
#include "testsuite-settings.h"
29
#include "testsuite-objects.h"
30
#include "testsuite-log.h"
31
#include "testsuite-script.h"
32
#include "testsuite-binary.h"
33
#include "testsuite-result.h"
34
#include "testsuite-smtp.h"
35
36
#include <string.h>
37
#include <fcntl.h>
38
#include <unistd.h>
39
#include <time.h>
40
#include <sys/stat.h>
41
#include <sys/types.h>
42
43
/*
44
 * Global data
45
 */
46
47
struct sieve_instance *testsuite_sieve_instance = NULL;
48
char *testsuite_test_path = NULL;
49
bool testsuite_silent = FALSE;
50
51
unsigned int test_failures;
52
53
static struct sieve_interpreter *testsuite_interp = NULL;
54
55
/* Test context */
56
57
static string_t *test_name;
58
static sieve_size_t test_block_end;
59
static unsigned int test_index;
60
61
/* Extension */
62
63
const struct sieve_extension *testsuite_ext;
64
65
/*
66
 * Validator context
67
 */
68
69
bool testsuite_validator_context_initialize(struct sieve_validator *valdtr)
70
0
{
71
0
  pool_t pool = sieve_validator_pool(valdtr);
72
0
  struct testsuite_validator_context *ctx =
73
0
    p_new(pool, struct testsuite_validator_context, 1);
74
75
  /* Setup object registry */
76
0
  ctx->object_registrations =
77
0
    sieve_validator_object_registry_create(valdtr);
78
0
  testsuite_register_core_objects(ctx);
79
80
0
  sieve_validator_extension_set_context(valdtr, testsuite_ext, ctx);
81
82
0
  return TRUE;
83
0
}
84
85
struct testsuite_validator_context *
86
testsuite_validator_context_get(struct sieve_validator *valdtr)
87
0
{
88
0
  return (struct testsuite_validator_context *)
89
0
    sieve_validator_extension_get_context(valdtr, testsuite_ext);
90
0
}
91
92
/*
93
 * Generator context
94
 */
95
96
bool testsuite_generator_context_initialize(
97
  struct sieve_generator *gentr, const struct sieve_extension *this_ext)
98
0
{
99
0
  pool_t pool = sieve_generator_pool(gentr);
100
0
  struct sieve_binary_block *sblock = sieve_generator_get_block(gentr);
101
0
  struct testsuite_generator_context *ctx =
102
0
    p_new(pool, struct testsuite_generator_context, 1);
103
104
  /* Setup exit jumplist */
105
0
  ctx->exit_jumps = sieve_jumplist_create(pool, sblock);
106
107
0
  sieve_generator_extension_set_context(gentr, this_ext, ctx);
108
109
0
  return TRUE;
110
0
}
111
112
/*
113
 * Interpreter context
114
 */
115
116
static void
117
testsuite_interpreter_free(const struct sieve_extension *ext ATTR_UNUSED,
118
         struct sieve_interpreter *interp ATTR_UNUSED,
119
         void *context)
120
0
{
121
0
  struct testsuite_interpreter_context *ctx =
122
0
    (struct testsuite_interpreter_context *)context;
123
124
0
  sieve_binary_unref(&ctx->compiled_script);
125
0
}
126
127
const struct sieve_interpreter_extension testsuite_interpreter_ext = {
128
  .ext_def = &testsuite_extension,
129
  .free = testsuite_interpreter_free,
130
};
131
132
bool testsuite_interpreter_context_initialize(
133
  struct sieve_interpreter *interp, const struct sieve_extension *this_ext)
134
0
{
135
0
  pool_t pool = sieve_interpreter_pool(interp);
136
0
  struct testsuite_interpreter_context *ctx =
137
0
    p_new(pool, struct testsuite_interpreter_context, 1);
138
139
0
  sieve_interpreter_extension_register(interp, this_ext,
140
0
               &testsuite_interpreter_ext, ctx);
141
0
  return TRUE;
142
0
}
143
144
struct testsuite_interpreter_context *
145
testsuite_interpreter_context_get(struct sieve_interpreter *interp,
146
          const struct sieve_extension *this_ext)
147
0
{
148
0
  struct testsuite_interpreter_context *ctx =
149
0
    sieve_interpreter_extension_get_context(interp, this_ext);
150
151
0
  return ctx;
152
0
}
153
154
/*
155
 * Test context
156
 */
157
158
static void testsuite_test_context_init(void)
159
0
{
160
0
  test_name = str_new(default_pool, 128);
161
0
  test_block_end = 0;
162
0
  test_index = 0;
163
0
  test_failures = 0;
164
0
}
165
166
int testsuite_test_start(const struct sieve_runtime_env *renv,
167
       string_t *name, sieve_size_t block_end)
168
0
{
169
0
  if (test_block_end != 0) {
170
0
    sieve_runtime_trace_error(renv, "already inside test block");
171
0
    return SIEVE_EXEC_BIN_CORRUPT;
172
0
  }
173
0
  str_truncate(test_name, 0);
174
0
  str_append_str(test_name, name);
175
176
0
  test_block_end = block_end;
177
0
  test_index++;
178
179
0
  return SIEVE_EXEC_OK;
180
0
}
181
182
int testsuite_test_fail(const struct sieve_runtime_env *renv,
183
      string_t *reason)
184
0
{
185
0
  return testsuite_test_fail_cstr(renv, str_c(reason));
186
0
}
187
188
int testsuite_test_failf(const struct sieve_runtime_env *renv,
189
       const char *fmt, ...)
190
0
{
191
0
  va_list args;
192
0
  int ret;
193
194
0
  va_start(args, fmt);
195
0
  ret = testsuite_test_fail_cstr(renv, t_strdup_vprintf(fmt, args));
196
0
  va_end(args);
197
198
0
  return ret;
199
0
}
200
201
int testsuite_test_fail_cstr(const struct sieve_runtime_env *renv,
202
           const char *reason)
203
0
{
204
0
  sieve_size_t end = test_block_end;
205
206
0
  if (!testsuite_silent) {
207
0
    if (str_len(test_name) == 0) {
208
0
      if (reason == NULL || *reason == '\0')
209
0
        printf("%2d: Test FAILED\n", test_index);
210
0
      else {
211
0
        printf("%2d: Test FAILED: %s\n",
212
0
               test_index, reason);
213
0
      }
214
0
    } else {
215
0
      if (reason == NULL || *reason == '\0') {
216
0
        printf("%2d: Test '%s' FAILED\n",
217
0
               test_index, str_c(test_name));
218
0
      } else {
219
0
        printf("%2d: Test '%s' FAILED: %s\n",
220
0
               test_index, str_c(test_name), reason);
221
0
      }
222
0
    }
223
0
  }
224
225
0
  test_failures++;
226
227
0
  if (end == 0)
228
0
    return SIEVE_EXEC_FAILURE;
229
0
  if (renv->interp != testsuite_interp) {
230
0
    sieve_interpreter_interrupt(renv->interp);
231
0
    return SIEVE_EXEC_OK;
232
0
  }
233
234
0
  str_truncate(test_name, 0);
235
0
  test_block_end = 0;
236
237
0
  return sieve_interpreter_program_jump_to(renv->interp, end, TRUE);
238
0
}
239
240
void testsuite_testcase_fail(const char *reason)
241
0
{
242
0
  if (!testsuite_silent) {
243
0
    if (reason == NULL || *reason == '\0')
244
0
      printf("XX: Test CASE FAILED\n");
245
0
    else
246
0
      printf("XX: Test CASE FAILED: %s\n", reason);
247
0
  }
248
0
  test_failures++;
249
0
}
250
251
int testsuite_test_succeed(const struct sieve_runtime_env *renv,
252
         sieve_size_t *address, string_t *reason)
253
0
{
254
0
  sieve_size_t end = test_block_end;
255
0
  int ret;
256
257
0
  if (!testsuite_silent) {
258
0
    if (str_len(test_name) == 0) {
259
0
      if (reason == NULL || str_len(reason) == 0)
260
0
        printf("%2d: Test SUCCEEDED\n", test_index);
261
0
      else {
262
0
        printf("%2d: Test SUCCEEDED: %s\n",
263
0
               test_index, str_c(reason));
264
0
      }
265
0
    } else {
266
0
      if (reason == NULL || str_len(reason) == 0) {
267
0
        printf("%2d: Test '%s' SUCCEEDED\n",
268
0
               test_index, str_c(test_name));
269
0
      } else {
270
0
        printf("%2d: Test '%s' SUCCEEDED: %s\n",
271
0
               test_index, str_c(test_name),
272
0
               str_c(reason));
273
0
      }
274
0
    }
275
0
  }
276
277
0
  str_truncate(test_name, 0);
278
0
  test_block_end = 0;
279
280
0
  if (*address > end) {
281
0
    sieve_runtime_trace_error(
282
0
      renv, "invalid test block end offset");
283
0
    return SIEVE_EXEC_BIN_CORRUPT;
284
0
  } else if (*address < end) {
285
0
    ret = sieve_interpreter_program_jump_to(
286
0
      renv->interp, end, FALSE);
287
0
    if (ret <= 0)
288
0
      return ret;
289
0
  }
290
291
0
  return SIEVE_EXEC_OK;
292
0
}
293
294
static void testsuite_test_context_deinit(void)
295
0
{
296
0
  str_free(&test_name);
297
0
}
298
299
bool testsuite_testcase_result(bool expect_failure)
300
0
{
301
0
  if (expect_failure) {
302
0
    if (test_failures < test_index) {
303
0
      if (!testsuite_silent) {
304
0
        printf("\nFAIL: Only %d of %d tests failed "
305
0
               "(all expected to fail).\n\n",
306
0
               test_failures, test_index);
307
0
      }
308
0
      return FALSE;
309
0
    }
310
311
0
    if (!testsuite_silent) {
312
0
      printf("\nPASS: %d tests failed "
313
0
             "(expected to fail).\n\n",
314
0
             (test_index == 0 ? 1 : test_index));
315
0
    }
316
0
    return TRUE;
317
0
  }
318
319
0
  if (test_failures > 0) {
320
0
    if (!testsuite_silent) {
321
0
      printf("\nFAIL: %d of %d tests failed.\n\n",
322
0
             test_failures, test_index);
323
0
    }
324
0
    return FALSE;
325
0
  }
326
327
0
  if (!testsuite_silent)
328
0
    printf("\nPASS: %d tests succeeded.\n\n", test_index);
329
0
  return TRUE;
330
0
}
331
332
/*
333
 * Testsuite temporary directory
334
 */
335
336
static char *testsuite_tmp_dir;
337
338
static void testsuite_tmp_dir_init(const char *tmp_path)
339
0
{
340
0
  if (tmp_path == NULL)
341
0
    tmp_path = "/tmp";
342
343
0
  string_t *dir = t_str_new(256);
344
0
  str_append(dir, tmp_path);
345
0
  str_append_c(dir, '/');
346
0
  str_append(dir, "sieve-testsuite");
347
0
  str_append_c(dir, '-');
348
349
0
  if (safe_mkstemp_dir_pid(dir, 0700) < 0)
350
0
    i_fatal("safe_mkstemp_dir(%s) failed: %m", str_c(dir));
351
0
  testsuite_tmp_dir = i_strdup(str_c(dir));
352
0
}
353
354
void testsuite_tmp_dir_deinit(void)
355
0
{
356
0
  if (testsuite_tmp_dir == NULL)
357
0
    return;
358
359
0
  const char *error = NULL;
360
0
  if (unlink_directory(testsuite_tmp_dir,
361
0
           UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0)
362
0
    i_warning("failed to remove temporary directory '%s': %s.",
363
0
        testsuite_tmp_dir, error);
364
365
0
  i_free(testsuite_tmp_dir);
366
0
}
367
368
const char *testsuite_tmp_dir_get(void)
369
0
{
370
0
  return testsuite_tmp_dir;
371
0
}
372
373
/*
374
 * Main testsuite init/run/deinit
375
 */
376
377
void testsuite_init(struct sieve_instance *svinst, const char *test_path,
378
        const char *wdir_path, bool log_stdout)
379
0
{
380
0
  int ret;
381
382
0
  testsuite_sieve_instance = svinst;
383
384
0
  testsuite_test_context_init();
385
0
  testsuite_log_init(log_stdout);
386
0
  testsuite_tmp_dir_init(wdir_path);
387
388
0
  testsuite_script_init();
389
0
  testsuite_binary_init();
390
0
  testsuite_smtp_init();
391
392
0
  ret = sieve_extension_register(svinst, &testsuite_extension, TRUE,
393
0
               &testsuite_ext);
394
0
  i_assert(ret == 0);
395
396
0
  testsuite_test_path = i_strdup(test_path);
397
0
}
398
399
int testsuite_run(struct sieve_binary *sbin,
400
      struct sieve_error_handler *ehandler)
401
0
{
402
0
  struct sieve_result *result;
403
0
  int ret = 0;
404
405
  /* Create the interpreter */
406
0
  testsuite_interp = sieve_interpreter_create(
407
0
    sbin, NULL, &testsuite_execute_env, ehandler);
408
0
  if (testsuite_interp == NULL)
409
0
    return SIEVE_EXEC_BIN_CORRUPT;
410
411
  /* Run the interpreter */
412
0
  result = testsuite_result_get();
413
0
  ret = sieve_interpreter_run(testsuite_interp, result);
414
415
  /* Free the interpreter */
416
0
  sieve_interpreter_free(&testsuite_interp);
417
418
0
  return ret;
419
0
}
420
421
void testsuite_deinit(void)
422
0
{
423
0
  i_free(testsuite_test_path);
424
425
0
  testsuite_smtp_deinit();
426
0
  testsuite_binary_deinit();
427
0
  testsuite_script_deinit();
428
429
0
  testsuite_tmp_dir_deinit();
430
0
  testsuite_log_deinit();
431
0
  testsuite_test_context_deinit();
432
0
}