diff options
Diffstat (limited to 'src/lib/test-ioloop.c')
-rw-r--r-- | src/lib/test-ioloop.c | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/lib/test-ioloop.c b/src/lib/test-ioloop.c new file mode 100644 index 0000000..89f6888 --- /dev/null +++ b/src/lib/test-ioloop.c @@ -0,0 +1,404 @@ +/* Copyright (c) 2015-2018 Dovecot authors, see the included COPYING file */ + +#include "test-lib.h" +#include "net.h" +#include "time-util.h" +#include "ioloop.h" +#include "istream.h" + +#include <unistd.h> + +struct test_ctx { + bool got_left; + bool got_right; + bool got_to; +}; + +static void timeout_callback(struct timeval *tv) +{ + i_gettimeofday(tv); + io_loop_stop(current_ioloop); +} + +static void test_ioloop_fd_cb_left(struct test_ctx *ctx) +{ + ctx->got_left = TRUE; + if (ctx->got_left && ctx->got_right) + io_loop_stop(current_ioloop); +} + +static void test_ioloop_fd_cb_right(struct test_ctx *ctx) +{ + ctx->got_right = TRUE; + if (ctx->got_left && ctx->got_right) + io_loop_stop(current_ioloop); +} + +static void test_ioloop_fd_to(struct test_ctx *ctx) +{ + ctx->got_to = TRUE; + io_loop_stop(current_ioloop); +} + +static void test_ioloop_fd(void) +{ + test_begin("ioloop fd"); + + struct test_ctx test_ctx; + int fds[2]; + int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + + test_assert(ret == 0); + if (ret < 0) { + i_error("socketpair() failed: %m"); + test_end(); + return; + } + + i_zero(&test_ctx); + + struct ioloop *ioloop = io_loop_create(); + + struct io *io_left = + io_add(fds[0], IO_READ, + test_ioloop_fd_cb_left, &test_ctx); + struct io *io_right = + io_add(fds[1], IO_READ, + test_ioloop_fd_cb_right, &test_ctx); + + struct timeout *to = timeout_add(2000, test_ioloop_fd_to, &test_ctx); + + if (write(fds[0], "ltr", 3) != 3 || + write(fds[1], "rtl", 3) != 3) + i_fatal("write() failed: %m"); + + io_loop_run(ioloop); + + timeout_remove(&to); + io_remove(&io_left); + io_remove(&io_right); + + test_assert(test_ctx.got_to == FALSE); + test_assert(test_ctx.got_left == TRUE); + test_assert(test_ctx.got_right == TRUE); + + io_loop_destroy(&ioloop); + i_close_fd(&fds[0]); + i_close_fd(&fds[1]); + + test_end(); +} + +static void test_ioloop_timeout(void) +{ + struct ioloop *ioloop, *ioloop2; + struct timeout *to, *to2; + struct timeval tv_start, tv_callback; + + test_begin("ioloop timeout"); + + ioloop = io_loop_create(); + + /* add a timeout by moving it from another ioloop */ + ioloop2 = io_loop_create(); + test_assert(io_loop_is_empty(ioloop)); + test_assert(io_loop_is_empty(ioloop2)); + to2 = timeout_add(1000, timeout_callback, &tv_callback); + test_assert(io_loop_is_empty(ioloop)); + test_assert(!io_loop_is_empty(ioloop2)); + io_loop_set_current(ioloop); + to2 = io_loop_move_timeout(&to2); + test_assert(!io_loop_is_empty(ioloop)); + test_assert(io_loop_is_empty(ioloop2)); + io_loop_set_current(ioloop2); + io_loop_destroy(&ioloop2); + + sleep(1); + + /* add & remove immediately */ + to = timeout_add(1000, timeout_callback, &tv_callback); + timeout_remove(&to); + + /* add the timeout we're actually testing below */ + to = timeout_add(1000, timeout_callback, &tv_callback); + i_gettimeofday(&tv_start); + io_loop_run(ioloop); + test_assert(timeval_diff_msecs(&tv_callback, &tv_start) >= 500); + timeout_remove(&to); + timeout_remove(&to2); + test_assert(io_loop_is_empty(ioloop)); + io_loop_destroy(&ioloop); + + test_end(); +} + +static void zero_timeout_callback(unsigned int *counter) +{ + *counter += 1; +} + +static void test_ioloop_zero_timeout(void) +{ + struct ioloop *ioloop; + struct timeout *to; + struct io *io; + unsigned int counter = 0; + int fd[2]; + + test_begin("ioloop zero timeout"); + + if (pipe(fd) < 0) + i_fatal("pipe() failed: %m"); + switch (fork()) { + case (pid_t)-1: + i_fatal("fork() failed: %m"); + case 0: + sleep(1); + char c = 0; + if (write(fd[1], &c, 1) < 0) + i_fatal("write(pipe) failed: %m"); + test_exit(0); + default: + break; + } + + ioloop = io_loop_create(); + to = timeout_add_short(0, zero_timeout_callback, &counter); + io = io_add(fd[0], IO_READ, io_loop_stop, ioloop); + + io_loop_run(ioloop); + test_assert_ucmp(counter, >, 1000); + + timeout_remove(&to); + io_remove(&io); + io_loop_destroy(&ioloop); + test_end(); +} + +struct zero_timeout_recreate_ctx { + struct timeout *to; + unsigned int counter; +}; + +static void +zero_timeout_recreate_callback(struct zero_timeout_recreate_ctx *ctx) +{ + timeout_remove(&ctx->to); + ctx->to = timeout_add_short(0, zero_timeout_recreate_callback, ctx); + ctx->counter++; +} + +static void test_ioloop_zero_timeout_recreate(void) +{ + struct ioloop *ioloop; + struct io *io; + struct zero_timeout_recreate_ctx ctx = { .counter = 0 }; + int fd[2]; + + test_begin("ioloop zero timeout recreate"); + + if (pipe(fd) < 0) + i_fatal("pipe() failed: %m"); + switch (fork()) { + case (pid_t)-1: + i_fatal("fork() failed: %m"); + case 0: + sleep(1); + char c = 0; + if (write(fd[1], &c, 1) < 0) + i_fatal("write(pipe) failed: %m"); + test_exit(0); + default: + break; + } + + ioloop = io_loop_create(); + ctx.to = timeout_add_short(0, zero_timeout_recreate_callback, &ctx); + io = io_add(fd[0], IO_READ, io_loop_stop, ioloop); + + io_loop_run(ioloop); + test_assert_ucmp(ctx.counter, >, 1000); + + timeout_remove(&ctx.to); + io_remove(&io); + io_loop_destroy(&ioloop); + test_end(); +} + +static void io_callback(void *context ATTR_UNUSED) +{ +} + +static void test_ioloop_find_fd_conditions(void) +{ + static struct { + enum io_condition condition; + int fd[2]; + struct io *io; + } tests[] = { + { IO_ERROR, { -1, -1 }, NULL }, + { IO_READ, { -1, -1 }, NULL }, + { IO_WRITE, { -1, -1 }, NULL }, + { IO_READ | IO_WRITE, { -1, -1 }, NULL }, + { IO_READ, { -1, -1 }, NULL } /* read+write as separate ios */ + }; + struct ioloop *ioloop; + struct io *io; + unsigned int i; + + test_begin("ioloop find fd conditions"); + + ioloop = io_loop_create(); + + for (i = 0; i < N_ELEMENTS(tests); i++) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, tests[i].fd) < 0) + i_fatal("socketpair() failed: %m"); + tests[i].io = io_add(tests[i].fd[0], tests[i].condition, io_callback, NULL); + } + io = io_add(tests[i-1].fd[0], IO_WRITE, io_callback, NULL); + tests[i-1].condition |= IO_WRITE; + + for (i = 0; i < N_ELEMENTS(tests); i++) + test_assert_idx(io_loop_find_fd_conditions(ioloop, tests[i].fd[0]) == tests[i].condition, i); + + io_remove(&io); + for (i = 0; i < N_ELEMENTS(tests); i++) { + io_remove(&tests[i].io); + i_close_fd(&tests[i].fd[0]); + i_close_fd(&tests[i].fd[1]); + } + io_loop_destroy(&ioloop); + + test_end(); +} + +static void io_callback_pending_io(void *context ATTR_UNUSED) +{ + io_loop_stop(current_ioloop); +} + +static void test_ioloop_pending_io(void) +{ + test_begin("ioloop pending io"); + + struct istream *is = i_stream_create_from_data("data", 4); + struct ioloop *ioloop = io_loop_create(); + test_assert(io_loop_is_empty(ioloop)); + struct io *io = io_add_istream(is, io_callback_pending_io, NULL); + test_assert(!io_loop_is_empty(ioloop)); + io_loop_set_current(ioloop); + io_set_pending(io); + io_loop_run(ioloop); + io_remove(&io); + i_stream_unref(&is); + io_loop_destroy(&ioloop); + + test_end(); +} + +static void test_ioloop_context_callback(struct ioloop_context *ctx) +{ + test_assert(io_loop_get_current_context(current_ioloop) == ctx); + io_loop_stop(current_ioloop); +} + +static void test_ioloop_context(void) +{ + test_begin("ioloop context"); + struct ioloop *ioloop = io_loop_create(); + struct ioloop_context *ctx = io_loop_context_new(ioloop); + + test_assert(io_loop_get_current_context(current_ioloop) == NULL); + io_loop_context_activate(ctx); + test_assert(io_loop_get_current_context(current_ioloop) == ctx); + struct timeout *to = timeout_add(0, test_ioloop_context_callback, ctx); + + io_loop_run(ioloop); + test_assert(io_loop_get_current_context(current_ioloop) == NULL); + /* test that we don't crash at deinit if we leave the context active */ + io_loop_context_activate(ctx); + test_assert(io_loop_get_current_context(current_ioloop) == ctx); + + timeout_remove(&to); + io_loop_context_unref(&ctx); + io_loop_destroy(&ioloop); + test_end(); +} + +static void test_ioloop_context_events_run(struct event *root_event) +{ + struct ioloop *ioloop = io_loop_create(); + struct ioloop_context *ctx1, *ctx2; + + /* create context 1 */ + ctx1 = io_loop_context_new(ioloop); + io_loop_context_switch(ctx1); + struct event *ctx1_event1 = event_create(NULL); + event_push_global(ctx1_event1); + struct event *ctx1_event2 = event_create(NULL); + event_push_global(ctx1_event2); + io_loop_context_deactivate(ctx1); + + test_assert(event_get_global() == root_event); + + /* create context 2 */ + ctx2 = io_loop_context_new(ioloop); + io_loop_context_switch(ctx2); + struct event *ctx2_event1 = event_create(NULL); + event_push_global(ctx2_event1); + io_loop_context_deactivate(ctx2); + + test_assert(event_get_global() == root_event); + + /* test switching contexts */ + io_loop_context_switch(ctx1); + test_assert(event_get_global() == ctx1_event2); + io_loop_context_switch(ctx2); + test_assert(event_get_global() == ctx2_event1); + + /* test popping away events */ + io_loop_context_switch(ctx1); + event_pop_global(ctx1_event2); + io_loop_context_switch(ctx2); + event_pop_global(ctx2_event1); + io_loop_context_switch(ctx1); + test_assert(event_get_global() == ctx1_event1); + io_loop_context_switch(ctx2); + test_assert(event_get_global() == root_event); + + io_loop_context_deactivate(ctx2); + io_loop_context_unref(&ctx1); + io_loop_context_unref(&ctx2); + io_loop_destroy(&ioloop); + + event_unref(&ctx1_event1); + event_unref(&ctx1_event2); + event_unref(&ctx2_event1); +} + +static void test_ioloop_context_events(void) +{ + test_begin("ioloop context - no root event"); + test_ioloop_context_events_run(NULL); + test_end(); + + test_begin("ioloop context - with root event"); + struct event *root_event = event_create(NULL); + event_push_global(root_event); + test_ioloop_context_events_run(root_event); + event_pop_global(root_event); + event_unref(&root_event); + test_end(); +} + +void test_ioloop(void) +{ + test_ioloop_timeout(); + test_ioloop_zero_timeout(); + test_ioloop_zero_timeout_recreate(); + test_ioloop_find_fd_conditions(); + test_ioloop_pending_io(); + test_ioloop_fd(); + test_ioloop_context(); + test_ioloop_context_events(); +} |