From: Aki Tuomi Date: Fri, 7 Oct 2016 16:49:24 +0000 (+0300) Subject: lib-program-client: Add test suite for program client X-Git-Tag: 2.2.26~213 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e5e08bff9f8961a6cecc89d8899890f54c167bad;p=thirdparty%2Fdovecot%2Fcore.git lib-program-client: Add test suite for program client --- diff --git a/src/lib-program-client/Makefile.am b/src/lib-program-client/Makefile.am index a31e101e64..14fbd3d219 100644 --- a/src/lib-program-client/Makefile.am +++ b/src/lib-program-client/Makefile.am @@ -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 index 0000000000..29b2c18ecc --- /dev/null +++ b/src/lib-program-client/test-program-client-local.c @@ -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 index 0000000000..07219f2d10 --- /dev/null +++ b/src/lib-program-client/test-program-client-remote.c @@ -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 + +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); +}