]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: server: Created test program that tests error conditions.
authorStephan Bosch <stephan@dovecot.fi>
Sun, 19 Jun 2016 19:31:10 +0000 (21:31 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 20 Jun 2016 17:50:12 +0000 (20:50 +0300)
Currently it is very limited, but it is due to be extended soon towards testing most common error conditions.

src/lib-http/Makefile.am
src/lib-http/test-http-server-errors.c [new file with mode: 0644]

index a1c972d8ede5b88bc896d5d888002371af1f2819..967002e7b81fb5361de383d15c482028cb295a63 100644 (file)
@@ -65,7 +65,8 @@ test_nocheck_programs = \
        test-http-payload \
        test-http-client \
        test-http-client-errors \
-       test-http-server
+       test-http-server \
+       test-http-server-errors
 
 noinst_PROGRAMS = $(test_programs) $(test_nocheck_programs)
 
@@ -175,6 +176,13 @@ test_http_server_LDADD = \
 test_http_server_DEPENDENCIES = \
        $(test_http_deps)
 
+test_http_server_errors_SOURCES = test-http-server-errors.c
+test_http_server_errors_LDFLAGS = -export-dynamic
+test_http_server_errors_LDADD = \
+       $(test_http_libs)
+test_http_server_errors_DEPENDENCIES = \
+       $(test_http_deps)
+
 check: check-am check-test
 check-test: all-am
        for bin in $(test_programs); do \
diff --git a/src/lib-http/test-http-server-errors.c b/src/lib-http/test-http-server-errors.c
new file mode 100644 (file)
index 0000000..9c432ce
--- /dev/null
@@ -0,0 +1,754 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "connection.h"
+#include "test-common.h"
+#include "http-url.h"
+#include "http-request.h"
+#include "http-server.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+
+#define SERVER_MAX_TIMEOUT_MSECS 10*1000
+
+/*
+ * Types
+ */
+
+struct client_connection {
+       struct connection conn;
+
+       pool_t pool;
+};
+
+typedef void (*test_server_init_t)
+       (const struct http_server_settings *server_set);
+typedef void (*test_client_init_t)(unsigned int index);
+
+/*
+ * State
+ */
+
+/* common */
+static struct ip_addr bind_ip;
+static in_port_t bind_port = 0;
+static struct ioloop *ioloop;
+static bool debug = FALSE;
+
+/* server */
+static struct http_server *http_server = NULL;
+static struct io *io_listen;
+static int fd_listen = -1;
+static void (*test_server_request)(struct http_server_request *req);
+
+/* client */
+static pid_t *client_pids = NULL;
+static struct connection_list *client_conn_list;
+static unsigned int client_pids_count = 0;
+static unsigned int client_index;
+static void (*test_client_connected)(struct client_connection *conn);
+static void (*test_client_input)(struct client_connection *conn);
+
+/*
+ * Forward declarations
+ */
+
+/* server */
+static void
+test_server_defaults(struct http_server_settings *http_set);
+static void
+test_server_run(const struct http_server_settings *http_set);
+
+/* client */
+static void test_client_run(unsigned int index);
+
+/* test*/
+static void test_run_client_server(
+       const struct http_server_settings *server_set,
+       test_server_init_t server_test,
+       test_client_init_t client_test,
+       unsigned int client_tests_count)
+       ATTR_NULL(3);
+
+/*
+ * Slow request
+ */
+
+/* client */
+
+static void
+test_slow_request_input(struct client_connection *conn ATTR_UNUSED)
+{
+       /* do nothing */
+}
+
+static void
+test_slow_request_connected(struct client_connection *conn)
+{
+       (void)o_stream_send_str(conn->conn.output,
+               "GET / HTTP/1.1\r\n"
+               "Host: example.com\r\n"
+               "\r\n");
+}
+
+static void test_client_slow_request(unsigned int index)
+{
+       test_client_input = test_slow_request_input;
+       test_client_connected = test_slow_request_connected;
+       test_client_run(index);
+}
+
+/* server */
+
+struct _slow_request {
+       struct http_server_request *req;
+       struct timeout *to_delay;
+       unsigned int serviced:1;
+};
+
+static void
+test_server_slow_request_destroyed(void *context)
+{
+       struct _slow_request *ctx = (struct _slow_request *)context;
+       test_assert(ctx->serviced);
+       if (ctx->to_delay != NULL)
+               timeout_remove(&ctx->to_delay);
+       i_free(ctx);
+       io_loop_stop(ioloop);
+}
+
+static void
+test_server_slow_request_delayed(struct _slow_request *ctx)
+{
+       struct http_server_response *resp;
+       struct http_server_request *req = ctx->req;
+
+       resp = http_server_response_create(req, 200, "OK");
+       http_server_response_submit(resp);
+       ctx->serviced = TRUE;
+
+       http_server_request_unref(&req);
+}
+
+static void
+test_server_slow_request_request(
+       struct http_server_request *req)
+{
+       const struct http_request *hreq =
+               http_server_request_get(req);
+       struct _slow_request *ctx;
+
+       if (debug) {
+               i_debug("REQUEST: %s %s HTTP/%u.%u",
+                       hreq->method, hreq->target_raw,
+                       hreq->version_major, hreq->version_minor);
+       }
+
+       ctx = i_new(struct _slow_request, 1);
+       ctx->req = req;
+
+       http_server_request_set_destroy_callback(req,
+               test_server_slow_request_destroyed, ctx);
+
+       http_server_request_ref(req);
+       ctx->to_delay = timeout_add
+               (4000, test_server_slow_request_delayed, ctx);
+}
+
+static void test_server_slow_request
+(const struct http_server_settings *server_set)
+{
+       test_server_request = test_server_slow_request_request;
+       test_server_run(server_set);
+}
+
+/* test */
+
+static void test_slow_request(void)
+{
+       struct http_server_settings http_server_set;
+
+       test_server_defaults(&http_server_set);
+       http_server_set.max_client_idle_time_msecs = 1000;
+
+       test_begin("slow request");
+       test_run_client_server(&http_server_set,
+               test_server_slow_request,
+               test_client_slow_request, 1);
+       test_end();
+}
+
+/*
+ * Hanging request payload
+ */
+
+/* client */
+
+static void
+test_hanging_request_payload_connected(struct client_connection *conn)
+{
+       (void)o_stream_send_str(conn->conn.output,
+               "GET / HTTP/1.1\r\n"
+               "Host: example.com\r\n"
+               "Content-Length: 1000\r\n"
+               "\r\n"
+               "To be continued... or not");
+}
+
+static void test_client_hanging_request_payload(unsigned int index)
+{
+       test_client_connected = test_hanging_request_payload_connected;
+       test_client_run(index);
+}
+
+/* server */
+
+struct _hanging_request_payload {
+       struct http_server_request *req;
+       struct istream *payload_input;
+       struct io *io;
+       unsigned int serviced:1;
+};
+
+static void
+test_server_hanging_request_payload_destroyed(void *context)
+{
+       struct _hanging_request_payload *ctx =
+               (struct _hanging_request_payload *)context;
+       test_assert(!ctx->serviced);
+       if (ctx->io != NULL)
+               io_remove(&ctx->io);
+       i_free(ctx);
+       io_loop_stop(ioloop);
+}
+
+static void
+test_server_hanging_request_payload_input(struct _hanging_request_payload *ctx)
+{
+       struct http_server_response *resp;
+       struct http_server_request *req = ctx->req;
+       const unsigned char *data;
+       size_t size;
+       int ret;
+
+       if (debug)
+               i_debug("test server: got more payload");
+
+       while ((ret=i_stream_read_data
+               (ctx->payload_input, &data, &size, 0)) > 0) {
+               i_stream_skip(ctx->payload_input, size);
+       }
+
+       if (ret == 0)
+               return;
+       if (ret < 0) {
+               if (debug) {
+                       i_debug("test server: failed to read payload: %s",
+                               i_stream_get_error(ctx->payload_input));
+               }
+               i_stream_unref(&ctx->payload_input);
+               io_remove(&ctx->io);
+               http_server_request_fail_close(req,
+                       400, "Bad request");
+               http_server_request_unref(&req);
+               return;
+       }
+               
+       resp = http_server_response_create(req, 200, "OK");
+       http_server_response_submit(resp);
+       ctx->serviced = TRUE;
+
+       i_stream_unref(&ctx->payload_input);
+       http_server_request_unref(&req);
+}
+
+static void
+test_server_hanging_request_payload_request(
+       struct http_server_request *req)
+{
+       const struct http_request *hreq =
+               http_server_request_get(req);
+       struct _hanging_request_payload *ctx;
+
+       if (debug) {
+               i_debug("REQUEST: %s %s HTTP/%u.%u",
+                       hreq->method, hreq->target_raw,
+                       hreq->version_major, hreq->version_minor);
+       }
+
+       ctx = i_new(struct _hanging_request_payload, 1);
+       ctx->req = req;
+
+       http_server_request_set_destroy_callback(req,
+               test_server_hanging_request_payload_destroyed, ctx);
+
+       ctx->payload_input =
+               http_server_request_get_payload_input(req, FALSE);
+
+       http_server_request_ref(req);
+       ctx->io = io_add_istream(ctx->payload_input,
+               test_server_hanging_request_payload_input, ctx);
+       test_server_hanging_request_payload_input(ctx);
+}
+
+static void test_server_hanging_request_payload
+(const struct http_server_settings *server_set)
+{
+       test_server_request = test_server_hanging_request_payload_request;
+       test_server_run(server_set);
+}
+
+/* test */
+
+static void test_hanging_request_payload(void)
+{
+       struct http_server_settings http_server_set;
+
+       test_server_defaults(&http_server_set);
+       http_server_set.max_client_idle_time_msecs = 1000;
+
+       test_begin("hanging request payload");
+       test_run_client_server(&http_server_set,
+               test_server_hanging_request_payload,
+               test_client_hanging_request_payload, 1);
+       test_end();
+}
+
+/*
+ * Hanging response payload
+ */
+
+/* client */
+
+static void
+test_hanging_response_payload_connected(struct client_connection *conn)
+{
+       (void)o_stream_send_str(conn->conn.output,
+               "GET / HTTP/1.1\r\n"
+               "Host: example.com\r\n"
+               "Content-Length: 18\r\n"
+               "\r\n"
+               "Complete payload\r\n");
+}
+
+static void test_client_hanging_response_payload(unsigned int index)
+{
+       test_client_connected = test_hanging_response_payload_connected;
+       test_client_run(index);
+}
+
+/* server */
+
+struct _hanging_response_payload {
+       struct http_server_request *req;
+       struct istream *payload_input;
+       struct io *io;
+       unsigned int serviced:1;
+};
+
+static void
+test_server_hanging_response_payload_destroyed(void *context)
+{
+       struct _hanging_response_payload *ctx =
+               (struct _hanging_response_payload *)context;
+       test_assert(!ctx->serviced);
+       if (ctx->io != NULL)
+               io_remove(&ctx->io);
+       i_free(ctx);
+       io_loop_stop(ioloop);
+}
+
+static void
+test_server_hanging_response_payload_request(
+       struct http_server_request *req)
+{
+       const struct http_request *hreq =
+               http_server_request_get(req);
+       struct http_server_response *resp;
+       struct _hanging_response_payload *ctx;
+       string_t *payload;
+       unsigned int i;
+
+       if (debug) {
+               i_debug("REQUEST: %s %s HTTP/%u.%u",
+                       hreq->method, hreq->target_raw,
+                       hreq->version_major, hreq->version_minor);
+       }
+
+       ctx = i_new(struct _hanging_response_payload, 1);
+       ctx->req = req;
+
+       http_server_request_set_destroy_callback(req,
+               test_server_hanging_response_payload_destroyed, ctx);
+
+       resp = http_server_response_create(req, 200, "OK");
+       T_BEGIN {
+               payload = t_str_new(204800);
+               for (i = 0; i < 3200; i++) {
+                       str_append(payload,
+                               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
+                               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n");
+               }
+
+               http_server_response_set_payload_data
+                       (resp, str_data(payload), str_len(payload));
+       } T_END;
+       http_server_response_submit(resp);
+}
+
+static void test_server_hanging_response_payload
+(const struct http_server_settings *server_set)
+{
+       test_server_request = test_server_hanging_response_payload_request;
+       test_server_run(server_set);
+}
+
+/* test */
+
+static void test_hanging_response_payload(void)
+{
+       struct http_server_settings http_server_set;
+
+       test_server_defaults(&http_server_set);
+       http_server_set.socket_send_buffer_size = 4096;
+       http_server_set.max_client_idle_time_msecs = 1000;
+
+       test_begin("hanging response payload");
+       test_run_client_server(&http_server_set,
+               test_server_hanging_response_payload,
+               test_client_hanging_response_payload, 1);
+       test_end();
+}
+
+/*
+ * All tests
+ */
+
+static void (*test_functions[])(void) = {
+       test_slow_request,
+       test_hanging_request_payload,
+       test_hanging_response_payload,
+       NULL
+};
+
+/*
+ * Test client
+ */
+
+/* client connection */
+
+static void
+client_connection_input(struct connection *_conn)
+{
+       struct client_connection *conn = (struct client_connection *)_conn;
+       
+       if (test_client_input != NULL)
+               test_client_input(conn);
+}
+
+static void
+client_connection_connected(struct connection *_conn, bool success)
+{
+       struct client_connection *conn = (struct client_connection *)_conn;
+
+       if (success && test_client_connected != NULL)
+               test_client_connected(conn);
+}
+
+static void
+client_connection_init(const struct ip_addr *ip, in_port_t port)
+{
+       struct client_connection *conn;
+       pool_t pool;
+
+       pool = pool_alloconly_create("client connection", 256);
+       conn = p_new(pool, struct client_connection, 1);
+       conn->pool = pool;
+
+       connection_init_client_ip(client_conn_list,
+               &conn->conn, ip, port);
+       (void)connection_client_connect(&conn->conn);
+}
+
+static void
+server_connection_deinit(struct client_connection **_conn)
+{
+       struct client_connection *conn = *_conn;
+
+       *_conn = NULL;
+
+       connection_deinit(&conn->conn);
+       pool_unref(&conn->pool);
+}
+
+static void
+client_connection_destroy(struct connection *_conn)
+{
+       struct client_connection *conn =
+               (struct client_connection *)_conn;
+
+       server_connection_deinit(&conn);
+}
+
+/* */
+
+static struct connection_settings client_connection_set = {
+       .input_max_size = (size_t)-1,
+       .output_max_size = (size_t)-1,
+       .client = TRUE
+};
+
+static const struct connection_vfuncs client_connection_vfuncs = {
+       .destroy = client_connection_destroy,
+       .client_connected = client_connection_connected,
+       .input = client_connection_input
+};
+
+static void test_client_run(unsigned int index)
+{
+       client_index = index;
+
+       if (debug)
+               i_debug("client connecting to %u", bind_port);
+
+       client_conn_list = connection_list_init
+               (&client_connection_set, &client_connection_vfuncs);
+
+       client_connection_init(&bind_ip, bind_port);
+
+       io_loop_run(ioloop);
+
+       /* close server socket */
+       io_remove(&io_listen);
+
+       connection_list_deinit(&client_conn_list);
+}
+
+/*
+ * Test server
+ */
+
+static void
+test_server_defaults(struct http_server_settings *http_set)
+{
+       /* server settings */
+       memset(http_set, 0, sizeof(*http_set));
+       http_set->max_client_idle_time_msecs = 5*1000;
+       http_set->max_pipelined_requests = 1;
+       http_set->debug = debug;
+}
+
+/* client connection */
+
+static void
+server_handle_request(void *context ATTR_UNUSED,
+       struct http_server_request *req)
+{
+       test_server_request(req);
+}
+
+struct http_server_callbacks http_server_callbacks = {
+       .handle_request = server_handle_request
+};
+
+static void
+server_connection_accept(void *context ATTR_UNUSED)
+{
+       int fd;
+
+       /* accept new client */
+       fd = net_accept(fd_listen, NULL, NULL);
+       if (fd == -1)
+               return;
+       if (fd == -2) {
+               i_fatal("test server: accept() failed: %m");
+       }
+
+       (void)http_server_connection_create(http_server, fd, fd, FALSE,
+               &http_server_callbacks, NULL);
+}
+
+/* */
+
+static void
+test_server_timeout(void *context ATTR_UNUSED)
+{
+       i_fatal("Server timed out");
+       io_loop_stop(ioloop);
+}
+
+static void
+test_server_run(const struct http_server_settings *http_set)
+{
+       struct timeout *to;
+
+       to = timeout_add(SERVER_MAX_TIMEOUT_MSECS,
+               test_server_timeout, NULL);
+
+       /* open server socket */
+       io_listen = io_add(fd_listen,
+               IO_READ, server_connection_accept, (void *)NULL);
+
+       http_server = http_server_init(http_set);
+
+       io_loop_run(ioloop);
+
+       /* close server socket */
+       io_remove(&io_listen);
+       timeout_remove(&to);
+
+       http_server_deinit(&http_server);
+}
+
+/*
+ * Tests
+ */
+
+static int test_open_server_fd(void)
+{
+       int fd = net_listen(&bind_ip, &bind_port, 128);
+       if (debug)
+               i_debug("server listening on %u", bind_port);
+       if (fd == -1) {
+               i_fatal("listen(%s:%u) failed: %m",
+                       net_ip2addr(&bind_ip), bind_port);
+       }
+       return fd;
+}
+
+static void test_clients_kill_all(void)
+{
+       unsigned int i;
+
+       if (client_pids_count > 0) {
+               for (i = 0; i < client_pids_count; i++) {
+                       if (client_pids[i] != (pid_t)-1) {
+                               (void)kill(client_pids[i], SIGKILL);
+                               (void)waitpid(client_pids[i], NULL, 0);
+                               client_pids[i] = -1;
+                       }
+               }
+       }
+       client_pids_count = 0;
+}
+
+static void test_run_client_server(
+       const struct http_server_settings *server_set,
+       test_server_init_t server_test,
+       test_client_init_t client_test,
+       unsigned int client_tests_count)
+{
+       unsigned int i;
+
+       client_pids = NULL;
+       client_pids_count = 0;
+
+       fd_listen = test_open_server_fd();
+
+       if (client_tests_count > 0) {
+               client_pids = i_new(pid_t, client_tests_count);
+               for (i = 0; i < client_tests_count; i++)
+                       client_pids[i] = (pid_t)-1;
+               client_pids_count = client_tests_count;
+
+               for (i = 0; i < client_tests_count; i++) {
+                       if ((client_pids[i] = fork()) == (pid_t)-1)
+                               i_fatal("fork() failed: %m");
+                       if (client_pids[i] == 0) {
+                               client_pids[i] = (pid_t)-1;
+                               client_pids_count = 0;
+                               hostpid_init();
+                               if (debug)
+                                       i_debug("client[%d]: PID=%s", i+1, my_pid);
+                               /* child: client */
+                               usleep(100000); /* wait a little for server setup */
+                               i_close_fd(&fd_listen);
+                               ioloop = io_loop_create();
+                               client_test(i);
+                               io_loop_destroy(&ioloop);
+                               i_free(client_pids);
+                               /* wait for it to be killed; this way, valgrind will not
+                                  object to this process going away inelegantly. */
+                               sleep(60);
+                               exit(1);
+                       }
+               }
+               if (debug)
+                       i_debug("server: PID=%s", my_pid);
+       }
+
+       /* parent: server */
+
+       ioloop = io_loop_create();
+       server_test(server_set);
+       io_loop_destroy(&ioloop);
+
+       i_close_fd(&fd_listen);
+
+       test_clients_kill_all();
+       i_free(client_pids);
+}
+
+/*
+ * Main
+ */
+
+volatile sig_atomic_t terminating = 0;
+
+static void
+test_signal_handler(int signo)
+{
+       if (terminating != 0)
+               raise(signo);
+       terminating = 1;
+
+       /* make sure we don't leave any pesky children alive */
+       test_clients_kill_all();
+
+       (void)signal(signo, SIG_DFL);
+       raise(signo);
+}
+
+static void test_atexit(void)
+{
+       test_clients_kill_all();
+}
+
+int main(int argc, char *argv[])
+{
+       int c;
+
+       atexit(test_atexit);
+       (void)signal(SIGCHLD, SIG_IGN);
+       (void)signal(SIGTERM, test_signal_handler);
+       (void)signal(SIGQUIT, test_signal_handler);
+       (void)signal(SIGINT, test_signal_handler);
+       (void)signal(SIGSEGV, test_signal_handler);
+       (void)signal(SIGABRT, test_signal_handler);
+
+  while ((c = getopt(argc, argv, "D")) > 0) {
+               switch (c) {
+               case 'D':
+                       debug = TRUE;
+                       break;
+               default:
+                       i_fatal("Usage: %s [-D]", argv[0]);
+               }
+  }
+
+       /* listen on localhost */
+       memset(&bind_ip, 0, sizeof(bind_ip));
+       bind_ip.family = AF_INET;
+       bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK);  
+
+       test_run(test_functions);
+}