From: Aki Tuomi Date: Tue, 27 Nov 2018 10:28:12 +0000 (+0200) Subject: lib: Add test-connection.c X-Git-Tag: 2.3.9~1062 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c93355ea6d8876c1acb6d2525ab198212a6a9be1;p=thirdparty%2Fdovecot%2Fcore.git lib: Add test-connection.c --- diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index b8e2c8cea2..a4f6f54b2e 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -337,6 +337,7 @@ test_lib_SOURCES = \ test-bsearch-insert-pos.c \ test-buffer.c \ test-byteorder.c \ + test-connection.c \ test-crc32.c \ test-data-stack.c \ test-event-filter.c \ diff --git a/src/lib/test-connection.c b/src/lib/test-connection.c new file mode 100644 index 0000000000..efe5b18fd6 --- /dev/null +++ b/src/lib/test-connection.c @@ -0,0 +1,455 @@ +/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "ioloop.h" +#include "connection.h" +#include "istream.h" +#include "ostream.h" +#include "strnum.h" +#include "strescape.h" + +#include + +static const struct connection_settings client_set = +{ + .service_name_in = "TEST-S", + .service_name_out = "TEST-C", + .major_version = 1, + .minor_version = 0, + .client = TRUE, + .input_max_size = (size_t)-1, + .output_max_size = (size_t)-1, +}; + +static const struct connection_settings server_set = +{ + .service_name_in = "TEST-C", + .service_name_out = "TEST-S", + .major_version = 1, + .minor_version = 0, + .client = FALSE, + .input_max_size = (size_t)-1, + .output_max_size = (size_t)-1, +}; + +static bool received_quit = FALSE; +static bool was_resumed = FALSE; +static bool was_idle_killed = FALSE; +static int received_count = 0; + +static void test_connection_run(const struct connection_settings *set_s, + const struct connection_settings *set_c, + const struct connection_vfuncs *v_s, + const struct connection_vfuncs *v_c) +{ + int fds[2]; + + struct ioloop *loop = io_loop_create(); + struct connection_list *clients = connection_list_init(set_c, v_c); + struct connection_list *servers = connection_list_init(set_s, v_s); + struct connection *conn_c = i_new(struct connection, 1); + struct connection *conn_s = i_new(struct connection, 1); + + test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); + conn_s->ioloop = loop; + conn_c->ioloop = loop; + + connection_init_server(servers, conn_s, "client", fds[1], fds[1]); + connection_init_client_fd(clients, conn_c, "server", fds[0], fds[0]); + + io_loop_run(loop); + + connection_deinit(conn_c); + connection_deinit(conn_s); + i_free(conn_c); + i_free(conn_s); + + connection_list_deinit(&clients); + connection_list_deinit(&servers); + + io_loop_destroy(&loop); +} + +/* BEGIN SIMPLE TEST */ + +static void test_connection_simple_client_connected(struct connection *conn, bool success) +{ + if (conn->list->set.client) + o_stream_nsend_str(conn->output, "QUIT\n"); + test_assert(success); +}; + +static int +test_connection_simple_input_args(struct connection *conn, const char *const *args) +{ + if (strcmp(args[0], "QUIT") == 0) { + received_quit = TRUE; + connection_disconnect(conn); + return 0; + } + i_error("invalid input"); + return -1; +} + +static void test_connection_simple_destroy(struct connection *conn) +{ + io_loop_stop(conn->ioloop); + connection_disconnect(conn); +} + +static const struct connection_vfuncs simple_v = +{ + .client_connected = test_connection_simple_client_connected, + .input_args = test_connection_simple_input_args, + .destroy = test_connection_simple_destroy, +}; + +static void test_connection_simple(void) +{ + test_begin("connection simple"); + + test_connection_run(&server_set, &client_set, &simple_v, &simple_v); + + test_assert(received_quit); + received_quit = FALSE; + + test_end(); +} + +/* BEGIN NO INPUT TEST */ + +static const struct connection_settings no_input_client_set = +{ + .service_name_in = "TEST-S", + .service_name_out = "TEST-C", + .major_version = 1, + .minor_version = 0, + .client = TRUE, + .input_max_size = 0, + .output_max_size = (size_t)-1, +}; + +static const struct connection_settings no_input_server_set = +{ + .service_name_in = "TEST-C", + .service_name_out = "TEST-S", + .major_version = 1, + .minor_version = 0, + .client = FALSE, + .input_max_size = 0, + .output_max_size = (size_t)-1, +}; + +static void +test_connection_no_input_input(struct connection *conn) +{ + const char *input; + struct istream *is = i_stream_create_fd(conn->fd_in, -1); + i_stream_set_blocking(is, FALSE); + while ((input = i_stream_read_next_line(is)) != NULL) { + const char *const *args = t_strsplit_tabescaped(input); + if (!conn->handshake_received) { + if (connection_verify_version(conn, args) > -1) + conn->handshake_received = TRUE; + continue; + } + if (strcmp(args[0], "QUIT") == 0) { + received_quit = TRUE; + io_loop_stop(conn->ioloop); + break; + } + } + i_stream_unref(&is); +} + +static const struct connection_vfuncs no_input_v = +{ + .client_connected = test_connection_simple_client_connected, + .input = test_connection_no_input_input, + .destroy = test_connection_simple_destroy, +}; + +static void test_connection_no_input(void) +{ + test_begin("connection no input stream"); + + test_connection_run(&no_input_server_set, &no_input_client_set, + &no_input_v, &no_input_v); + + test_assert(received_quit); + received_quit = FALSE; + + test_end(); +} + +/* BEGIN HANDSHAKE TEST */ +static void test_connection_custom_handshake_client_connected(struct connection *conn, bool success) +{ + if (conn->list->set.client) + o_stream_nsend_str(conn->output, "HANDSHAKE\tFRIEND\n"); + test_assert(success); +}; + +static int test_connection_custom_handshake_args(struct connection *conn, + const char *const *args) +{ + if (!conn->version_received) { + if (connection_verify_version(conn, args) < 0) + return -1; + return 0; + } + if (!conn->handshake_received) { + if (strcmp(args[0], "HANDSHAKE") == 0 && + strcmp(args[1], "FRIEND") == 0) { + if (!conn->list->set.client) + o_stream_nsend_str(conn->output, "HANDSHAKE\tFRIEND\n"); + else + o_stream_nsend_str(conn->output, "QUIT\n"); + return 1; + } + return -1; + } + return 1; +} + +static const struct connection_vfuncs custom_handshake_v = +{ + .client_connected = test_connection_custom_handshake_client_connected, + .input_args = test_connection_simple_input_args, + .handshake_args = test_connection_custom_handshake_args, + .destroy = test_connection_simple_destroy, +}; + +static void test_connection_custom_handshake(void) +{ + test_begin("connection custom handshake"); + + test_connection_run(&server_set, &client_set, &custom_handshake_v, + &custom_handshake_v); + + test_assert(received_quit); + received_quit = FALSE; + + test_end(); +} + +/* BEGIN PING PONG TEST */ + +static int test_connection_ping_pong_input_args(struct connection *conn, const char *const *args) +{ + unsigned int n; + test_assert(args[0] != NULL && args[1] != NULL); + if (args[0] == NULL || args[1] == NULL) + return -1; + if (str_to_uint(args[1], &n) < 0) + return -1; + if (n > 10) + o_stream_nsend_str(conn->output, "QUIT\t0\n"); + else if (strcmp(args[0], "QUIT") == 0) + connection_disconnect(conn); + else if (strcmp(args[0], "PING") == 0) { + received_count++; + o_stream_nsend_str(conn->output, t_strdup_printf("PONG\t%u\n", n+1)); + } else if (strcmp(args[0], "PONG") == 0) + o_stream_nsend_str(conn->output, t_strdup_printf("PING\t%u\n", n)); + else + return -1; + return 1; +} + +static void test_connection_ping_pong_client_connected(struct connection *conn, bool success) +{ + o_stream_nsend_str(conn->output, "PING\t1\n"); + test_assert(success); +}; + +static const struct connection_vfuncs ping_pong_v = +{ + .client_connected = test_connection_ping_pong_client_connected, + .input_args = test_connection_ping_pong_input_args, + .destroy = test_connection_simple_destroy, +}; + +static void test_connection_ping_pong(void) +{ + test_begin("connection ping pong"); + + test_connection_run(&server_set, &client_set, &ping_pong_v, + &ping_pong_v); + + test_assert(received_count == 10); + + test_end(); +} + +/* BEGIN INPUT FULL TEST */ + +static const struct connection_settings input_full_client_set = +{ + .service_name_in = "TEST-S", + .service_name_out = "TEST-C", + .major_version = 1, + .minor_version = 0, + .client = TRUE, + .input_max_size = 100, + .output_max_size = (size_t)-1, +}; + +static int test_connection_input_full_input_args(struct connection *conn, + const char *const *args ATTR_UNUSED) +{ + /* send a long line */ + for (unsigned int i = 0; i < 200; i++) + o_stream_nsend(conn->output, "c", 1); + return 1; +} + +static void test_connection_input_full_destroy(struct connection *conn) +{ + test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_BUFFER_FULL || + conn->list->set.client == FALSE); + test_connection_simple_destroy(conn); +} + +static const struct connection_vfuncs input_full_v = +{ + .client_connected = test_connection_simple_client_connected, + .input_args = test_connection_input_full_input_args, + .destroy = test_connection_input_full_destroy, +}; + +static void test_connection_input_full(void) +{ + test_begin("connection input full"); + + test_connection_run(&server_set, &input_full_client_set, &input_full_v, + &simple_v); + test_end(); +} + +/* BEGIN RESUME TEST */ +static struct timeout *to_send_quit = NULL; +static struct timeout *to_resume = NULL; + +static void test_connection_resume_client_connected(struct connection *conn, bool success) +{ + test_assert(success); + o_stream_nsend_str(conn->output, "BEGIN\n"); +} + +static void test_connection_resume_continue(struct connection *conn) +{ + timeout_remove(&to_resume); + /* ensure QUIT wasn't received early */ + was_resumed = !received_quit; + connection_input_resume(conn); +} + +static void test_connection_resume_send_quit(struct connection *conn) +{ + timeout_remove(&to_send_quit); + o_stream_nsend_str(conn->output, "QUIT\n"); +} + +static int test_connection_resume_input_args(struct connection *conn, + const char *const *args) +{ + test_assert(args[0] != NULL); + if (args[0] == NULL) + return -1; + + if (strcmp(args[0], "BEGIN") == 0) { + o_stream_nsend_str(conn->output, "HALT\n"); + to_send_quit = timeout_add_short(10, test_connection_resume_send_quit, conn); + } else if (strcmp(args[0], "HALT") == 0) { + connection_input_halt(conn); + to_resume = timeout_add_short(100, test_connection_resume_continue, conn); + } else if (strcmp(args[0], "QUIT") == 0) { + received_quit = TRUE; + connection_disconnect(conn); + } + + return 1; +} + +static const struct connection_vfuncs resume_v = +{ + .client_connected = test_connection_resume_client_connected, + .input_args = test_connection_resume_input_args, + .destroy = test_connection_simple_destroy, +}; + +static void test_connection_resume(void) +{ + test_begin("connection resume"); + + was_resumed = received_quit = FALSE; + test_connection_run(&server_set, &client_set, &resume_v, &resume_v); + + test_assert(was_resumed); + test_assert(received_quit); + was_resumed = received_quit = FALSE; + + test_end(); +} + +/* BEGIN IDLE KILL TEST */ + +static void +test_connection_idle_kill_client_connected(struct connection *conn ATTR_UNUSED, + bool success) +{ + test_assert(success); +}; + +static const struct connection_settings idle_kill_server_set = +{ + .service_name_in = "TEST-C", + .service_name_out = "TEST-S", + .major_version = 1, + .minor_version = 0, + .client = FALSE, + .input_max_size = (size_t)-1, + .output_max_size = (size_t)-1, + .input_idle_timeout_secs = 1, +}; + +static void test_connection_idle_kill_timeout(struct connection *conn) +{ + was_idle_killed = TRUE; + o_stream_nsend_str(conn->output, "QUIT\n"); +} + +static const struct connection_vfuncs idle_kill_v = +{ + .client_connected = test_connection_idle_kill_client_connected, + .input_args = test_connection_simple_input_args, + .destroy = test_connection_simple_destroy, + .idle_timeout = test_connection_idle_kill_timeout, +}; + +static void test_connection_idle_kill(void) +{ + test_begin("connection idle kill"); + + was_idle_killed = received_quit = FALSE; + test_connection_run(&idle_kill_server_set, &client_set, &idle_kill_v, + &idle_kill_v); + + test_assert(received_quit); + test_assert(was_idle_killed); + was_idle_killed = received_quit = FALSE; + + test_end(); +} + +void test_connection(void) +{ + test_connection_simple(); + test_connection_no_input(); + test_connection_custom_handshake(); + test_connection_ping_pong(); + test_connection_input_full(); + test_connection_resume(); + test_connection_idle_kill(); +} diff --git a/src/lib/test-lib.inc b/src/lib/test-lib.inc index bf97c95842..94acc428b8 100644 --- a/src/lib/test-lib.inc +++ b/src/lib/test-lib.inc @@ -11,6 +11,7 @@ TEST(test_bits) TEST(test_bsearch_insert_pos) TEST(test_buffer) TEST(test_byteorder) +TEST(test_connection) TEST(test_crc32) TEST(test_data_stack) FATAL(fatal_data_stack)