]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-program-client: Add test suite for program client
authorAki Tuomi <aki.tuomi@dovecot.fi>
Fri, 7 Oct 2016 16:49:24 +0000 (19:49 +0300)
committerAki Tuomi <aki.tuomi@dovecot.fi>
Sun, 9 Oct 2016 20:46:36 +0000 (23:46 +0300)
src/lib-program-client/Makefile.am
src/lib-program-client/test-program-client-local.c [new file with mode: 0644]
src/lib-program-client/test-program-client-remote.c [new file with mode: 0644]

index a31e101e64f7599e8c267650bb4be5677cd63f23..14fbd3d219cc6f7f616999c53d6b640db697dd4f 100644 (file)
@@ -1,7 +1,8 @@
 noinst_LTLIBRARIES = libprogram_client.la
 
 AM_CPPFLAGS = \
-       -I$(top_srcdir)/src/lib
+       -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-test
 
 libprogram_client_la_SOURCES = \
        program-client.c \
@@ -16,3 +17,27 @@ noinst_HEADERS = \
 
 pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
+
+test_programs = \
+       test-program-client-local \
+       test-program-client-remote
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+       libprogram_client.la \
+       ../lib-test/libtest.la \
+       ../lib/liblib.la \
+       $(MODULE_LIBS)
+
+test_program_client_local_SOURCE = test-program-client-local.c
+test_program_client_local_LDADD = $(test_libs)
+
+test_program_client_remote_SOURCE = test-program-client-remote.c
+test_program_client_remote_LDADD = $(test_libs)
+
+check: check-am check-test
+check-test: all-am
+       for bin in $(test_programs); do \
+         if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+       done
diff --git a/src/lib-program-client/test-program-client-local.c b/src/lib-program-client/test-program-client-local.c
new file mode 100644 (file)
index 0000000..29b2c18
--- /dev/null
@@ -0,0 +1,171 @@
+/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "test-lib.h"
+#include "buffer.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "program-client.h"
+
+static const char *pclient_test_io_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
+                                           "Praesent vehicula ac leo vel placerat. Nullam placerat \n"
+                                           "volutpat leo, sed ultricies felis pulvinar quis. Nam \n"
+                                           "tempus, augue ut tempor cursus, neque felis commodo lacus, \n"
+                                           "sit amet tincidunt arcu justo vel augue. Proin dapibus \n"
+                                           "vulputate maximus. Mauris congue lacus felis, sed varius \n"
+                                           "leo finibus sagittis. Cum sociis natoque penatibus et magnis \n"
+                                           "dis parturient montes, nascetur ridiculus mus. Aliquam \n"
+                                           "laoreet arcu a hendrerit consequat. Duis vitae erat tellus.";
+
+static
+struct program_client_settings pc_set = {
+       .client_connect_timeout_msecs = 5000,
+       .input_idle_timeout_secs = 1000,
+       .gid = -1,
+       .uid = -1,
+       .debug = FALSE,
+};
+
+static
+void test_program_success(void) {
+       test_begin("test_program_success");
+
+       const char *const args[] = {
+               "hello", "world", NULL
+       };
+
+       struct program_client *pc =
+               program_client_local_create("/bin/echo", args, &pc_set);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       test_assert(program_client_run(pc) == 1);
+       test_assert(strcmp(str_c(output), "hello world\n") == 0);
+
+       program_client_destroy(&pc);
+
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       test_end();
+}
+
+static
+void test_program_io_sync(void) {
+       test_begin("test_program_io (sync)");
+
+       const char *const args[] = {
+               NULL
+       };
+
+       struct program_client *pc =
+               program_client_local_create("/bin/cat", args, &pc_set);
+
+       struct istream *is = test_istream_create(pclient_test_io_string);
+       program_client_set_input(pc, is);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       test_assert(program_client_run(pc) == 1);
+       test_assert(strcmp(str_c(output), pclient_test_io_string) == 0);
+
+       program_client_destroy(&pc);
+
+       i_stream_unref(&is);
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       test_end();
+}
+
+static
+void test_program_io_async_callback(int result, int *ret)
+{
+       *ret = result;
+       test_assert(result == 1);
+       io_loop_stop(current_ioloop);
+}
+
+static
+void test_program_io_async(void) {
+       test_begin("test_program_io (async)");
+
+       int ret;
+       struct ioloop *ioloop = io_loop_create();
+       io_loop_set_current(ioloop);
+
+       const char *const args[] = {
+               NULL
+       };
+
+       struct program_client *pc =
+               program_client_local_create("/bin/cat", args, &pc_set);
+
+       struct istream *is = test_istream_create(pclient_test_io_string);
+       program_client_set_input(pc, is);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       program_client_run_async(pc, test_program_io_async_callback, &ret);
+
+       io_loop_run(ioloop);
+
+       test_assert(strcmp(str_c(output), pclient_test_io_string) == 0);
+
+       program_client_destroy(&pc);
+
+       i_stream_unref(&is);
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       io_loop_destroy(&ioloop);
+
+       test_end();
+}
+
+static
+void test_program_failure(void) {
+       test_begin("test_program_failure");
+
+       const char *const args[] = {
+               NULL
+       };
+
+       struct program_client *pc =
+               program_client_local_create("/bin/false", args, &pc_set);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       test_assert(program_client_run(pc) == 0);
+       test_assert(strcmp(str_c(output), "") == 0);
+
+       program_client_destroy(&pc);
+
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       test_end();
+}
+
+int main(void)
+{
+       void (*tests[])(void) = {
+               test_program_success,
+               test_program_io_sync,
+               test_program_io_async,
+               test_program_failure,
+               NULL
+       };
+
+       return test_run(tests);
+}
diff --git a/src/lib-program-client/test-program-client-remote.c b/src/lib-program-client/test-program-client-remote.c
new file mode 100644 (file)
index 0000000..07219f2
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright (c) 2002-2016 Dovecot authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "test-lib.h"
+#include "mempool.h"
+#include "buffer.h"
+#include "str.h"
+#include "array.h"
+#include "istream.h"
+#include "ostream.h"
+#include "net.h"
+#include "iostream-temp.h"
+#include "program-client.h"
+
+#include <unistd.h>
+
+static const char *TEST_SOCKET = "/tmp/program-client-test.sock";
+static const char *pclient_test_io_string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
+                                           "Praesent vehicula ac leo vel placerat. Nullam placerat \n"
+                                           "volutpat leo, sed ultricies felis pulvinar quis. Nam \n"
+                                           "tempus, augue ut tempor cursus, neque felis commodo lacus, \n"
+                                           "sit amet tincidunt arcu justo vel augue. Proin dapibus \n"
+                                           "vulputate maximus. Mauris congue lacus felis, sed varius \n"
+                                           "leo finibus sagittis. Cum sociis natoque penatibus et magnis \n"
+                                           "dis parturient montes, nascetur ridiculus mus. Aliquam \n"
+                                           "laoreet arcu a hendrerit consequat. Duis vitae erat tellus.";
+
+static
+struct program_client_settings pc_set = {
+       .client_connect_timeout_msecs = 1000,
+       .input_idle_timeout_secs = 5000,
+       .gid = -1,
+       .uid = -1,
+       .debug = TRUE,
+};
+
+static
+struct test_server {
+       struct ioloop *ioloop;
+       struct io *io;
+       struct timeout *to;
+       struct test_client *client;
+       int listen_fd;
+} test_globals;
+
+struct test_client {
+       pool_t pool;
+       int fd;
+       struct io *io;
+       struct istream *in;
+       struct ostream *out;
+       struct ostream *os_body;
+       struct istream *body;
+       ARRAY_TYPE(const_string) args;
+       enum {
+               CLIENT_STATE_INIT,
+               CLIENT_STATE_VERSION,
+               CLIENT_STATE_ARGS,
+               CLIENT_STATE_BODY
+       } state;
+};
+
+static
+void test_program_client_destroy(struct test_client **_client)
+{
+       struct test_client *client = *_client;
+       *_client = NULL;
+
+       if (o_stream_nfinish(client->out) != 0)
+               i_error("output error: %s", o_stream_get_error(client->out));
+
+       io_remove(&client->io);
+       o_stream_unref(&client->out);
+       i_stream_unref(&client->in);
+       if (client->os_body != NULL)
+               o_stream_unref(&client->os_body);
+       if (client->body != NULL)
+               i_stream_unref(&client->body);
+       close(client->fd);
+       pool_unref(&client->pool);
+       test_globals.client = NULL;
+}
+
+static
+int test_program_input_handle(struct test_client *client, char *line)
+{
+       size_t siz;
+       ssize_t ret;
+       const unsigned char *data;
+       int cmp;
+       const char *arg;
+
+       switch(client->state) {
+       case CLIENT_STATE_INIT:
+               test_assert((cmp = strcmp(line, "VERSION\tscript\t3\t0")) == 0);
+               if (cmp == 0) {
+                       client->state = CLIENT_STATE_VERSION;
+               } else
+                       return -1;
+               break;
+       case CLIENT_STATE_VERSION:
+               test_assert((cmp = strcmp(line, "-")) == 0);
+               if (cmp == 0)
+                       client->state = CLIENT_STATE_ARGS;
+               else
+                       return -1;
+               break;
+       case CLIENT_STATE_ARGS:
+               if  (strcmp(line, "") == 0) {
+                       array_append_zero(&client->args);
+                       client->state = CLIENT_STATE_BODY;
+                       return 0;
+               }
+               arg = p_strdup(client->pool, line);
+               array_append(&client->args, &arg, 1);
+               break;
+       case CLIENT_STATE_BODY:
+               client->os_body = iostream_temp_create_named("/tmp", 0, "test_program_input body");
+
+               do {
+                       data = i_stream_get_data(client->in, &siz);
+                       o_stream_nsend(client->os_body, data, siz);
+                       i_stream_skip(client->in, siz);
+               } while ((ret = i_stream_read(client->in))>0);
+
+               if (ret == 0 && !client->in->eof) break;
+               if (ret == -1 && !client->in->eof) return -1;
+
+               if (o_stream_nfinish(client->os_body)<0) {
+                       i_fatal("Could not write response: %s",
+                               o_stream_get_error(client->os_body));
+               }
+
+               client->body = iostream_temp_finish(&client->os_body, -1);
+
+               return 1;
+       }
+       return 0;
+}
+
+static
+void test_program_run(struct test_client *client)
+{
+       const char *arg;
+       size_t siz;
+       int ret;
+       const unsigned char *data;
+       test_assert(array_count(&client->args) > 0);
+       arg = *array_idx(&client->args, 0);
+       if (strcmp(arg, "test_program_success")==0) {
+               /* return hello world */
+               o_stream_nsend_str(client->out, t_strdup_printf("%s %s\n+\n",
+                                  *array_idx(&client->args, 1),
+                                  *array_idx(&client->args, 2)));
+       } else if (strcmp(arg, "test_program_io")==0) {
+               do {
+                       data = i_stream_get_data(client->body, &siz);
+                       o_stream_nsend(client->out, data, siz);
+                       i_stream_skip(client->body, siz);
+               } while((ret = i_stream_read(client->body))>0);
+               o_stream_nsend_str(client->out, "+\n");
+       } else if (strcmp(arg, "test_program_failure")==0) {
+               o_stream_nsend_str(client->out, "-\n");
+       }
+}
+
+static
+void test_program_input(struct test_client *client)
+
+{
+       char *line;
+
+       if (client->state == CLIENT_STATE_BODY) {
+               if (test_program_input_handle(client, NULL)==0 && !client->in->eof) return;
+       } else {
+               line = i_stream_read_next_line(client->in);
+               if ((line == NULL && !client->in->eof) ||
+                   (line != NULL && test_program_input_handle(client, line) == 0))
+                       return;
+       }
+
+       if (client->in->eof)
+               test_program_run(client);
+
+       if (client->state != CLIENT_STATE_BODY) {
+               if (client->in->eof)
+                       i_warning("Client prematurely disconnected");
+               else
+                       i_warning("Client sent invalid line: %s", line);
+       }
+
+       test_program_client_destroy(&client);
+}
+
+static
+void test_program_connected(struct test_server *server)
+{
+       int fd;
+       i_assert(server->client == NULL);
+       fd = net_accept(server->listen_fd, NULL, NULL); /* makes no sense on unix */
+       if (fd < 0)
+               i_fatal("Failed to accept connection: %m");
+
+       pool_t pool = pool_alloconly_create("test_program client", 1024);
+       struct test_client *client = p_new(pool, struct test_client, 1);
+       client->pool = pool;
+       client->fd = fd;
+       client->in = i_stream_create_fd(fd, -1, FALSE);
+       client->out = o_stream_create_fd(fd, -1, FALSE);
+       client->io = io_add_istream(client->in, test_program_input, client);
+       p_array_init(&client->args, client->pool, 2);
+       server->client = client;
+}
+
+static
+void test_program_setup(void) {
+       test_begin("test_program_setup");
+       test_globals.ioloop = io_loop_create();
+       io_loop_set_current(test_globals.ioloop);
+
+       /* create listener */
+       test_globals.listen_fd = net_listen_unix_unlink_stale(TEST_SOCKET, 100);
+       if (test_globals.listen_fd < 0)
+               i_fatal("Cannot create unix listener: %m");
+
+       test_globals.io = io_add(test_globals.listen_fd, IO_READ, test_program_connected, &test_globals);
+       test_end();
+}
+
+static
+void test_program_teardown(void)
+{
+       test_begin("test_program_teardown");
+       if (test_globals.client)
+               test_program_client_destroy(&test_globals.client);
+       io_remove(&test_globals.io);
+       close(test_globals.listen_fd);
+       io_loop_destroy(&test_globals.ioloop);
+       test_end();
+}
+
+static
+void test_program_async_callback(int result, int *ret)
+{
+       *ret = result;
+       io_loop_stop(current_ioloop);
+}
+
+static
+void test_program_success(void) {
+       test_begin("test_program_success");
+       int ret;
+
+       const char *const args[] = {
+               "test_program_success", "hello", "world", NULL
+       };
+
+       struct program_client *pc =
+               program_client_remote_create(TEST_SOCKET, args, &pc_set, FALSE);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       program_client_run_async(pc, test_program_async_callback, &ret);
+
+       io_loop_run(current_ioloop);
+
+       test_assert(ret == 1);
+       test_assert(strcmp(str_c(output), "hello world\n") == 0);
+
+       program_client_destroy(&pc);
+
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       test_end();
+}
+
+static
+void test_program_io(void) {
+       test_begin("test_program_io (async)");
+
+       int ret;
+
+       const char *const args[] = {
+               "test_program_io", NULL
+       };
+
+       struct program_client *pc =
+               program_client_remote_create(TEST_SOCKET, args, &pc_set, FALSE);
+
+       struct istream *is = test_istream_create(pclient_test_io_string);
+       program_client_set_input(pc, is);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       program_client_run_async(pc, test_program_async_callback, &ret);
+
+       io_loop_run(current_ioloop);
+
+       test_assert(ret == 1);
+
+       test_assert(strcmp(str_c(output), pclient_test_io_string) == 0);
+
+       program_client_destroy(&pc);
+
+       i_stream_unref(&is);
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       test_end();
+}
+
+static
+void test_program_failure(void) {
+       test_begin("test_program_failure");
+
+       int ret;
+
+       const char *const args[] = {
+               "test_program_failure", NULL
+       };
+
+       struct program_client *pc =
+               program_client_remote_create(TEST_SOCKET, args, &pc_set, FALSE);
+
+       buffer_t *output = buffer_create_dynamic(default_pool, 16);
+       struct ostream *os = o_stream_create_buffer(output);
+       program_client_set_output(pc, os);
+
+       program_client_run_async(pc, test_program_async_callback, &ret);
+
+       io_loop_run(current_ioloop);
+
+       test_assert(ret == 0);
+
+       program_client_destroy(&pc);
+
+       o_stream_unref(&os);
+       buffer_free(&output);
+
+       test_end();
+}
+
+static
+void test_program_noreply(void) {
+       test_begin("test_program_noreply");
+
+       int ret;
+
+       const char *const args[] = {
+               "test_program_success", "hello", "world", NULL
+       };
+
+       struct program_client *pc =
+               program_client_remote_create(TEST_SOCKET, args, &pc_set, TRUE);
+
+       program_client_run_async(pc, test_program_async_callback, &ret);
+
+       io_loop_run(current_ioloop);
+
+       test_assert(ret == 1);
+
+       program_client_destroy(&pc);
+
+       test_end();
+}
+
+int main(void)
+{
+       void (*tests[])(void) = {
+               test_program_setup,
+               test_program_success,
+               test_program_io,
+               test_program_failure,
+               test_program_noreply,
+               test_program_teardown,
+               NULL
+       };
+
+       return test_run(tests);
+}