]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-mail: Add I/O test for istream-dot/ostream-dot
authorStephan Bosch <stephan.bosch@open-xchange.com>
Tue, 27 Jan 2026 02:54:21 +0000 (03:54 +0100)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Fri, 6 Feb 2026 13:08:57 +0000 (13:08 +0000)
src/lib-mail/Makefile.am
src/lib-mail/test-iostream-dot.c [new file with mode: 0644]

index 8a4a0ef0bd5fe0b09cf8968623d70ec475e92d67..c02b338817d4793f8d43a79058f58f20b2b4fabb 100644 (file)
@@ -83,6 +83,7 @@ pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
 
 test_programs = \
+       test-iostream-dot \
        test-istream-dot \
        test-istream-attachment \
        test-istream-binary-converter \
@@ -169,6 +170,10 @@ test_istream_dot_SOURCES = test-istream-dot.c
 test_istream_dot_LDADD = $(test_libs)
 test_istream_dot_DEPENDENCIES = $(test_deps)
 
+test_iostream_dot_SOURCES = test-iostream-dot.c
+test_iostream_dot_LDADD = $(test_libs)
+test_iostream_dot_DEPENDENCIES = $(test_deps)
+
 test_istream_qp_decoder_SOURCES = test-istream-qp-decoder.c
 test_istream_qp_decoder_LDADD = $(test_libs)
 test_istream_qp_decoder_DEPENDENCIES = $(test_deps)
diff --git a/src/lib-mail/test-iostream-dot.c b/src/lib-mail/test-iostream-dot.c
new file mode 100644 (file)
index 0000000..995d4db
--- /dev/null
@@ -0,0 +1,184 @@
+/* Copyright (c) 2026 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "randgen.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-dot.h"
+#include "ostream-dot.h"
+#include "test-common.h"
+
+#include "hex-binary.h"
+
+static void test_iostream_dot_random_io(void)
+{
+       unsigned char in_buf[1024];
+       size_t in_buf_size;
+       buffer_t *enc_buf, *dec_buf;
+       unsigned int i, j;
+       int ret;
+
+       enc_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
+       dec_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
+
+       test_begin("dot istream/ostream random I/O");
+
+       for (i = 0; !test_has_failed() && i < 2000; i++) {
+               struct istream *input1, *input2;
+               struct ostream *output1, *output2;
+               struct istream *top_input;
+               const unsigned char *data;
+               size_t size, in_pos, out_pos;
+
+               /* Initialize test data*/
+               in_buf_size = i_rand_minmax(1, sizeof(in_buf));
+               for (j = 0; j < in_buf_size; j++) {
+                       in_buf[j] = i_rand_limit(256);
+                       if (in_buf[j] == '\n' &&
+                           (j == 0 || in_buf[j - 1] != '\r') ) {
+                               if (j + 1 == in_buf_size)
+                                       in_buf[j] = ' ';
+                               else {
+                                       in_buf[j] = '\r';
+                                       in_buf[j + 1] = '\n';
+                               }
+                       }
+               }
+
+               /* Reset encode output buffer */
+               buffer_set_used_size(enc_buf, 0);
+
+               /* Create input stream for test data */
+               input1 = test_istream_create_data(in_buf, in_buf_size);
+               i_stream_set_name(input1, "[data]");
+
+               /* Create output stream for test data */
+               output1 = test_ostream_create_nonblocking(
+                       enc_buf, i_rand_minmax(3, 4096)); /* < 3 not supported */
+               /* Create dot output stream */
+               output2 = o_stream_create_dot(output1, FALSE);
+               o_stream_set_name(output2, "[encoder]");
+
+               /* Compress the data incrementally */
+               in_pos = out_pos = 0;
+               ret = 0;
+               test_istream_set_size(input1, in_pos);
+               while (ret == 0) {
+                       enum ostream_send_istream_result res;
+
+                       res = o_stream_send_istream(output2, input1);
+                       switch(res) {
+                       case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+                       case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+                               ret = -1;
+                               break;
+                       case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+                               out_pos += i_rand_limit(512);
+                               test_ostream_set_max_output_size(
+                                       output1, out_pos);
+                               ret = 0;
+                               break;
+                       case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+                               in_pos += i_rand_limit(512);
+                               if (in_pos > in_buf_size)
+                                       in_pos = in_buf_size;
+                               test_istream_set_size(input1, in_pos);
+                               ret = 0;
+                               break;
+                       case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+                               /* finish it */
+                               ret = o_stream_finish(output2);
+                               break;
+                       }
+               }
+
+               /* Clean up */
+               i_stream_unref(&input1);
+               o_stream_unref(&output1);
+               o_stream_unref(&output2);
+
+               /* Reset decode output buffer */
+               buffer_set_used_size(dec_buf, 0);
+
+               /* Create input stream for compressed data */
+               input1 = i_stream_create_from_buffer(enc_buf);
+               i_stream_set_name(input1, "[dot-data]");
+
+               /* Create decompressor stream */
+               input2 =  i_stream_create_dot(input1, ISTREAM_DOT_TRIM_TRAIL |
+                                                     ISTREAM_DOT_STRICT_EOT);
+               i_stream_set_name(input2, "[decoder]");
+
+               /* Assign random buffer sizes */
+               i_stream_set_max_buffer_size(input2, i_rand_minmax(1, 4096));
+
+               /* Read the outer stream in full with random increments. */
+               top_input = input2;
+               while ((ret = i_stream_read_more(
+                       top_input, &data, &size)) > 0) {
+                       size_t ch = i_rand_limit(512);
+
+                       size = I_MIN(size, ch);
+                       buffer_append(dec_buf, data, size);
+                       i_stream_skip(top_input, size);
+               }
+               if (ret < 0 && top_input->stream_errno == 0) {
+                       data = i_stream_get_data(top_input, &size);
+                       if (size > 0) {
+                               buffer_append(dec_buf, data, size);
+                               i_stream_skip(top_input, size);
+                       }
+               }
+
+               /* Assert stream status */
+               test_assert_idx(ret < 0 && top_input->stream_errno == 0, i);
+
+               if (in_buf_size > 2 && in_buf[in_buf_size-2] == '\r' &&
+                   in_buf[in_buf_size-1] == '\n')
+                       in_buf_size -= 2;
+
+               /* Assert input/output equality */
+               test_assert_memcmp_idx(in_buf, in_buf_size, dec_buf->data, dec_buf->used, i);
+               if (test_has_failed()) {
+                       i_info("EXPECTED INPUT: %s", binary_to_hex(in_buf, in_buf_size));
+                       i_info("ACTUAL   INPUT: %s", binary_to_hex(dec_buf->data, dec_buf->used));
+               }
+
+               if (top_input->stream_errno != 0) {
+                       i_error("%s: %s", i_stream_get_name(input1),
+                              i_stream_get_error(input1));
+                       i_error("%s: %s", i_stream_get_name(input2),
+                              i_stream_get_error(input2));
+               }
+
+               if (test_has_failed()) {
+                       i_info("Test parameters: size=%zu",
+                               in_buf_size);
+               }
+
+               /* Clean up */
+               i_stream_unref(&input1);
+               i_stream_unref(&input2);
+       }
+       test_end();
+       buffer_free(&enc_buf);
+       buffer_free(&dec_buf);
+}
+
+int main(void)
+{
+       static void (*const test_functions[])(void) = {
+               test_iostream_dot_random_io,
+               NULL,
+       };
+
+       int ret;
+       struct ioloop *ioloop;
+
+       ioloop = io_loop_create();
+       ret = test_run(test_functions);
+       io_loop_destroy(&ioloop);
+
+       return ret;
+}