diff options
Diffstat (limited to 'libevent/test')
46 files changed, 26750 insertions, 0 deletions
diff --git a/libevent/test/Makefile.nmake b/libevent/test/Makefile.nmake new file mode 100644 index 0000000..30c3eb7 --- /dev/null +++ b/libevent/test/Makefile.nmake @@ -0,0 +1,79 @@ +# WATCH OUT! This makefile is a work in progress. -*- makefile -*- + +!IFDEF OPENSSL_DIR +SSL_CFLAGS=/I$(OPENSSL_DIR)\include /DEVENT__HAVE_OPENSSL +SSL_OBJS=regress_ssl.obj +SSL_LIBS=..\libevent_openssl.lib $(OPENSSL_DIR)\lib\libeay32.lib $(OPENSSL_DIR)\lib\ssleay32.lib gdi32.lib User32.lib +!ELSE +SSL_CFLAGS= +SSL_OBJS= +SSL_LIBS= +!ENDIF + +CFLAGS=/I.. /I../WIN32-Code /I../WIN32-Code/nmake /I../include /I../compat /DHAVE_CONFIG_H /DTINYTEST_LOCAL $(SSL_CFLAGS) + +CFLAGS=$(CFLAGS) /Ox /W3 /wd4996 /nologo + +REGRESS_OBJS=regress.obj regress_buffer.obj regress_http.obj regress_dns.obj \ + regress_testutils.obj \ + regress_rpc.obj regress.gen.obj \ + regress_et.obj regress_bufferevent.obj \ + regress_listener.obj regress_util.obj tinytest.obj \ + regress_main.obj regress_minheap.obj regress_iocp.obj \ + regress_thread.obj regress_finalize.obj $(SSL_OBJS) + +OTHER_OBJS=test-init.obj test-eof.obj test-closed.obj test-weof.obj test-time.obj \ + bench.obj bench_cascade.obj bench_http.obj bench_httpclient.obj \ + test-changelist.obj \ + print-winsock-errors.obj + +PROGRAMS=regress.exe \ + test-init.exe test-eof.exe test-closed.exe test-weof.exe test-time.exe \ + test-changelist.exe \ + print-winsock-errors.exe + +# Disabled for now: +# bench.exe bench_cascade.exe bench_http.exe bench_httpclient.exe + + +LIBS=..\libevent.lib ws2_32.lib shell32.lib advapi32.lib + +all: $(PROGRAMS) + +regress.exe: $(REGRESS_OBJS) + $(CC) $(CFLAGS) $(LIBS) $(SSL_LIBS) $(REGRESS_OBJS) + +test-init.exe: test-init.obj + $(CC) $(CFLAGS) $(LIBS) test-init.obj +test-eof.exe: test-eof.obj + $(CC) $(CFLAGS) $(LIBS) test-eof.obj +test-closed.exe: test-closed.obj + $(CC) $(CFLAGS) $(LIBS) test-closed.obj +test-changelist.exe: test-changelist.obj + $(CC) $(CFLAGS) $(LIBS) test-changelist.obj +test-weof.exe: test-weof.obj + $(CC) $(CFLAGS) $(LIBS) test-weof.obj +test-time.exe: test-time.obj + $(CC) $(CFLAGS) $(LIBS) test-time.obj + +print-winsock-errors.exe: print-winsock-errors.obj + $(CC) $(CFLAGS) $(LIBS) print-winsock-errors.obj + +bench.exe: bench.obj + $(CC) $(CFLAGS) $(LIBS) bench.obj +bench_cascade.exe: bench_cascade.obj + $(CC) $(CFLAGS) $(LIBS) bench_cascade.obj +bench_http.exe: bench_http.obj + $(CC) $(CFLAGS) $(LIBS) bench_http.obj +bench_httpclient.exe: bench_httpclient.obj + $(CC) $(CFLAGS) $(LIBS) bench_httpclient.obj + +regress.gen.c regress.gen.h: regress.rpc ../event_rpcgen.py + echo // > regress.gen.c + echo #define NO_PYTHON_EXISTS > regress.gen.h + -python ..\event_rpcgen.py regress.rpc + +clean: + -del $(REGRESS_OBJS) + -del $(OTHER_OBJS) + -del $(PROGRAMS) diff --git a/libevent/test/bench.c b/libevent/test/bench.c new file mode 100644 index 0000000..f2af4d3 --- /dev/null +++ b/libevent/test/bench.c @@ -0,0 +1,235 @@ +/* + * Copyright 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Mon 03/10/2003 - Modified by Davide Libenzi <davidel@xmailserver.org> + * + * Added chain event propagation to improve the sensitivity of + * the measure respect to the event loop efficency. + * + * + */ + +#include "event2/event-config.h" +#include "../util-internal.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#else +#include <sys/socket.h> +#include <signal.h> +#include <sys/resource.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> + +#ifdef _WIN32 +#include <getopt.h> +#endif + +#include <event.h> +#include <evutil.h> + +static ev_ssize_t count, fired; +static int writes, failures; +static evutil_socket_t *pipes; +static int num_pipes, num_active, num_writes; +static struct event *events; +static struct event_base *base; + + +static void +read_cb(evutil_socket_t fd, short which, void *arg) +{ + ev_intptr_t idx = (ev_intptr_t) arg, widx = idx + 1; + unsigned char ch; + ev_ssize_t n; + + n = recv(fd, (char*)&ch, sizeof(ch), 0); + if (n >= 0) + count += n; + else + failures++; + if (writes) { + if (widx >= num_pipes) + widx -= num_pipes; + n = send(pipes[2 * widx + 1], "e", 1, 0); + if (n != 1) + failures++; + writes--; + fired++; + } +} + +static struct timeval * +run_once(void) +{ + evutil_socket_t *cp, space; + long i; + static struct timeval ts, te; + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + if (event_initialized(&events[i])) + event_del(&events[i]); + event_assign(&events[i], base, cp[0], EV_READ | EV_PERSIST, read_cb, (void *)(ev_intptr_t) i); + event_add(&events[i], NULL); + } + + event_base_loop(base, EVLOOP_ONCE | EVLOOP_NONBLOCK); + + fired = 0; + space = num_pipes / num_active; + space = space * 2; + for (i = 0; i < num_active; i++, fired++) + (void) send(pipes[i * space + 1], "e", 1, 0); + + count = 0; + writes = num_writes; + { + int xcount = 0; + evutil_gettimeofday(&ts, NULL); + do { + event_base_loop(base, EVLOOP_ONCE | EVLOOP_NONBLOCK); + xcount++; + } while (count != fired); + evutil_gettimeofday(&te, NULL); + + if (xcount != count) + fprintf(stderr, "Xcount: %d, Rcount: " EV_SSIZE_FMT "\n", + xcount, count); + } + + evutil_timersub(&te, &ts, &te); + + return (&te); +} + +int +main(int argc, char **argv) +{ +#ifdef EVENT__HAVE_SETRLIMIT + struct rlimit rl; +#endif + int i, c; + struct timeval *tv; + evutil_socket_t *cp; + const char **methods; + const char *method = NULL; + struct event_config *cfg = NULL; + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + num_pipes = 100; + num_active = 1; + num_writes = num_pipes; + while ((c = getopt(argc, argv, "n:a:w:m:l")) != -1) { + switch (c) { + case 'n': + num_pipes = atoi(optarg); + break; + case 'a': + num_active = atoi(optarg); + break; + case 'w': + num_writes = atoi(optarg); + break; + case 'm': + method = optarg; + break; + case 'l': + methods = event_get_supported_methods(); + fprintf(stdout, "Using Libevent %s. Available methods are:\n", + event_get_version()); + for (i = 0; methods[i] != NULL; ++i) + printf(" %s\n", methods[i]); + exit(0); + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + +#ifdef EVENT__HAVE_SETRLIMIT + rl.rlim_cur = rl.rlim_max = num_pipes * 2 + 50; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + perror("setrlimit"); + exit(1); + } +#endif + + events = calloc(num_pipes, sizeof(struct event)); + pipes = calloc(num_pipes * 2, sizeof(evutil_socket_t)); + if (events == NULL || pipes == NULL) { + perror("malloc"); + exit(1); + } + + if (method != NULL) { + cfg = event_config_new(); + methods = event_get_supported_methods(); + for (i = 0; methods[i] != NULL; ++i) + if (strcmp(methods[i], method)) + event_config_avoid_method(cfg, methods[i]); + base = event_base_new_with_config(cfg); + event_config_free(cfg); + } else + base = event_base_new(); + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { +#ifdef USE_PIPES + if (pipe(cp) == -1) { +#else + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, cp) == -1) { +#endif + perror("pipe"); + exit(1); + } + } + + for (i = 0; i < 25; i++) { + tv = run_once(); + if (tv == NULL) + exit(1); + fprintf(stdout, "%ld\n", + tv->tv_sec * 1000000L + tv->tv_usec); + } + + exit(0); +} diff --git a/libevent/test/bench_cascade.c b/libevent/test/bench_cascade.c new file mode 100644 index 0000000..29a3203 --- /dev/null +++ b/libevent/test/bench_cascade.c @@ -0,0 +1,188 @@ +/* + * Copyright 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <getopt.h> +#else /* _WIN32 */ +#include <sys/socket.h> +#include <sys/resource.h> +#endif +#include <signal.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include <event.h> +#include <evutil.h> + +/* + * This benchmark tests how quickly we can propagate a write down a chain + * of socket pairs. We start by writing to the first socket pair and all + * events will fire subsequently until the last socket pair has been reached + * and the benchmark terminates. + */ + +static int fired; +static evutil_socket_t *pipes; +static struct event *events; + +static void +read_cb(evutil_socket_t fd, short which, void *arg) +{ + char ch; + evutil_socket_t sock = (evutil_socket_t)(ev_intptr_t)arg; + + (void) recv(fd, &ch, sizeof(ch), 0); + if (sock >= 0) { + if (send(sock, "e", 1, 0) < 0) + perror("send"); + } + fired++; +} + +static struct timeval * +run_once(int num_pipes) +{ + int i; + evutil_socket_t *cp; + static struct timeval ts, te, tv_timeout; + + events = (struct event *)calloc(num_pipes, sizeof(struct event)); + pipes = (evutil_socket_t *)calloc(num_pipes * 2, sizeof(evutil_socket_t)); + + if (events == NULL || pipes == NULL) { + perror("malloc"); + exit(1); + } + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, cp) == -1) { + perror("socketpair"); + exit(1); + } + } + + /* measurements includes event setup */ + evutil_gettimeofday(&ts, NULL); + + /* provide a default timeout for events */ + evutil_timerclear(&tv_timeout); + tv_timeout.tv_sec = 60; + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + evutil_socket_t fd = i < num_pipes - 1 ? cp[3] : -1; + event_set(&events[i], cp[0], EV_READ, read_cb, + (void *)(ev_intptr_t)fd); + event_add(&events[i], &tv_timeout); + } + + fired = 0; + + /* kick everything off with a single write */ + if (send(pipes[1], "e", 1, 0) < 0) + perror("send"); + + event_dispatch(); + + evutil_gettimeofday(&te, NULL); + evutil_timersub(&te, &ts, &te); + + for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2) { + event_del(&events[i]); + evutil_closesocket(cp[0]); + evutil_closesocket(cp[1]); + } + + free(pipes); + free(events); + + return (&te); +} + +int +main(int argc, char **argv) +{ +#ifdef EVENT__HAVE_SETRLIMIT + struct rlimit rl; +#endif + int i, c; + struct timeval *tv; + + int num_pipes = 100; +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + + while ((c = getopt(argc, argv, "n:")) != -1) { + switch (c) { + case 'n': + num_pipes = atoi(optarg); + break; + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + +#ifdef EVENT__HAVE_SETRLIMIT + rl.rlim_cur = rl.rlim_max = num_pipes * 2 + 50; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + perror("setrlimit"); + exit(1); + } +#endif + + event_init(); + + for (i = 0; i < 25; i++) { + tv = run_once(num_pipes); + if (tv == NULL) + exit(1); + fprintf(stdout, "%ld\n", + tv->tv_sec * 1000000L + tv->tv_usec); + } + +#ifdef _WIN32 + WSACleanup(); +#endif + + exit(0); +} diff --git a/libevent/test/bench_http.c b/libevent/test/bench_http.c new file mode 100644 index 0000000..80377ee --- /dev/null +++ b/libevent/test/bench_http.c @@ -0,0 +1,200 @@ +/* + * Copyright 2008-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> +#endif +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "event2/event.h" +#include "event2/buffer.h" +#include "event2/util.h" +#include "event2/http.h" +#include "event2/thread.h" + +static void http_basic_cb(struct evhttp_request *req, void *arg); + +static char *content; +static size_t content_len = 0; + +static void +http_basic_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + + evbuffer_add(evb, content, content_len); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +#if LIBEVENT_VERSION_NUMBER >= 0x02000200 +static void +http_ref_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + + evbuffer_add_reference(evb, content, content_len, NULL, NULL); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} +#endif + +int +main(int argc, char **argv) +{ + struct event_config *cfg = event_config_new(); + struct event_base *base; + struct evhttp *http; + int i; + int c; + int use_iocp = 0; + ev_uint16_t port = 8080; + char *endptr = NULL; + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#else + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return (1); +#endif + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + for (i = 1; i < argc; ++i) { + if (*argv[i] != '-') + continue; + + c = argv[i][1]; + + if ((c == 'p' || c == 'l') && i + 1 >= argc) { + fprintf(stderr, "-%c requires argument.\n", c); + exit(1); + } + + switch (c) { + case 'p': + if (i+1 >= argc || !argv[i+1]) { + fprintf(stderr, "Missing port\n"); + exit(1); + } + port = (int)strtol(argv[i+1], &endptr, 10); + if (*endptr != '\0') { + fprintf(stderr, "Bad port\n"); + exit(1); + } + break; + case 'l': + if (i+1 >= argc || !argv[i+1]) { + fprintf(stderr, "Missing content length\n"); + exit(1); + } + content_len = (size_t)strtol(argv[i+1], &endptr, 10); + if (*endptr != '\0' || content_len == 0) { + fprintf(stderr, "Bad content length\n"); + exit(1); + } + break; +#ifdef _WIN32 + case 'i': + use_iocp = 1; +#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED + evthread_use_windows_threads(); +#endif + event_config_set_flag(cfg,EVENT_BASE_FLAG_STARTUP_IOCP); + break; +#endif + default: + fprintf(stderr, "Illegal argument \"%c\"\n", c); + exit(1); + } + } + + base = event_base_new_with_config(cfg); + if (!base) { + fprintf(stderr, "creating event_base failed. Exiting.\n"); + return 1; + } + + http = evhttp_new(base); + + content = malloc(content_len); + if (content == NULL) { + fprintf(stderr, "Cannot allocate content\n"); + exit(1); + } else { + int i = 0; + for (i = 0; i < (int)content_len; ++i) + content[i] = (i & 255); + } + + evhttp_set_cb(http, "/ind", http_basic_cb, NULL); + fprintf(stderr, "/ind - basic content (memory copy)\n"); + + evhttp_set_cb(http, "/ref", http_ref_cb, NULL); + fprintf(stderr, "/ref - basic content (reference)\n"); + + fprintf(stderr, "Serving %d bytes on port %d using %s\n", + (int)content_len, port, + use_iocp? "IOCP" : event_base_get_method(base)); + + evhttp_bind_socket(http, "0.0.0.0", port); + +#ifdef _WIN32 + if (use_iocp) { + struct timeval tv={99999999,0}; + event_base_loopexit(base, &tv); + } +#endif + event_base_dispatch(base); + +#ifdef _WIN32 + WSACleanup(); +#endif + + /* NOTREACHED */ + return (0); +} diff --git a/libevent/test/bench_httpclient.c b/libevent/test/bench_httpclient.c new file mode 100644 index 0000000..7c7ee47 --- /dev/null +++ b/libevent/test/bench_httpclient.c @@ -0,0 +1,240 @@ +/* + * Copyright 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* for EVUTIL_ERR_CONNECT_RETRIABLE macro */ +#include "util-internal.h" + +#include <sys/types.h> +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +# ifdef _XOPEN_SOURCE_EXTENDED +# include <arpa/inet.h> +# endif +#endif +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "event2/event.h" +#include "event2/bufferevent.h" +#include "event2/buffer.h" +#include "event2/util.h" + +const char *resource = NULL; +struct event_base *base = NULL; + +int total_n_handled = 0; +int total_n_errors = 0; +int total_n_launched = 0; +size_t total_n_bytes = 0; +struct timeval total_time = {0,0}; +int n_errors = 0; + +const int PARALLELISM = 200; +const int N_REQUESTS = 20000; + +struct request_info { + size_t n_read; + struct timeval started; +}; + +static int launch_request(void); +static void readcb(struct bufferevent *b, void *arg); +static void errorcb(struct bufferevent *b, short what, void *arg); + +static void +readcb(struct bufferevent *b, void *arg) +{ + struct request_info *ri = arg; + struct evbuffer *input = bufferevent_get_input(b); + size_t n = evbuffer_get_length(input); + + ri->n_read += n; + evbuffer_drain(input, n); +} + +static void +errorcb(struct bufferevent *b, short what, void *arg) +{ + struct request_info *ri = arg; + struct timeval now, diff; + if (what & BEV_EVENT_EOF) { + ++total_n_handled; + total_n_bytes += ri->n_read; + evutil_gettimeofday(&now, NULL); + evutil_timersub(&now, &ri->started, &diff); + evutil_timeradd(&diff, &total_time, &total_time); + + if (total_n_handled && (total_n_handled%1000)==0) + printf("%d requests done\n",total_n_handled); + + if (total_n_launched < N_REQUESTS) { + if (launch_request() < 0) + perror("Can't launch"); + } + } else { + ++total_n_errors; + perror("Unexpected error"); + } + + bufferevent_setcb(b, NULL, NULL, NULL, NULL); + free(ri); + bufferevent_disable(b, EV_READ|EV_WRITE); + bufferevent_free(b); +} + +static void +frob_socket(evutil_socket_t sock) +{ +#ifdef EVENT__HAVE_STRUCT_LINGER + struct linger l; +#endif + int one = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one))<0) + perror("setsockopt(SO_REUSEADDR)"); +#ifdef EVENT__HAVE_STRUCT_LINGER + l.l_onoff = 1; + l.l_linger = 0; + if (setsockopt(sock, SOL_SOCKET, SO_LINGER, (void*)&l, sizeof(l))<0) + perror("setsockopt(SO_LINGER)"); +#endif +} + +static int +launch_request(void) +{ + evutil_socket_t sock; + struct sockaddr_in sin; + struct bufferevent *b; + + struct request_info *ri; + + memset(&sin, 0, sizeof(sin)); + + ++total_n_launched; + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); + sin.sin_port = htons(8080); + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -1; + if (evutil_make_socket_nonblocking(sock) < 0) { + evutil_closesocket(sock); + return -1; + } + frob_socket(sock); + if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0) { + int e = evutil_socket_geterror(sock); + if (! EVUTIL_ERR_CONNECT_RETRIABLE(e)) { + evutil_closesocket(sock); + return -1; + } + } + + ri = malloc(sizeof(*ri)); + ri->n_read = 0; + evutil_gettimeofday(&ri->started, NULL); + + b = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE); + + bufferevent_setcb(b, readcb, NULL, errorcb, ri); + bufferevent_enable(b, EV_READ|EV_WRITE); + + evbuffer_add_printf(bufferevent_get_output(b), + "GET %s HTTP/1.0\r\n\r\n", resource); + + return 0; +} + + +int +main(int argc, char **argv) +{ + int i; + struct timeval start, end, total; + long long usec; + double throughput; + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + + resource = "/ref"; + + setvbuf(stdout, NULL, _IONBF, 0); + + base = event_base_new(); + + for (i=0; i < PARALLELISM; ++i) { + if (launch_request() < 0) + perror("launch"); + } + + evutil_gettimeofday(&start, NULL); + + event_base_dispatch(base); + + evutil_gettimeofday(&end, NULL); + evutil_timersub(&end, &start, &total); + usec = total_time.tv_sec * (long long)1000000 + total_time.tv_usec; + + if (!total_n_handled) { + puts("Nothing worked. You probably did something dumb."); + return 0; + } + + + throughput = total_n_handled / + (total.tv_sec+ ((double)total.tv_usec)/1000000.0); + +#ifdef _WIN32 +#define I64_FMT "%I64d" +#define I64_TYP __int64 +#else +#define I64_FMT "%lld" +#define I64_TYP long long int +#endif + + printf("\n%d requests in %d.%06d sec. (%.2f throughput)\n" + "Each took about %.02f msec latency\n" + I64_FMT "bytes read. %d errors.\n", + total_n_handled, + (int)total.tv_sec, (int)total.tv_usec, + throughput, + (double)(usec/1000) / total_n_handled, + (I64_TYP)total_n_bytes, n_errors); + +#ifdef _WIN32 + WSACleanup(); +#endif + + return 0; +} diff --git a/libevent/test/check-dumpevents.py b/libevent/test/check-dumpevents.py new file mode 100755 index 0000000..3e1df30 --- /dev/null +++ b/libevent/test/check-dumpevents.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# Post-process the output of test-dumpevents and check it for correctness. +# + +import math +import re +import sys + +text = sys.stdin.readlines() + +try: + expect_inserted_pos = text.index("Inserted:\n") + expect_active_pos = text.index("Active:\n") + got_inserted_pos = text.index("Inserted events:\n") + got_active_pos = text.index("Active events:\n") +except ValueError: + sys.stderr.write("Missing expected dividing line in dumpevents output") + sys.exit(1) + +if not (expect_inserted_pos < expect_active_pos < + got_inserted_pos < got_active_pos): + sys.stderr.write("Sections out of order in dumpevents output") + sys.exit(1) + +now,T= text[1].split() +T = float(T) + +want_inserted = set(text[expect_inserted_pos+1:expect_active_pos]) +want_active = set(text[expect_active_pos+1:got_inserted_pos-1]) +got_inserted = set(text[got_inserted_pos+1:got_active_pos]) +got_active = set(text[got_active_pos+1:]) + +pat = re.compile(r'Timeout=([0-9\.]+)') +def replace_time(m): + t = float(m.group(1)) + if .9 < abs(t-T) < 1.1: + return "Timeout=T+1" + elif 2.4 < abs(t-T) < 2.6: + return "Timeout=T+2.5" + else: + return m.group(0) + +cleaned_inserted = set( pat.sub(replace_time, s) for s in got_inserted + if "Internal" not in s) + +if cleaned_inserted != want_inserted: + sys.stderr.write("Inserted event lists were not as expected!") + sys.exit(1) + +if set(got_active) != set(want_active): + sys.stderr.write("Active event lists were not as expected!") + sys.exit(1) + diff --git a/libevent/test/include.am b/libevent/test/include.am new file mode 100644 index 0000000..0437524 --- /dev/null +++ b/libevent/test/include.am @@ -0,0 +1,176 @@ +# test/Makefile.am for libevent +# Copyright 2000-2007 Niels Provos +# Copyright 2007-2012 Niels Provos and Nick Mathewson +# +# See LICENSE for copying information. + +regress_CPPFLAGS = -DTINYTEST_LOCAL + +EXTRA_DIST+= \ + test/check-dumpevents.py \ + test/regress.gen.c \ + test/regress.gen.h \ + test/regress.rpc \ + test/rpcgen_wrapper.sh \ + test/print-winsock-errors.c \ + test/test.sh + +TESTPROGRAMS = \ + test/bench \ + test/bench_cascade \ + test/bench_http \ + test/bench_httpclient \ + test/test-changelist \ + test/test-dumpevents \ + test/test-eof \ + test/test-closed \ + test/test-fdleak \ + test/test-init \ + test/test-ratelim \ + test/test-time \ + test/test-weof \ + test/regress + +if BUILD_REGRESS +noinst_PROGRAMS += $(TESTPROGRAMS) +EXTRA_PROGRAMS+= test/regress +endif + +noinst_HEADERS+= \ + test/regress.h \ + test/regress_thread.h \ + test/tinytest.h \ + test/tinytest_local.h \ + test/tinytest_macros.h + +TESTS = \ + test_runner_epoll \ + test_runner_select \ + test_runner_kqueue \ + test_runner_evport \ + test_runner_devpoll \ + test_runner_poll \ + test_runner_win32 \ + test_runner_timerfd \ + test_runner_changelist \ + test_runner_timerfd_changelist +LOG_COMPILER = true +TESTS_COMPILER = true + +test_runner_epoll: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b EPOLL +test_runner_select: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b SELECT +test_runner_kqueue: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b KQUEUE +test_runner_evport: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b EVPORT +test_runner_devpoll: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b DEVPOLL +test_runner_poll: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b POLL +test_runner_win32: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b WIN32 +test_runner_timerfd: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b "" -t +test_runner_changelist: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b "" -c +test_runner_timerfd_changelist: $(top_srcdir)/test/test.sh + $(top_srcdir)/test/test.sh -b "" -T + +DISTCLEANFILES += test/regress.gen.c test/regress.gen.h + +if BUILD_REGRESS +BUILT_SOURCES += test/regress.gen.c test/regress.gen.h +endif + +test_test_init_SOURCES = test/test-init.c +test_test_init_LDADD = libevent_core.la +test_test_dumpevents_SOURCES = test/test-dumpevents.c +test_test_dumpevents_LDADD = libevent_core.la +test_test_eof_SOURCES = test/test-eof.c +test_test_eof_LDADD = libevent_core.la +test_test_closed_SOURCES = test/test-closed.c +test_test_closed_LDADD = libevent_core.la +test_test_changelist_SOURCES = test/test-changelist.c +test_test_changelist_LDADD = libevent_core.la +test_test_weof_SOURCES = test/test-weof.c +test_test_weof_LDADD = libevent_core.la +test_test_time_SOURCES = test/test-time.c +test_test_time_LDADD = libevent_core.la +test_test_ratelim_SOURCES = test/test-ratelim.c +test_test_ratelim_LDADD = libevent_core.la -lm +test_test_fdleak_SOURCES = test/test-fdleak.c +test_test_fdleak_LDADD = libevent_core.la + +test_regress_SOURCES = \ + test/regress.c \ + test/regress.gen.c \ + test/regress.gen.h \ + test/regress_buffer.c \ + test/regress_bufferevent.c \ + test/regress_dns.c \ + test/regress_et.c \ + test/regress_finalize.c \ + test/regress_http.c \ + test/regress_listener.c \ + test/regress_main.c \ + test/regress_minheap.c \ + test/regress_rpc.c \ + test/regress_testutils.c \ + test/regress_testutils.h \ + test/regress_util.c \ + test/tinytest.c \ + $(regress_thread_SOURCES) \ + $(regress_zlib_SOURCES) + +if PTHREADS +regress_thread_SOURCES = test/regress_thread.c +PTHREAD_LIBS += libevent_pthreads.la +endif +if BUILD_WIN32 +if THREADS +regress_thread_SOURCES = test/regress_thread.c +endif +endif +if ZLIB_REGRESS +regress_zlib_SOURCES = test/regress_zlib.c +endif +if BUILD_WIN32 +test_regress_SOURCES += test/regress_iocp.c +endif + +test_regress_LDADD = $(LIBEVENT_GC_SECTIONS) libevent_core.la libevent_extra.la $(PTHREAD_LIBS) $(ZLIB_LIBS) +test_regress_CPPFLAGS = $(AM_CPPFLAGS) $(PTHREAD_CFLAGS) $(ZLIB_CFLAGS) -Itest +test_regress_LDFLAGS = $(PTHREAD_CFLAGS) + +if OPENSSL +test_regress_SOURCES += test/regress_ssl.c +test_regress_CPPFLAGS += $(OPENSSL_INCS) +test_regress_LDADD += libevent_openssl.la $(OPENSSL_LIBS) ${OPENSSL_LIBADD} +endif + +test_bench_SOURCES = test/bench.c +test_bench_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la +test_bench_cascade_SOURCES = test/bench_cascade.c +test_bench_cascade_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la +test_bench_http_SOURCES = test/bench_http.c +test_bench_http_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la +test_bench_httpclient_SOURCES = test/bench_httpclient.c +test_bench_httpclient_LDADD = $(LIBEVENT_GC_SECTIONS) libevent_core.la + +test/regress.gen.c test/regress.gen.h: test/rpcgen-attempted + +test/rpcgen-attempted: test/regress.rpc event_rpcgen.py test/rpcgen_wrapper.sh + $(AM_V_GEN)date -u > $@ + $(AM_V_at)if $(srcdir)/test/rpcgen_wrapper.sh $(srcdir)/test; then \ + true; \ + else \ + echo "No Python installed; stubbing out RPC test." >&2; \ + echo " "> test/regress.gen.c; \ + echo "#define NO_PYTHON_EXISTS" > test/regress.gen.h; \ + fi + +CLEANFILES += test/rpcgen-attempted + +$(TESTPROGRAMS) : libevent.la diff --git a/libevent/test/print-winsock-errors.c b/libevent/test/print-winsock-errors.c new file mode 100644 index 0000000..64d6b0e --- /dev/null +++ b/libevent/test/print-winsock-errors.c @@ -0,0 +1,86 @@ +#include <winsock2.h> +#include <windows.h> + +#include <stdlib.h> +#include <stdio.h> + +#include "event2/event.h" +#include "event2/util.h" +#include "event2/thread.h" + +#define E(x) printf (#x " -> \"%s\"\n", evutil_socket_error_to_string (x)); + +int main (int argc, char **argv) +{ + int i, j; + const char *s1, *s2; + +#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED + evthread_use_windows_threads (); +#endif + + s1 = evutil_socket_error_to_string (WSAEINTR); + + for (i = 0; i < 3; i++) { + printf ("\niteration %d:\n\n", i); + E(WSAEINTR); + E(WSAEACCES); + E(WSAEFAULT); + E(WSAEINVAL); + E(WSAEMFILE); + E(WSAEWOULDBLOCK); + E(WSAEINPROGRESS); + E(WSAEALREADY); + E(WSAENOTSOCK); + E(WSAEDESTADDRREQ); + E(WSAEMSGSIZE); + E(WSAEPROTOTYPE); + E(WSAENOPROTOOPT); + E(WSAEPROTONOSUPPORT); + E(WSAESOCKTNOSUPPORT); + E(WSAEOPNOTSUPP); + E(WSAEPFNOSUPPORT); + E(WSAEAFNOSUPPORT); + E(WSAEADDRINUSE); + E(WSAEADDRNOTAVAIL); + E(WSAENETDOWN); + E(WSAENETUNREACH); + E(WSAENETRESET); + E(WSAECONNABORTED); + E(WSAECONNRESET); + E(WSAENOBUFS); + E(WSAEISCONN); + E(WSAENOTCONN); + E(WSAESHUTDOWN); + E(WSAETIMEDOUT); + E(WSAECONNREFUSED); + E(WSAEHOSTDOWN); + E(WSAEHOSTUNREACH); + E(WSAEPROCLIM); + E(WSASYSNOTREADY); + E(WSAVERNOTSUPPORTED); + E(WSANOTINITIALISED); + E(WSAEDISCON); + E(WSATYPE_NOT_FOUND); + E(WSAHOST_NOT_FOUND); + E(WSATRY_AGAIN); + E(WSANO_RECOVERY); + E(WSANO_DATA); + E(0xdeadbeef); /* test the case where no message is available */ + + /* fill up the hash table a bit to make sure it grows properly */ + for (j = 0; j < 50; j++) { + int err; + evutil_secure_rng_get_bytes(&err, sizeof(err)); + evutil_socket_error_to_string(err); + } + } + + s2 = evutil_socket_error_to_string (WSAEINTR); + if (s1 != s2) + printf ("caching failed!\n"); + + libevent_global_shutdown (); + + return EXIT_SUCCESS; +} diff --git a/libevent/test/regress.c b/libevent/test/regress.c new file mode 100644 index 0000000..08c30fa --- /dev/null +++ b/libevent/test/regress.c @@ -0,0 +1,3615 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <sys/wait.h> +#include <limits.h> +#include <signal.h> +#include <unistd.h> +#include <netdb.h> +#endif +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> + +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/event_compat.h" +#include "event2/tag.h" +#include "event2/buffer.h" +#include "event2/buffer_compat.h" +#include "event2/util.h" +#include "event-internal.h" +#include "evthread-internal.h" +#include "log-internal.h" +#include "time-internal.h" + +#include "regress.h" +#include "regress_thread.h" + +#ifndef _WIN32 +#include "regress.gen.h" +#endif + +evutil_socket_t pair[2]; +int test_ok; +int called; +struct event_base *global_base; + +static char wbuf[4096]; +static char rbuf[4096]; +static int woff; +static int roff; +static int usepersist; +static struct timeval tset; +static struct timeval tcalled; + + +#define TEST1 "this is a test" + +#ifdef _WIN32 +#define write(fd,buf,len) send((fd),(buf),(int)(len),0) +#define read(fd,buf,len) recv((fd),(buf),(int)(len),0) +#endif + +struct basic_cb_args +{ + struct event_base *eb; + struct event *ev; + unsigned int callcount; +}; + +static void +simple_read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + + if (len) { + if (!called) { + if (event_add(arg, NULL) == -1) + exit(1); + } + } else if (called == 1) + test_ok = 1; + + called++; +} + +static void +basic_read_cb(evutil_socket_t fd, short event, void *data) +{ + char buf[256]; + int len; + struct basic_cb_args *arg = data; + + len = read(fd, buf, sizeof(buf)); + + if (len < 0) { + tt_fail_perror("read (callback)"); + } else { + switch (arg->callcount++) { + case 0: /* first call: expect to read data; cycle */ + if (len > 0) + return; + + tt_fail_msg("EOF before data read"); + break; + + case 1: /* second call: expect EOF; stop */ + if (len > 0) + tt_fail_msg("not all data read on first cycle"); + break; + + default: /* third call: should not happen */ + tt_fail_msg("too many cycles"); + } + } + + event_del(arg->ev); + event_base_loopexit(arg->eb, NULL); +} + +static void +dummy_read_cb(evutil_socket_t fd, short event, void *arg) +{ +} + +static void +simple_write_cb(evutil_socket_t fd, short event, void *arg) +{ + int len; + + len = write(fd, TEST1, strlen(TEST1) + 1); + if (len == -1) + test_ok = 0; + else + test_ok = 1; +} + +static void +multiple_write_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + int len; + + len = 128; + if (woff + len >= (int)sizeof(wbuf)) + len = sizeof(wbuf) - woff; + + len = write(fd, wbuf + woff, len); + if (len == -1) { + fprintf(stderr, "%s: write\n", __func__); + if (usepersist) + event_del(ev); + return; + } + + woff += len; + + if (woff >= (int)sizeof(wbuf)) { + shutdown(fd, EVUTIL_SHUT_WR); + if (usepersist) + event_del(ev); + return; + } + + if (!usepersist) { + if (event_add(ev, NULL) == -1) + exit(1); + } +} + +static void +multiple_read_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + int len; + + len = read(fd, rbuf + roff, sizeof(rbuf) - roff); + if (len == -1) + fprintf(stderr, "%s: read\n", __func__); + if (len <= 0) { + if (usepersist) + event_del(ev); + return; + } + + roff += len; + if (!usepersist) { + if (event_add(ev, NULL) == -1) + exit(1); + } +} + +static void +timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + evutil_gettimeofday(&tcalled, NULL); +} + +struct both { + struct event ev; + int nread; +}; + +static void +combined_read_cb(evutil_socket_t fd, short event, void *arg) +{ + struct both *both = arg; + char buf[128]; + int len; + + len = read(fd, buf, sizeof(buf)); + if (len == -1) + fprintf(stderr, "%s: read\n", __func__); + if (len <= 0) + return; + + both->nread += len; + if (event_add(&both->ev, NULL) == -1) + exit(1); +} + +static void +combined_write_cb(evutil_socket_t fd, short event, void *arg) +{ + struct both *both = arg; + char buf[128]; + int len; + + len = sizeof(buf); + if (len > both->nread) + len = both->nread; + + memset(buf, 'q', len); + + len = write(fd, buf, len); + if (len == -1) + fprintf(stderr, "%s: write\n", __func__); + if (len <= 0) { + shutdown(fd, EVUTIL_SHUT_WR); + return; + } + + both->nread -= len; + if (event_add(&both->ev, NULL) == -1) + exit(1); +} + +/* These macros used to replicate the work of the legacy test wrapper code */ +#define setup_test(x) do { \ + if (!in_legacy_test_wrapper) { \ + TT_FAIL(("Legacy test %s not wrapped properly", x)); \ + return; \ + } \ + } while (0) +#define cleanup_test() setup_test("cleanup") + +static void +test_simpleread(void) +{ + struct event ev; + + /* Very simple read test */ + setup_test("Simple read: "); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + shutdown(pair[0], EVUTIL_SHUT_WR); + + event_set(&ev, pair[1], EV_READ, simple_read_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static void +test_simplewrite(void) +{ + struct event ev; + + /* Very simple write test */ + setup_test("Simple write: "); + + event_set(&ev, pair[0], EV_WRITE, simple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static void +simpleread_multiple_cb(evutil_socket_t fd, short event, void *arg) +{ + if (++called == 2) + test_ok = 1; +} + +static void +test_simpleread_multiple(void) +{ + struct event one, two; + + /* Very simple read test */ + setup_test("Simple read to multiple evens: "); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + shutdown(pair[0], EVUTIL_SHUT_WR); + + event_set(&one, pair[1], EV_READ, simpleread_multiple_cb, NULL); + if (event_add(&one, NULL) == -1) + exit(1); + event_set(&two, pair[1], EV_READ, simpleread_multiple_cb, NULL); + if (event_add(&two, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +static int have_closed = 0; +static int premature_event = 0; +static void +simpleclose_close_fd_cb(evutil_socket_t s, short what, void *ptr) +{ + evutil_socket_t **fds = ptr; + TT_BLATHER(("Closing")); + evutil_closesocket(*fds[0]); + evutil_closesocket(*fds[1]); + *fds[0] = -1; + *fds[1] = -1; + have_closed = 1; +} + +static void +record_event_cb(evutil_socket_t s, short what, void *ptr) +{ + short *whatp = ptr; + if (!have_closed) + premature_event = 1; + *whatp = what; + TT_BLATHER(("Recorded %d on socket %d", (int)what, (int)s)); +} + +static void +test_simpleclose_rw(void *ptr) +{ + /* Test that a close of FD is detected as a read and as a write. */ + struct event_base *base = event_base_new(); + evutil_socket_t pair1[2]={-1,-1}, pair2[2] = {-1, -1}; + evutil_socket_t *to_close[2]; + struct event *rev=NULL, *wev=NULL, *closeev=NULL; + struct timeval tv; + short got_read_on_close = 0, got_write_on_close = 0; + char buf[1024]; + memset(buf, 99, sizeof(buf)); +#ifdef _WIN32 +#define LOCAL_SOCKETPAIR_AF AF_INET +#else +#define LOCAL_SOCKETPAIR_AF AF_UNIX +#endif + if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, pair1)<0) + TT_DIE(("socketpair: %s", strerror(errno))); + if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, pair2)<0) + TT_DIE(("socketpair: %s", strerror(errno))); + if (evutil_make_socket_nonblocking(pair1[1]) < 0) + TT_DIE(("make_socket_nonblocking")); + if (evutil_make_socket_nonblocking(pair2[1]) < 0) + TT_DIE(("make_socket_nonblocking")); + + /** Stuff pair2[1] full of data, until write fails */ + while (1) { + int r = write(pair2[1], buf, sizeof(buf)); + if (r<0) { + int err = evutil_socket_geterror(pair2[1]); + if (! EVUTIL_ERR_RW_RETRIABLE(err)) + TT_DIE(("write failed strangely: %s", + evutil_socket_error_to_string(err))); + break; + } + } + to_close[0] = &pair1[0]; + to_close[1] = &pair2[0]; + + closeev = event_new(base, -1, EV_TIMEOUT, simpleclose_close_fd_cb, + to_close); + rev = event_new(base, pair1[1], EV_READ, record_event_cb, + &got_read_on_close); + TT_BLATHER(("Waiting for read on %d", (int)pair1[1])); + wev = event_new(base, pair2[1], EV_WRITE, record_event_cb, + &got_write_on_close); + TT_BLATHER(("Waiting for write on %d", (int)pair2[1])); + tv.tv_sec = 0; + tv.tv_usec = 100*1000; /* Close pair1[0] after a little while, and make + * sure we get a read event. */ + event_add(closeev, &tv); + event_add(rev, NULL); + event_add(wev, NULL); + /* Don't let the test go on too long. */ + tv.tv_sec = 0; + tv.tv_usec = 200*1000; + event_base_loopexit(base, &tv); + event_base_loop(base, 0); + + tt_int_op(got_read_on_close, ==, EV_READ); + tt_int_op(got_write_on_close, ==, EV_WRITE); + tt_int_op(premature_event, ==, 0); + +end: + if (pair1[0] >= 0) + evutil_closesocket(pair1[0]); + if (pair1[1] >= 0) + evutil_closesocket(pair1[1]); + if (pair2[0] >= 0) + evutil_closesocket(pair2[0]); + if (pair2[1] >= 0) + evutil_closesocket(pair2[1]); + if (rev) + event_free(rev); + if (wev) + event_free(wev); + if (closeev) + event_free(closeev); + if (base) + event_base_free(base); +} + +static void +test_simpleclose(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + evutil_socket_t *pair = data->pair; + const char *flags = (const char *)data->setup_data; + int et = !!strstr(flags, "ET"); + int persist = !!strstr(flags, "persist"); + short events = EV_CLOSED | (et ? EV_ET : 0) | (persist ? EV_PERSIST : 0); + struct event *ev = NULL; + short got_event; + + if (!(event_base_get_features(data->base) & EV_FEATURE_EARLY_CLOSE)) + tt_skip(); + + /* XXX: should this code moved to regress_et.c ? */ + if (et && !(event_base_get_features(data->base) & EV_FEATURE_ET)) + tt_skip(); + + ev = event_new(base, pair[0], events, record_event_cb, &got_event); + tt_assert(ev); + tt_assert(!event_add(ev, NULL)); + + got_event = 0; + if (strstr(flags, "close")) { + tt_assert(!evutil_closesocket(pair[1])); + /* avoid closing in setup routines */ + pair[1] = -1; + } else if (strstr(flags, "shutdown")) { + tt_assert(!shutdown(pair[1], EVUTIL_SHUT_WR)); + } else { + tt_abort_msg("unknown flags"); + } + + /* w/o edge-triggerd but w/ persist it will not stop */ + if (!et && persist) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10000; + tt_assert(!event_base_loopexit(base, &tv)); + } + + tt_int_op(event_base_loop(base, EVLOOP_NONBLOCK), ==, !persist); + tt_int_op(got_event, ==, (events & ~EV_PERSIST)); + +end: + if (ev) + event_free(ev); +} + +static void +test_multiple(void) +{ + struct event ev, ev2; + int i; + + /* Multiple read and write test */ + setup_test("Multiple read/write: "); + memset(rbuf, 0, sizeof(rbuf)); + for (i = 0; i < (int)sizeof(wbuf); i++) + wbuf[i] = i; + + roff = woff = 0; + usepersist = 0; + + event_set(&ev, pair[0], EV_WRITE, multiple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_set(&ev2, pair[1], EV_READ, multiple_read_cb, &ev2); + if (event_add(&ev2, NULL) == -1) + exit(1); + event_dispatch(); + + if (roff == woff) + test_ok = memcmp(rbuf, wbuf, sizeof(wbuf)) == 0; + + cleanup_test(); +} + +static void +test_persistent(void) +{ + struct event ev, ev2; + int i; + + /* Multiple read and write test with persist */ + setup_test("Persist read/write: "); + memset(rbuf, 0, sizeof(rbuf)); + for (i = 0; i < (int)sizeof(wbuf); i++) + wbuf[i] = i; + + roff = woff = 0; + usepersist = 1; + + event_set(&ev, pair[0], EV_WRITE|EV_PERSIST, multiple_write_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_set(&ev2, pair[1], EV_READ|EV_PERSIST, multiple_read_cb, &ev2); + if (event_add(&ev2, NULL) == -1) + exit(1); + event_dispatch(); + + if (roff == woff) + test_ok = memcmp(rbuf, wbuf, sizeof(wbuf)) == 0; + + cleanup_test(); +} + +static void +test_combined(void) +{ + struct both r1, r2, w1, w2; + + setup_test("Combined read/write: "); + memset(&r1, 0, sizeof(r1)); + memset(&r2, 0, sizeof(r2)); + memset(&w1, 0, sizeof(w1)); + memset(&w2, 0, sizeof(w2)); + + w1.nread = 4096; + w2.nread = 8192; + + event_set(&r1.ev, pair[0], EV_READ, combined_read_cb, &r1); + event_set(&w1.ev, pair[0], EV_WRITE, combined_write_cb, &w1); + event_set(&r2.ev, pair[1], EV_READ, combined_read_cb, &r2); + event_set(&w2.ev, pair[1], EV_WRITE, combined_write_cb, &w2); + tt_assert(event_add(&r1.ev, NULL) != -1); + tt_assert(!event_add(&w1.ev, NULL)); + tt_assert(!event_add(&r2.ev, NULL)); + tt_assert(!event_add(&w2.ev, NULL)); + event_dispatch(); + + if (r1.nread == 8192 && r2.nread == 4096) + test_ok = 1; + +end: + cleanup_test(); +} + +static void +test_simpletimeout(void) +{ + struct timeval tv; + struct event ev; + + setup_test("Simple timeout: "); + + tv.tv_usec = 200*1000; + tv.tv_sec = 0; + evutil_timerclear(&tcalled); + evtimer_set(&ev, timeout_cb, NULL); + evtimer_add(&ev, &tv); + + evutil_gettimeofday(&tset, NULL); + event_dispatch(); + test_timeval_diff_eq(&tset, &tcalled, 200); + + test_ok = 1; +end: + cleanup_test(); +} + +static void +periodic_timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + int *count = arg; + + (*count)++; + if (*count == 6) { + /* call loopexit only once - on slow machines(?), it is + * apparently possible for this to get called twice. */ + test_ok = 1; + event_base_loopexit(global_base, NULL); + } +} + +static void +test_persistent_timeout(void) +{ + struct timeval tv; + struct event ev; + int count = 0; + + evutil_timerclear(&tv); + tv.tv_usec = 10000; + + event_assign(&ev, global_base, -1, EV_TIMEOUT|EV_PERSIST, + periodic_timeout_cb, &count); + event_add(&ev, &tv); + + event_dispatch(); + + event_del(&ev); +} + +static void +test_persistent_timeout_jump(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event ev; + int count = 0; + struct timeval msec100 = { 0, 100 * 1000 }; + struct timeval msec50 = { 0, 50 * 1000 }; + struct timeval msec300 = { 0, 300 * 1000 }; + + event_assign(&ev, data->base, -1, EV_PERSIST, periodic_timeout_cb, &count); + event_add(&ev, &msec100); + /* Wait for a bit */ + evutil_usleep_(&msec300); + event_base_loopexit(data->base, &msec50); + event_base_dispatch(data->base); + tt_int_op(count, ==, 1); + +end: + event_del(&ev); +} + +struct persist_active_timeout_called { + int n; + short events[16]; + struct timeval tvs[16]; +}; + +static void +activate_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + event_active(ev, EV_READ, 1); +} + +static void +persist_active_timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + struct persist_active_timeout_called *c = arg; + if (c->n < 15) { + c->events[c->n] = event; + evutil_gettimeofday(&c->tvs[c->n], NULL); + ++c->n; + } +} + +static void +test_persistent_active_timeout(void *ptr) +{ + struct timeval tv, tv2, tv_exit, start; + struct event ev; + struct persist_active_timeout_called res; + + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + + memset(&res, 0, sizeof(res)); + + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + event_assign(&ev, base, -1, EV_TIMEOUT|EV_PERSIST, + persist_active_timeout_cb, &res); + event_add(&ev, &tv); + + tv2.tv_sec = 0; + tv2.tv_usec = 100 * 1000; + event_base_once(base, -1, EV_TIMEOUT, activate_cb, &ev, &tv2); + + tv_exit.tv_sec = 0; + tv_exit.tv_usec = 600 * 1000; + event_base_loopexit(base, &tv_exit); + + event_base_assert_ok_(base); + evutil_gettimeofday(&start, NULL); + + event_base_dispatch(base); + event_base_assert_ok_(base); + + tt_int_op(res.n, ==, 3); + tt_int_op(res.events[0], ==, EV_READ); + tt_int_op(res.events[1], ==, EV_TIMEOUT); + tt_int_op(res.events[2], ==, EV_TIMEOUT); + test_timeval_diff_eq(&start, &res.tvs[0], 100); + test_timeval_diff_eq(&start, &res.tvs[1], 300); + test_timeval_diff_eq(&start, &res.tvs[2], 500); +end: + event_del(&ev); +} + +struct common_timeout_info { + struct event ev; + struct timeval called_at; + int which; + int count; +}; + +static void +common_timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + struct common_timeout_info *ti = arg; + ++ti->count; + evutil_gettimeofday(&ti->called_at, NULL); + if (ti->count >= 4) + event_del(&ti->ev); +} + +static void +test_common_timeout(void *ptr) +{ + struct basic_test_data *data = ptr; + + struct event_base *base = data->base; + int i; + struct common_timeout_info info[100]; + + struct timeval start; + struct timeval tmp_100_ms = { 0, 100*1000 }; + struct timeval tmp_200_ms = { 0, 200*1000 }; + struct timeval tmp_5_sec = { 5, 0 }; + struct timeval tmp_5M_usec = { 0, 5*1000*1000 }; + + const struct timeval *ms_100, *ms_200, *sec_5; + + ms_100 = event_base_init_common_timeout(base, &tmp_100_ms); + ms_200 = event_base_init_common_timeout(base, &tmp_200_ms); + sec_5 = event_base_init_common_timeout(base, &tmp_5_sec); + tt_assert(ms_100); + tt_assert(ms_200); + tt_assert(sec_5); + tt_ptr_op(event_base_init_common_timeout(base, &tmp_200_ms), + ==, ms_200); + tt_ptr_op(event_base_init_common_timeout(base, ms_200), ==, ms_200); + tt_ptr_op(event_base_init_common_timeout(base, &tmp_5M_usec), ==, sec_5); + tt_int_op(ms_100->tv_sec, ==, 0); + tt_int_op(ms_200->tv_sec, ==, 0); + tt_int_op(sec_5->tv_sec, ==, 5); + tt_int_op(ms_100->tv_usec, ==, 100000|0x50000000); + tt_int_op(ms_200->tv_usec, ==, 200000|0x50100000); + tt_int_op(sec_5->tv_usec, ==, 0|0x50200000); + + memset(info, 0, sizeof(info)); + + for (i=0; i<100; ++i) { + info[i].which = i; + event_assign(&info[i].ev, base, -1, EV_TIMEOUT|EV_PERSIST, + common_timeout_cb, &info[i]); + if (i % 2) { + if ((i%20)==1) { + /* Glass-box test: Make sure we survive the + * transition to non-common timeouts. It's + * a little tricky. */ + event_add(&info[i].ev, ms_200); + event_add(&info[i].ev, &tmp_100_ms); + } else if ((i%20)==3) { + /* Check heap-to-common too. */ + event_add(&info[i].ev, &tmp_200_ms); + event_add(&info[i].ev, ms_100); + } else if ((i%20)==5) { + /* Also check common-to-common. */ + event_add(&info[i].ev, ms_200); + event_add(&info[i].ev, ms_100); + } else { + event_add(&info[i].ev, ms_100); + } + } else { + event_add(&info[i].ev, ms_200); + } + } + + event_base_assert_ok_(base); + evutil_gettimeofday(&start, NULL); + event_base_dispatch(base); + + event_base_assert_ok_(base); + + for (i=0; i<10; ++i) { + tt_int_op(info[i].count, ==, 4); + if (i % 2) { + test_timeval_diff_eq(&start, &info[i].called_at, 400); + } else { + test_timeval_diff_eq(&start, &info[i].called_at, 800); + } + } + + /* Make sure we can free the base with some events in. */ + for (i=0; i<100; ++i) { + if (i % 2) { + event_add(&info[i].ev, ms_100); + } else { + event_add(&info[i].ev, ms_200); + } + } + +end: + event_base_free(data->base); /* need to do this here before info is + * out-of-scope */ + data->base = NULL; +} + +#ifndef _WIN32 + +#define current_base event_global_current_base_ +extern struct event_base *current_base; + +static void +fork_signal_cb(evutil_socket_t fd, short events, void *arg) +{ + event_del(arg); +} + +int child_pair[2] = { -1, -1 }; +static void +simple_child_read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + if (write(child_pair[0], "", 1) < 0) + tt_fail_perror("write"); + + if (len) { + if (!called) { + if (event_add(arg, NULL) == -1) + exit(1); + } + } else if (called == 1) + test_ok = 1; + + called++; +} + +#define TEST_FORK_EXIT_SUCCESS 76 +static void fork_wait_check(int pid) +{ + int status; + + TT_BLATHER(("Before waitpid")); + +#ifdef WNOWAIT + if ((waitpid(pid, &status, WNOWAIT) == -1 && errno == EINVAL) && +#else + if ( +#endif + waitpid(pid, &status, 0) == -1) { + perror("waitpid"); + exit(1); + } + TT_BLATHER(("After waitpid")); + + if (WEXITSTATUS(status) != TEST_FORK_EXIT_SUCCESS) { + fprintf(stdout, "FAILED (exit): %d\n", WEXITSTATUS(status)); + exit(1); + } +} +static void +test_fork(void) +{ + char c; + struct event ev, sig_ev, usr_ev, existing_ev; + pid_t pid; + + setup_test("After fork: "); + + { + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, child_pair) == -1) { + fprintf(stderr, "%s: socketpair\n", __func__); + exit(1); + } + + if (evutil_make_socket_nonblocking(child_pair[0]) == -1) { + fprintf(stderr, "fcntl(O_NONBLOCK)"); + exit(1); + } + } + + tt_assert(current_base); + evthread_make_base_notifiable(current_base); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + event_set(&ev, pair[1], EV_READ, simple_child_read_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + + evsignal_set(&sig_ev, SIGCHLD, fork_signal_cb, &sig_ev); + evsignal_add(&sig_ev, NULL); + + evsignal_set(&existing_ev, SIGUSR2, fork_signal_cb, &existing_ev); + evsignal_add(&existing_ev, NULL); + + event_base_assert_ok_(current_base); + TT_BLATHER(("Before fork")); + if ((pid = regress_fork()) == 0) { + /* in the child */ + TT_BLATHER(("In child, before reinit")); + event_base_assert_ok_(current_base); + if (event_reinit(current_base) == -1) { + fprintf(stdout, "FAILED (reinit)\n"); + exit(1); + } + TT_BLATHER(("After reinit")); + event_base_assert_ok_(current_base); + TT_BLATHER(("After assert-ok")); + + evsignal_del(&sig_ev); + + evsignal_set(&usr_ev, SIGUSR1, fork_signal_cb, &usr_ev); + evsignal_add(&usr_ev, NULL); + kill(getpid(), SIGUSR1); + kill(getpid(), SIGUSR2); + + called = 0; + + event_dispatch(); + + event_base_free(current_base); + + /* we do not send an EOF; simple_read_cb requires an EOF + * to set test_ok. we just verify that the callback was + * called. */ + exit(test_ok != 0 || called != 2 ? -2 : TEST_FORK_EXIT_SUCCESS); + } + + /** wait until client read first message */ + if (read(child_pair[1], &c, 1) < 0) { + tt_fail_perror("read"); + } + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + fork_wait_check(pid); + + /* test that the current event loop still works */ + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + fprintf(stderr, "%s: write\n", __func__); + } + + shutdown(pair[0], EVUTIL_SHUT_WR); + + evsignal_set(&usr_ev, SIGUSR1, fork_signal_cb, &usr_ev); + evsignal_add(&usr_ev, NULL); + kill(getpid(), SIGUSR1); + kill(getpid(), SIGUSR2); + + event_dispatch(); + + evsignal_del(&sig_ev); + tt_int_op(test_ok, ==, 1); + + end: + cleanup_test(); + if (child_pair[0] != -1) + evutil_closesocket(child_pair[0]); + if (child_pair[1] != -1) + evutil_closesocket(child_pair[1]); +} + +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED +static void* del_wait_thread(void *arg) +{ + struct timeval tv_start, tv_end; + + evutil_gettimeofday(&tv_start, NULL); + event_dispatch(); + evutil_gettimeofday(&tv_end, NULL); + + test_timeval_diff_eq(&tv_start, &tv_end, 300); + + end: + return NULL; +} + +static void +del_wait_cb(evutil_socket_t fd, short event, void *arg) +{ + struct timeval delay = { 0, 300*1000 }; + TT_BLATHER(("Sleeping: %i", test_ok)); + evutil_usleep_(&delay); + ++test_ok; +} + +static void +test_del_wait(void) +{ + struct event ev; + THREAD_T thread; + + setup_test("event_del will wait: "); + + event_set(&ev, pair[1], EV_READ|EV_PERSIST, del_wait_cb, &ev); + event_add(&ev, NULL); + + THREAD_START(thread, del_wait_thread, NULL); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + { + struct timeval delay = { 0, 30*1000 }; + evutil_usleep_(&delay); + } + + { + struct timeval tv_start, tv_end; + evutil_gettimeofday(&tv_start, NULL); + event_del(&ev); + evutil_gettimeofday(&tv_end, NULL); + test_timeval_diff_eq(&tv_start, &tv_end, 270); + } + + THREAD_JOIN(thread); + + tt_int_op(test_ok, ==, 1); + + end: + ; +} + +static void null_cb(evutil_socket_t fd, short what, void *arg) {} +static void* test_del_notify_thread(void *arg) +{ + event_dispatch(); + return NULL; +} +static void +test_del_notify(void) +{ + struct event ev; + THREAD_T thread; + + test_ok = 1; + + event_set(&ev, -1, EV_READ, null_cb, &ev); + event_add(&ev, NULL); + + THREAD_START(thread, test_del_notify_thread, NULL); + + { + struct timeval delay = { 0, 1000 }; + evutil_usleep_(&delay); + } + + event_del(&ev); + THREAD_JOIN(thread); +} +#endif + +static void +signal_cb_sa(int sig) +{ + test_ok = 2; +} + +static void +signal_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + + evsignal_del(ev); + test_ok = 1; +} + +static void +test_simplesignal_impl(int find_reorder) +{ + struct event ev; + struct itimerval itv; + + evsignal_set(&ev, SIGALRM, signal_cb, &ev); + evsignal_add(&ev, NULL); + /* find bugs in which operations are re-ordered */ + if (find_reorder) { + evsignal_del(&ev); + evsignal_add(&ev, NULL); + } + + memset(&itv, 0, sizeof(itv)); + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 100000; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + goto skip_simplesignal; + + event_dispatch(); + skip_simplesignal: + if (evsignal_del(&ev) == -1) + test_ok = 0; + + cleanup_test(); +} + +static void +test_simplestsignal(void) +{ + setup_test("Simplest one signal: "); + test_simplesignal_impl(0); +} + +static void +test_simplesignal(void) +{ + setup_test("Simple signal: "); + test_simplesignal_impl(1); +} + +static void +test_multiplesignal(void) +{ + struct event ev_one, ev_two; + struct itimerval itv; + + setup_test("Multiple signal: "); + + evsignal_set(&ev_one, SIGALRM, signal_cb, &ev_one); + evsignal_add(&ev_one, NULL); + + evsignal_set(&ev_two, SIGALRM, signal_cb, &ev_two); + evsignal_add(&ev_two, NULL); + + memset(&itv, 0, sizeof(itv)); + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 100000; + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + goto skip_simplesignal; + + event_dispatch(); + + skip_simplesignal: + if (evsignal_del(&ev_one) == -1) + test_ok = 0; + if (evsignal_del(&ev_two) == -1) + test_ok = 0; + + cleanup_test(); +} + +static void +test_immediatesignal(void) +{ + struct event ev; + + test_ok = 0; + evsignal_set(&ev, SIGUSR1, signal_cb, &ev); + evsignal_add(&ev, NULL); + kill(getpid(), SIGUSR1); + event_loop(EVLOOP_NONBLOCK); + evsignal_del(&ev); + cleanup_test(); +} + +static void +test_signal_dealloc(void) +{ + /* make sure that evsignal_event is event_del'ed and pipe closed */ + struct event ev; + struct event_base *base = event_init(); + evsignal_set(&ev, SIGUSR1, signal_cb, &ev); + evsignal_add(&ev, NULL); + evsignal_del(&ev); + event_base_free(base); + /* If we got here without asserting, we're fine. */ + test_ok = 1; + cleanup_test(); +} + +static void +test_signal_pipeloss(void) +{ + /* make sure that the base1 pipe is closed correctly. */ + struct event_base *base1, *base2; + int pipe1; + test_ok = 0; + base1 = event_init(); + pipe1 = base1->sig.ev_signal_pair[0]; + base2 = event_init(); + event_base_free(base2); + event_base_free(base1); + if (close(pipe1) != -1 || errno!=EBADF) { + /* fd must be closed, so second close gives -1, EBADF */ + printf("signal pipe not closed. "); + test_ok = 0; + } else { + test_ok = 1; + } + cleanup_test(); +} + +/* + * make two bases to catch signals, use both of them. this only works + * for event mechanisms that use our signal pipe trick. kqueue handles + * signals internally, and all interested kqueues get all the signals. + */ +static void +test_signal_switchbase(void) +{ + struct event ev1, ev2; + struct event_base *base1, *base2; + int is_kqueue; + test_ok = 0; + base1 = event_init(); + base2 = event_init(); + is_kqueue = !strcmp(event_get_method(),"kqueue"); + evsignal_set(&ev1, SIGUSR1, signal_cb, &ev1); + evsignal_set(&ev2, SIGUSR1, signal_cb, &ev2); + if (event_base_set(base1, &ev1) || + event_base_set(base2, &ev2) || + event_add(&ev1, NULL) || + event_add(&ev2, NULL)) { + fprintf(stderr, "%s: cannot set base, add\n", __func__); + exit(1); + } + + tt_ptr_op(event_get_base(&ev1), ==, base1); + tt_ptr_op(event_get_base(&ev2), ==, base2); + + test_ok = 0; + /* can handle signal before loop is called */ + kill(getpid(), SIGUSR1); + event_base_loop(base2, EVLOOP_NONBLOCK); + if (is_kqueue) { + if (!test_ok) + goto end; + test_ok = 0; + } + event_base_loop(base1, EVLOOP_NONBLOCK); + if (test_ok && !is_kqueue) { + test_ok = 0; + + /* set base1 to handle signals */ + event_base_loop(base1, EVLOOP_NONBLOCK); + kill(getpid(), SIGUSR1); + event_base_loop(base1, EVLOOP_NONBLOCK); + event_base_loop(base2, EVLOOP_NONBLOCK); + } +end: + event_base_free(base1); + event_base_free(base2); + cleanup_test(); +} + +/* + * assert that a signal event removed from the event queue really is + * removed - with no possibility of it's parent handler being fired. + */ +static void +test_signal_assert(void) +{ + struct event ev; + struct event_base *base = event_init(); + test_ok = 0; + /* use SIGCONT so we don't kill ourselves when we signal to nowhere */ + evsignal_set(&ev, SIGCONT, signal_cb, &ev); + evsignal_add(&ev, NULL); + /* + * if evsignal_del() fails to reset the handler, it's current handler + * will still point to evsig_handler(). + */ + evsignal_del(&ev); + + kill(getpid(), SIGCONT); +#if 0 + /* only way to verify we were in evsig_handler() */ + /* XXXX Now there's no longer a good way. */ + if (base->sig.evsig_caught) + test_ok = 0; + else + test_ok = 1; +#else + test_ok = 1; +#endif + + event_base_free(base); + cleanup_test(); + return; +} + +/* + * assert that we restore our previous signal handler properly. + */ +static void +test_signal_restore(void) +{ + struct event ev; + struct event_base *base = event_init(); +#ifdef EVENT__HAVE_SIGACTION + struct sigaction sa; +#endif + + test_ok = 0; +#ifdef EVENT__HAVE_SIGACTION + sa.sa_handler = signal_cb_sa; + sa.sa_flags = 0x0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, NULL) == -1) + goto out; +#else + if (signal(SIGUSR1, signal_cb_sa) == SIG_ERR) + goto out; +#endif + evsignal_set(&ev, SIGUSR1, signal_cb, &ev); + evsignal_add(&ev, NULL); + evsignal_del(&ev); + + kill(getpid(), SIGUSR1); + /* 1 == signal_cb, 2 == signal_cb_sa, we want our previous handler */ + if (test_ok != 2) + test_ok = 0; +out: + event_base_free(base); + cleanup_test(); + return; +} + +static void +signal_cb_swp(int sig, short event, void *arg) +{ + called++; + if (called < 5) + kill(getpid(), sig); + else + event_loopexit(NULL); +} +static void +timeout_cb_swp(evutil_socket_t fd, short event, void *arg) +{ + if (called == -1) { + struct timeval tv = {5, 0}; + + called = 0; + evtimer_add((struct event *)arg, &tv); + kill(getpid(), SIGUSR1); + return; + } + test_ok = 0; + event_loopexit(NULL); +} + +static void +test_signal_while_processing(void) +{ + struct event_base *base = event_init(); + struct event ev, ev_timer; + struct timeval tv = {0, 0}; + + setup_test("Receiving a signal while processing other signal: "); + + called = -1; + test_ok = 1; + signal_set(&ev, SIGUSR1, signal_cb_swp, NULL); + signal_add(&ev, NULL); + evtimer_set(&ev_timer, timeout_cb_swp, &ev_timer); + evtimer_add(&ev_timer, &tv); + event_dispatch(); + + event_base_free(base); + cleanup_test(); + return; +} +#endif + +static void +test_free_active_base(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base1; + struct event ev1; + + base1 = event_init(); + tt_assert(base1); + event_assign(&ev1, base1, data->pair[1], EV_READ, dummy_read_cb, NULL); + event_add(&ev1, NULL); + event_base_free(base1); /* should not crash */ + + base1 = event_init(); + tt_assert(base1); + event_assign(&ev1, base1, data->pair[0], 0, dummy_read_cb, NULL); + event_active(&ev1, EV_READ, 1); + event_base_free(base1); +end: + ; +} + +static void +test_manipulate_active_events(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev1; + + event_assign(&ev1, base, -1, EV_TIMEOUT, dummy_read_cb, NULL); + + /* Make sure an active event is pending. */ + event_active(&ev1, EV_READ, 1); + tt_int_op(event_pending(&ev1, EV_READ|EV_TIMEOUT|EV_WRITE, NULL), + ==, EV_READ); + + /* Make sure that activating an event twice works. */ + event_active(&ev1, EV_WRITE, 1); + tt_int_op(event_pending(&ev1, EV_READ|EV_TIMEOUT|EV_WRITE, NULL), + ==, EV_READ|EV_WRITE); + +end: + event_del(&ev1); +} + +static void +event_selfarg_cb(evutil_socket_t fd, short event, void *arg) +{ + struct event *ev = arg; + struct event_base *base = event_get_base(ev); + event_base_assert_ok_(base); + event_base_loopexit(base, NULL); + tt_want(ev == event_base_get_running_event(base)); +} + +static void +test_event_new_selfarg(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event *ev = event_new(base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + + event_active(ev, EV_READ, 1); + event_base_dispatch(base); + + event_free(ev); +} + +static void +test_event_assign_selfarg(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + + event_assign(&ev, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + event_active(&ev, EV_READ, 1); + event_base_dispatch(base); +} + +static void +test_event_base_get_num_events(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + int event_count_active; + int event_count_virtual; + int event_count_added; + int event_count_active_virtual; + int event_count_active_added; + int event_count_virtual_added; + int event_count_active_added_virtual; + + struct timeval qsec = {0, 100000}; + + event_assign(&ev, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + + event_add(&ev, &qsec); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + /* libevent itself adds a timeout event, so the event_count is 2 here */ + tt_int_op(event_count_added, ==, 2); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 2); + tt_int_op(event_count_virtual_added, ==, 2); + tt_int_op(event_count_active_added_virtual, ==, 2); + + event_active(&ev, EV_READ, 1); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 1); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 3); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 4); + tt_int_op(event_count_virtual_added, ==, 3); + tt_int_op(event_count_active_added_virtual, ==, 4); + + event_base_loop(base, 0); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 0); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 0); + tt_int_op(event_count_active_added_virtual, ==, 0); + + event_base_add_virtual_(base); + event_count_active = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE); + event_count_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL); + event_count_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ADDED); + event_count_active_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_VIRTUAL); + event_count_active_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE|EVENT_BASE_COUNT_ADDED); + event_count_virtual_added = event_base_get_num_events(base, + EVENT_BASE_COUNT_VIRTUAL|EVENT_BASE_COUNT_ADDED); + event_count_active_added_virtual = event_base_get_num_events(base, + EVENT_BASE_COUNT_ACTIVE| + EVENT_BASE_COUNT_ADDED| + EVENT_BASE_COUNT_VIRTUAL); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 1); + tt_int_op(event_count_added, ==, 0); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 1); + tt_int_op(event_count_active_added_virtual, ==, 1); + +end: + ; +} + +static void +test_event_base_get_max_events(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + struct event ev2; + int event_count_active; + int event_count_virtual; + int event_count_added; + int event_count_active_virtual; + int event_count_active_added; + int event_count_virtual_added; + int event_count_active_added_virtual; + + struct timeval qsec = {0, 100000}; + + event_assign(&ev, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + event_assign(&ev2, base, -1, EV_READ, event_selfarg_cb, + event_self_cbarg()); + + event_add(&ev, &qsec); + event_add(&ev2, &qsec); + event_del(&ev2); + + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 0); + + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + /* libevent itself adds a timeout event, so the event_count is 4 here */ + tt_int_op(event_count_added, ==, 4); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 4); + tt_int_op(event_count_virtual_added, ==, 4); + tt_int_op(event_count_active_added_virtual, ==, 4); + + event_active(&ev, EV_READ, 1); + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 0); + + tt_int_op(event_count_active, ==, 1); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 4); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 5); + tt_int_op(event_count_virtual_added, ==, 4); + tt_int_op(event_count_active_added_virtual, ==, 5); + + event_base_loop(base, 0); + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 1); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 1); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 1); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 1); + + tt_int_op(event_count_active, ==, 1); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 4); + tt_int_op(event_count_active_virtual, ==, 0); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 0); + tt_int_op(event_count_active_added_virtual, ==, 0); + + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 0); + tt_int_op(event_count_added, ==, 0); + + event_base_add_virtual_(base); + event_count_active = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE, 0); + event_count_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ADDED, 0); + event_count_active_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_VIRTUAL, 0); + event_count_active_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | EVENT_BASE_COUNT_ADDED, 0); + event_count_virtual_added = event_base_get_max_events(base, + EVENT_BASE_COUNT_VIRTUAL | EVENT_BASE_COUNT_ADDED, 0); + event_count_active_added_virtual = event_base_get_max_events(base, + EVENT_BASE_COUNT_ACTIVE | + EVENT_BASE_COUNT_ADDED | + EVENT_BASE_COUNT_VIRTUAL, 0); + + tt_int_op(event_count_active, ==, 0); + tt_int_op(event_count_virtual, ==, 1); + tt_int_op(event_count_added, ==, 0); + tt_int_op(event_count_active_virtual, ==, 1); + tt_int_op(event_count_active_added, ==, 0); + tt_int_op(event_count_virtual_added, ==, 1); + tt_int_op(event_count_active_added_virtual, ==, 1); + +end: + ; +} + +static void +test_bad_assign(void *ptr) +{ + struct event ev; + int r; + /* READ|SIGNAL is not allowed */ + r = event_assign(&ev, NULL, -1, EV_SIGNAL|EV_READ, dummy_read_cb, NULL); + tt_int_op(r,==,-1); + +end: + ; +} + +static int reentrant_cb_run = 0; + +static void +bad_reentrant_run_loop_cb(evutil_socket_t fd, short what, void *ptr) +{ + struct event_base *base = ptr; + int r; + reentrant_cb_run = 1; + /* This reentrant call to event_base_loop should be detected and + * should fail */ + r = event_base_loop(base, 0); + tt_int_op(r, ==, -1); +end: + ; +} + +static void +test_bad_reentrant(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event ev; + int r; + event_assign(&ev, base, -1, + 0, bad_reentrant_run_loop_cb, base); + + event_active(&ev, EV_WRITE, 1); + r = event_base_loop(base, 0); + tt_int_op(r, ==, 1); + tt_int_op(reentrant_cb_run, ==, 1); +end: + ; +} + +static int n_write_a_byte_cb=0; +static int n_read_and_drain_cb=0; +static int n_activate_other_event_cb=0; +static void +write_a_byte_cb(evutil_socket_t fd, short what, void *arg) +{ + char buf[] = "x"; + if (write(fd, buf, 1) == 1) + ++n_write_a_byte_cb; +} +static void +read_and_drain_cb(evutil_socket_t fd, short what, void *arg) +{ + char buf[128]; + int n; + ++n_read_and_drain_cb; + while ((n = read(fd, buf, sizeof(buf))) > 0) + ; +} + +static void +activate_other_event_cb(evutil_socket_t fd, short what, void *other_) +{ + struct event *ev_activate = other_; + ++n_activate_other_event_cb; + event_active_later_(ev_activate, EV_READ); +} + +static void +test_active_later(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event *ev1 = NULL, *ev2 = NULL; + struct event ev3, ev4; + struct timeval qsec = {0, 100000}; + ev1 = event_new(data->base, data->pair[0], EV_READ|EV_PERSIST, read_and_drain_cb, NULL); + ev2 = event_new(data->base, data->pair[1], EV_WRITE|EV_PERSIST, write_a_byte_cb, NULL); + event_assign(&ev3, data->base, -1, 0, activate_other_event_cb, &ev4); + event_assign(&ev4, data->base, -1, 0, activate_other_event_cb, &ev3); + event_add(ev1, NULL); + event_add(ev2, NULL); + event_active_later_(&ev3, EV_READ); + + event_base_loopexit(data->base, &qsec); + + event_base_loop(data->base, 0); + + TT_BLATHER(("%d write calls, %d read calls, %d activate-other calls.", + n_write_a_byte_cb, n_read_and_drain_cb, n_activate_other_event_cb)); + event_del(&ev3); + event_del(&ev4); + + tt_int_op(n_write_a_byte_cb, ==, n_activate_other_event_cb); + tt_int_op(n_write_a_byte_cb, >, 100); + tt_int_op(n_read_and_drain_cb, >, 100); + tt_int_op(n_activate_other_event_cb, >, 100); + + event_active_later_(&ev4, EV_READ); + event_active(&ev4, EV_READ, 1); /* This should make the event + active immediately. */ + tt_assert((ev4.ev_flags & EVLIST_ACTIVE) != 0); + tt_assert((ev4.ev_flags & EVLIST_ACTIVE_LATER) == 0); + + /* Now leave this one around, so that event_free sees it and removes + * it. */ + event_active_later_(&ev3, EV_READ); + event_base_assert_ok_(data->base); + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + + event_base_free(data->base); + data->base = NULL; +} + + +static void incr_arg_cb(evutil_socket_t fd, short what, void *arg) +{ + int *intptr = arg; + (void) fd; (void) what; + ++*intptr; +} +static void remove_timers_cb(evutil_socket_t fd, short what, void *arg) +{ + struct event **ep = arg; + (void) fd; (void) what; + event_remove_timer(ep[0]); + event_remove_timer(ep[1]); +} +static void send_a_byte_cb(evutil_socket_t fd, short what, void *arg) +{ + evutil_socket_t *sockp = arg; + (void) fd; (void) what; + if (write(*sockp, "A", 1) < 0) + tt_fail_perror("write"); +} +struct read_not_timeout_param +{ + struct event **ev; + int events; + int count; +}; +static void read_not_timeout_cb(evutil_socket_t fd, short what, void *arg) +{ + struct read_not_timeout_param *rntp = arg; + char c; + ev_ssize_t n; + (void) fd; (void) what; + n = read(fd, &c, 1); + tt_int_op(n, ==, 1); + rntp->events |= what; + ++rntp->count; + if(2 == rntp->count) event_del(rntp->ev[0]); +end: + ; +} + +static void +test_event_remove_timeout(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = data->base; + struct event *ev[5]; + int ev1_fired=0; + struct timeval ms25 = { 0, 25*1000 }, + ms40 = { 0, 40*1000 }, + ms75 = { 0, 75*1000 }, + ms125 = { 0, 125*1000 }; + struct read_not_timeout_param rntp = { ev, 0, 0 }; + + event_base_assert_ok_(base); + + ev[0] = event_new(base, data->pair[0], EV_READ|EV_PERSIST, + read_not_timeout_cb, &rntp); + ev[1] = evtimer_new(base, incr_arg_cb, &ev1_fired); + ev[2] = evtimer_new(base, remove_timers_cb, ev); + ev[3] = evtimer_new(base, send_a_byte_cb, &data->pair[1]); + ev[4] = evtimer_new(base, send_a_byte_cb, &data->pair[1]); + tt_assert(base); + event_add(ev[2], &ms25); /* remove timers */ + event_add(ev[4], &ms40); /* write to test if timer re-activates */ + event_add(ev[0], &ms75); /* read */ + event_add(ev[1], &ms75); /* timer */ + event_add(ev[3], &ms125); /* timeout. */ + event_base_assert_ok_(base); + + event_base_dispatch(base); + + tt_int_op(ev1_fired, ==, 0); + tt_int_op(rntp.events, ==, EV_READ); + + event_base_assert_ok_(base); +end: + event_free(ev[0]); + event_free(ev[1]); + event_free(ev[2]); + event_free(ev[3]); + event_free(ev[4]); +} + +static void +test_event_base_new(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_base *base = 0; + struct event ev1; + struct basic_cb_args args; + + int towrite = (int)strlen(TEST1)+1; + int len = write(data->pair[0], TEST1, towrite); + + if (len < 0) + tt_abort_perror("initial write"); + else if (len != towrite) + tt_abort_printf(("initial write fell short (%d of %d bytes)", + len, towrite)); + + if (shutdown(data->pair[0], EVUTIL_SHUT_WR)) + tt_abort_perror("initial write shutdown"); + + base = event_base_new(); + if (!base) + tt_abort_msg("failed to create event base"); + + args.eb = base; + args.ev = &ev1; + args.callcount = 0; + event_assign(&ev1, base, data->pair[1], + EV_READ|EV_PERSIST, basic_read_cb, &args); + + if (event_add(&ev1, NULL)) + tt_abort_perror("initial event_add"); + + if (event_base_loop(base, 0)) + tt_abort_msg("unsuccessful exit from event loop"); + +end: + if (base) + event_base_free(base); +} + +static void +test_loopexit(void) +{ + struct timeval tv, tv_start, tv_end; + struct event ev; + + setup_test("Loop exit: "); + + tv.tv_usec = 0; + tv.tv_sec = 60*60*24; + evtimer_set(&ev, timeout_cb, NULL); + evtimer_add(&ev, &tv); + + tv.tv_usec = 300*1000; + tv.tv_sec = 0; + event_loopexit(&tv); + + evutil_gettimeofday(&tv_start, NULL); + event_dispatch(); + evutil_gettimeofday(&tv_end, NULL); + + evtimer_del(&ev); + + tt_assert(event_base_got_exit(global_base)); + tt_assert(!event_base_got_break(global_base)); + + test_timeval_diff_eq(&tv_start, &tv_end, 300); + + test_ok = 1; +end: + cleanup_test(); +} + +static void +test_loopexit_multiple(void) +{ + struct timeval tv, tv_start, tv_end; + struct event_base *base; + + setup_test("Loop Multiple exit: "); + + base = event_base_new(); + + tv.tv_usec = 200*1000; + tv.tv_sec = 0; + event_base_loopexit(base, &tv); + + tv.tv_usec = 0; + tv.tv_sec = 3; + event_base_loopexit(base, &tv); + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(base); + evutil_gettimeofday(&tv_end, NULL); + + tt_assert(event_base_got_exit(base)); + tt_assert(!event_base_got_break(base)); + + event_base_free(base); + + test_timeval_diff_eq(&tv_start, &tv_end, 200); + + test_ok = 1; + +end: + cleanup_test(); +} + +static void +break_cb(evutil_socket_t fd, short events, void *arg) +{ + test_ok = 1; + event_loopbreak(); +} + +static void +fail_cb(evutil_socket_t fd, short events, void *arg) +{ + test_ok = 0; +} + +static void +test_loopbreak(void) +{ + struct event ev1, ev2; + struct timeval tv; + + setup_test("Loop break: "); + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_set(&ev1, break_cb, NULL); + evtimer_add(&ev1, &tv); + evtimer_set(&ev2, fail_cb, NULL); + evtimer_add(&ev2, &tv); + + event_dispatch(); + + tt_assert(!event_base_got_exit(global_base)); + tt_assert(event_base_got_break(global_base)); + + evtimer_del(&ev1); + evtimer_del(&ev2); + +end: + cleanup_test(); +} + +static struct event *readd_test_event_last_added = NULL; +static void +re_add_read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + struct event *ev_other = arg; + ev_ssize_t n_read; + + readd_test_event_last_added = ev_other; + + n_read = read(fd, buf, sizeof(buf)); + + if (n_read < 0) { + tt_fail_perror("read"); + event_base_loopbreak(event_get_base(ev_other)); + } else { + event_add(ev_other, NULL); + ++test_ok; + } +} +static void +test_nonpersist_readd(void *_data) +{ + struct event ev1, ev2; + struct basic_test_data *data = _data; + + memset(&ev1, 0, sizeof(ev1)); + memset(&ev2, 0, sizeof(ev2)); + + tt_assert(!event_assign(&ev1, data->base, data->pair[0], EV_READ, re_add_read_cb, &ev2)); + tt_assert(!event_assign(&ev2, data->base, data->pair[1], EV_READ, re_add_read_cb, &ev1)); + + tt_int_op(write(data->pair[0], "Hello", 5), ==, 5); + tt_int_op(write(data->pair[1], "Hello", 5), ==, 5); + + tt_int_op(event_add(&ev1, NULL), ==, 0); + tt_int_op(event_add(&ev2, NULL), ==, 0); + tt_int_op(event_base_loop(data->base, EVLOOP_ONCE), ==, 0); + tt_int_op(test_ok, ==, 2); + + /* At this point, we executed both callbacks. Whichever one got + * called first added the second, but the second then immediately got + * deleted before its callback was called. At this point, though, it + * re-added the first. + */ + tt_assert(readd_test_event_last_added); + if (readd_test_event_last_added == &ev1) { + tt_assert(event_pending(&ev1, EV_READ, NULL) && !event_pending(&ev2, EV_READ, NULL)); + } else { + tt_assert(event_pending(&ev2, EV_READ, NULL) && !event_pending(&ev1, EV_READ, NULL)); + } + +end: + if (event_initialized(&ev1)) + event_del(&ev1); + if (event_initialized(&ev2)) + event_del(&ev2); +} + +struct test_pri_event { + struct event ev; + int count; +}; + +static void +test_priorities_cb(evutil_socket_t fd, short what, void *arg) +{ + struct test_pri_event *pri = arg; + struct timeval tv; + + if (pri->count == 3) { + event_loopexit(NULL); + return; + } + + pri->count++; + + evutil_timerclear(&tv); + event_add(&pri->ev, &tv); +} + +static void +test_priorities_impl(int npriorities) +{ + struct test_pri_event one, two; + struct timeval tv; + + TT_BLATHER(("Testing Priorities %d: ", npriorities)); + + event_base_priority_init(global_base, npriorities); + + memset(&one, 0, sizeof(one)); + memset(&two, 0, sizeof(two)); + + timeout_set(&one.ev, test_priorities_cb, &one); + if (event_priority_set(&one.ev, 0) == -1) { + fprintf(stderr, "%s: failed to set priority", __func__); + exit(1); + } + + timeout_set(&two.ev, test_priorities_cb, &two); + if (event_priority_set(&two.ev, npriorities - 1) == -1) { + fprintf(stderr, "%s: failed to set priority", __func__); + exit(1); + } + + evutil_timerclear(&tv); + + if (event_add(&one.ev, &tv) == -1) + exit(1); + if (event_add(&two.ev, &tv) == -1) + exit(1); + + event_dispatch(); + + event_del(&one.ev); + event_del(&two.ev); + + if (npriorities == 1) { + if (one.count == 3 && two.count == 3) + test_ok = 1; + } else if (npriorities == 2) { + /* Two is called once because event_loopexit is priority 1 */ + if (one.count == 3 && two.count == 1) + test_ok = 1; + } else { + if (one.count == 3 && two.count == 0) + test_ok = 1; + } +} + +static void +test_priorities(void) +{ + test_priorities_impl(1); + if (test_ok) + test_priorities_impl(2); + if (test_ok) + test_priorities_impl(3); +} + +/* priority-active-inversion: activate a higher-priority event, and make sure + * it keeps us from running a lower-priority event first. */ +static int n_pai_calls = 0; +static struct event pai_events[3]; + +static void +prio_active_inversion_cb(evutil_socket_t fd, short what, void *arg) +{ + int *call_order = arg; + *call_order = n_pai_calls++; + if (n_pai_calls == 1) { + /* This should activate later, even though it shares a + priority with us. */ + event_active(&pai_events[1], EV_READ, 1); + /* This should activate next, since its priority is higher, + even though we activated it second. */ + event_active(&pai_events[2], EV_TIMEOUT, 1); + } +} + +static void +test_priority_active_inversion(void *data_) +{ + struct basic_test_data *data = data_; + struct event_base *base = data->base; + int call_order[3]; + int i; + tt_int_op(event_base_priority_init(base, 8), ==, 0); + + n_pai_calls = 0; + memset(call_order, 0, sizeof(call_order)); + + for (i=0;i<3;++i) { + event_assign(&pai_events[i], data->base, -1, 0, + prio_active_inversion_cb, &call_order[i]); + } + + event_priority_set(&pai_events[0], 4); + event_priority_set(&pai_events[1], 4); + event_priority_set(&pai_events[2], 0); + + event_active(&pai_events[0], EV_WRITE, 1); + + event_base_dispatch(base); + tt_int_op(n_pai_calls, ==, 3); + tt_int_op(call_order[0], ==, 0); + tt_int_op(call_order[1], ==, 2); + tt_int_op(call_order[2], ==, 1); +end: + ; +} + + +static void +test_multiple_cb(evutil_socket_t fd, short event, void *arg) +{ + if (event & EV_READ) + test_ok |= 1; + else if (event & EV_WRITE) + test_ok |= 2; +} + +static void +test_multiple_events_for_same_fd(void) +{ + struct event e1, e2; + + setup_test("Multiple events for same fd: "); + + event_set(&e1, pair[0], EV_READ, test_multiple_cb, NULL); + event_add(&e1, NULL); + event_set(&e2, pair[0], EV_WRITE, test_multiple_cb, NULL); + event_add(&e2, NULL); + event_loop(EVLOOP_ONCE); + event_del(&e2); + + if (write(pair[1], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + event_loop(EVLOOP_ONCE); + event_del(&e1); + + if (test_ok != 3) + test_ok = 0; + + cleanup_test(); +} + +int evtag_decode_int(ev_uint32_t *pnumber, struct evbuffer *evbuf); +int evtag_decode_int64(ev_uint64_t *pnumber, struct evbuffer *evbuf); +int evtag_encode_tag(struct evbuffer *evbuf, ev_uint32_t number); +int evtag_decode_tag(ev_uint32_t *pnumber, struct evbuffer *evbuf); + +static void +read_once_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + + if (called) { + test_ok = 0; + } else if (len) { + /* Assumes global pair[0] can be used for writing */ + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + test_ok = 0; + } else { + test_ok = 1; + } + } + + called++; +} + +static void +test_want_only_once(void) +{ + struct event ev; + struct timeval tv; + + /* Very simple read test */ + setup_test("Want read only once: "); + + if (write(pair[0], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + /* Setup the loop termination */ + evutil_timerclear(&tv); + tv.tv_usec = 300*1000; + event_loopexit(&tv); + + event_set(&ev, pair[1], EV_READ, read_once_cb, &ev); + if (event_add(&ev, NULL) == -1) + exit(1); + event_dispatch(); + + cleanup_test(); +} + +#define TEST_MAX_INT 6 + +static void +evtag_int_test(void *ptr) +{ + struct evbuffer *tmp = evbuffer_new(); + ev_uint32_t integers[TEST_MAX_INT] = { + 0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000 + }; + ev_uint32_t integer; + ev_uint64_t big_int; + int i; + + evtag_init(); + + for (i = 0; i < TEST_MAX_INT; i++) { + int oldlen, newlen; + oldlen = (int)EVBUFFER_LENGTH(tmp); + evtag_encode_int(tmp, integers[i]); + newlen = (int)EVBUFFER_LENGTH(tmp); + TT_BLATHER(("encoded 0x%08x with %d bytes", + (unsigned)integers[i], newlen - oldlen)); + big_int = integers[i]; + big_int *= 1000000000; /* 1 billion */ + evtag_encode_int64(tmp, big_int); + } + + for (i = 0; i < TEST_MAX_INT; i++) { + tt_int_op(evtag_decode_int(&integer, tmp), !=, -1); + tt_uint_op(integer, ==, integers[i]); + tt_int_op(evtag_decode_int64(&big_int, tmp), !=, -1); + tt_assert((big_int / 1000000000) == integers[i]); + } + + tt_uint_op(EVBUFFER_LENGTH(tmp), ==, 0); +end: + evbuffer_free(tmp); +} + +static void +evtag_fuzz(void *ptr) +{ + unsigned char buffer[4096]; + struct evbuffer *tmp = evbuffer_new(); + struct timeval tv; + int i, j; + + int not_failed = 0; + + evtag_init(); + + for (j = 0; j < 100; j++) { + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = test_weakrand(); + evbuffer_drain(tmp, -1); + evbuffer_add(tmp, buffer, sizeof(buffer)); + + if (evtag_unmarshal_timeval(tmp, 0, &tv) != -1) + not_failed++; + } + + /* The majority of decodes should fail */ + tt_int_op(not_failed, <, 10); + + /* Now insert some corruption into the tag length field */ + evbuffer_drain(tmp, -1); + evutil_timerclear(&tv); + tv.tv_sec = 1; + evtag_marshal_timeval(tmp, 0, &tv); + evbuffer_add(tmp, buffer, sizeof(buffer)); + + ((char *)EVBUFFER_DATA(tmp))[1] = '\xff'; + if (evtag_unmarshal_timeval(tmp, 0, &tv) != -1) { + tt_abort_msg("evtag_unmarshal_timeval should have failed"); + } + +end: + evbuffer_free(tmp); +} + +static void +evtag_tag_encoding(void *ptr) +{ + struct evbuffer *tmp = evbuffer_new(); + ev_uint32_t integers[TEST_MAX_INT] = { + 0xaf0, 0x1000, 0x1, 0xdeadbeef, 0x00, 0xbef000 + }; + ev_uint32_t integer; + int i; + + evtag_init(); + + for (i = 0; i < TEST_MAX_INT; i++) { + int oldlen, newlen; + oldlen = (int)EVBUFFER_LENGTH(tmp); + evtag_encode_tag(tmp, integers[i]); + newlen = (int)EVBUFFER_LENGTH(tmp); + TT_BLATHER(("encoded 0x%08x with %d bytes", + (unsigned)integers[i], newlen - oldlen)); + } + + for (i = 0; i < TEST_MAX_INT; i++) { + tt_int_op(evtag_decode_tag(&integer, tmp), !=, -1); + tt_uint_op(integer, ==, integers[i]); + } + + tt_uint_op(EVBUFFER_LENGTH(tmp), ==, 0); + +end: + evbuffer_free(tmp); +} + +static void +evtag_test_peek(void *ptr) +{ + struct evbuffer *tmp = evbuffer_new(); + ev_uint32_t u32; + + evtag_marshal_int(tmp, 30, 0); + evtag_marshal_string(tmp, 40, "Hello world"); + + tt_int_op(evtag_peek(tmp, &u32), ==, 1); + tt_int_op(u32, ==, 30); + tt_int_op(evtag_peek_length(tmp, &u32), ==, 0); + tt_int_op(u32, ==, 1+1+1); + tt_int_op(evtag_consume(tmp), ==, 0); + + tt_int_op(evtag_peek(tmp, &u32), ==, 1); + tt_int_op(u32, ==, 40); + tt_int_op(evtag_peek_length(tmp, &u32), ==, 0); + tt_int_op(u32, ==, 1+1+11); + tt_int_op(evtag_payload_length(tmp, &u32), ==, 0); + tt_int_op(u32, ==, 11); + +end: + evbuffer_free(tmp); +} + + +static void +test_methods(void *ptr) +{ + const char **methods = event_get_supported_methods(); + struct event_config *cfg = NULL; + struct event_base *base = NULL; + const char *backend; + int n_methods = 0; + + tt_assert(methods); + + backend = methods[0]; + while (*methods != NULL) { + TT_BLATHER(("Support method: %s", *methods)); + ++methods; + ++n_methods; + } + + cfg = event_config_new(); + assert(cfg != NULL); + + tt_int_op(event_config_avoid_method(cfg, backend), ==, 0); + event_config_set_flag(cfg, EVENT_BASE_FLAG_IGNORE_ENV); + + base = event_base_new_with_config(cfg); + if (n_methods > 1) { + tt_assert(base); + tt_str_op(backend, !=, event_base_get_method(base)); + } else { + tt_assert(base == NULL); + } + +end: + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static void +test_version(void *arg) +{ + const char *vstr; + ev_uint32_t vint; + int major, minor, patch, n; + + vstr = event_get_version(); + vint = event_get_version_number(); + + tt_assert(vstr); + tt_assert(vint); + + tt_str_op(vstr, ==, LIBEVENT_VERSION); + tt_int_op(vint, ==, LIBEVENT_VERSION_NUMBER); + + n = sscanf(vstr, "%d.%d.%d", &major, &minor, &patch); + tt_assert(3 == n); + tt_int_op((vint&0xffffff00), ==, ((major<<24)|(minor<<16)|(patch<<8))); +end: + ; +} + +static void +test_base_features(void *arg) +{ + struct event_base *base = NULL; + struct event_config *cfg = NULL; + + cfg = event_config_new(); + + tt_assert(0 == event_config_require_features(cfg, EV_FEATURE_ET)); + + base = event_base_new_with_config(cfg); + if (base) { + tt_int_op(EV_FEATURE_ET, ==, + event_base_get_features(base) & EV_FEATURE_ET); + } else { + base = event_base_new(); + tt_int_op(0, ==, event_base_get_features(base) & EV_FEATURE_ET); + } + +end: + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +#ifdef EVENT__HAVE_SETENV +#define SETENV_OK +#elif !defined(EVENT__HAVE_SETENV) && defined(EVENT__HAVE_PUTENV) +static void setenv(const char *k, const char *v, int o_) +{ + char b[256]; + evutil_snprintf(b, sizeof(b), "%s=%s",k,v); + putenv(b); +} +#define SETENV_OK +#endif + +#ifdef EVENT__HAVE_UNSETENV +#define UNSETENV_OK +#elif !defined(EVENT__HAVE_UNSETENV) && defined(EVENT__HAVE_PUTENV) +static void unsetenv(const char *k) +{ + char b[256]; + evutil_snprintf(b, sizeof(b), "%s=",k); + putenv(b); +} +#define UNSETENV_OK +#endif + +#if defined(SETENV_OK) && defined(UNSETENV_OK) +static void +methodname_to_envvar(const char *mname, char *buf, size_t buflen) +{ + char *cp; + evutil_snprintf(buf, buflen, "EVENT_NO%s", mname); + for (cp = buf; *cp; ++cp) { + *cp = EVUTIL_TOUPPER_(*cp); + } +} +#endif + +static void +test_base_environ(void *arg) +{ + struct event_base *base = NULL; + struct event_config *cfg = NULL; + +#if defined(SETENV_OK) && defined(UNSETENV_OK) + const char **basenames; + int i, n_methods=0; + char varbuf[128]; + const char *defaultname, *ignoreenvname; + + /* See if unsetenv works before we rely on it. */ + setenv("EVENT_NOWAFFLES", "1", 1); + unsetenv("EVENT_NOWAFFLES"); + if (getenv("EVENT_NOWAFFLES") != NULL) { +#ifndef EVENT__HAVE_UNSETENV + TT_DECLARE("NOTE", ("Can't fake unsetenv; skipping test")); +#else + TT_DECLARE("NOTE", ("unsetenv doesn't work; skipping test")); +#endif + tt_skip(); + } + + basenames = event_get_supported_methods(); + for (i = 0; basenames[i]; ++i) { + methodname_to_envvar(basenames[i], varbuf, sizeof(varbuf)); + unsetenv(varbuf); + ++n_methods; + } + + base = event_base_new(); + tt_assert(base); + + defaultname = event_base_get_method(base); + TT_BLATHER(("default is <%s>", defaultname)); + event_base_free(base); + base = NULL; + + /* Can we disable the method with EVENT_NOfoo ? */ + if (!strcmp(defaultname, "epoll (with changelist)")) { + setenv("EVENT_NOEPOLL", "1", 1); + ignoreenvname = "epoll"; + } else { + methodname_to_envvar(defaultname, varbuf, sizeof(varbuf)); + setenv(varbuf, "1", 1); + ignoreenvname = defaultname; + } + + /* Use an empty cfg rather than NULL so a failure doesn't exit() */ + cfg = event_config_new(); + base = event_base_new_with_config(cfg); + event_config_free(cfg); + cfg = NULL; + if (n_methods == 1) { + tt_assert(!base); + } else { + tt_assert(base); + tt_str_op(defaultname, !=, event_base_get_method(base)); + event_base_free(base); + base = NULL; + } + + /* Can we disable looking at the environment with IGNORE_ENV ? */ + cfg = event_config_new(); + event_config_set_flag(cfg, EVENT_BASE_FLAG_IGNORE_ENV); + base = event_base_new_with_config(cfg); + tt_assert(base); + tt_str_op(ignoreenvname, ==, event_base_get_method(base)); +#else + tt_skip(); +#endif + +end: + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static void +read_called_once_cb(evutil_socket_t fd, short event, void *arg) +{ + tt_int_op(event, ==, EV_READ); + called += 1; +end: + ; +} + +static void +timeout_called_once_cb(evutil_socket_t fd, short event, void *arg) +{ + tt_int_op(event, ==, EV_TIMEOUT); + called += 100; +end: + ; +} + +static void +immediate_called_twice_cb(evutil_socket_t fd, short event, void *arg) +{ + tt_int_op(event, ==, EV_TIMEOUT); + called += 1000; +end: + ; +} + +static void +test_event_once(void *ptr) +{ + struct basic_test_data *data = ptr; + struct timeval tv; + int r; + + tv.tv_sec = 0; + tv.tv_usec = 50*1000; + called = 0; + r = event_base_once(data->base, data->pair[0], EV_READ, + read_called_once_cb, NULL, NULL); + tt_int_op(r, ==, 0); + r = event_base_once(data->base, -1, EV_TIMEOUT, + timeout_called_once_cb, NULL, &tv); + tt_int_op(r, ==, 0); + r = event_base_once(data->base, -1, 0, NULL, NULL, NULL); + tt_int_op(r, <, 0); + r = event_base_once(data->base, -1, EV_TIMEOUT, + immediate_called_twice_cb, NULL, NULL); + tt_int_op(r, ==, 0); + tv.tv_sec = 0; + tv.tv_usec = 0; + r = event_base_once(data->base, -1, EV_TIMEOUT, + immediate_called_twice_cb, NULL, &tv); + tt_int_op(r, ==, 0); + + if (write(data->pair[1], TEST1, strlen(TEST1)+1) < 0) { + tt_fail_perror("write"); + } + + shutdown(data->pair[1], EVUTIL_SHUT_WR); + + event_base_dispatch(data->base); + + tt_int_op(called, ==, 2101); +end: + ; +} + +static void +test_event_once_never(void *ptr) +{ + struct basic_test_data *data = ptr; + struct timeval tv; + + /* Have one trigger in 10 seconds (don't worry, because) */ + tv.tv_sec = 10; + tv.tv_usec = 0; + called = 0; + event_base_once(data->base, -1, EV_TIMEOUT, + timeout_called_once_cb, NULL, &tv); + + /* But shut down the base in 75 msec. */ + tv.tv_sec = 0; + tv.tv_usec = 75*1000; + event_base_loopexit(data->base, &tv); + + event_base_dispatch(data->base); + + tt_int_op(called, ==, 0); +end: + ; +} + +static void +test_event_pending(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event *r=NULL, *w=NULL, *t=NULL; + struct timeval tv, now, tv2; + + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + r = event_new(data->base, data->pair[0], EV_READ, simple_read_cb, + NULL); + w = event_new(data->base, data->pair[1], EV_WRITE, simple_write_cb, + NULL); + t = evtimer_new(data->base, timeout_cb, NULL); + + tt_assert(r); + tt_assert(w); + tt_assert(t); + + evutil_gettimeofday(&now, NULL); + event_add(r, NULL); + event_add(t, &tv); + + tt_assert( event_pending(r, EV_READ, NULL)); + tt_assert(!event_pending(w, EV_WRITE, NULL)); + tt_assert(!event_pending(r, EV_WRITE, NULL)); + tt_assert( event_pending(r, EV_READ|EV_WRITE, NULL)); + tt_assert(!event_pending(r, EV_TIMEOUT, NULL)); + tt_assert( event_pending(t, EV_TIMEOUT, NULL)); + tt_assert( event_pending(t, EV_TIMEOUT, &tv2)); + + tt_assert(evutil_timercmp(&tv2, &now, >)); + + test_timeval_diff_eq(&now, &tv2, 500); + +end: + if (r) { + event_del(r); + event_free(r); + } + if (w) { + event_del(w); + event_free(w); + } + if (t) { + event_del(t); + event_free(t); + } +} + +static void +dfd_cb(evutil_socket_t fd, short e, void *data) +{ + *(int*)data = (int)e; +} + +static void +test_event_closed_fd_poll(void *arg) +{ + struct timeval tv; + struct event *e; + struct basic_test_data *data = (struct basic_test_data *)arg; + int i = 0; + + if (strcmp(event_base_get_method(data->base), "poll")) { + tinytest_set_test_skipped_(); + return; + } + + e = event_new(data->base, data->pair[0], EV_READ, dfd_cb, &i); + tt_assert(e); + + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + event_add(e, &tv); + tt_assert(event_pending(e, EV_READ, NULL)); + close(data->pair[0]); + data->pair[0] = -1; /** avoids double-close */ + event_base_loop(data->base, EVLOOP_ONCE); + tt_int_op(i, ==, EV_READ); + +end: + if (e) { + event_del(e); + event_free(e); + } +} + +#ifndef _WIN32 +/* You can't do this test on windows, since dup2 doesn't work on sockets */ + +/* Regression test for our workaround for a fun epoll/linux related bug + * where fd2 = dup(fd1); add(fd2); close(fd2); dup2(fd1,fd2); add(fd2) + * will get you an EEXIST */ +static void +test_dup_fd(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1=NULL, *ev2=NULL; + int fd, dfd=-1; + int ev1_got, ev2_got; + + tt_int_op(write(data->pair[0], "Hello world", + strlen("Hello world")), >, 0); + fd = data->pair[1]; + + dfd = dup(fd); + tt_int_op(dfd, >=, 0); + + ev1 = event_new(base, fd, EV_READ|EV_PERSIST, dfd_cb, &ev1_got); + ev2 = event_new(base, dfd, EV_READ|EV_PERSIST, dfd_cb, &ev2_got); + ev1_got = ev2_got = 0; + event_add(ev1, NULL); + event_add(ev2, NULL); + event_base_loop(base, EVLOOP_ONCE); + tt_int_op(ev1_got, ==, EV_READ); + tt_int_op(ev2_got, ==, EV_READ); + + /* Now close and delete dfd then dispatch. We need to do the + * dispatch here so that when we add it later, we think there + * was an intermediate delete. */ + close(dfd); + event_del(ev2); + ev1_got = ev2_got = 0; + event_base_loop(base, EVLOOP_ONCE); + tt_want_int_op(ev1_got, ==, EV_READ); + tt_int_op(ev2_got, ==, 0); + + /* Re-duplicate the fd. We need to get the same duplicated + * value that we closed to provoke the epoll quirk. Also, we + * need to change the events to write, or else the old lingering + * read event will make the test pass whether the change was + * successful or not. */ + tt_int_op(dup2(fd, dfd), ==, dfd); + event_free(ev2); + ev2 = event_new(base, dfd, EV_WRITE|EV_PERSIST, dfd_cb, &ev2_got); + event_add(ev2, NULL); + ev1_got = ev2_got = 0; + event_base_loop(base, EVLOOP_ONCE); + tt_want_int_op(ev1_got, ==, EV_READ); + tt_int_op(ev2_got, ==, EV_WRITE); + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + if (dfd >= 0) + close(dfd); +} +#endif + +#ifdef EVENT__DISABLE_MM_REPLACEMENT +static void +test_mm_functions(void *arg) +{ + tinytest_set_test_skipped_(); +} +#else +static int +check_dummy_mem_ok(void *mem_) +{ + char *mem = mem_; + mem -= 16; + return !memcmp(mem, "{[<guardedram>]}", 16); +} + +static void * +dummy_malloc(size_t len) +{ + char *mem = malloc(len+16); + memcpy(mem, "{[<guardedram>]}", 16); + return mem+16; +} + +static void * +dummy_realloc(void *mem_, size_t len) +{ + char *mem = mem_; + if (!mem) + return dummy_malloc(len); + tt_want(check_dummy_mem_ok(mem_)); + mem -= 16; + mem = realloc(mem, len+16); + return mem+16; +} + +static void +dummy_free(void *mem_) +{ + char *mem = mem_; + tt_want(check_dummy_mem_ok(mem_)); + mem -= 16; + free(mem); +} + +static void +test_mm_functions(void *arg) +{ + struct event_base *b = NULL; + struct event_config *cfg = NULL; + event_set_mem_functions(dummy_malloc, dummy_realloc, dummy_free); + cfg = event_config_new(); + event_config_avoid_method(cfg, "Nonesuch"); + b = event_base_new_with_config(cfg); + tt_assert(b); + tt_assert(check_dummy_mem_ok(b)); +end: + if (cfg) + event_config_free(cfg); + if (b) + event_base_free(b); +} +#endif + +static void +many_event_cb(evutil_socket_t fd, short event, void *arg) +{ + int *calledp = arg; + *calledp += 1; +} + +static void +test_many_events(void *arg) +{ + /* Try 70 events that should all be ready at once. This will + * exercise the "resize" code on most of the backends, and will make + * sure that we can get past the 64-handle limit of some windows + * functions. */ +#define MANY 70 + + struct basic_test_data *data = arg; + struct event_base *base = data->base; + int one_at_a_time = data->setup_data != NULL; + evutil_socket_t sock[MANY]; + struct event *ev[MANY]; + int called[MANY]; + int i; + int loopflags = EVLOOP_NONBLOCK, evflags=0; + if (one_at_a_time) { + loopflags |= EVLOOP_ONCE; + evflags = EV_PERSIST; + } + + memset(sock, 0xff, sizeof(sock)); + memset(ev, 0, sizeof(ev)); + memset(called, 0, sizeof(called)); + + for (i = 0; i < MANY; ++i) { + /* We need an event that will hit the backend, and that will + * be ready immediately. "Send a datagram" is an easy + * instance of that. */ + sock[i] = socket(AF_INET, SOCK_DGRAM, 0); + tt_assert(sock[i] >= 0); + tt_assert(!evutil_make_socket_nonblocking(sock[i])); + called[i] = 0; + ev[i] = event_new(base, sock[i], EV_WRITE|evflags, + many_event_cb, &called[i]); + event_add(ev[i], NULL); + if (one_at_a_time) + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + } + + event_base_loop(base, loopflags); + + for (i = 0; i < MANY; ++i) { + if (one_at_a_time) + tt_int_op(called[i], ==, MANY - i + 1); + else + tt_int_op(called[i], ==, 1); + } + +end: + for (i = 0; i < MANY; ++i) { + if (ev[i]) + event_free(ev[i]); + if (sock[i] >= 0) + evutil_closesocket(sock[i]); + } +#undef MANY +} + +static void +test_struct_event_size(void *arg) +{ + tt_int_op(event_get_struct_event_size(), <=, sizeof(struct event)); +end: + ; +} + +static void +test_get_assignment(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1 = NULL; + const char *str = "foo"; + + struct event_base *b; + evutil_socket_t s; + short what; + event_callback_fn cb; + void *cb_arg; + + ev1 = event_new(base, data->pair[1], EV_READ, dummy_read_cb, (void*)str); + event_get_assignment(ev1, &b, &s, &what, &cb, &cb_arg); + + tt_ptr_op(b, ==, base); + tt_fd_op(s, ==, data->pair[1]); + tt_int_op(what, ==, EV_READ); + tt_ptr_op(cb, ==, dummy_read_cb); + tt_ptr_op(cb_arg, ==, str); + + /* Now make sure this doesn't crash. */ + event_get_assignment(ev1, NULL, NULL, NULL, NULL, NULL); + +end: + if (ev1) + event_free(ev1); +} + +struct foreach_helper { + int count; + const struct event *ev; +}; + +static int +foreach_count_cb(const struct event_base *base, const struct event *ev, void *arg) +{ + struct foreach_helper *h = event_get_callback_arg(ev); + struct timeval *tv = arg; + if (event_get_callback(ev) != timeout_cb) + return 0; + tt_ptr_op(event_get_base(ev), ==, base); + tt_int_op(tv->tv_sec, ==, 10); + h->ev = ev; + h->count++; + return 0; +end: + return -1; +} + +static int +foreach_find_cb(const struct event_base *base, const struct event *ev, void *arg) +{ + const struct event **ev_out = arg; + struct foreach_helper *h = event_get_callback_arg(ev); + if (event_get_callback(ev) != timeout_cb) + return 0; + if (h->count == 99) { + *ev_out = ev; + return 101; + } + return 0; +} + +static void +test_event_foreach(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev[5]; + struct foreach_helper visited[5]; + int i; + struct timeval ten_sec = {10,0}; + const struct event *ev_found = NULL; + + for (i = 0; i < 5; ++i) { + visited[i].count = 0; + visited[i].ev = NULL; + ev[i] = event_new(base, -1, 0, timeout_cb, &visited[i]); + } + + tt_int_op(-1, ==, event_base_foreach_event(NULL, foreach_count_cb, NULL)); + tt_int_op(-1, ==, event_base_foreach_event(base, NULL, NULL)); + + event_add(ev[0], &ten_sec); + event_add(ev[1], &ten_sec); + event_active(ev[1], EV_TIMEOUT, 1); + event_active(ev[2], EV_TIMEOUT, 1); + event_add(ev[3], &ten_sec); + /* Don't touch ev[4]. */ + + tt_int_op(0, ==, event_base_foreach_event(base, foreach_count_cb, + &ten_sec)); + tt_int_op(1, ==, visited[0].count); + tt_int_op(1, ==, visited[1].count); + tt_int_op(1, ==, visited[2].count); + tt_int_op(1, ==, visited[3].count); + tt_ptr_op(ev[0], ==, visited[0].ev); + tt_ptr_op(ev[1], ==, visited[1].ev); + tt_ptr_op(ev[2], ==, visited[2].ev); + tt_ptr_op(ev[3], ==, visited[3].ev); + + visited[2].count = 99; + tt_int_op(101, ==, event_base_foreach_event(base, foreach_find_cb, + &ev_found)); + tt_ptr_op(ev_found, ==, ev[2]); + +end: + for (i=0; i<5; ++i) { + event_free(ev[i]); + } +} + +static struct event_base *cached_time_base = NULL; +static int cached_time_reset = 0; +static int cached_time_sleep = 0; +static void +cache_time_cb(evutil_socket_t fd, short what, void *arg) +{ + struct timeval *tv = arg; + tt_int_op(0, ==, event_base_gettimeofday_cached(cached_time_base, tv)); + if (cached_time_sleep) { + struct timeval delay = { 0, 30*1000 }; + evutil_usleep_(&delay); + } + if (cached_time_reset) { + event_base_update_cache_time(cached_time_base); + } +end: + ; +} + +static void +test_gettimeofday_cached(void *arg) +{ + struct basic_test_data *data = arg; + struct event_config *cfg = NULL; + struct event_base *base = NULL; + struct timeval tv1, tv2, tv3, now; + struct event *ev1=NULL, *ev2=NULL, *ev3=NULL; + int cached_time_disable = strstr(data->setup_data, "disable") != NULL; + + cfg = event_config_new(); + if (cached_time_disable) { + event_config_set_flag(cfg, EVENT_BASE_FLAG_NO_CACHE_TIME); + } + cached_time_base = base = event_base_new_with_config(cfg); + tt_assert(base); + + /* Try gettimeofday_cached outside of an event loop. */ + evutil_gettimeofday(&now, NULL); + tt_int_op(0, ==, event_base_gettimeofday_cached(NULL, &tv1)); + tt_int_op(0, ==, event_base_gettimeofday_cached(base, &tv2)); + tt_int_op(timeval_msec_diff(&tv1, &tv2), <, 10); + tt_int_op(timeval_msec_diff(&tv1, &now), <, 10); + + cached_time_reset = strstr(data->setup_data, "reset") != NULL; + cached_time_sleep = strstr(data->setup_data, "sleep") != NULL; + + ev1 = event_new(base, -1, 0, cache_time_cb, &tv1); + ev2 = event_new(base, -1, 0, cache_time_cb, &tv2); + ev3 = event_new(base, -1, 0, cache_time_cb, &tv3); + + event_active(ev1, EV_TIMEOUT, 1); + event_active(ev2, EV_TIMEOUT, 1); + event_active(ev3, EV_TIMEOUT, 1); + + event_base_dispatch(base); + + if (cached_time_reset && cached_time_sleep) { + tt_int_op(labs(timeval_msec_diff(&tv1,&tv2)), >, 10); + tt_int_op(labs(timeval_msec_diff(&tv2,&tv3)), >, 10); + } else if (cached_time_disable && cached_time_sleep) { + tt_int_op(labs(timeval_msec_diff(&tv1,&tv2)), >, 10); + tt_int_op(labs(timeval_msec_diff(&tv2,&tv3)), >, 10); + } else if (! cached_time_disable) { + tt_assert(evutil_timercmp(&tv1, &tv2, ==)); + tt_assert(evutil_timercmp(&tv2, &tv3, ==)); + } + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + if (ev3) + event_free(ev3); + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static void +tabf_cb(evutil_socket_t fd, short what, void *arg) +{ + int *ptr = arg; + *ptr = what; + *ptr += 0x10000; +} + +static void +test_evmap_invalid_slots(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1 = NULL, *ev2 = NULL; + int e1, e2; +#ifndef _WIN32 + struct event *ev3 = NULL, *ev4 = NULL; + int e3, e4; +#endif + + ev1 = evsignal_new(base, -1, dummy_read_cb, (void *)base); + ev2 = evsignal_new(base, NSIG, dummy_read_cb, (void *)base); + tt_assert(ev1); + tt_assert(ev2); + e1 = event_add(ev1, NULL); + e2 = event_add(ev2, NULL); + tt_int_op(e1, !=, 0); + tt_int_op(e2, !=, 0); +#ifndef _WIN32 + ev3 = event_new(base, INT_MAX, EV_READ, dummy_read_cb, (void *)base); + ev4 = event_new(base, INT_MAX / 2, EV_READ, dummy_read_cb, (void *)base); + tt_assert(ev3); + tt_assert(ev4); + e3 = event_add(ev3, NULL); + e4 = event_add(ev4, NULL); + tt_int_op(e3, !=, 0); + tt_int_op(e4, !=, 0); +#endif + +end: + event_free(ev1); + event_free(ev2); +#ifndef _WIN32 + event_free(ev3); + event_free(ev4); +#endif +} + +static void +test_active_by_fd(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev1 = NULL, *ev2 = NULL, *ev3 = NULL, *ev4 = NULL; + int e1,e2,e3,e4; +#ifndef _WIN32 + struct event *evsig = NULL; + int es; +#endif + struct timeval tenmin = { 600, 0 }; + + /* Ensure no crash on nonexistent FD. */ + event_base_active_by_fd(base, 1000, EV_READ); + + /* Ensure no crash on bogus FD. */ + event_base_active_by_fd(base, -1, EV_READ); + + /* Ensure no crash on nonexistent/bogus signal. */ + event_base_active_by_signal(base, 1000); + event_base_active_by_signal(base, -1); + + event_base_assert_ok_(base); + + e1 = e2 = e3 = e4 = 0; + ev1 = event_new(base, data->pair[0], EV_READ, tabf_cb, &e1); + ev2 = event_new(base, data->pair[0], EV_WRITE, tabf_cb, &e2); + ev3 = event_new(base, data->pair[1], EV_READ, tabf_cb, &e3); + ev4 = event_new(base, data->pair[1], EV_READ, tabf_cb, &e4); + tt_assert(ev1); + tt_assert(ev2); + tt_assert(ev3); + tt_assert(ev4); +#ifndef _WIN32 + evsig = event_new(base, SIGHUP, EV_SIGNAL, tabf_cb, &es); + tt_assert(evsig); + event_add(evsig, &tenmin); +#endif + + event_add(ev1, &tenmin); + event_add(ev2, NULL); + event_add(ev3, NULL); + event_add(ev4, &tenmin); + + + event_base_assert_ok_(base); + + /* Trigger 2, 3, 4 */ + event_base_active_by_fd(base, data->pair[0], EV_WRITE); + event_base_active_by_fd(base, data->pair[1], EV_READ); + event_base_active_by_fd(base, data->pair[1], EV_TIMEOUT); +#ifndef _WIN32 + event_base_active_by_signal(base, SIGHUP); +#endif + + event_base_assert_ok_(base); + + event_base_loop(base, EVLOOP_ONCE); + + tt_int_op(e1, ==, 0); + tt_int_op(e2, ==, EV_WRITE | 0x10000); + tt_int_op(e3, ==, EV_READ | 0x10000); + /* Mask out EV_WRITE here, since it could be genuinely writeable. */ + tt_int_op((e4 & ~EV_WRITE), ==, EV_READ | EV_TIMEOUT | 0x10000); +#ifndef _WIN32 + tt_int_op(es, ==, EV_SIGNAL | 0x10000); +#endif + +end: + if (ev1) + event_free(ev1); + if (ev2) + event_free(ev2); + if (ev3) + event_free(ev3); + if (ev4) + event_free(ev4); +#ifndef _WIN32 + if (evsig) + event_free(evsig); +#endif +} + +struct testcase_t main_testcases[] = { + /* Some converted-over tests */ + { "methods", test_methods, TT_FORK, NULL, NULL }, + { "version", test_version, 0, NULL, NULL }, + BASIC(base_features, TT_FORK|TT_NO_LOGS), + { "base_environ", test_base_environ, TT_FORK, NULL, NULL }, + + BASIC(event_base_new, TT_FORK|TT_NEED_SOCKETPAIR), + BASIC(free_active_base, TT_FORK|TT_NEED_SOCKETPAIR), + + BASIC(manipulate_active_events, TT_FORK|TT_NEED_BASE), + BASIC(event_new_selfarg, TT_FORK|TT_NEED_BASE), + BASIC(event_assign_selfarg, TT_FORK|TT_NEED_BASE), + BASIC(event_base_get_num_events, TT_FORK|TT_NEED_BASE), + BASIC(event_base_get_max_events, TT_FORK|TT_NEED_BASE), + BASIC(evmap_invalid_slots, TT_FORK|TT_NEED_BASE), + + BASIC(bad_assign, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), + BASIC(bad_reentrant, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), + BASIC(active_later, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR|TT_RETRIABLE), + BASIC(event_remove_timeout, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR), + + /* These are still using the old API */ + LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE), + { "persistent_timeout_jump", test_persistent_timeout_jump, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "persistent_active_timeout", test_persistent_active_timeout, + TT_FORK|TT_NEED_BASE|TT_RETRIABLE, &basic_setup, NULL }, + LEGACY(priorities, TT_FORK|TT_NEED_BASE), + BASIC(priority_active_inversion, TT_FORK|TT_NEED_BASE), + { "common_timeout", test_common_timeout, TT_FORK|TT_NEED_BASE, + &basic_setup, NULL }, + + /* These legacy tests may not all need all of these flags. */ + LEGACY(simpleread, TT_ISOLATED), + LEGACY(simpleread_multiple, TT_ISOLATED), + LEGACY(simplewrite, TT_ISOLATED), + { "simpleclose_rw", test_simpleclose_rw, TT_FORK, &basic_setup, NULL }, + /* simpleclose */ + { "simpleclose_close", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close" }, + { "simpleclose_shutdown", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown" }, + /* simpleclose_*_persist */ + { "simpleclose_close_persist", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close_persist" }, + { "simpleclose_shutdown_persist", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown_persist" }, + /* simpleclose_*_et */ + { "simpleclose_close_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close_ET" }, + { "simpleclose_shutdown_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown_ET" }, + /* simpleclose_*_persist_et */ + { "simpleclose_close_persist_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"close_persist_ET" }, + { "simpleclose_shutdown_persist_et", test_simpleclose, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, + &basic_setup, (void *)"shutdown_persist_ET" }, + LEGACY(multiple, TT_ISOLATED), + LEGACY(persistent, TT_ISOLATED), + LEGACY(combined, TT_ISOLATED), + LEGACY(simpletimeout, TT_ISOLATED), + LEGACY(loopbreak, TT_ISOLATED), + LEGACY(loopexit, TT_ISOLATED), + LEGACY(loopexit_multiple, TT_ISOLATED), + { "nonpersist_readd", test_nonpersist_readd, TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE, &basic_setup, NULL }, + LEGACY(multiple_events_for_same_fd, TT_ISOLATED), + LEGACY(want_only_once, TT_ISOLATED), + { "event_once", test_event_once, TT_ISOLATED, &basic_setup, NULL }, + { "event_once_never", test_event_once_never, TT_ISOLATED, &basic_setup, NULL }, + { "event_pending", test_event_pending, TT_ISOLATED, &basic_setup, + NULL }, + { "event_closed_fd_poll", test_event_closed_fd_poll, TT_ISOLATED, &basic_setup, + NULL }, + +#ifndef _WIN32 + { "dup_fd", test_dup_fd, TT_ISOLATED, &basic_setup, NULL }, +#endif + { "mm_functions", test_mm_functions, TT_FORK, NULL, NULL }, + { "many_events", test_many_events, TT_ISOLATED, &basic_setup, NULL }, + { "many_events_slow_add", test_many_events, TT_ISOLATED, &basic_setup, (void*)1 }, + + { "struct_event_size", test_struct_event_size, 0, NULL, NULL }, + BASIC(get_assignment, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR), + + BASIC(event_foreach, TT_FORK|TT_NEED_BASE), + { "gettimeofday_cached", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"" }, + { "gettimeofday_cached_sleep", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"sleep" }, + { "gettimeofday_cached_reset", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"sleep reset" }, + { "gettimeofday_cached_disabled", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"sleep disable" }, + { "gettimeofday_cached_disabled_nosleep", test_gettimeofday_cached, TT_FORK, &basic_setup, (void*)"disable" }, + + BASIC(active_by_fd, TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR), + +#ifndef _WIN32 + LEGACY(fork, TT_ISOLATED), +#endif + +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED + LEGACY(del_wait, TT_ISOLATED|TT_NEED_THREADS|TT_RETRIABLE), + LEGACY(del_notify, TT_ISOLATED|TT_NEED_THREADS), +#endif + + END_OF_TESTCASES +}; + +struct testcase_t evtag_testcases[] = { + { "int", evtag_int_test, TT_FORK, NULL, NULL }, + { "fuzz", evtag_fuzz, TT_FORK, NULL, NULL }, + { "encoding", evtag_tag_encoding, TT_FORK, NULL, NULL }, + { "peek", evtag_test_peek, 0, NULL, NULL }, + + END_OF_TESTCASES +}; + +struct testcase_t signal_testcases[] = { +#ifndef _WIN32 + LEGACY(simplestsignal, TT_ISOLATED), + LEGACY(simplesignal, TT_ISOLATED), + LEGACY(multiplesignal, TT_ISOLATED), + LEGACY(immediatesignal, TT_ISOLATED), + LEGACY(signal_dealloc, TT_ISOLATED), + LEGACY(signal_pipeloss, TT_ISOLATED), + LEGACY(signal_switchbase, TT_ISOLATED|TT_NO_LOGS), + LEGACY(signal_restore, TT_ISOLATED), + LEGACY(signal_assert, TT_ISOLATED), + LEGACY(signal_while_processing, TT_ISOLATED), +#endif + END_OF_TESTCASES +}; + diff --git a/libevent/test/regress.gen.c b/libevent/test/regress.gen.c new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/libevent/test/regress.gen.c @@ -0,0 +1 @@ + diff --git a/libevent/test/regress.gen.h b/libevent/test/regress.gen.h new file mode 100644 index 0000000..7130984 --- /dev/null +++ b/libevent/test/regress.gen.h @@ -0,0 +1 @@ +#define NO_PYTHON_EXISTS diff --git a/libevent/test/regress.h b/libevent/test/regress.h new file mode 100644 index 0000000..43cb4ea --- /dev/null +++ b/libevent/test/regress.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2000-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef REGRESS_H_INCLUDED_ +#define REGRESS_H_INCLUDED_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tinytest.h" +#include "tinytest_macros.h" + +extern struct testcase_t main_testcases[]; +extern struct testcase_t evtag_testcases[]; +extern struct testcase_t evbuffer_testcases[]; +extern struct testcase_t finalize_testcases[]; +extern struct testcase_t bufferevent_testcases[]; +extern struct testcase_t bufferevent_iocp_testcases[]; +extern struct testcase_t util_testcases[]; +extern struct testcase_t signal_testcases[]; +extern struct testcase_t http_testcases[]; +extern struct testcase_t http_iocp_testcases[]; +extern struct testcase_t dns_testcases[]; +extern struct testcase_t rpc_testcases[]; +extern struct testcase_t edgetriggered_testcases[]; +extern struct testcase_t minheap_testcases[]; +extern struct testcase_t iocp_testcases[]; +extern struct testcase_t ssl_testcases[]; +extern struct testcase_t listener_testcases[]; +extern struct testcase_t listener_iocp_testcases[]; +extern struct testcase_t thread_testcases[]; + +extern struct evutil_weakrand_state test_weakrand_state; + +#define test_weakrand() (evutil_weakrand_(&test_weakrand_state)) + +void regress_threads(void *); +void test_bufferevent_zlib(void *); + +/* Helpers to wrap old testcases */ +extern evutil_socket_t pair[2]; +extern int test_ok; +extern int called; +extern struct event_base *global_base; +extern int in_legacy_test_wrapper; + +int regress_make_tmpfile(const void *data, size_t datalen, char **filename_out); + +struct basic_test_data { + struct event_base *base; + evutil_socket_t pair[2]; + + void (*legacy_test_fn)(void); + + void *setup_data; +}; +extern const struct testcase_setup_t basic_setup; + + +extern const struct testcase_setup_t legacy_setup; +void run_legacy_test_fn(void *ptr); + +extern int libevent_tests_running_in_debug_mode; + +/* A couple of flags that basic/legacy_setup can support. */ +#define TT_NEED_SOCKETPAIR TT_FIRST_USER_FLAG +#define TT_NEED_BASE (TT_FIRST_USER_FLAG<<1) +#define TT_NEED_DNS (TT_FIRST_USER_FLAG<<2) +#define TT_LEGACY (TT_FIRST_USER_FLAG<<3) +#define TT_NEED_THREADS (TT_FIRST_USER_FLAG<<4) +#define TT_NO_LOGS (TT_FIRST_USER_FLAG<<5) +#define TT_ENABLE_IOCP_FLAG (TT_FIRST_USER_FLAG<<6) +#define TT_ENABLE_IOCP (TT_ENABLE_IOCP_FLAG|TT_NEED_THREADS) +#define TT_ENABLE_DEBUG_MODE (TT_ENABLE_IOCP_FLAG<<7) + +/* All the flags that a legacy test needs. */ +#define TT_ISOLATED TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE + + +#define BASIC(name,flags) \ + { #name, test_## name, flags, &basic_setup, NULL } + +#define LEGACY(name,flags) \ + { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ + test_## name } + +struct evutil_addrinfo; +struct evutil_addrinfo *ai_find_by_family(struct evutil_addrinfo *ai, int f); +struct evutil_addrinfo *ai_find_by_protocol(struct evutil_addrinfo *ai, int p); +int test_ai_eq_(const struct evutil_addrinfo *ai, const char *sockaddr_port, + int socktype, int protocol, int line); + +#define test_ai_eq(ai, str, s, p) do { \ + if (test_ai_eq_((ai), (str), (s), (p), __LINE__)<0) \ + goto end; \ + } while (0) + +#define test_timeval_diff_leq(tv1, tv2, diff, tolerance) \ + tt_int_op(labs(timeval_msec_diff((tv1), (tv2)) - diff), <=, tolerance) + +#define test_timeval_diff_eq(tv1, tv2, diff) \ + test_timeval_diff_leq((tv1), (tv2), (diff), 50) + +long timeval_msec_diff(const struct timeval *start, const struct timeval *end); + +#ifndef _WIN32 +pid_t regress_fork(void); +#endif + +#ifdef EVENT__HAVE_OPENSSL +#include <openssl/ssl.h> +EVP_PKEY *ssl_getkey(void); +X509 *ssl_getcert(EVP_PKEY *key); +SSL_CTX *get_ssl_ctx(void); +void init_ssl(void); +#endif + +void * basic_test_setup(const struct testcase_t *testcase); +int basic_test_cleanup(const struct testcase_t *testcase, void *ptr); + +#ifdef __cplusplus +} +#endif + +#endif /* REGRESS_H_INCLUDED_ */ diff --git a/libevent/test/regress.rpc b/libevent/test/regress.rpc new file mode 100644 index 0000000..0ee904e --- /dev/null +++ b/libevent/test/regress.rpc @@ -0,0 +1,25 @@ +/* tests data packing and unpacking */ + +struct msg { + string /* sender */ from_name = 1; /* be verbose */ + string to_name = 2; + optional struct[kill] attack = 3; + array struct[run] run = 4; +} + +struct kill { + string weapon = 0x10121; + string action = 2; + array int how_often = 3; +} + +struct run { + string how = 1; + optional bytes some_bytes = 2; + + bytes fixed_bytes[24] = 3; + array string notes = 4; + + optional int64 large_number = 5; + array int other_numbers = 6; +} diff --git a/libevent/test/regress_buffer.c b/libevent/test/regress_buffer.c new file mode 100644 index 0000000..f259b92 --- /dev/null +++ b/libevent/test/regress_buffer.c @@ -0,0 +1,2870 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <netdb.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "event2/event.h" +#include "event2/buffer.h" +#include "event2/buffer_compat.h" +#include "event2/util.h" + +#include "defer-internal.h" +#include "evbuffer-internal.h" +#include "log-internal.h" + +#include "regress.h" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +/* Validates that an evbuffer is good. Returns false if it isn't, true if it + * is*/ +static int +evbuffer_validate_(struct evbuffer *buf) +{ + struct evbuffer_chain *chain; + size_t sum = 0; + int found_last_with_datap = 0; + + if (buf->first == NULL) { + tt_assert(buf->last == NULL); + tt_assert(buf->total_len == 0); + } + + chain = buf->first; + + tt_assert(buf->last_with_datap); + if (buf->last_with_datap == &buf->first) + found_last_with_datap = 1; + + while (chain != NULL) { + if (&chain->next == buf->last_with_datap) + found_last_with_datap = 1; + sum += chain->off; + if (chain->next == NULL) { + tt_assert(buf->last == chain); + } + tt_assert(chain->buffer_len >= chain->misalign + chain->off); + chain = chain->next; + } + + if (buf->first) + tt_assert(*buf->last_with_datap); + + if (*buf->last_with_datap) { + chain = *buf->last_with_datap; + if (chain->off == 0 || buf->total_len == 0) { + tt_assert(chain->off == 0) + tt_assert(chain == buf->first); + tt_assert(buf->total_len == 0); + } + chain = chain->next; + while (chain != NULL) { + tt_assert(chain->off == 0); + chain = chain->next; + } + } else { + tt_assert(buf->last_with_datap == &buf->first); + } + tt_assert(found_last_with_datap); + + tt_assert(sum == buf->total_len); + return 1; + end: + return 0; +} + +static void +evbuffer_get_waste(struct evbuffer *buf, size_t *allocatedp, size_t *wastedp, size_t *usedp) +{ + struct evbuffer_chain *chain; + size_t a, w, u; + int n = 0; + u = a = w = 0; + + chain = buf->first; + /* skip empty at start */ + while (chain && chain->off==0) { + ++n; + a += chain->buffer_len; + chain = chain->next; + } + /* first nonempty chain: stuff at the end only is wasted. */ + if (chain) { + ++n; + a += chain->buffer_len; + u += chain->off; + if (chain->next && chain->next->off) + w += (size_t)(chain->buffer_len - (chain->misalign + chain->off)); + chain = chain->next; + } + /* subsequent nonempty chains */ + while (chain && chain->off) { + ++n; + a += chain->buffer_len; + w += (size_t)chain->misalign; + u += chain->off; + if (chain->next && chain->next->off) + w += (size_t) (chain->buffer_len - (chain->misalign + chain->off)); + chain = chain->next; + } + /* subsequent empty chains */ + while (chain) { + ++n; + a += chain->buffer_len; + } + *allocatedp = a; + *wastedp = w; + *usedp = u; +} + +#define evbuffer_validate(buf) \ + TT_STMT_BEGIN if (!evbuffer_validate_(buf)) TT_DIE(("Buffer format invalid")); TT_STMT_END + +static void +test_evbuffer(void *ptr) +{ + static char buffer[512], *tmp; + struct evbuffer *evb = evbuffer_new(); + struct evbuffer *evb_two = evbuffer_new(); + size_t sz_tmp; + int i; + + evbuffer_validate(evb); + evbuffer_add_printf(evb, "%s/%d", "hello", 1); + evbuffer_validate(evb); + + tt_assert(evbuffer_get_length(evb) == 7); + tt_assert(!memcmp((char*)EVBUFFER_DATA(evb), "hello/1", 1)); + + evbuffer_add_buffer(evb, evb_two); + evbuffer_validate(evb); + + evbuffer_drain(evb, strlen("hello/")); + evbuffer_validate(evb); + tt_assert(evbuffer_get_length(evb) == 1); + tt_assert(!memcmp((char*)EVBUFFER_DATA(evb), "1", 1)); + + evbuffer_add_printf(evb_two, "%s", "/hello"); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_two); + evbuffer_validate(evb); + + tt_assert(evbuffer_get_length(evb_two) == 0); + tt_assert(evbuffer_get_length(evb) == 7); + tt_assert(!memcmp((char*)EVBUFFER_DATA(evb), "1/hello", 7)); + + memset(buffer, 0, sizeof(buffer)); + evbuffer_add(evb, buffer, sizeof(buffer)); + evbuffer_validate(evb); + tt_assert(evbuffer_get_length(evb) == 7 + 512); + + tmp = (char *)evbuffer_pullup(evb, 7 + 512); + tt_assert(tmp); + tt_assert(!strncmp(tmp, "1/hello", 7)); + tt_assert(!memcmp(tmp + 7, buffer, sizeof(buffer))); + evbuffer_validate(evb); + + evbuffer_prepend(evb, "something", 9); + evbuffer_validate(evb); + evbuffer_prepend(evb, "else", 4); + evbuffer_validate(evb); + + tmp = (char *)evbuffer_pullup(evb, 4 + 9 + 7); + tt_assert(!strncmp(tmp, "elsesomething1/hello", 4 + 9 + 7)); + evbuffer_validate(evb); + + evbuffer_drain(evb, -1); + evbuffer_validate(evb); + evbuffer_drain(evb_two, -1); + evbuffer_validate(evb); + + for (i = 0; i < 3; ++i) { + evbuffer_add(evb_two, buffer, sizeof(buffer)); + evbuffer_validate(evb_two); + evbuffer_add_buffer(evb, evb_two); + evbuffer_validate(evb); + evbuffer_validate(evb_two); + } + + tt_assert(evbuffer_get_length(evb_two) == 0); + tt_assert(evbuffer_get_length(evb) == i * sizeof(buffer)); + + /* test remove buffer */ + sz_tmp = (size_t)(sizeof(buffer)*2.5); + evbuffer_remove_buffer(evb, evb_two, sz_tmp); + tt_assert(evbuffer_get_length(evb_two) == sz_tmp); + tt_assert(evbuffer_get_length(evb) == sizeof(buffer) / 2); + evbuffer_validate(evb); + + if (memcmp(evbuffer_pullup( + evb, -1), buffer, sizeof(buffer) / 2) != 0 || + memcmp(evbuffer_pullup( + evb_two, -1), buffer, sizeof(buffer)) != 0) + tt_abort_msg("Pullup did not preserve content"); + + evbuffer_validate(evb); + + + /* testing one-vector reserve and commit */ + { + struct evbuffer_iovec v[1]; + char *buf; + int i, j, r; + + for (i = 0; i < 3; ++i) { + r = evbuffer_reserve_space(evb, 10000, v, 1); + tt_int_op(r, ==, 1); + tt_assert(v[0].iov_len >= 10000); + tt_assert(v[0].iov_base != NULL); + + evbuffer_validate(evb); + buf = v[0].iov_base; + for (j = 0; j < 10000; ++j) { + buf[j] = j; + } + evbuffer_validate(evb); + + tt_int_op(evbuffer_commit_space(evb, v, 1), ==, 0); + evbuffer_validate(evb); + + tt_assert(evbuffer_get_length(evb) >= 10000); + + evbuffer_drain(evb, j * 5000); + evbuffer_validate(evb); + } + } + + end: + evbuffer_free(evb); + evbuffer_free(evb_two); +} + +static void +no_cleanup(const void *data, size_t datalen, void *extra) +{ +} + +static void +test_evbuffer_remove_buffer_with_empty(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + char buf[2] = { 'A', 'A' }; + + evbuffer_validate(src); + evbuffer_validate(dst); + + /* setup the buffers */ + /* we need more data in src than we will move later */ + evbuffer_add_reference(src, buf, sizeof(buf), no_cleanup, NULL); + evbuffer_add_reference(src, buf, sizeof(buf), no_cleanup, NULL); + /* we need one buffer in dst and one empty buffer at the end */ + evbuffer_add(dst, buf, sizeof(buf)); + evbuffer_add_reference(dst, buf, 0, no_cleanup, NULL); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "AAAA", 4); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "AA", 2); + + /* move three bytes over */ + evbuffer_remove_buffer(src, dst, 3); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "A", 1); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "AAAAA", 5); + + end: + evbuffer_free(src); + evbuffer_free(dst); +} + +static void +test_evbuffer_remove_buffer_with_empty2(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, "foo", 3, NULL, NULL); + + evbuffer_add_reference(src, "foo", 3, NULL, NULL); + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_add_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, "foo", 3, NULL, NULL); + + evbuffer_add_reference(dst, "foo", 3, NULL, NULL); + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_add_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 9); + tt_int_op(evbuffer_get_length(dst), ==, 9); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "foofoofoo", 9); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoofoo", 9); + + evbuffer_remove_buffer(src, dst, 8); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_int_op(evbuffer_get_length(src), ==, 1); + tt_int_op(evbuffer_get_length(dst), ==, 17); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "o", 1); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoofoofoofoofo", 17); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_remove_buffer_with_empty3(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + + evbuffer_add_reference(src, "foo", 3, NULL, NULL); + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_prepend_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + + evbuffer_add_reference(dst, "foo", 3, NULL, NULL); + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_prepend_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 6); + tt_int_op(evbuffer_get_length(dst), ==, 6); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "foofoo", 6); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoo", 6); + + evbuffer_remove_buffer(src, dst, 5); + + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_int_op(evbuffer_get_length(src), ==, 1); + tt_int_op(evbuffer_get_length(dst), ==, 11); + + tt_mem_op(evbuffer_pullup(src, -1), ==, "o", 1); + tt_mem_op(evbuffer_pullup(dst, -1), ==, "foofoofoofo", 11); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_pullup_with_empty(void *ptr) +{ + struct evbuffer *buf = NULL; + + buf = evbuffer_new(); + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + evbuffer_validate(buf); + tt_int_op(evbuffer_get_length(buf), ==, 3); + tt_mem_op(evbuffer_pullup(buf, -1), ==, "foo", 3); + + evbuffer_free(buf); + buf = evbuffer_new(); + evbuffer_validate(buf); + tt_int_op(evbuffer_get_length(buf), ==, 0); + tt_int_op(evbuffer_pullup(buf, -1), ==, NULL); + + evbuffer_free(buf); + buf = evbuffer_new(); + evbuffer_add(buf, "foo", 3); + evbuffer_add_reference(buf, NULL, 0, NULL, NULL); + evbuffer_validate(buf); + tt_mem_op(evbuffer_pullup(buf, 3), ==, "foo", 3); + + end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_remove_buffer_with_empty_front(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add_reference(buf1, "foo", 3, NULL, NULL), ==, 0); + tt_int_op(evbuffer_prepend(buf1, "", 0), ==, 0); + tt_int_op(evbuffer_remove_buffer(buf1, buf2, 1), ==, 1); + tt_int_op(evbuffer_add(buf1, "bar", 3), ==, 0); + tt_mem_op(evbuffer_pullup(buf1, -1), ==, "oobar", 5); + + evbuffer_validate(buf1); + evbuffer_validate(buf2); + + end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_remove_buffer_adjust_last_with_datap_with_empty(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add(buf1, "aaaaaa", 6), ==, 0); + + // buf1: aaaaaab + // buf2: + { + struct evbuffer_iovec iovecs[2]; + /** we want two chains, to leave one chain empty */ + tt_int_op(evbuffer_reserve_space(buf1, 971, iovecs, 2), ==, 2); + tt_int_op(iovecs[0].iov_len, >=, 1); + tt_int_op(iovecs[1].iov_len, >=, 1); + tt_assert(*(char *)(iovecs[0].iov_base) = 'b'); + tt_assert(iovecs[0].iov_len = 1); + tt_int_op(evbuffer_commit_space(buf1, iovecs, 1), ==, 0); + } + + // buf1: aaaaaab + // buf2: dddcc + tt_int_op(evbuffer_add(buf2, "cc", 2), ==, 0); + tt_int_op(evbuffer_prepend(buf2, "ddd", 3), ==, 0); + + // buf1: + // buf2: aaaaaabdddcc + tt_int_op(evbuffer_prepend_buffer(buf2, buf1), ==, 0); + + // buf1: aaaaaabdddcc + // buf2: + tt_int_op(evbuffer_add_buffer(buf1, buf2), ==, 0); + + // buf1: c + // buf2: aaaaaabdddc + tt_int_op(evbuffer_remove_buffer(buf1, buf2, 11), ==, 11); + + // This fails today, we observe "aaaaaabcddd" instead! + tt_mem_op(evbuffer_pullup(buf2, -1), ==, "aaaaaabdddc", 11); + + evbuffer_validate(buf1); + evbuffer_validate(buf2); + + end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_add_buffer_with_empty(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(src, "foo", 3, NULL, NULL); + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_add_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(dst, "foo", 3, NULL, NULL); + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_add_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 6); + tt_int_op(evbuffer_get_length(dst), ==, 6); + + evbuffer_validate(src); + evbuffer_validate(dst); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_add_buffer_with_empty2(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer *buf = evbuffer_new(); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(src, NULL, 0, NULL, NULL); + evbuffer_add_buffer(src, buf); + + evbuffer_add(buf, "foo", 3); + + evbuffer_add_reference(dst, NULL, 0, NULL, NULL); + evbuffer_add_buffer(dst, buf); + + tt_int_op(evbuffer_get_length(src), ==, 3); + tt_int_op(evbuffer_get_length(dst), ==, 3); + + evbuffer_validate(src); + evbuffer_validate(dst); + + end: + evbuffer_free(src); + evbuffer_free(dst); + evbuffer_free(buf); +} + +static void +test_evbuffer_reserve2(void *ptr) +{ + /* Test the two-vector cases of reserve/commit. */ + struct evbuffer *buf = evbuffer_new(); + int n, i; + struct evbuffer_iovec v[2]; + size_t remaining; + char *cp, *cp2; + + /* First chunk will necessarily be one chunk. Use 512 bytes of it.*/ + n = evbuffer_reserve_space(buf, 1024, v, 2); + tt_int_op(n, ==, 1); + tt_int_op(evbuffer_get_length(buf), ==, 0); + tt_assert(v[0].iov_base != NULL); + tt_int_op(v[0].iov_len, >=, 1024); + memset(v[0].iov_base, 'X', 512); + cp = v[0].iov_base; + remaining = v[0].iov_len - 512; + v[0].iov_len = 512; + evbuffer_validate(buf); + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); + tt_int_op(evbuffer_get_length(buf), ==, 512); + evbuffer_validate(buf); + + /* Ask for another same-chunk request, in an existing chunk. Use 8 + * bytes of it. */ + n = evbuffer_reserve_space(buf, 32, v, 2); + tt_int_op(n, ==, 1); + tt_assert(cp + 512 == v[0].iov_base); + tt_int_op(remaining, ==, v[0].iov_len); + memset(v[0].iov_base, 'Y', 8); + v[0].iov_len = 8; + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); + tt_int_op(evbuffer_get_length(buf), ==, 520); + remaining -= 8; + evbuffer_validate(buf); + + /* Now ask for a request that will be split. Use only one byte of it, + though. */ + n = evbuffer_reserve_space(buf, remaining+64, v, 2); + tt_int_op(n, ==, 2); + tt_assert(cp + 520 == v[0].iov_base); + tt_int_op(remaining, ==, v[0].iov_len); + tt_assert(v[1].iov_base); + tt_assert(v[1].iov_len >= 64); + cp2 = v[1].iov_base; + memset(v[0].iov_base, 'Z', 1); + v[0].iov_len = 1; + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 1)); + tt_int_op(evbuffer_get_length(buf), ==, 521); + remaining -= 1; + evbuffer_validate(buf); + + /* Now ask for a request that will be split. Use some of the first + * part and some of the second. */ + n = evbuffer_reserve_space(buf, remaining+64, v, 2); + evbuffer_validate(buf); + tt_int_op(n, ==, 2); + tt_assert(cp + 521 == v[0].iov_base); + tt_int_op(remaining, ==, v[0].iov_len); + tt_assert(v[1].iov_base == cp2); + tt_assert(v[1].iov_len >= 64); + memset(v[0].iov_base, 'W', 400); + v[0].iov_len = 400; + memset(v[1].iov_base, 'x', 60); + v[1].iov_len = 60; + tt_int_op(0, ==, evbuffer_commit_space(buf, v, 2)); + tt_int_op(evbuffer_get_length(buf), ==, 981); + evbuffer_validate(buf); + + /* Now peek to make sure stuff got made how we like. */ + memset(v,0,sizeof(v)); + n = evbuffer_peek(buf, -1, NULL, v, 2); + tt_int_op(n, ==, 2); + tt_int_op(v[0].iov_len, ==, 921); + tt_int_op(v[1].iov_len, ==, 60); + + cp = v[0].iov_base; + for (i=0; i<512; ++i) + tt_int_op(cp[i], ==, 'X'); + for (i=512; i<520; ++i) + tt_int_op(cp[i], ==, 'Y'); + for (i=520; i<521; ++i) + tt_int_op(cp[i], ==, 'Z'); + for (i=521; i<921; ++i) + tt_int_op(cp[i], ==, 'W'); + + cp = v[1].iov_base; + for (i=0; i<60; ++i) + tt_int_op(cp[i], ==, 'x'); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_reserve_many(void *ptr) +{ + /* This is a glass-box test to handle expanding a buffer with more + * chunks and reallocating chunks as needed */ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer_iovec v[8]; + int n; + size_t sz; + int add_data = ptr && !strcmp(ptr, "add"); + int fill_first = ptr && !strcmp(ptr, "fill"); + char *cp1, *cp2; + + /* When reserving the the first chunk, we just allocate it */ + n = evbuffer_reserve_space(buf, 128, v, 2); + evbuffer_validate(buf); + tt_int_op(n, ==, 1); + tt_assert(v[0].iov_len >= 128); + sz = v[0].iov_len; + cp1 = v[0].iov_base; + if (add_data) { + *(char*)v[0].iov_base = 'X'; + v[0].iov_len = 1; + n = evbuffer_commit_space(buf, v, 1); + tt_int_op(n, ==, 0); + } else if (fill_first) { + memset(v[0].iov_base, 'X', v[0].iov_len); + n = evbuffer_commit_space(buf, v, 1); + tt_int_op(n, ==, 0); + n = evbuffer_reserve_space(buf, 128, v, 2); + tt_int_op(n, ==, 1); + sz = v[0].iov_len; + tt_assert(v[0].iov_base != cp1); + cp1 = v[0].iov_base; + } + + /* Make another chunk get added. */ + n = evbuffer_reserve_space(buf, sz+128, v, 2); + evbuffer_validate(buf); + tt_int_op(n, ==, 2); + sz = v[0].iov_len + v[1].iov_len; + tt_int_op(sz, >=, v[0].iov_len+128); + if (add_data) { + tt_assert(v[0].iov_base == cp1 + 1); + } else { + tt_assert(v[0].iov_base == cp1); + } + cp1 = v[0].iov_base; + cp2 = v[1].iov_base; + + /* And a third chunk. */ + n = evbuffer_reserve_space(buf, sz+128, v, 3); + evbuffer_validate(buf); + tt_int_op(n, ==, 3); + tt_assert(cp1 == v[0].iov_base); + tt_assert(cp2 == v[1].iov_base); + sz = v[0].iov_len + v[1].iov_len + v[2].iov_len; + + /* Now force a reallocation by asking for more space in only 2 + * buffers. */ + n = evbuffer_reserve_space(buf, sz+128, v, 2); + evbuffer_validate(buf); + if (add_data) { + tt_int_op(n, ==, 2); + tt_assert(cp1 == v[0].iov_base); + } else { + tt_int_op(n, ==, 1); + } + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_reserve_with_empty(void *ptr) +{ + struct evbuffer *buf; + struct evbuffer_iovec v[2]; + + tt_assert(buf = evbuffer_new()); + evbuffer_add(buf, "a", 1); + tt_int_op(evbuffer_reserve_space(buf, 1<<12, v, 2), ==, 2); + v[0].iov_len = 1; + *(char *)v[0].iov_base = 'b'; + tt_int_op(evbuffer_commit_space(buf, v, 1), ==, 0); + evbuffer_add(buf, "c", 1); + tt_mem_op(evbuffer_pullup(buf, -1), ==, "abc", 2); + + evbuffer_validate(buf); + + end: + if (buf) + evbuffer_free(buf); +} + +/* regression for evbuffer_expand_fast_() with invalid last_with_datap that has + * been left after evbuffer_prepend() with empty chain in it */ +static void +test_evbuffer_reserve_invalid_last_with_datap(void *ptr) +{ + struct evbuffer *buf = NULL; + struct evbuffer_iovec vec[2]; + const int nvec = ARRAY_SIZE(vec); + int i, avec; + + buf = evbuffer_new(); + tt_assert(buf); + + /* prepend with an empty chain */ + evbuffer_add_reference(buf, "", 0, NULL, NULL); + evbuffer_prepend(buf, "foo", 3); + /* after invalid last_with_datap will create new chain */ + evbuffer_add(buf, "", 0); + /* we need to create at least 2 "used" (in evbuffer_expand_fast_()) chains */ + tt_int_op(avec = evbuffer_reserve_space(buf, 1<<12, vec, nvec), >=, 1); + for (i = 0; i < avec; ++i) + vec[i].iov_len = 0; + tt_int_op(evbuffer_commit_space(buf, vec, avec), ==, 0); + + /* and an actual problem, that triggers an assert(chain == buf->first) in + * evbuffer_expand_fast_() */ + tt_int_op(evbuffer_reserve_space(buf, 1<<13, vec, nvec), >=, 1); + + evbuffer_validate(buf); + +end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_expand(void *ptr) +{ + char data[4096]; + struct evbuffer *buf; + size_t a,w,u; + void *buffer; + + memset(data, 'X', sizeof(data)); + + /* Make sure that expand() works on an empty buffer */ + buf = evbuffer_new(); + tt_int_op(evbuffer_expand(buf, 20000), ==, 0); + evbuffer_validate(buf); + a=w=u=0; + evbuffer_get_waste(buf, &a,&w,&u); + tt_assert(w == 0); + tt_assert(u == 0); + tt_assert(a >= 20000); + tt_assert(buf->first); + tt_assert(buf->first == buf->last); + tt_assert(buf->first->off == 0); + tt_assert(buf->first->buffer_len >= 20000); + + /* Make sure that expand() works as a no-op when there's enough + * contiguous space already. */ + buffer = buf->first->buffer; + evbuffer_add(buf, data, 1024); + tt_int_op(evbuffer_expand(buf, 1024), ==, 0); + tt_assert(buf->first->buffer == buffer); + evbuffer_validate(buf); + evbuffer_free(buf); + + /* Make sure that expand() can work by moving misaligned data + * when it makes sense to do so. */ + buf = evbuffer_new(); + evbuffer_add(buf, data, 400); + { + int n = (int)(buf->first->buffer_len - buf->first->off - 1); + tt_assert(n < (int)sizeof(data)); + evbuffer_add(buf, data, n); + } + tt_assert(buf->first == buf->last); + tt_assert(buf->first->off == buf->first->buffer_len - 1); + evbuffer_drain(buf, buf->first->off - 1); + tt_assert(1 == evbuffer_get_length(buf)); + tt_assert(buf->first->misalign > 0); + tt_assert(buf->first->off == 1); + buffer = buf->first->buffer; + tt_assert(evbuffer_expand(buf, 40) == 0); + tt_assert(buf->first == buf->last); + tt_assert(buf->first->off == 1); + tt_assert(buf->first->buffer == buffer); + tt_assert(buf->first->misalign == 0); + evbuffer_validate(buf); + evbuffer_free(buf); + + /* add, expand, pull-up: This used to crash libevent. */ + buf = evbuffer_new(); + + evbuffer_add(buf, data, sizeof(data)); + evbuffer_add(buf, data, sizeof(data)); + evbuffer_add(buf, data, sizeof(data)); + + evbuffer_validate(buf); + evbuffer_expand(buf, 1024); + evbuffer_validate(buf); + evbuffer_pullup(buf, -1); + evbuffer_validate(buf); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_expand_overflow(void *ptr) +{ + struct evbuffer *buf; + + buf = evbuffer_new(); + evbuffer_add(buf, "1", 1); + evbuffer_expand(buf, EVBUFFER_CHAIN_MAX); + evbuffer_validate(buf); + + evbuffer_expand(buf, EV_SIZE_MAX); + evbuffer_validate(buf); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_add1(void *ptr) +{ + struct evbuffer *buf; + char *str; + + buf = evbuffer_new(); + evbuffer_add(buf, "1", 1); + evbuffer_validate(buf); + evbuffer_expand(buf, 2048); + evbuffer_validate(buf); + evbuffer_add(buf, "2", 1); + evbuffer_validate(buf); + evbuffer_add_printf(buf, "3"); + evbuffer_validate(buf); + + tt_assert(evbuffer_get_length(buf) == 3); + str = (char *)evbuffer_pullup(buf, -1); + tt_assert(str[0] == '1'); + tt_assert(str[1] == '2'); + tt_assert(str[2] == '3'); +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_add2(void *ptr) +{ + struct evbuffer *buf; + static char data[4096]; + int data_len = MIN_BUFFER_SIZE-EVBUFFER_CHAIN_SIZE-10; + char *str; + int len; + + memset(data, 'P', sizeof(data)); + buf = evbuffer_new(); + evbuffer_add(buf, data, data_len); + evbuffer_validate(buf); + evbuffer_expand(buf, 100); + evbuffer_validate(buf); + evbuffer_add(buf, "2", 1); + evbuffer_validate(buf); + evbuffer_add_printf(buf, "3"); + evbuffer_validate(buf); + + len = evbuffer_get_length(buf); + tt_assert(len == data_len+2); + str = (char *)evbuffer_pullup(buf, -1); + tt_assert(str[len-3] == 'P'); + tt_assert(str[len-2] == '2'); + tt_assert(str[len-1] == '3'); +end: + evbuffer_free(buf); +} + +static int reference_cb_called; +static void +reference_cb(const void *data, size_t len, void *extra) +{ + tt_str_op(data, ==, "this is what we add as read-only memory."); + tt_int_op(len, ==, strlen(data)); + tt_want(extra == (void *)0xdeadaffe); + ++reference_cb_called; +end: + ; +} + +static void +test_evbuffer_reference(void *ptr) +{ + struct evbuffer *src = evbuffer_new(); + struct evbuffer *dst = evbuffer_new(); + struct evbuffer_iovec v[1]; + const char *data = "this is what we add as read-only memory."; + reference_cb_called = 0; + + tt_assert(evbuffer_add_reference(src, data, strlen(data), + reference_cb, (void *)0xdeadaffe) != -1); + + evbuffer_reserve_space(dst, strlen(data), v, 1); + tt_assert(evbuffer_remove(src, v[0].iov_base, 10) != -1); + + evbuffer_validate(src); + evbuffer_validate(dst); + + /* make sure that we don't write data at the beginning */ + evbuffer_prepend(src, "aaaaa", 5); + evbuffer_validate(src); + evbuffer_drain(src, 5); + + tt_assert(evbuffer_remove(src, ((char*)(v[0].iov_base)) + 10, + strlen(data) - 10) != -1); + + v[0].iov_len = strlen(data); + + evbuffer_commit_space(dst, v, 1); + evbuffer_validate(src); + evbuffer_validate(dst); + + tt_int_op(reference_cb_called, ==, 1); + + tt_assert(!memcmp(evbuffer_pullup(dst, strlen(data)), + data, strlen(data))); + evbuffer_validate(dst); + + end: + evbuffer_free(dst); + evbuffer_free(src); +} + +static void +test_evbuffer_reference2(void *ptr) +{ + struct evbuffer *buf; + static char data[4096]; + int data_len = MIN_BUFFER_SIZE-EVBUFFER_CHAIN_SIZE-10; + char *str; + int len; + + memset(data, 'P', sizeof(data)); + buf = evbuffer_new(); + evbuffer_add(buf, data, data_len); + evbuffer_validate(buf); + evbuffer_expand(buf, 100); + evbuffer_validate(buf); + evbuffer_add_reference(buf, "2", 1, no_cleanup, NULL); + evbuffer_validate(buf); + evbuffer_add_printf(buf, "3"); + evbuffer_validate(buf); + + len = evbuffer_get_length(buf); + tt_assert(len == data_len+2); + str = (char *)evbuffer_pullup(buf, -1); + tt_assert(str[len-3] == 'P'); + tt_assert(str[len-2] == '2'); + tt_assert(str[len-1] == '3'); +end: + evbuffer_free(buf); +} + +static struct event_base *addfile_test_event_base; +static int addfile_test_done_writing; +static int addfile_test_total_written; +static int addfile_test_total_read; + +static void +addfile_test_writecb(evutil_socket_t fd, short what, void *arg) +{ + struct evbuffer *b = arg; + int r; + evbuffer_validate(b); + while (evbuffer_get_length(b)) { + r = evbuffer_write(b, fd); + if (r > 0) { + addfile_test_total_written += r; + TT_BLATHER(("Wrote %d/%d bytes", r, addfile_test_total_written)); + } else { + int e = evutil_socket_geterror(fd); + if (EVUTIL_ERR_RW_RETRIABLE(e)) + return; + tt_fail_perror("write"); + event_base_loopexit(addfile_test_event_base,NULL); + } + evbuffer_validate(b); + } + addfile_test_done_writing = 1; + return; +end: + event_base_loopexit(addfile_test_event_base,NULL); +} + +static void +addfile_test_readcb(evutil_socket_t fd, short what, void *arg) +{ + struct evbuffer *b = arg; + int e, r = 0; + do { + r = evbuffer_read(b, fd, 1024); + if (r > 0) { + addfile_test_total_read += r; + TT_BLATHER(("Read %d/%d bytes", r, addfile_test_total_read)); + } + } while (r > 0); + if (r < 0) { + e = evutil_socket_geterror(fd); + if (! EVUTIL_ERR_RW_RETRIABLE(e)) { + tt_fail_perror("read"); + event_base_loopexit(addfile_test_event_base,NULL); + } + } + if (addfile_test_done_writing && + addfile_test_total_read >= addfile_test_total_written) { + event_base_loopexit(addfile_test_event_base,NULL); + } +} + +static void +test_evbuffer_add_file(void *ptr) +{ + struct basic_test_data *testdata = ptr; + const char *impl = testdata->setup_data; + struct evbuffer *src = evbuffer_new(), *dest = evbuffer_new(); + char *tmpfilename = NULL; + char *data = NULL; + const char *expect_data; + size_t datalen, expect_len; + const char *compare; + int fd = -1; + int want_ismapping = -1, want_cansendfile = -1; + unsigned flags = 0; + int use_segment = 1, use_bigfile = 0, map_from_offset = 0, + view_from_offset = 0; + struct evbuffer_file_segment *seg = NULL; + ev_off_t starting_offset = 0, mapping_len = -1; + ev_off_t segment_offset = 0, segment_len = -1; + struct event *rev=NULL, *wev=NULL; + struct event_base *base = testdata->base; + evutil_socket_t pair[2] = {-1, -1}; + struct evutil_weakrand_state seed = { 123456789U }; + + /* This test is highly parameterized based on substrings of its + * argument. The strings are: */ + tt_assert(impl); + if (strstr(impl, "nosegment")) { + /* If nosegment is set, use the older evbuffer_add_file + * interface */ + use_segment = 0; + } + if (strstr(impl, "bigfile")) { + /* If bigfile is set, use a 512K file. Else use a smaller + * one. */ + use_bigfile = 1; + } + if (strstr(impl, "map_offset")) { + /* If map_offset is set, we build the file segment starting + * from a point other than byte 0 and ending somewhere other + * than the last byte. Otherwise we map the whole thing */ + map_from_offset = 1; + } + if (strstr(impl, "offset_in_segment")) { + /* If offset_in_segment is set, we add a subsection of the + * file semgment starting from a point other than byte 0 of + * the segment. */ + view_from_offset = 1; + } + if (strstr(impl, "sendfile")) { + /* If sendfile is set, we try to use a sendfile/splice style + * backend. */ + flags = EVBUF_FS_DISABLE_MMAP; + want_cansendfile = 1; + want_ismapping = 0; + } else if (strstr(impl, "mmap")) { + /* If sendfile is set, we try to use a mmap/CreateFileMapping + * style backend. */ + flags = EVBUF_FS_DISABLE_SENDFILE; + want_ismapping = 1; + want_cansendfile = 0; + } else if (strstr(impl, "linear")) { + /* If linear is set, we try to use a read-the-whole-thing + * backend. */ + flags = EVBUF_FS_DISABLE_SENDFILE|EVBUF_FS_DISABLE_MMAP; + want_ismapping = 0; + want_cansendfile = 0; + } else if (strstr(impl, "default")) { + /* The caller doesn't care which backend we use. */ + ; + } else { + /* The caller must choose a backend. */ + TT_DIE(("Didn't recognize the implementation")); + } + + if (use_bigfile) { + unsigned int i; + datalen = 1024*512; + data = malloc(1024*512); + tt_assert(data); + for (i = 0; i < datalen; ++i) + data[i] = (char)evutil_weakrand_(&seed); + } else { + data = strdup("here is a relatively small string."); + tt_assert(data); + datalen = strlen(data); + } + + fd = regress_make_tmpfile(data, datalen, &tmpfilename); + + if (map_from_offset) { + starting_offset = datalen/4 + 1; + mapping_len = datalen / 2 - 1; + expect_data = data + starting_offset; + expect_len = mapping_len; + } else { + expect_data = data; + expect_len = datalen; + } + if (view_from_offset) { + tt_assert(use_segment); /* Can't do this with add_file*/ + segment_offset = expect_len / 3; + segment_len = expect_len / 2; + expect_data = expect_data + segment_offset; + expect_len = segment_len; + } + + if (use_segment) { + seg = evbuffer_file_segment_new(fd, starting_offset, + mapping_len, flags); + tt_assert(seg); + if (want_ismapping >= 0) { + if (seg->is_mapping != (unsigned)want_ismapping) + tt_skip(); + } + if (want_cansendfile >= 0) { + if (seg->can_sendfile != (unsigned)want_cansendfile) + tt_skip(); + } + } + + /* Say that it drains to a fd so that we can use sendfile. */ + evbuffer_set_flags(src, EVBUFFER_FLAG_DRAINS_TO_FD); + +#if defined(EVENT__HAVE_SENDFILE) && defined(__sun__) && defined(__svr4__) + /* We need to use a pair of AF_INET sockets, since Solaris + doesn't support sendfile() over AF_UNIX. */ + if (evutil_ersatz_socketpair_(AF_INET, SOCK_STREAM, 0, pair) == -1) + tt_abort_msg("ersatz_socketpair failed"); +#else + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + tt_abort_msg("socketpair failed"); +#endif + evutil_make_socket_nonblocking(pair[0]); + evutil_make_socket_nonblocking(pair[1]); + + tt_assert(fd != -1); + + if (use_segment) { + tt_assert(evbuffer_add_file_segment(src, seg, + segment_offset, segment_len)!=-1); + } else { + tt_assert(evbuffer_add_file(src, fd, starting_offset, + mapping_len) != -1); + } + + evbuffer_validate(src); + + addfile_test_event_base = base; + addfile_test_done_writing = 0; + addfile_test_total_written = 0; + addfile_test_total_read = 0; + + wev = event_new(base, pair[0], EV_WRITE|EV_PERSIST, + addfile_test_writecb, src); + rev = event_new(base, pair[1], EV_READ|EV_PERSIST, + addfile_test_readcb, dest); + + event_add(wev, NULL); + event_add(rev, NULL); + event_base_dispatch(base); + + evbuffer_validate(src); + evbuffer_validate(dest); + + tt_assert(addfile_test_done_writing); + tt_int_op(addfile_test_total_written, ==, expect_len); + tt_int_op(addfile_test_total_read, ==, expect_len); + + compare = (char *)evbuffer_pullup(dest, expect_len); + tt_assert(compare != NULL); + if (memcmp(compare, expect_data, expect_len)) { + tt_abort_msg("Data from add_file differs."); + } + + evbuffer_validate(dest); + end: + if (data) + free(data); + if (seg) + evbuffer_file_segment_free(seg); + if (src) + evbuffer_free(src); + if (dest) + evbuffer_free(dest); + if (pair[0] >= 0) + evutil_closesocket(pair[0]); + if (pair[1] >= 0) + evutil_closesocket(pair[1]); + if (wev) + event_free(wev); + if (rev) + event_free(rev); + if (tmpfilename) { + unlink(tmpfilename); + free(tmpfilename); + } +} + +static int file_segment_cleanup_cb_called_count = 0; +static struct evbuffer_file_segment const* file_segment_cleanup_cb_called_with = NULL; +static int file_segment_cleanup_cb_called_with_flags = 0; +static void* file_segment_cleanup_cb_called_with_arg = NULL; +static void +file_segment_cleanup_cp(struct evbuffer_file_segment const* seg, int flags, void* arg) +{ + ++file_segment_cleanup_cb_called_count; + file_segment_cleanup_cb_called_with = seg; + file_segment_cleanup_cb_called_with_flags = flags; + file_segment_cleanup_cb_called_with_arg = arg; +} + +static void +test_evbuffer_file_segment_add_cleanup_cb(void* ptr) +{ + char *tmpfilename = NULL; + int fd = -1; + struct evbuffer *evb = NULL; + struct evbuffer_file_segment *seg = NULL, *segptr; + char const* arg = "token"; + + fd = regress_make_tmpfile("file_segment_test_file", 22, &tmpfilename); + tt_int_op(fd, >=, 0); + + evb = evbuffer_new(); + tt_assert(evb); + + segptr = seg = evbuffer_file_segment_new(fd, 0, -1, 0); + tt_assert(seg); + + evbuffer_file_segment_add_cleanup_cb( + seg, &file_segment_cleanup_cp, (void*)arg); + + tt_assert(fd != -1); + + tt_assert(evbuffer_add_file_segment(evb, seg, 0, -1)!=-1); + + evbuffer_validate(evb); + + tt_int_op(file_segment_cleanup_cb_called_count, ==, 0); + evbuffer_file_segment_free(seg); + seg = NULL; /* Prevent double-free. */ + + tt_int_op(file_segment_cleanup_cb_called_count, ==, 0); + evbuffer_free(evb); + evb = NULL; /* pevent double-free */ + + tt_int_op(file_segment_cleanup_cb_called_count, ==, 1); + tt_assert(file_segment_cleanup_cb_called_with == segptr); + tt_assert(file_segment_cleanup_cb_called_with_flags == 0); + tt_assert(file_segment_cleanup_cb_called_with_arg == (void*)arg); + +end: + if (evb) + evbuffer_free(evb); + if (seg) + evbuffer_file_segment_free(seg); + if (tmpfilename) { + unlink(tmpfilename); + free(tmpfilename); + } +} + +#ifndef EVENT__DISABLE_MM_REPLACEMENT +static void * +failing_malloc(size_t how_much) +{ + errno = ENOMEM; + return NULL; +} +#endif + +static void +test_evbuffer_readln(void *ptr) +{ + struct evbuffer *evb = evbuffer_new(); + struct evbuffer *evb_tmp = evbuffer_new(); + const char *s; + char *cp = NULL; + size_t sz; + +#define tt_line_eq(content) \ + TT_STMT_BEGIN \ + if (!cp || sz != strlen(content) || strcmp(cp, content)) { \ + TT_DIE(("Wanted %s; got %s [%d]", content, cp, (int)sz)); \ + } \ + TT_STMT_END + + /* Test EOL_ANY. */ + s = "complex silly newline\r\n\n\r\n\n\rmore\0\n"; + evbuffer_add(evb, s, strlen(s)+2); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_line_eq("complex silly newline"); + free(cp); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (!cp || sz != 5 || memcmp(cp, "more\0\0", 6)) + tt_abort_msg("Not as expected"); + tt_uint_op(evbuffer_get_length(evb), ==, 0); + evbuffer_validate(evb); + s = "\nno newline"; + evbuffer_add(evb, s, strlen(s)); + free(cp); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_assert(!cp); + evbuffer_validate(evb); + evbuffer_drain(evb, evbuffer_get_length(evb)); + tt_assert(evbuffer_get_length(evb) == 0); + evbuffer_validate(evb); + + /* Test EOL_CRLF */ + s = "Line with\rin the middle\nLine with good crlf\r\n\nfinal\n"; + evbuffer_add(evb, s, strlen(s)); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("Line with\rin the middle"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("Line with good crlf"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("final"); + s = "x"; + evbuffer_validate(evb); + evbuffer_add(evb, s, 1); + evbuffer_validate(evb); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_assert(!cp); + evbuffer_validate(evb); + + /* Test CRLF_STRICT */ + s = " and a bad crlf\nand a good one\r\n\r\nMore\r"; + evbuffer_add(evb, s, strlen(s)); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("x and a bad crlf\nand a good one"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + evbuffer_validate(evb); + evbuffer_add(evb, "\n", 1); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("More"); + free(cp); + tt_assert(evbuffer_get_length(evb) == 0); + evbuffer_validate(evb); + + s = "An internal CR\r is not an eol\r\nNor is a lack of one"; + evbuffer_add(evb, s, strlen(s)); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("An internal CR\r is not an eol"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + evbuffer_validate(evb); + + evbuffer_add(evb, "\r\n", 2); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("Nor is a lack of one"); + free(cp); + tt_assert(evbuffer_get_length(evb) == 0); + evbuffer_validate(evb); + + /* Test LF */ + s = "An\rand a nl\n\nText"; + evbuffer_add(evb, s, strlen(s)); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("An\rand a nl"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_assert(!cp); + free(cp); + evbuffer_add(evb, "\n", 1); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("Text"); + free(cp); + evbuffer_validate(evb); + + /* Test NUL */ + tt_int_op(evbuffer_get_length(evb), ==, 0); + { + char x[] = + "NUL\n\0\0" + "The all-zeros character which may serve\0" + "to accomplish time fill\0and media fill"; + /* Add all but the final NUL of x. */ + evbuffer_add(evb, x, sizeof(x)-1); + } + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq("NUL\n"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq(""); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq("The all-zeros character which may serve"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_line_eq("to accomplish time fill"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_NUL); + tt_ptr_op(cp, ==, NULL); + evbuffer_drain(evb, -1); + + /* Test CRLF_STRICT - across boundaries*/ + s = " and a bad crlf\nand a good one\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + s = "\n\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + s = "\nMore\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(" and a bad crlf\nand a good one"); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(""); + free(cp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + free(cp); + evbuffer_validate(evb); + evbuffer_add(evb, "\n", 1); + evbuffer_validate(evb); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("More"); + free(cp); cp = NULL; + evbuffer_validate(evb); + tt_assert(evbuffer_get_length(evb) == 0); + + /* Test memory problem*/ + s = "one line\ntwo line\nblue line"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_validate(evb); + evbuffer_add_buffer(evb, evb_tmp); + evbuffer_validate(evb); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("one line"); + free(cp); cp = NULL; + evbuffer_validate(evb); + + /* the next call to readline should fail */ +#ifndef EVENT__DISABLE_MM_REPLACEMENT + event_set_mem_functions(failing_malloc, realloc, free); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_assert(cp == NULL); + evbuffer_validate(evb); + + /* now we should get the next line back */ + event_set_mem_functions(malloc, realloc, free); +#endif + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("two line"); + free(cp); cp = NULL; + evbuffer_validate(evb); + + end: + evbuffer_free(evb); + evbuffer_free(evb_tmp); + if (cp) free(cp); +} + +static void +test_evbuffer_search_eol(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer_ptr ptr1, ptr2; + const char *s; + size_t eol_len; + + s = "string! \r\n\r\nx\n"; + evbuffer_add(buf, s, strlen(s)); + eol_len = -1; + ptr1 = evbuffer_search_eol(buf, NULL, &eol_len, EVBUFFER_EOL_CRLF); + tt_int_op(ptr1.pos, ==, 8); + tt_int_op(eol_len, ==, 2); + + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_CRLF); + tt_int_op(ptr2.pos, ==, 8); + tt_int_op(eol_len, ==, 2); + + evbuffer_ptr_set(buf, &ptr1, 1, EVBUFFER_PTR_ADD); + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_CRLF); + tt_int_op(ptr2.pos, ==, 9); + tt_int_op(eol_len, ==, 1); + + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_CRLF_STRICT); + tt_int_op(ptr2.pos, ==, 10); + tt_int_op(eol_len, ==, 2); + + eol_len = -1; + ptr1 = evbuffer_search_eol(buf, NULL, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr1.pos, ==, 9); + tt_int_op(eol_len, ==, 1); + + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr2.pos, ==, 9); + tt_int_op(eol_len, ==, 1); + + evbuffer_ptr_set(buf, &ptr1, 1, EVBUFFER_PTR_ADD); + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr2.pos, ==, 11); + tt_int_op(eol_len, ==, 1); + + tt_assert(evbuffer_ptr_set(buf, &ptr1, evbuffer_get_length(buf), EVBUFFER_PTR_SET) == 0); + eol_len = -1; + ptr2 = evbuffer_search_eol(buf, &ptr1, &eol_len, EVBUFFER_EOL_LF); + tt_int_op(ptr2.pos, ==, -1); + tt_int_op(eol_len, ==, 0); + +end: + evbuffer_free(buf); +} + +static void +test_evbuffer_iterative(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + const char *abc = "abcdefghijklmnopqrstvuwxyzabcdefghijklmnopqrstvuwxyzabcdefghijklmnopqrstvuwxyzabcdefghijklmnopqrstvuwxyz"; + unsigned i, j, sum, n; + + sum = 0; + n = 0; + for (i = 0; i < 1000; ++i) { + for (j = 1; j < strlen(abc); ++j) { + char format[32]; + evutil_snprintf(format, sizeof(format), "%%%u.%us", j, j); + evbuffer_add_printf(buf, format, abc); + + /* Only check for rep violations every so often. + Walking over the whole list of chains can get + pretty expensive as it gets long. + */ + if ((n % 337) == 0) + evbuffer_validate(buf); + + sum += j; + n++; + } + } + evbuffer_validate(buf); + + tt_uint_op(sum, ==, evbuffer_get_length(buf)); + + { + size_t a,w,u; + a=w=u=0; + evbuffer_get_waste(buf, &a, &w, &u); + if (0) + printf("Allocated: %u.\nWasted: %u.\nUsed: %u.", + (unsigned)a, (unsigned)w, (unsigned)u); + tt_assert( ((double)w)/a < .125); + } + end: + evbuffer_free(buf); + +} + +static void +test_evbuffer_find(void *ptr) +{ + unsigned char* p; + const char* test1 = "1234567890\r\n"; + const char* test2 = "1234567890\r"; +#define EVBUFFER_INITIAL_LENGTH 256 + char test3[EVBUFFER_INITIAL_LENGTH]; + unsigned int i; + struct evbuffer * buf = evbuffer_new(); + + tt_assert(buf); + + /* make sure evbuffer_find doesn't match past the end of the buffer */ + evbuffer_add(buf, (unsigned char*)test1, strlen(test1)); + evbuffer_validate(buf); + evbuffer_drain(buf, strlen(test1)); + evbuffer_validate(buf); + evbuffer_add(buf, (unsigned char*)test2, strlen(test2)); + evbuffer_validate(buf); + p = evbuffer_find(buf, (unsigned char*)"\r\n", 2); + tt_want(p == NULL); + + /* + * drain the buffer and do another find; in r309 this would + * read past the allocated buffer causing a valgrind error. + */ + evbuffer_drain(buf, strlen(test2)); + evbuffer_validate(buf); + for (i = 0; i < EVBUFFER_INITIAL_LENGTH; ++i) + test3[i] = 'a'; + test3[EVBUFFER_INITIAL_LENGTH - 1] = 'x'; + evbuffer_add(buf, (unsigned char *)test3, EVBUFFER_INITIAL_LENGTH); + evbuffer_validate(buf); + p = evbuffer_find(buf, (unsigned char *)"xy", 2); + tt_want(p == NULL); + + /* simple test for match at end of allocated buffer */ + p = evbuffer_find(buf, (unsigned char *)"ax", 2); + tt_assert(p != NULL); + tt_want(strncmp((char*)p, "ax", 2) == 0); + +end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_ptr_set(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer_ptr pos; + struct evbuffer_iovec v[1]; + + tt_assert(buf); + + tt_int_op(evbuffer_get_length(buf), ==, 0); + + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + tt_assert(pos.pos == 0); + tt_assert(evbuffer_ptr_set(buf, &pos, 1, EVBUFFER_PTR_ADD) == -1); + tt_assert(pos.pos == -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 1, EVBUFFER_PTR_SET) == -1); + tt_assert(pos.pos == -1); + + /* create some chains */ + evbuffer_reserve_space(buf, 5000, v, 1); + v[0].iov_len = 5000; + memset(v[0].iov_base, 1, v[0].iov_len); + evbuffer_commit_space(buf, v, 1); + evbuffer_validate(buf); + + evbuffer_reserve_space(buf, 4000, v, 1); + v[0].iov_len = 4000; + memset(v[0].iov_base, 2, v[0].iov_len); + evbuffer_commit_space(buf, v, 1); + + evbuffer_reserve_space(buf, 3000, v, 1); + v[0].iov_len = 3000; + memset(v[0].iov_base, 3, v[0].iov_len); + evbuffer_commit_space(buf, v, 1); + evbuffer_validate(buf); + + tt_int_op(evbuffer_get_length(buf), ==, 12000); + + tt_assert(evbuffer_ptr_set(buf, &pos, 13000, EVBUFFER_PTR_SET) == -1); + tt_assert(pos.pos == -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + tt_assert(pos.pos == 0); + tt_assert(evbuffer_ptr_set(buf, &pos, 13000, EVBUFFER_PTR_ADD) == -1); + + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + tt_assert(pos.pos == 0); + tt_assert(evbuffer_ptr_set(buf, &pos, 10000, EVBUFFER_PTR_ADD) == 0); + tt_assert(pos.pos == 10000); + tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == 0); + tt_assert(pos.pos == 11000); + tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == 0); + tt_assert(pos.pos == 12000); + tt_assert(evbuffer_ptr_set(buf, &pos, 1000, EVBUFFER_PTR_ADD) == -1); + tt_assert(pos.pos == -1); + +end: + if (buf) + evbuffer_free(buf); +} + +static void +test_evbuffer_search(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer *tmp = evbuffer_new(); + struct evbuffer_ptr pos, end; + + tt_assert(buf); + tt_assert(tmp); + + pos = evbuffer_search(buf, "x", 1, NULL); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search(buf, "x", 1, &pos); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "x", 1, &pos, &pos); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 0, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "x", 1, &pos, NULL); + tt_int_op(pos.pos, ==, -1); + + /* set up our chains */ + evbuffer_add_printf(tmp, "hello"); /* 5 chars */ + evbuffer_add_buffer(buf, tmp); + evbuffer_add_printf(tmp, "foo"); /* 3 chars */ + evbuffer_add_buffer(buf, tmp); + evbuffer_add_printf(tmp, "cat"); /* 3 chars */ + evbuffer_add_buffer(buf, tmp); + evbuffer_add_printf(tmp, "attack"); + evbuffer_add_buffer(buf, tmp); + + pos = evbuffer_search(buf, "attack", 6, NULL); + tt_int_op(pos.pos, ==, 11); + pos = evbuffer_search(buf, "attacker", 8, NULL); + tt_int_op(pos.pos, ==, -1); + + /* test continuing search */ + pos = evbuffer_search(buf, "oc", 2, NULL); + tt_int_op(pos.pos, ==, 7); + pos = evbuffer_search(buf, "cat", 3, &pos); + tt_int_op(pos.pos, ==, 8); + pos = evbuffer_search(buf, "tacking", 7, &pos); + tt_int_op(pos.pos, ==, -1); + + evbuffer_ptr_set(buf, &pos, 5, EVBUFFER_PTR_SET); + pos = evbuffer_search(buf, "foo", 3, &pos); + tt_int_op(pos.pos, ==, 5); + + evbuffer_ptr_set(buf, &pos, 2, EVBUFFER_PTR_ADD); + pos = evbuffer_search(buf, "tat", 3, &pos); + tt_int_op(pos.pos, ==, 10); + + /* test bounded search. */ + /* Set "end" to the first t in "attack". */ + evbuffer_ptr_set(buf, &end, 12, EVBUFFER_PTR_SET); + pos = evbuffer_search_range(buf, "foo", 3, NULL, &end); + tt_int_op(pos.pos, ==, 5); + pos = evbuffer_search_range(buf, "foocata", 7, NULL, &end); + tt_int_op(pos.pos, ==, 5); + pos = evbuffer_search_range(buf, "foocatat", 8, NULL, &end); + tt_int_op(pos.pos, ==, -1); + pos = evbuffer_search_range(buf, "ack", 3, NULL, &end); + tt_int_op(pos.pos, ==, -1); + + /* Set "end" after the last byte in the buffer. */ + tt_assert(evbuffer_ptr_set(buf, &end, 17, EVBUFFER_PTR_SET) == 0); + + pos = evbuffer_search_range(buf, "attack", 6, NULL, &end); + tt_int_op(pos.pos, ==, 11); + tt_assert(evbuffer_ptr_set(buf, &pos, 11, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "attack", 6, &pos, &end); + tt_int_op(pos.pos, ==, 11); + tt_assert(evbuffer_ptr_set(buf, &pos, 17, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "attack", 6, &pos, &end); + tt_int_op(pos.pos, ==, -1); + tt_assert(evbuffer_ptr_set(buf, &pos, 17, EVBUFFER_PTR_SET) == 0); + pos = evbuffer_search_range(buf, "attack", 6, &pos, NULL); + tt_int_op(pos.pos, ==, -1); + +end: + if (buf) + evbuffer_free(buf); + if (tmp) + evbuffer_free(tmp); +} + +static void +log_change_callback(struct evbuffer *buffer, + const struct evbuffer_cb_info *cbinfo, + void *arg) +{ + + size_t old_len = cbinfo->orig_size; + size_t new_len = old_len + cbinfo->n_added - cbinfo->n_deleted; + struct evbuffer *out = arg; + evbuffer_add_printf(out, "%lu->%lu; ", (unsigned long)old_len, + (unsigned long)new_len); +} +static void +self_draining_callback(struct evbuffer *evbuffer, size_t old_len, + size_t new_len, void *arg) +{ + if (new_len > old_len) + evbuffer_drain(evbuffer, new_len); +} + +static void +test_evbuffer_callbacks(void *ptr) +{ + struct evbuffer *buf = evbuffer_new(); + struct evbuffer *buf_out1 = evbuffer_new(); + struct evbuffer *buf_out2 = evbuffer_new(); + struct evbuffer_cb_entry *cb1, *cb2; + + tt_assert(buf); + tt_assert(buf_out1); + tt_assert(buf_out2); + + cb1 = evbuffer_add_cb(buf, log_change_callback, buf_out1); + cb2 = evbuffer_add_cb(buf, log_change_callback, buf_out2); + + /* Let's run through adding and deleting some stuff from the buffer + * and turning the callbacks on and off and removing them. The callback + * adds a summary of length changes to buf_out1/buf_out2 when called. */ + /* size: 0-> 36. */ + evbuffer_add_printf(buf, "The %d magic words are spotty pudding", 2); + evbuffer_validate(buf); + evbuffer_cb_clear_flags(buf, cb2, EVBUFFER_CB_ENABLED); + evbuffer_drain(buf, 10); /*36->26*/ + evbuffer_validate(buf); + evbuffer_prepend(buf, "Hello", 5);/*26->31*/ + evbuffer_cb_set_flags(buf, cb2, EVBUFFER_CB_ENABLED); + evbuffer_add_reference(buf, "Goodbye", 7, NULL, NULL); /*31->38*/ + evbuffer_remove_cb_entry(buf, cb1); + evbuffer_validate(buf); + evbuffer_drain(buf, evbuffer_get_length(buf)); /*38->0*/; + tt_assert(-1 == evbuffer_remove_cb(buf, log_change_callback, NULL)); + evbuffer_add(buf, "X", 1); /* 0->1 */ + tt_assert(!evbuffer_remove_cb(buf, log_change_callback, buf_out2)); + evbuffer_validate(buf); + + tt_str_op((const char *) evbuffer_pullup(buf_out1, -1), ==, + "0->36; 36->26; 26->31; 31->38; "); + tt_str_op((const char *) evbuffer_pullup(buf_out2, -1), ==, + "0->36; 31->38; 38->0; 0->1; "); + evbuffer_drain(buf_out1, evbuffer_get_length(buf_out1)); + evbuffer_drain(buf_out2, evbuffer_get_length(buf_out2)); + /* Let's test the obsolete buffer_setcb function too. */ + cb1 = evbuffer_add_cb(buf, log_change_callback, buf_out1); + tt_assert(cb1 != NULL); + cb2 = evbuffer_add_cb(buf, log_change_callback, buf_out2); + tt_assert(cb2 != NULL); + tt_int_op(evbuffer_setcb(buf, self_draining_callback, NULL), ==, 0); + evbuffer_add_printf(buf, "This should get drained right away."); + tt_uint_op(evbuffer_get_length(buf), ==, 0); + tt_uint_op(evbuffer_get_length(buf_out1), ==, 0); + tt_uint_op(evbuffer_get_length(buf_out2), ==, 0); + tt_int_op(evbuffer_setcb(buf, NULL, NULL), ==, 0); + evbuffer_add_printf(buf, "This will not."); + tt_str_op((const char *) evbuffer_pullup(buf, -1), ==, "This will not."); + evbuffer_validate(buf); + evbuffer_drain(buf, evbuffer_get_length(buf)); + evbuffer_validate(buf); +#if 0 + /* Now let's try a suspended callback. */ + cb1 = evbuffer_add_cb(buf, log_change_callback, buf_out1); + cb2 = evbuffer_add_cb(buf, log_change_callback, buf_out2); + evbuffer_cb_suspend(buf,cb2); + evbuffer_prepend(buf,"Hello world",11); /*0->11*/ + evbuffer_validate(buf); + evbuffer_cb_suspend(buf,cb1); + evbuffer_add(buf,"more",4); /* 11->15 */ + evbuffer_cb_unsuspend(buf,cb2); + evbuffer_drain(buf, 4); /* 15->11 */ + evbuffer_cb_unsuspend(buf,cb1); + evbuffer_drain(buf, evbuffer_get_length(buf)); /* 11->0 */ + + tt_str_op(evbuffer_pullup(buf_out1, -1), ==, + "0->11; 11->11; 11->0; "); + tt_str_op(evbuffer_pullup(buf_out2, -1), ==, + "0->15; 15->11; 11->0; "); +#endif + + /* the next call to readline should fail */ +#ifndef EVENT__DISABLE_MM_REPLACEMENT + event_set_mem_functions(failing_malloc, realloc, free); + tt_int_op(evbuffer_setcb(buf, self_draining_callback, NULL), ==, -1); + evbuffer_validate(buf); + event_set_mem_functions(malloc, realloc, free); +#endif + + end: + if (buf) + evbuffer_free(buf); + if (buf_out1) + evbuffer_free(buf_out1); + if (buf_out2) + evbuffer_free(buf_out2); +} + +static int ref_done_cb_called_count = 0; +static void *ref_done_cb_called_with = NULL; +static const void *ref_done_cb_called_with_data = NULL; +static size_t ref_done_cb_called_with_len = 0; +static void ref_done_cb(const void *data, size_t len, void *info) +{ + ++ref_done_cb_called_count; + ref_done_cb_called_with = info; + ref_done_cb_called_with_data = data; + ref_done_cb_called_with_len = len; +} + +static void +test_evbuffer_add_reference(void *ptr) +{ + const char chunk1[] = "If you have found the answer to such a problem"; + const char chunk2[] = "you ought to write it up for publication"; + /* -- Knuth's "Notes on the Exercises" from TAOCP */ + char tmp[16]; + size_t len1 = strlen(chunk1), len2=strlen(chunk2); + + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + evbuffer_add_reference(buf1, chunk1, len1, ref_done_cb, (void*)111); + evbuffer_add(buf1, ", ", 2); + evbuffer_add_reference(buf1, chunk2, len2, ref_done_cb, (void*)222); + tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2); + + /* Make sure we can drain a little from a reference. */ + tt_int_op(evbuffer_remove(buf1, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "If you", 6), ==, 0); + tt_int_op(evbuffer_remove(buf1, tmp, 5), ==, 5); + tt_int_op(memcmp(tmp, " have", 5), ==, 0); + + /* Make sure that prepending does not meddle with immutable data */ + tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0); + tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); + evbuffer_validate(buf1); + + /* Make sure that when the chunk is over, the callback is invoked. */ + evbuffer_drain(buf1, 7); /* Remove prepended stuff. */ + evbuffer_drain(buf1, len1-11-1); /* remove all but one byte of chunk1 */ + tt_int_op(ref_done_cb_called_count, ==, 0); + evbuffer_remove(buf1, tmp, 1); + tt_int_op(tmp[0], ==, 'm'); + tt_assert(ref_done_cb_called_with == (void*)111); + tt_assert(ref_done_cb_called_with_data == chunk1); + tt_assert(ref_done_cb_called_with_len == len1); + tt_int_op(ref_done_cb_called_count, ==, 1); + evbuffer_validate(buf1); + + /* Drain some of the remaining chunk, then add it to another buffer */ + evbuffer_drain(buf1, 6); /* Remove the ", you ". */ + buf2 = evbuffer_new(); + tt_assert(buf2); + tt_int_op(ref_done_cb_called_count, ==, 1); + evbuffer_add(buf2, "I ", 2); + + evbuffer_add_buffer(buf2, buf1); + tt_int_op(ref_done_cb_called_count, ==, 1); + evbuffer_remove(buf2, tmp, 16); + tt_int_op(memcmp("I ought to write", tmp, 16), ==, 0); + evbuffer_drain(buf2, evbuffer_get_length(buf2)); + tt_int_op(ref_done_cb_called_count, ==, 2); + tt_assert(ref_done_cb_called_with == (void*)222); + evbuffer_validate(buf2); + + /* Now add more stuff to buf1 and make sure that it gets removed on + * free. */ + evbuffer_add(buf1, "You shake and shake the ", 24); + evbuffer_add_reference(buf1, "ketchup bottle", 14, ref_done_cb, + (void*)3333); + evbuffer_add(buf1, ". Nothing comes and then a lot'll.", 35); + evbuffer_free(buf1); + buf1 = NULL; + tt_int_op(ref_done_cb_called_count, ==, 3); + tt_assert(ref_done_cb_called_with == (void*)3333); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_multicast(void *ptr) +{ + const char chunk1[] = "If you have found the answer to such a problem"; + const char chunk2[] = "you ought to write it up for publication"; + /* -- Knuth's "Notes on the Exercises" from TAOCP */ + char tmp[16]; + size_t len1 = strlen(chunk1), len2=strlen(chunk2); + + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + evbuffer_add(buf1, chunk1, len1); + evbuffer_add(buf1, ", ", 2); + evbuffer_add(buf1, chunk2, len2); + tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add_buffer_reference(buf2, buf1), ==, 0); + /* nested references are not allowed */ + tt_int_op(evbuffer_add_buffer_reference(buf2, buf2), ==, -1); + tt_int_op(evbuffer_add_buffer_reference(buf1, buf2), ==, -1); + + /* both buffers contain the same amount of data */ + tt_int_op(evbuffer_get_length(buf1), ==, evbuffer_get_length(buf1)); + + /* Make sure we can drain a little from the first buffer. */ + tt_int_op(evbuffer_remove(buf1, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "If you", 6), ==, 0); + tt_int_op(evbuffer_remove(buf1, tmp, 5), ==, 5); + tt_int_op(memcmp(tmp, " have", 5), ==, 0); + + /* Make sure that prepending does not meddle with immutable data */ + tt_int_op(evbuffer_prepend(buf1, "I have ", 7), ==, 0); + tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); + evbuffer_validate(buf1); + + /* Make sure we can drain a little from the second buffer. */ + tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "If you", 6), ==, 0); + tt_int_op(evbuffer_remove(buf2, tmp, 5), ==, 5); + tt_int_op(memcmp(tmp, " have", 5), ==, 0); + + /* Make sure that prepending does not meddle with immutable data */ + tt_int_op(evbuffer_prepend(buf2, "I have ", 7), ==, 0); + tt_int_op(memcmp(chunk1, "If you", 6), ==, 0); + evbuffer_validate(buf2); + + /* Make sure the data can be read from the second buffer when the first is freed */ + evbuffer_free(buf1); + buf1 = NULL; + + tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, "I have", 6), ==, 0); + + tt_int_op(evbuffer_remove(buf2, tmp, 6), ==, 6); + tt_int_op(memcmp(tmp, " foun", 6), ==, 0); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_multicast_drain(void *ptr) +{ + const char chunk1[] = "If you have found the answer to such a problem"; + const char chunk2[] = "you ought to write it up for publication"; + /* -- Knuth's "Notes on the Exercises" from TAOCP */ + size_t len1 = strlen(chunk1), len2=strlen(chunk2); + + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + evbuffer_add(buf1, chunk1, len1); + evbuffer_add(buf1, ", ", 2); + evbuffer_add(buf1, chunk2, len2); + tt_int_op(evbuffer_get_length(buf1), ==, len1+len2+2); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + tt_int_op(evbuffer_add_buffer_reference(buf2, buf1), ==, 0); + tt_int_op(evbuffer_get_length(buf2), ==, len1+len2+2); + tt_int_op(evbuffer_drain(buf1, evbuffer_get_length(buf1)), ==, 0); + tt_int_op(evbuffer_get_length(buf2), ==, len1+len2+2); + tt_int_op(evbuffer_drain(buf2, evbuffer_get_length(buf2)), ==, 0); + evbuffer_validate(buf1); + evbuffer_validate(buf2); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +check_prepend(struct evbuffer *buffer, + const struct evbuffer_cb_info *cbinfo, + void *arg) +{ + tt_int_op(cbinfo->orig_size, ==, 3); + tt_int_op(cbinfo->n_added, ==, 8096); + tt_int_op(cbinfo->n_deleted, ==, 0); +end: + ; +} +/* Some cases that we didn't get in test_evbuffer() above, for more coverage. */ +static void +test_evbuffer_prepend(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + char tmp[128], *buffer = malloc(8096); + int n; + + buf1 = evbuffer_new(); + tt_assert(buf1); + + /* Case 0: The evbuffer is entirely empty. */ + evbuffer_prepend(buf1, "This string has 29 characters", 29); + evbuffer_validate(buf1); + + /* Case 1: Prepend goes entirely in new chunk. */ + evbuffer_prepend(buf1, "Short.", 6); + evbuffer_validate(buf1); + + /* Case 2: prepend goes entirely in first chunk. */ + evbuffer_drain(buf1, 6+11); + evbuffer_prepend(buf1, "it", 2); + evbuffer_validate(buf1); + tt_assert(!memcmp(buf1->first->buffer+buf1->first->misalign, + "it has", 6)); + + /* Case 3: prepend is split over multiple chunks. */ + evbuffer_prepend(buf1, "It is no longer true to say ", 28); + evbuffer_validate(buf1); + n = evbuffer_remove(buf1, tmp, sizeof(tmp)-1); + tt_int_op(n, >=, 0); + tmp[n]='\0'; + tt_str_op(tmp,==,"It is no longer true to say it has 29 characters"); + + buf2 = evbuffer_new(); + tt_assert(buf2); + + /* Case 4: prepend a buffer to an empty buffer. */ + n = 999; + evbuffer_add_printf(buf1, "Here is string %d. ", n++); + evbuffer_prepend_buffer(buf2, buf1); + evbuffer_validate(buf2); + + /* Case 5: prepend a buffer to a nonempty buffer. */ + evbuffer_add_printf(buf1, "Here is string %d. ", n++); + evbuffer_prepend_buffer(buf2, buf1); + evbuffer_validate(buf2); + evbuffer_validate(buf1); + n = evbuffer_remove(buf2, tmp, sizeof(tmp)-1); + tt_int_op(n, >=, 0); + tmp[n]='\0'; + tt_str_op(tmp,==,"Here is string 1000. Here is string 999. "); + + /* Case 5: evbuffer_prepend() will need a new buffer, with callbacks */ + memset(buffer, 'A', 8096); + evbuffer_free(buf2); + buf2 = evbuffer_new(); + tt_assert(buf2); + evbuffer_prepend(buf2, "foo", 3); + evbuffer_add_cb(buf2, check_prepend, NULL); + evbuffer_prepend(buf2, buffer, 8096); + evbuffer_remove_cb(buf2, check_prepend, NULL); + evbuffer_validate(buf2); + tt_nstr_op(8096,(char *)evbuffer_pullup(buf2, 8096),==,buffer); + evbuffer_drain(buf2, 8096); + tt_nstr_op(3,(char *)evbuffer_pullup(buf2, 3),==,"foo"); + evbuffer_drain(buf2, 3); + +end: + free(buffer); + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); + +} + +static void +test_evbuffer_empty_reference_prepend(void *ptr) +{ + struct evbuffer *buf = NULL; + + buf = evbuffer_new(); + tt_assert(buf); + + /** empty chain could leave invalid last_with_datap */ + evbuffer_add_reference(buf, "", 0, NULL, NULL); + evbuffer_validate(buf); + evbuffer_prepend(buf, "foo", 3); + + evbuffer_validate(buf); + tt_assert(!strncmp((char *)evbuffer_pullup(buf, -1), "foo", 3)); + evbuffer_validate(buf); + +end: + if (buf) + evbuffer_free(buf); +} +static void +test_evbuffer_empty_reference_prepend_buffer(void *ptr) +{ + struct evbuffer *buf1 = NULL, *buf2 = NULL; + + buf1 = evbuffer_new(); + tt_assert(buf1); + buf2 = evbuffer_new(); + tt_assert(buf2); + + /** empty chain could leave invalid last_with_datap */ + evbuffer_add_reference(buf1, "", 0, NULL, NULL); + evbuffer_validate(buf1); + evbuffer_add(buf2, "foo", 3); + evbuffer_validate(buf2); + evbuffer_prepend_buffer(buf2, buf1); + evbuffer_validate(buf2); + + tt_assert(!strncmp((char *)evbuffer_pullup(buf2, -1), "foo", 3)); + evbuffer_validate(buf2); + + tt_assert(evbuffer_pullup(buf1, -1) == NULL); + evbuffer_validate(buf2); + +end: + if (buf1) + evbuffer_free(buf1); + if (buf2) + evbuffer_free(buf2); +} + +static void +test_evbuffer_peek_first_gt(void *info) +{ + struct evbuffer *buf = NULL, *tmp_buf = NULL; + struct evbuffer_ptr ptr; + struct evbuffer_iovec v[2]; + + buf = evbuffer_new(); + tmp_buf = evbuffer_new(); + evbuffer_add_printf(tmp_buf, "Contents of chunk 100\n"); + evbuffer_add_buffer(buf, tmp_buf); + evbuffer_add_printf(tmp_buf, "Contents of chunk 1\n"); + evbuffer_add_buffer(buf, tmp_buf); + + evbuffer_ptr_set(buf, &ptr, 0, EVBUFFER_PTR_SET); + + /** The only case that matters*/ + tt_int_op(evbuffer_peek(buf, -1, &ptr, NULL, 0), ==, 2); + /** Just in case */ + tt_int_op(evbuffer_peek(buf, -1, &ptr, v, 2), ==, 2); + + evbuffer_ptr_set(buf, &ptr, 20, EVBUFFER_PTR_ADD); + tt_int_op(evbuffer_peek(buf, -1, &ptr, NULL, 0), ==, 2); + tt_int_op(evbuffer_peek(buf, -1, &ptr, v, 2), ==, 2); + tt_int_op(evbuffer_peek(buf, 2, &ptr, NULL, 0), ==, 1); + tt_int_op(evbuffer_peek(buf, 2, &ptr, v, 2), ==, 1); + tt_int_op(evbuffer_peek(buf, 3, &ptr, NULL, 0), ==, 2); + tt_int_op(evbuffer_peek(buf, 3, &ptr, v, 2), ==, 2); + +end: + if (buf) + evbuffer_free(buf); + if (tmp_buf) + evbuffer_free(tmp_buf); +} + +static void +test_evbuffer_peek(void *info) +{ + struct evbuffer *buf = NULL, *tmp_buf = NULL; + int i; + struct evbuffer_iovec v[20]; + struct evbuffer_ptr ptr; + +#define tt_iov_eq(v, s) \ + tt_int_op((v)->iov_len, ==, strlen(s)); \ + tt_assert(!memcmp((v)->iov_base, (s), strlen(s))) + + /* Let's make a very fragmented buffer. */ + buf = evbuffer_new(); + tmp_buf = evbuffer_new(); + for (i = 0; i < 16; ++i) { + evbuffer_add_printf(tmp_buf, "Contents of chunk [%d]\n", i); + evbuffer_add_buffer(buf, tmp_buf); + } + + /* How many chunks do we need for everything? */ + i = evbuffer_peek(buf, -1, NULL, NULL, 0); + tt_int_op(i, ==, 16); + + /* Simple peek: get everything. */ + i = evbuffer_peek(buf, -1, NULL, v, 20); + tt_int_op(i, ==, 16); /* we used only 16 chunks. */ + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_iov_eq(&v[3], "Contents of chunk [3]\n"); + tt_iov_eq(&v[12], "Contents of chunk [12]\n"); + tt_iov_eq(&v[15], "Contents of chunk [15]\n"); + + /* Just get one chunk worth. */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, -1, NULL, v, 1); + tt_int_op(i, ==, 1); + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_assert(v[1].iov_base == NULL); + + /* Suppose we want at least the first 40 bytes. */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 40, NULL, v, 16); + tt_int_op(i, ==, 2); + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_iov_eq(&v[1], "Contents of chunk [1]\n"); + tt_assert(v[2].iov_base == NULL); + + /* How many chunks do we need for 100 bytes? */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 100, NULL, NULL, 0); + tt_int_op(i, ==, 5); + tt_assert(v[0].iov_base == NULL); + + /* Now we ask for more bytes than we provide chunks for */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 60, NULL, v, 1); + tt_int_op(i, ==, 3); + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_assert(v[1].iov_base == NULL); + + /* Now we ask for more bytes than the buffer has. */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(buf, 65536, NULL, v, 20); + tt_int_op(i, ==, 16); /* we used only 16 chunks. */ + tt_iov_eq(&v[0], "Contents of chunk [0]\n"); + tt_iov_eq(&v[3], "Contents of chunk [3]\n"); + tt_iov_eq(&v[12], "Contents of chunk [12]\n"); + tt_iov_eq(&v[15], "Contents of chunk [15]\n"); + tt_assert(v[16].iov_base == NULL); + + /* What happens if we try an empty buffer? */ + memset(v, 0, sizeof(v)); + i = evbuffer_peek(tmp_buf, -1, NULL, v, 20); + tt_int_op(i, ==, 0); + tt_assert(v[0].iov_base == NULL); + memset(v, 0, sizeof(v)); + i = evbuffer_peek(tmp_buf, 50, NULL, v, 20); + tt_int_op(i, ==, 0); + tt_assert(v[0].iov_base == NULL); + + /* Okay, now time to have fun with pointers. */ + memset(v, 0, sizeof(v)); + evbuffer_ptr_set(buf, &ptr, 30, EVBUFFER_PTR_SET); + i = evbuffer_peek(buf, 50, &ptr, v, 20); + tt_int_op(i, ==, 3); + tt_iov_eq(&v[0], " of chunk [1]\n"); + tt_iov_eq(&v[1], "Contents of chunk [2]\n"); + tt_iov_eq(&v[2], "Contents of chunk [3]\n"); /*more than we asked for*/ + + /* advance to the start of another chain. */ + memset(v, 0, sizeof(v)); + evbuffer_ptr_set(buf, &ptr, 14, EVBUFFER_PTR_ADD); + i = evbuffer_peek(buf, 44, &ptr, v, 20); + tt_int_op(i, ==, 2); + tt_iov_eq(&v[0], "Contents of chunk [2]\n"); + tt_iov_eq(&v[1], "Contents of chunk [3]\n"); /*more than we asked for*/ + + /* peek at the end of the buffer */ + memset(v, 0, sizeof(v)); + tt_assert(evbuffer_ptr_set(buf, &ptr, evbuffer_get_length(buf), EVBUFFER_PTR_SET) == 0); + i = evbuffer_peek(buf, 44, &ptr, v, 20); + tt_int_op(i, ==, 0); + tt_assert(v[0].iov_base == NULL); + +end: + if (buf) + evbuffer_free(buf); + if (tmp_buf) + evbuffer_free(tmp_buf); +} + +/* Check whether evbuffer freezing works right. This is called twice, + once with the argument "start" and once with the argument "end". + When we test "start", we freeze the start of an evbuffer and make sure + that modifying the start of the buffer doesn't work. When we test + "end", we freeze the end of an evbuffer and make sure that modifying + the end of the buffer doesn't work. + */ +static void +test_evbuffer_freeze(void *ptr) +{ + struct basic_test_data *testdata = ptr; + evutil_socket_t *pair = testdata->pair; + struct evbuffer *buf = NULL, *buf_two = NULL, *tmp_buf = NULL; + const char string[] = /* Year's End, Richard Wilbur */ + "I've known the wind by water banks to shake\n" + "The late leaves down, which frozen where they fell\n" + "And held in ice as dancers in a spell\n" + "Fluttered all winter long into a lake..."; + const int start = !strcmp(testdata->setup_data, "start"); + const char tmpfilecontent[] = "file_freeze_test_file"; + char *cp; + char charbuf[128]; + char *tmpfilename = NULL; + int fd = -1; + int r; + size_t orig_length, len; + struct evbuffer_iovec v[1]; + + if (!start) + tt_str_op(testdata->setup_data, ==, "end"); + + buf = evbuffer_new(); + buf_two = evbuffer_new(); + tmp_buf = evbuffer_new(); + tt_assert(tmp_buf); + + evbuffer_add(buf, string, strlen(string)); + evbuffer_add(buf_two, "abc", 3); + evbuffer_add(tmp_buf, "xyz", 3); + evbuffer_freeze(buf, start); /* Freeze the start or the end.*/ + evbuffer_freeze(buf_two, start); + +#define FREEZE_EQ(a, startcase, endcase) \ + do { \ + if (start) { \ + tt_int_op((a), ==, (startcase)); \ + } else { \ + tt_int_op((a), ==, (endcase)); \ + } \ + } while (0) + + + orig_length = evbuffer_get_length(buf); + + /* These functions all manipulate the end of buf. */ + r = evbuffer_add(buf, "abc", 0); + FREEZE_EQ(r, 0, -1); + r = evbuffer_reserve_space(buf, 10, v, 1); + FREEZE_EQ(r, 1, -1); + if (r == 1) { + memset(v[0].iov_base, 'X', 10); + v[0].iov_len = 10; + } + r = evbuffer_commit_space(buf, v, 1); + FREEZE_EQ(r, 0, -1); + r = evbuffer_add_reference(buf, string, 5, NULL, NULL); + FREEZE_EQ(r, 0, -1); + r = evbuffer_add_printf(buf, "Hello %s", "world"); + FREEZE_EQ(r, 11, -1); + + r = evbuffer_add_buffer(buf, tmp_buf); + FREEZE_EQ(r, 0, -1); + len = strlen(tmpfilecontent); + fd = regress_make_tmpfile(tmpfilecontent, len, &tmpfilename); + r = evbuffer_add_file(buf, fd, 0, len); + FREEZE_EQ(r, 0, -1); + + if (start) + evbuffer_add(tmp_buf, "xyz", 3); + + tt_assert(evbuffer_get_length(tmp_buf)); + len = evbuffer_get_length(tmp_buf); + evbuffer_write(tmp_buf, pair[0]); + r = evbuffer_read(buf, pair[1], -1); + FREEZE_EQ(r, len, -1); + + if (!start) + tt_int_op(orig_length, ==, evbuffer_get_length(buf)); + + orig_length = evbuffer_get_length(buf); + + /* These functions all manipulate the start of buf. */ + r = evbuffer_remove(buf, charbuf, 1); + FREEZE_EQ(r, -1, 1); + r = evbuffer_drain(buf, 3); + FREEZE_EQ(r, -1, 0); + r = evbuffer_prepend(buf, "dummy", 5); + FREEZE_EQ(r, -1, 0); + cp = evbuffer_readln(buf, NULL, EVBUFFER_EOL_LF); + FREEZE_EQ(cp==NULL, 1, 0); + if (cp) + free(cp); + + evbuffer_add(tmp_buf, "xyz", 3); + tt_assert(evbuffer_get_length(tmp_buf)); + r = evbuffer_remove_buffer(buf, tmp_buf, 3); + FREEZE_EQ(r, -1, 3); + r = evbuffer_drain(buf, 3); + FREEZE_EQ(r, -1, 0); + r = evbuffer_prepend_buffer(buf, tmp_buf); + FREEZE_EQ(r, -1, 0); + + len = evbuffer_get_length(buf); + r = evbuffer_write(buf, pair[0]); + evbuffer_read(tmp_buf, pair[1], -1); + FREEZE_EQ(r, -1, len); + len = evbuffer_get_length(buf_two); + r = evbuffer_write_atmost(buf_two, pair[0], -1); + evbuffer_read(tmp_buf, pair[1], -1); + FREEZE_EQ(r, -1, len); + + if (start) + tt_int_op(orig_length, ==, evbuffer_get_length(buf)); + +end: + if (buf) + evbuffer_free(buf); + + if (buf_two) + evbuffer_free(buf_two); + + if (tmp_buf) + evbuffer_free(tmp_buf); + + if (tmpfilename) { + unlink(tmpfilename); + free(tmpfilename); + } +} + +static void +test_evbuffer_add_iovec(void * ptr) +{ + struct evbuffer * buf = NULL; + struct evbuffer_iovec vec[4]; + const char * data[] = { + "Guilt resembles a sword with two edges.", + "On the one hand, it cuts for Justice, imposing practical morality upon those who fear it.", + "Conscience does not always adhere to rational judgment.", + "Guilt is always a self-imposed burden, but it is not always rightly imposed." + /* -- R.A. Salvatore, _Sojurn_ */ + }; + size_t expected_length = 0; + size_t returned_length = 0; + int i; + + buf = evbuffer_new(); + + tt_assert(buf); + + for (i = 0; i < 4; i++) { + vec[i].iov_len = strlen(data[i]); + vec[i].iov_base = (char*) data[i]; + expected_length += vec[i].iov_len; + } + + returned_length = evbuffer_add_iovec(buf, vec, 4); + + tt_int_op(returned_length, ==, evbuffer_get_length(buf)); + tt_int_op(evbuffer_get_length(buf), ==, expected_length); + + for (i = 0; i < 4; i++) { + char charbuf[1024]; + + memset(charbuf, 0, 1024); + evbuffer_remove(buf, charbuf, strlen(data[i])); + tt_assert(strcmp(charbuf, data[i]) == 0); + } + + tt_assert(evbuffer_get_length(buf) == 0); +end: + if (buf) { + evbuffer_free(buf); + } +} + +static void +test_evbuffer_copyout(void *dummy) +{ + const char string[] = + "Still they skirmish to and fro, men my messmates on the snow " + "When we headed off the aurochs turn for turn; " + "When the rich Allobrogenses never kept amanuenses, " + "And our only plots were piled in lakes at Berne."; + /* -- Kipling, "In The Neolithic Age" */ + char tmp[1024]; + struct evbuffer_ptr ptr; + struct evbuffer *buf; + + (void)dummy; + + buf = evbuffer_new(); + tt_assert(buf); + + tt_int_op(strlen(string), ==, 206); + + /* Ensure separate chains */ + evbuffer_add_reference(buf, string, 80, no_cleanup, NULL); + evbuffer_add_reference(buf, string+80, 80, no_cleanup, NULL); + evbuffer_add(buf, string+160, strlen(string)-160); + + tt_int_op(206, ==, evbuffer_get_length(buf)); + + /* First, let's test plain old copyout. */ + + /* Copy a little from the beginning. */ + tt_int_op(10, ==, evbuffer_copyout(buf, tmp, 10)); + tt_int_op(0, ==, memcmp(tmp, "Still they", 10)); + + /* Now copy more than a little from the beginning */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(100, ==, evbuffer_copyout(buf, tmp, 100)); + tt_int_op(0, ==, memcmp(tmp, string, 100)); + + /* Copy too much; ensure truncation. */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(206, ==, evbuffer_copyout(buf, tmp, 230)); + tt_int_op(0, ==, memcmp(tmp, string, 206)); + + /* That was supposed to be nondestructive, btw */ + tt_int_op(206, ==, evbuffer_get_length(buf)); + + /* Now it's time to test copyout_from! First, let's start in the + * first chain. */ + evbuffer_ptr_set(buf, &ptr, 15, EVBUFFER_PTR_SET); + memset(tmp, 0, sizeof(tmp)); + tt_int_op(10, ==, evbuffer_copyout_from(buf, &ptr, tmp, 10)); + tt_int_op(0, ==, memcmp(tmp, "mish to an", 10)); + + /* Right up to the end of the first chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(65, ==, evbuffer_copyout_from(buf, &ptr, tmp, 65)); + tt_int_op(0, ==, memcmp(tmp, string+15, 65)); + + /* Span into the second chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(90, ==, evbuffer_copyout_from(buf, &ptr, tmp, 90)); + tt_int_op(0, ==, memcmp(tmp, string+15, 90)); + + /* Span into the third chain */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(160, ==, evbuffer_copyout_from(buf, &ptr, tmp, 160)); + tt_int_op(0, ==, memcmp(tmp, string+15, 160)); + + /* Overrun */ + memset(tmp, 0, sizeof(tmp)); + tt_int_op(206-15, ==, evbuffer_copyout_from(buf, &ptr, tmp, 999)); + tt_int_op(0, ==, memcmp(tmp, string+15, 206-15)); + + /* That was supposed to be nondestructive, too */ + tt_int_op(206, ==, evbuffer_get_length(buf)); + +end: + if (buf) + evbuffer_free(buf); +} + +static void * +setup_passthrough(const struct testcase_t *testcase) +{ + return testcase->setup_data; +} +static int +cleanup_passthrough(const struct testcase_t *testcase, void *ptr) +{ + (void) ptr; + return 1; +} + +static const struct testcase_setup_t nil_setup = { + setup_passthrough, + cleanup_passthrough +}; + +struct testcase_t evbuffer_testcases[] = { + { "evbuffer", test_evbuffer, 0, NULL, NULL }, + { "remove_buffer_with_empty", test_evbuffer_remove_buffer_with_empty, 0, NULL, NULL }, + { "remove_buffer_with_empty2", test_evbuffer_remove_buffer_with_empty2, 0, NULL, NULL }, + { "remove_buffer_with_empty3", test_evbuffer_remove_buffer_with_empty3, 0, NULL, NULL }, + { "remove_buffer_with_empty_front", test_evbuffer_remove_buffer_with_empty_front, 0, NULL, NULL }, + { "remove_buffer_adjust_last_with_datap_with_empty", + test_evbuffer_remove_buffer_adjust_last_with_datap_with_empty, 0, NULL, NULL }, + { "add_buffer_with_empty", test_evbuffer_add_buffer_with_empty, 0, NULL, NULL }, + { "add_buffer_with_empty2", test_evbuffer_add_buffer_with_empty2, 0, NULL, NULL }, + { "reserve2", test_evbuffer_reserve2, 0, NULL, NULL }, + { "reserve_many", test_evbuffer_reserve_many, 0, NULL, NULL }, + { "reserve_many2", test_evbuffer_reserve_many, 0, &nil_setup, (void*)"add" }, + { "reserve_many3", test_evbuffer_reserve_many, 0, &nil_setup, (void*)"fill" }, + { "reserve_with_empty", test_evbuffer_reserve_with_empty, 0, NULL, NULL }, + { "reserve_invalid_last_with_datap", test_evbuffer_reserve_invalid_last_with_datap, TT_FORK, NULL, NULL }, + { "expand", test_evbuffer_expand, 0, NULL, NULL }, + { "expand_overflow", test_evbuffer_expand_overflow, 0, NULL, NULL }, + { "add1", test_evbuffer_add1, 0, NULL, NULL }, + { "add2", test_evbuffer_add2, 0, NULL, NULL }, + { "reference", test_evbuffer_reference, 0, NULL, NULL }, + { "reference2", test_evbuffer_reference2, 0, NULL, NULL }, + { "iterative", test_evbuffer_iterative, 0, NULL, NULL }, + { "readln", test_evbuffer_readln, TT_NO_LOGS, &basic_setup, NULL }, + { "search_eol", test_evbuffer_search_eol, 0, NULL, NULL }, + { "find", test_evbuffer_find, 0, NULL, NULL }, + { "ptr_set", test_evbuffer_ptr_set, 0, NULL, NULL }, + { "search", test_evbuffer_search, 0, NULL, NULL }, + { "callbacks", test_evbuffer_callbacks, 0, NULL, NULL }, + { "add_reference", test_evbuffer_add_reference, 0, NULL, NULL }, + { "multicast", test_evbuffer_multicast, 0, NULL, NULL }, + { "multicast_drain", test_evbuffer_multicast_drain, 0, NULL, NULL }, + { "prepend", test_evbuffer_prepend, TT_FORK, NULL, NULL }, + { "empty_reference_prepend", test_evbuffer_empty_reference_prepend, TT_FORK, NULL, NULL }, + { "empty_reference_prepend_buffer", test_evbuffer_empty_reference_prepend_buffer, TT_FORK, NULL, NULL }, + { "peek", test_evbuffer_peek, 0, NULL, NULL }, + { "peek_first_gt", test_evbuffer_peek_first_gt, 0, NULL, NULL }, + { "freeze_start", test_evbuffer_freeze, TT_NEED_SOCKETPAIR, &basic_setup, (void*)"start" }, + { "freeze_end", test_evbuffer_freeze, TT_NEED_SOCKETPAIR, &basic_setup, (void*)"end" }, + { "add_iovec", test_evbuffer_add_iovec, 0, NULL, NULL}, + { "copyout", test_evbuffer_copyout, 0, NULL, NULL}, + { "file_segment_add_cleanup_cb", test_evbuffer_file_segment_add_cleanup_cb, 0, NULL, NULL }, + { "pullup_with_empty", test_evbuffer_pullup_with_empty, 0, NULL, NULL }, + +#define ADDFILE_TEST(name, parameters) \ + { name, test_evbuffer_add_file, TT_FORK|TT_NEED_BASE, \ + &basic_setup, (void*)(parameters) } + +#define ADDFILE_TEST_GROUP(name, parameters) \ + ADDFILE_TEST(name "_sendfile", "sendfile " parameters), \ + ADDFILE_TEST(name "_mmap", "mmap " parameters), \ + ADDFILE_TEST(name "_linear", "linear " parameters) + + ADDFILE_TEST_GROUP("add_file", ""), + ADDFILE_TEST("add_file_nosegment", "default nosegment"), + + ADDFILE_TEST_GROUP("add_big_file", "bigfile"), + ADDFILE_TEST("add_big_file_nosegment", "default nosegment bigfile"), + + ADDFILE_TEST_GROUP("add_file_offset", "bigfile map_offset"), + ADDFILE_TEST("add_file_offset_nosegment", + "default nosegment bigfile map_offset"), + + ADDFILE_TEST_GROUP("add_file_offset2", "bigfile offset_in_segment"), + + ADDFILE_TEST_GROUP("add_file_offset3", + "bigfile offset_in_segment map_offset"), + + END_OF_TESTCASES +}; diff --git a/libevent/test/regress_bufferevent.c b/libevent/test/regress_bufferevent.c new file mode 100644 index 0000000..c276a0e --- /dev/null +++ b/libevent/test/regress_bufferevent.c @@ -0,0 +1,1467 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +/* The old tests here need assertions to work. */ +#undef NDEBUG + +/** + * - clang supports __has_feature + * - gcc supports __SANITIZE_ADDRESS__ + * + * Let's set __SANITIZE_ADDRESS__ if __has_feature(address_sanitizer) + */ +#ifndef __has_feature +#define __has_feature(x) 0 +#endif +#if !defined(__SANITIZE_ADDRESS__) && __has_feature(address_sanitizer) +#define __SANITIZE_ADDRESS__ +#endif + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <netdb.h> +#include <netinet/in.h> +#endif +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#ifdef EVENT__HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "event2/event-config.h" +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/event_compat.h" +#include "event2/tag.h" +#include "event2/buffer.h" +#include "event2/bufferevent.h" +#include "event2/bufferevent_compat.h" +#include "event2/bufferevent_struct.h" +#include "event2/listener.h" +#include "event2/util.h" + +#include "bufferevent-internal.h" +#include "evthread-internal.h" +#include "util-internal.h" +#ifdef _WIN32 +#include "iocp-internal.h" +#endif + +#include "regress.h" +#include "regress_testutils.h" + +/* + * simple bufferevent test + */ + +static void +readcb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bev->input) == 8333) { + struct evbuffer *evbuf = evbuffer_new(); + assert(evbuf != NULL); + + /* gratuitous test of bufferevent_read_buffer */ + bufferevent_read_buffer(bev, evbuf); + + bufferevent_disable(bev, EV_READ); + + if (evbuffer_get_length(evbuf) == 8333) { + test_ok++; + } + + evbuffer_free(evbuf); + } +} + +static void +writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bev->output) == 0) { + test_ok++; + } +} + +static void +errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; +} + +static void +test_bufferevent_impl(int use_pair, int flush) +{ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + char buffer[8333]; + int i; + int expected = 2; + + if (use_pair) { + struct bufferevent *pair[2]; + tt_assert(0 == bufferevent_pair_new(NULL, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + bufferevent_setcb(bev1, readcb, writecb, errorcb, bev1); + bufferevent_setcb(bev2, readcb, writecb, errorcb, NULL); + tt_fd_op(bufferevent_getfd(bev1), ==, EVUTIL_INVALID_SOCKET); + tt_ptr_op(bufferevent_get_underlying(bev1), ==, NULL); + tt_ptr_op(bufferevent_pair_get_partner(bev1), ==, bev2); + tt_ptr_op(bufferevent_pair_get_partner(bev2), ==, bev1); + } else { + bev1 = bufferevent_new(pair[0], readcb, writecb, errorcb, NULL); + bev2 = bufferevent_new(pair[1], readcb, writecb, errorcb, NULL); + tt_fd_op(bufferevent_getfd(bev1), ==, pair[0]); + tt_ptr_op(bufferevent_get_underlying(bev1), ==, NULL); + tt_ptr_op(bufferevent_pair_get_partner(bev1), ==, NULL); + tt_ptr_op(bufferevent_pair_get_partner(bev2), ==, NULL); + } + + { + /* Test getcb. */ + bufferevent_data_cb r, w; + bufferevent_event_cb e; + void *a; + bufferevent_getcb(bev1, &r, &w, &e, &a); + tt_ptr_op(r, ==, readcb); + tt_ptr_op(w, ==, writecb); + tt_ptr_op(e, ==, errorcb); + tt_ptr_op(a, ==, use_pair ? bev1 : NULL); + } + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + tt_int_op(bufferevent_get_enabled(bev1), ==, EV_WRITE); + tt_int_op(bufferevent_get_enabled(bev2), ==, EV_WRITE|EV_READ); + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = i; + + bufferevent_write(bev1, buffer, sizeof(buffer)); + if (flush >= 0) { + tt_int_op(bufferevent_flush(bev1, EV_WRITE, flush), >=, 0); + } + + event_dispatch(); + + bufferevent_free(bev2); + tt_ptr_op(bufferevent_pair_get_partner(bev1), ==, NULL); + bufferevent_free(bev1); + + /** Only pair call errorcb for BEV_FINISHED */ + if (use_pair && flush == BEV_FINISHED) { + expected = -1; + } + if (test_ok != expected) + test_ok = 0; +end: + ; +} + +static void test_bufferevent(void) { test_bufferevent_impl(0, -1); } +static void test_bufferevent_pair(void) { test_bufferevent_impl(1, -1); } + +static void test_bufferevent_flush_normal(void) { test_bufferevent_impl(0, BEV_NORMAL); } +static void test_bufferevent_flush_flush(void) { test_bufferevent_impl(0, BEV_FLUSH); } +static void test_bufferevent_flush_finished(void) { test_bufferevent_impl(0, BEV_FINISHED); } + +static void test_bufferevent_pair_flush_normal(void) { test_bufferevent_impl(1, BEV_NORMAL); } +static void test_bufferevent_pair_flush_flush(void) { test_bufferevent_impl(1, BEV_FLUSH); } +static void test_bufferevent_pair_flush_finished(void) { test_bufferevent_impl(1, BEV_FINISHED); } + +#if defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) && !defined(__SANITIZE_ADDRESS__) +/** + * Trace lock/unlock/alloc/free for locks. + * (More heavier then evthread_debug*) + */ +typedef struct +{ + void *lock; + enum { + ALLOC, FREE, + } status; + size_t locked /** allow recursive locking */; +} lock_wrapper; +struct lock_unlock_base +{ + /* Original callbacks */ + struct evthread_lock_callbacks cbs; + /* Map of locks */ + lock_wrapper *locks; + size_t nr_locks; +} lu_base = { + .locks = NULL, +}; + +static lock_wrapper *lu_find(void *lock_) +{ + size_t i; + for (i = 0; i < lu_base.nr_locks; ++i) { + lock_wrapper *lock = &lu_base.locks[i]; + if (lock->lock == lock_) + return lock; + } + return NULL; +} + +static void *trace_lock_alloc(unsigned locktype) +{ + void *lock; + ++lu_base.nr_locks; + lu_base.locks = realloc(lu_base.locks, + sizeof(lock_wrapper) * lu_base.nr_locks); + lock = lu_base.cbs.alloc(locktype); + lu_base.locks[lu_base.nr_locks - 1] = (lock_wrapper){ lock, ALLOC, 0 }; + return lock; +} +static void trace_lock_free(void *lock_, unsigned locktype) +{ + lock_wrapper *lock = lu_find(lock_); + if (!lock || lock->status == FREE || lock->locked) { + TT_FAIL(("lock: free error")); + } else { + lock->status = FREE; + lu_base.cbs.free(lock_, locktype); + } +} +static int trace_lock_lock(unsigned mode, void *lock_) +{ + lock_wrapper *lock = lu_find(lock_); + if (!lock || lock->status == FREE) { + TT_FAIL(("lock: lock error")); + return -1; + } else { + ++lock->locked; + return lu_base.cbs.lock(mode, lock_); + } +} +static int trace_lock_unlock(unsigned mode, void *lock_) +{ + lock_wrapper *lock = lu_find(lock_); + if (!lock || lock->status == FREE || !lock->locked) { + TT_FAIL(("lock: unlock error")); + return -1; + } else { + --lock->locked; + return lu_base.cbs.unlock(mode, lock_); + } +} +static void lock_unlock_free_thread_cbs(void) +{ + event_base_free(NULL); + + if (libevent_tests_running_in_debug_mode) + libevent_global_shutdown(); + + /** drop immutable flag */ + evthread_set_lock_callbacks(NULL); + /** avoid calling of event_global_setup_locks_() for new cbs */ + libevent_global_shutdown(); + /** drop immutable flag for non-debug ops (since called after shutdown) */ + evthread_set_lock_callbacks(NULL); +} + +static int use_lock_unlock_profiler(void) +{ + struct evthread_lock_callbacks cbs = { + EVTHREAD_LOCK_API_VERSION, + EVTHREAD_LOCKTYPE_RECURSIVE, + trace_lock_alloc, + trace_lock_free, + trace_lock_lock, + trace_lock_unlock, + }; + memcpy(&lu_base.cbs, evthread_get_lock_callbacks(), + sizeof(lu_base.cbs)); + { + lock_unlock_free_thread_cbs(); + + evthread_set_lock_callbacks(&cbs); + /** re-create debug locks correctly */ + evthread_enable_lock_debugging(); + + event_init(); + } + return 0; +} +static void free_lock_unlock_profiler(struct basic_test_data *data) +{ + /** fix "held_by" for kqueue */ + evthread_set_lock_callbacks(NULL); + + lock_unlock_free_thread_cbs(); + free(lu_base.locks); + data->base = NULL; +} + +static void test_bufferevent_pair_release_lock(void *arg) +{ + struct basic_test_data *data = arg; + use_lock_unlock_profiler(); + { + struct bufferevent *pair[2]; + if (!bufferevent_pair_new(NULL, BEV_OPT_THREADSAFE, pair)) { + bufferevent_free(pair[0]); + bufferevent_free(pair[1]); + } else + tt_abort_perror("bufferevent_pair_new"); + } + free_lock_unlock_profiler(data); +end: + ; +} +#endif + +/* + * test watermarks and bufferevent + */ + +static void +wm_readcb(struct bufferevent *bev, void *arg) +{ + struct evbuffer *evbuf = evbuffer_new(); + int len = (int)evbuffer_get_length(bev->input); + static int nread; + + assert(len >= 10 && len <= 20); + + assert(evbuf != NULL); + + /* gratuitous test of bufferevent_read_buffer */ + bufferevent_read_buffer(bev, evbuf); + + nread += len; + if (nread == 65000) { + bufferevent_disable(bev, EV_READ); + test_ok++; + } + + evbuffer_free(evbuf); +} + +static void +wm_writecb(struct bufferevent *bev, void *arg) +{ + assert(evbuffer_get_length(bev->output) <= 100); + if (evbuffer_get_length(bev->output) == 0) { + evbuffer_drain(bev->output, evbuffer_get_length(bev->output)); + test_ok++; + } +} + +static void +wm_errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; +} + +static void +test_bufferevent_watermarks_impl(int use_pair) +{ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + char buffer[65000]; + size_t low, high; + int i; + test_ok = 0; + + if (use_pair) { + struct bufferevent *pair[2]; + tt_assert(0 == bufferevent_pair_new(NULL, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + bufferevent_setcb(bev1, NULL, wm_writecb, errorcb, NULL); + bufferevent_setcb(bev2, wm_readcb, NULL, errorcb, NULL); + } else { + bev1 = bufferevent_new(pair[0], NULL, wm_writecb, wm_errorcb, NULL); + bev2 = bufferevent_new(pair[1], wm_readcb, NULL, wm_errorcb, NULL); + } + tt_assert(bev1); + tt_assert(bev2); + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + /* By default, low watermarks are set to 0 */ + bufferevent_getwatermark(bev1, EV_READ, &low, NULL); + tt_int_op(low, ==, 0); + bufferevent_getwatermark(bev2, EV_WRITE, &low, NULL); + tt_int_op(low, ==, 0); + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = (char)i; + + /* limit the reading on the receiving bufferevent */ + bufferevent_setwatermark(bev2, EV_READ, 10, 20); + + bufferevent_getwatermark(bev2, EV_READ, &low, &high); + tt_int_op(low, ==, 10); + tt_int_op(high, ==, 20); + + /* Tell the sending bufferevent not to notify us till it's down to + 100 bytes. */ + bufferevent_setwatermark(bev1, EV_WRITE, 100, 2000); + + bufferevent_getwatermark(bev1, EV_WRITE, &low, &high); + tt_int_op(low, ==, 100); + tt_int_op(high, ==, 2000); + + { + int r = bufferevent_getwatermark(bev1, EV_WRITE | EV_READ, &low, &high); + tt_int_op(r, !=, 0); + } + + bufferevent_write(bev1, buffer, sizeof(buffer)); + + event_dispatch(); + + tt_int_op(test_ok, ==, 2); + + /* The write callback drained all the data from outbuf, so we + * should have removed the write event... */ + tt_assert(!event_pending(&bev2->ev_write, EV_WRITE, NULL)); + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); +} + +static void +test_bufferevent_watermarks(void) +{ + test_bufferevent_watermarks_impl(0); +} + +static void +test_bufferevent_pair_watermarks(void) +{ + test_bufferevent_watermarks_impl(1); +} + +/* + * Test bufferevent filters + */ + +/* strip an 'x' from each byte */ + +static enum bufferevent_filter_result +bufferevent_input_filter(struct evbuffer *src, struct evbuffer *dst, + ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx) +{ + const unsigned char *buffer; + unsigned i; + + buffer = evbuffer_pullup(src, evbuffer_get_length(src)); + for (i = 0; i < evbuffer_get_length(src); i += 2) { + if (buffer[i] == '-') + continue; + + assert(buffer[i] == 'x'); + evbuffer_add(dst, buffer + i + 1, 1); + } + + evbuffer_drain(src, i); + return (BEV_OK); +} + +/* add an 'x' before each byte */ + +static enum bufferevent_filter_result +bufferevent_output_filter(struct evbuffer *src, struct evbuffer *dst, + ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx) +{ + const unsigned char *buffer; + unsigned i; + struct bufferevent **bevp = ctx; + + ++test_ok; + + if (test_ok == 1) { + buffer = evbuffer_pullup(src, evbuffer_get_length(src)); + for (i = 0; i < evbuffer_get_length(src); ++i) { + evbuffer_add(dst, "x", 1); + evbuffer_add(dst, buffer + i, 1); + } + evbuffer_drain(src, evbuffer_get_length(src)); + } else { + return BEV_ERROR; + } + + if (bevp && test_ok == 1) { + int prev = ++test_ok; + bufferevent_write(*bevp, "-", 1); + /* check that during this bufferevent_write() + * bufferevent_output_filter() will not be called again */ + assert(test_ok == prev); + --test_ok; + } + + return (BEV_OK); +} + +static void +test_bufferevent_filters_impl(int use_pair, int disable) +{ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + struct bufferevent *bev1_base = NULL, *bev2_base = NULL; + char buffer[8333]; + int i; + + test_ok = 0; + + if (use_pair) { + struct bufferevent *pair[2]; + tt_assert(0 == bufferevent_pair_new(NULL, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + } else { + bev1 = bufferevent_socket_new(NULL, pair[0], 0); + bev2 = bufferevent_socket_new(NULL, pair[1], 0); + } + bev1_base = bev1; + bev2_base = bev2; + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = i; + + bev1 = bufferevent_filter_new(bev1, NULL, bufferevent_output_filter, + BEV_OPT_CLOSE_ON_FREE, NULL, + disable ? &bev1 : NULL); + + bev2 = bufferevent_filter_new(bev2, bufferevent_input_filter, + NULL, BEV_OPT_CLOSE_ON_FREE, NULL, NULL); + bufferevent_setcb(bev1, NULL, writecb, errorcb, NULL); + bufferevent_setcb(bev2, readcb, NULL, errorcb, NULL); + + tt_ptr_op(bufferevent_get_underlying(bev1), ==, bev1_base); + tt_ptr_op(bufferevent_get_underlying(bev2), ==, bev2_base); + tt_fd_op(bufferevent_getfd(bev1), ==, bufferevent_getfd(bev1_base)); + tt_fd_op(bufferevent_getfd(bev2), ==, bufferevent_getfd(bev2_base)); + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + /* insert some filters */ + bufferevent_write(bev1, buffer, sizeof(buffer)); + + event_dispatch(); + + if (test_ok != 3 + !!disable) + test_ok = 0; + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); + +} + +static void test_bufferevent_filters(void) +{ test_bufferevent_filters_impl(0, 0); } +static void test_bufferevent_pair_filters(void) +{ test_bufferevent_filters_impl(1, 0); } +static void test_bufferevent_filters_disable(void) +{ test_bufferevent_filters_impl(0, 1); } +static void test_bufferevent_pair_filters_disable(void) +{ test_bufferevent_filters_impl(1, 1); } + + +static void +sender_writecb(struct bufferevent *bev, void *ctx) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + bufferevent_disable(bev,EV_READ|EV_WRITE); + TT_BLATHER(("Flushed %d: freeing it.", (int)bufferevent_getfd(bev))); + bufferevent_free(bev); + } +} + +static void +sender_errorcb(struct bufferevent *bev, short what, void *ctx) +{ + TT_FAIL(("Got sender error %d",(int)what)); +} + +static int bufferevent_connect_test_flags = 0; +static int bufferevent_trigger_test_flags = 0; +static int n_strings_read = 0; +static int n_reads_invoked = 0; +static int n_events_invoked = 0; + +#define TEST_STR "Now is the time for all good events to signal for " \ + "the good of their protocol" +static void +listen_cb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *sa, int socklen, void *arg) +{ + struct event_base *base = arg; + struct bufferevent *bev; + const char s[] = TEST_STR; + TT_BLATHER(("Got a request on socket %d", (int)fd )); + bev = bufferevent_socket_new(base, fd, bufferevent_connect_test_flags); + tt_assert(bev); + bufferevent_setcb(bev, NULL, sender_writecb, sender_errorcb, NULL); + bufferevent_write(bev, s, sizeof(s)); +end: + ; +} + +static evutil_socket_t +fake_listener_create(struct sockaddr_in *localhost) +{ + struct sockaddr *sa = (struct sockaddr *)localhost; + evutil_socket_t fd = -1; + ev_socklen_t slen = sizeof(*localhost); + + memset(localhost, 0, sizeof(*localhost)); + localhost->sin_port = 0; /* have the kernel pick a port */ + localhost->sin_addr.s_addr = htonl(0x7f000001L); + localhost->sin_family = AF_INET; + + /* bind, but don't listen or accept. should trigger + "Connection refused" reliably on most platforms. */ + fd = socket(localhost->sin_family, SOCK_STREAM, 0); + tt_assert(fd >= 0); + tt_assert(bind(fd, sa, slen) == 0); + tt_assert(getsockname(fd, sa, &slen) == 0); + + return fd; + +end: + return -1; +} + +static void +reader_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct event_base *base = ctx; + if (what & BEV_EVENT_ERROR) { + perror("foobar"); + TT_FAIL(("got connector error %d", (int)what)); + return; + } + if (what & BEV_EVENT_CONNECTED) { + TT_BLATHER(("connected on %d", (int)bufferevent_getfd(bev))); + bufferevent_enable(bev, EV_READ); + } + if (what & BEV_EVENT_EOF) { + char buf[512]; + size_t n; + n = bufferevent_read(bev, buf, sizeof(buf)-1); + tt_int_op(n, >=, 0); + buf[n] = '\0'; + tt_str_op(buf, ==, TEST_STR); + if (++n_strings_read == 2) + event_base_loopexit(base, NULL); + TT_BLATHER(("EOF on %d: %d strings read.", + (int)bufferevent_getfd(bev), n_strings_read)); + } +end: + ; +} + +static void +reader_eventcb_simple(struct bufferevent *bev, short what, void *ctx) +{ + TT_BLATHER(("Read eventcb simple invoked on %d.", + (int)bufferevent_getfd(bev))); + n_events_invoked++; +} + +static void +reader_readcb(struct bufferevent *bev, void *ctx) +{ + TT_BLATHER(("Read invoked on %d.", (int)bufferevent_getfd(bev))); + n_reads_invoked++; +} + +static void +test_bufferevent_connect(void *arg) +{ + struct basic_test_data *data = arg; + struct evconnlistener *lev=NULL; + struct bufferevent *bev1=NULL, *bev2=NULL; + struct sockaddr_in localhost; + struct sockaddr_storage ss; + struct sockaddr *sa; + ev_socklen_t slen; + + int be_flags=BEV_OPT_CLOSE_ON_FREE; + + if (strstr((char*)data->setup_data, "defer")) { + be_flags |= BEV_OPT_DEFER_CALLBACKS; + } + if (strstr((char*)data->setup_data, "unlocked")) { + be_flags |= BEV_OPT_UNLOCK_CALLBACKS; + } + if (strstr((char*)data->setup_data, "lock")) { + be_flags |= BEV_OPT_THREADSAFE; + } + bufferevent_connect_test_flags = be_flags; +#ifdef _WIN32 + if (!strcmp((char*)data->setup_data, "unset_connectex")) { + struct win32_extension_fns *ext = + (struct win32_extension_fns *) + event_get_win32_extension_fns_(); + ext->ConnectEx = NULL; + } +#endif + + memset(&localhost, 0, sizeof(localhost)); + + localhost.sin_port = 0; /* pick-a-port */ + localhost.sin_addr.s_addr = htonl(0x7f000001L); + localhost.sin_family = AF_INET; + sa = (struct sockaddr *)&localhost; + lev = evconnlistener_new_bind(data->base, listen_cb, data->base, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + 16, sa, sizeof(localhost)); + tt_assert(lev); + + sa = (struct sockaddr *)&ss; + slen = sizeof(ss); + if (regress_get_listener_addr(lev, sa, &slen) < 0) { + tt_abort_perror("getsockname"); + } + + tt_assert(!evconnlistener_enable(lev)); + bev1 = bufferevent_socket_new(data->base, -1, be_flags); + bev2 = bufferevent_socket_new(data->base, -1, be_flags); + tt_assert(bev1); + tt_assert(bev2); + bufferevent_setcb(bev1, reader_readcb,NULL, reader_eventcb, data->base); + bufferevent_setcb(bev2, reader_readcb,NULL, reader_eventcb, data->base); + + bufferevent_enable(bev1, EV_READ); + bufferevent_enable(bev2, EV_READ); + + tt_want(!bufferevent_socket_connect(bev1, sa, sizeof(localhost))); + tt_want(!bufferevent_socket_connect(bev2, sa, sizeof(localhost))); + + event_base_dispatch(data->base); + + tt_int_op(n_strings_read, ==, 2); + tt_int_op(n_reads_invoked, >=, 2); +end: + if (lev) + evconnlistener_free(lev); + + if (bev1) + bufferevent_free(bev1); + + if (bev2) + bufferevent_free(bev2); +} + +static void +close_socket_cb(evutil_socket_t fd, short what, void *arg) +{ + evutil_socket_t *fdp = arg; + if (*fdp >= 0) { + evutil_closesocket(*fdp); + *fdp = -1; + } +} + +static void +test_bufferevent_connect_fail_eventcb(void *arg) +{ + struct basic_test_data *data = arg; + int flags = BEV_OPT_CLOSE_ON_FREE | (long)data->setup_data; + struct event close_listener_event; + struct bufferevent *bev = NULL; + struct evconnlistener *lev = NULL; + struct sockaddr_in localhost; + struct timeval close_timeout = { 0, 300000 }; + ev_socklen_t slen = sizeof(localhost); + evutil_socket_t fake_listener = -1; + int r; + + fake_listener = fake_listener_create(&localhost); + + tt_int_op(n_events_invoked, ==, 0); + + bev = bufferevent_socket_new(data->base, -1, flags); + tt_assert(bev); + bufferevent_setcb(bev, reader_readcb, reader_readcb, + reader_eventcb_simple, data->base); + bufferevent_enable(bev, EV_READ|EV_WRITE); + tt_int_op(n_events_invoked, ==, 0); + tt_int_op(n_reads_invoked, ==, 0); + + /** @see also test_bufferevent_connect_fail() */ + r = bufferevent_socket_connect(bev, (struct sockaddr *)&localhost, slen); + /* XXXX we'd like to test the '0' case everywhere, but FreeBSD tells + * detects the error immediately, which is not really wrong of it. */ + tt_want(r == 0 || r == -1); + + tt_int_op(n_events_invoked, ==, 0); + tt_int_op(n_reads_invoked, ==, 0); + + /* Close the listener socket after a delay. This should trigger + "connection refused" on some other platforms, including OSX. */ + evtimer_assign(&close_listener_event, data->base, close_socket_cb, + &fake_listener); + event_add(&close_listener_event, &close_timeout); + + event_base_dispatch(data->base); + tt_int_op(n_events_invoked, ==, 1); + tt_int_op(n_reads_invoked, ==, 0); + +end: + if (lev) + evconnlistener_free(lev); + if (bev) + bufferevent_free(bev); + if (fake_listener >= 0) + evutil_closesocket(fake_listener); +} + +static void +want_fail_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct event_base *base = ctx; + const char *err; + evutil_socket_t s; + + if (what & BEV_EVENT_ERROR) { + s = bufferevent_getfd(bev); + err = evutil_socket_error_to_string(evutil_socket_geterror(s)); + TT_BLATHER(("connection failure on "EV_SOCK_FMT": %s", + EV_SOCK_ARG(s), err)); + test_ok = 1; + } else { + TT_FAIL(("didn't fail? what %hd", what)); + } + + event_base_loopexit(base, NULL); +} + +static void +test_bufferevent_connect_fail(void *arg) +{ + struct basic_test_data *data = (struct basic_test_data *)arg; + struct bufferevent *bev=NULL; + struct event close_listener_event; + int close_listener_event_added = 0; + struct timeval close_timeout = { 0, 300000 }; + struct sockaddr_in localhost; + ev_socklen_t slen = sizeof(localhost); + evutil_socket_t fake_listener = -1; + int r; + + test_ok = 0; + + fake_listener = fake_listener_create(&localhost); + bev = bufferevent_socket_new(data->base, -1, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + tt_assert(bev); + bufferevent_setcb(bev, NULL, NULL, want_fail_eventcb, data->base); + + r = bufferevent_socket_connect(bev, (struct sockaddr *)&localhost, slen); + /* XXXX we'd like to test the '0' case everywhere, but FreeBSD tells + * detects the error immediately, which is not really wrong of it. */ + tt_want(r == 0 || r == -1); + + /* Close the listener socket after a delay. This should trigger + "connection refused" on some other platforms, including OSX. */ + evtimer_assign(&close_listener_event, data->base, close_socket_cb, + &fake_listener); + event_add(&close_listener_event, &close_timeout); + close_listener_event_added = 1; + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + +end: + if (fake_listener >= 0) + evutil_closesocket(fake_listener); + + if (bev) + bufferevent_free(bev); + + if (close_listener_event_added) + event_del(&close_listener_event); +} + +struct timeout_cb_result { + struct timeval read_timeout_at; + struct timeval write_timeout_at; + struct timeval last_wrote_at; + struct timeval last_read_at; + int n_read_timeouts; + int n_write_timeouts; + int total_calls; +}; + +static void +bev_timeout_read_cb(struct bufferevent *bev, void *arg) +{ + struct timeout_cb_result *res = arg; + evutil_gettimeofday(&res->last_read_at, NULL); +} +static void +bev_timeout_write_cb(struct bufferevent *bev, void *arg) +{ + struct timeout_cb_result *res = arg; + evutil_gettimeofday(&res->last_wrote_at, NULL); +} +static void +bev_timeout_event_cb(struct bufferevent *bev, short what, void *arg) +{ + struct timeout_cb_result *res = arg; + ++res->total_calls; + + if ((what & (BEV_EVENT_READING|BEV_EVENT_TIMEOUT)) + == (BEV_EVENT_READING|BEV_EVENT_TIMEOUT)) { + evutil_gettimeofday(&res->read_timeout_at, NULL); + ++res->n_read_timeouts; + } + if ((what & (BEV_EVENT_WRITING|BEV_EVENT_TIMEOUT)) + == (BEV_EVENT_WRITING|BEV_EVENT_TIMEOUT)) { + evutil_gettimeofday(&res->write_timeout_at, NULL); + ++res->n_write_timeouts; + } +} + +static void +test_bufferevent_timeouts(void *arg) +{ + /* "arg" is a string containing "pair" and/or "filter". */ + struct bufferevent *bev1 = NULL, *bev2 = NULL; + struct basic_test_data *data = arg; + int use_pair = 0, use_filter = 0; + struct timeval tv_w, tv_r, started_at; + struct timeout_cb_result res1, res2; + + memset(&res1, 0, sizeof(res1)); + memset(&res2, 0, sizeof(res2)); + + if (strstr((char*)data->setup_data, "pair")) + use_pair = 1; + if (strstr((char*)data->setup_data, "filter")) + use_filter = 1; + + if (use_pair) { + struct bufferevent *p[2]; + tt_int_op(0, ==, bufferevent_pair_new(data->base, 0, p)); + bev1 = p[0]; + bev2 = p[1]; + } else { + bev1 = bufferevent_socket_new(data->base, data->pair[0], 0); + bev2 = bufferevent_socket_new(data->base, data->pair[1], 0); + } + tt_assert(bev1); + tt_assert(bev2); + + if (use_filter) { + struct bufferevent *bevf1, *bevf2; + bevf1 = bufferevent_filter_new(bev1, NULL, NULL, + BEV_OPT_CLOSE_ON_FREE, NULL, NULL); + bevf2 = bufferevent_filter_new(bev2, NULL, NULL, + BEV_OPT_CLOSE_ON_FREE, NULL, NULL); + tt_assert(bevf1); + tt_assert(bevf2); + bev1 = bevf1; + bev2 = bevf2; + } + + /* Do this nice and early. */ + bufferevent_disable(bev2, EV_READ); + + /* bev1 will try to write and read. Both will time out. */ + evutil_gettimeofday(&started_at, NULL); + tv_w.tv_sec = tv_r.tv_sec = 0; + tv_w.tv_usec = 100*1000; + tv_r.tv_usec = 150*1000; + bufferevent_setcb(bev1, bev_timeout_read_cb, bev_timeout_write_cb, + bev_timeout_event_cb, &res1); + bufferevent_set_timeouts(bev1, &tv_r, &tv_w); + bufferevent_write(bev1, "ABCDEFG", 7); + bufferevent_enable(bev1, EV_READ|EV_WRITE); + + /* bev2 has nothing to say, and isn't listening. */ + bufferevent_setcb(bev2, bev_timeout_read_cb, bev_timeout_write_cb, + bev_timeout_event_cb, &res2); + tv_w.tv_sec = tv_r.tv_sec = 0; + tv_w.tv_usec = 200*1000; + tv_r.tv_usec = 100*1000; + bufferevent_set_timeouts(bev2, &tv_r, &tv_w); + bufferevent_enable(bev2, EV_WRITE); + + tv_r.tv_sec = 0; + tv_r.tv_usec = 350000; + + event_base_loopexit(data->base, &tv_r); + event_base_dispatch(data->base); + + /* XXXX Test that actually reading or writing a little resets the + * timeouts. */ + + tt_want(res1.total_calls == 2); + tt_want(res1.n_read_timeouts == 1); + tt_want(res1.n_write_timeouts == 1); + tt_want(res2.total_calls == !(use_pair && !use_filter)); + tt_want(res2.n_write_timeouts == !(use_pair && !use_filter)); + tt_want(!res2.n_read_timeouts); + + test_timeval_diff_eq(&started_at, &res1.read_timeout_at, 150); + test_timeval_diff_eq(&started_at, &res1.write_timeout_at, 100); + +#define tt_assert_timeval_empty(tv) do { \ + tt_int_op((tv).tv_sec, ==, 0); \ + tt_int_op((tv).tv_usec, ==, 0); \ +} while(0) + tt_assert_timeval_empty(res1.last_read_at); + tt_assert_timeval_empty(res2.last_read_at); + tt_assert_timeval_empty(res2.last_wrote_at); + tt_assert_timeval_empty(res2.last_wrote_at); +#undef tt_assert_timeval_empty + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); +} + +static void +trigger_failure_cb(evutil_socket_t fd, short what, void *ctx) +{ + TT_FAIL(("The triggered callback did not fire or the machine is really slow (try increasing timeout).")); +} + +static void +trigger_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + struct event_base *base = ctx; + if (what == ~0) { + TT_BLATHER(("Event successfully triggered.")); + event_base_loopexit(base, NULL); + return; + } + reader_eventcb(bev, what, ctx); +} + +static void +trigger_readcb_triggered(struct bufferevent *bev, void *ctx) +{ + TT_BLATHER(("Read successfully triggered.")); + n_reads_invoked++; + bufferevent_trigger_event(bev, ~0, bufferevent_trigger_test_flags); +} + +static void +trigger_readcb(struct bufferevent *bev, void *ctx) +{ + struct timeval timeout = { 30, 0 }; + struct event_base *base = ctx; + size_t low, high, len; + int expected_reads; + + TT_BLATHER(("Read invoked on %d.", (int)bufferevent_getfd(bev))); + expected_reads = ++n_reads_invoked; + + bufferevent_setcb(bev, trigger_readcb_triggered, NULL, trigger_eventcb, ctx); + + bufferevent_getwatermark(bev, EV_READ, &low, &high); + len = evbuffer_get_length(bufferevent_get_input(bev)); + + bufferevent_setwatermark(bev, EV_READ, len + 1, 0); + bufferevent_trigger(bev, EV_READ, bufferevent_trigger_test_flags); + /* no callback expected */ + tt_int_op(n_reads_invoked, ==, expected_reads); + + if ((bufferevent_trigger_test_flags & BEV_TRIG_DEFER_CALLBACKS) || + (bufferevent_connect_test_flags & BEV_OPT_DEFER_CALLBACKS)) { + /* will be deferred */ + } else { + expected_reads++; + } + + event_base_once(base, -1, EV_TIMEOUT, trigger_failure_cb, NULL, &timeout); + + bufferevent_trigger(bev, EV_READ, + bufferevent_trigger_test_flags | BEV_TRIG_IGNORE_WATERMARKS); + tt_int_op(n_reads_invoked, ==, expected_reads); + + bufferevent_setwatermark(bev, EV_READ, low, high); +end: + ; +} + +static void +test_bufferevent_trigger(void *arg) +{ + struct basic_test_data *data = arg; + struct evconnlistener *lev=NULL; + struct bufferevent *bev=NULL; + struct sockaddr_in localhost; + struct sockaddr_storage ss; + struct sockaddr *sa; + ev_socklen_t slen; + + int be_flags=BEV_OPT_CLOSE_ON_FREE; + int trig_flags=0; + + if (strstr((char*)data->setup_data, "defer")) { + be_flags |= BEV_OPT_DEFER_CALLBACKS; + } + bufferevent_connect_test_flags = be_flags; + + if (strstr((char*)data->setup_data, "postpone")) { + trig_flags |= BEV_TRIG_DEFER_CALLBACKS; + } + bufferevent_trigger_test_flags = trig_flags; + + memset(&localhost, 0, sizeof(localhost)); + + localhost.sin_port = 0; /* pick-a-port */ + localhost.sin_addr.s_addr = htonl(0x7f000001L); + localhost.sin_family = AF_INET; + sa = (struct sockaddr *)&localhost; + lev = evconnlistener_new_bind(data->base, listen_cb, data->base, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + 16, sa, sizeof(localhost)); + tt_assert(lev); + + sa = (struct sockaddr *)&ss; + slen = sizeof(ss); + if (regress_get_listener_addr(lev, sa, &slen) < 0) { + tt_abort_perror("getsockname"); + } + + tt_assert(!evconnlistener_enable(lev)); + bev = bufferevent_socket_new(data->base, -1, be_flags); + tt_assert(bev); + bufferevent_setcb(bev, trigger_readcb, NULL, trigger_eventcb, data->base); + + bufferevent_enable(bev, EV_READ); + + tt_want(!bufferevent_socket_connect(bev, sa, sizeof(localhost))); + + event_base_dispatch(data->base); + + tt_int_op(n_reads_invoked, ==, 2); +end: + if (lev) + evconnlistener_free(lev); + + if (bev) + bufferevent_free(bev); +} + +static void +test_bufferevent_socket_filter_inactive(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL, *bevf = NULL; + + bev = bufferevent_socket_new(data->base, -1, 0); + tt_assert(bev); + bevf = bufferevent_filter_new(bev, NULL, NULL, 0, NULL, NULL); + tt_assert(bevf); + +end: + if (bevf) + bufferevent_free(bevf); + if (bev) + bufferevent_free(bev); +} + +static void +pair_flush_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + int *callback_what = ctx; + *callback_what = what; +} + +static void +test_bufferevent_pair_flush(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *pair[2]; + struct bufferevent *bev1 = NULL; + struct bufferevent *bev2 = NULL; + int callback_what = 0; + + tt_assert(0 == bufferevent_pair_new(data->base, 0, pair)); + bev1 = pair[0]; + bev2 = pair[1]; + tt_assert(0 == bufferevent_enable(bev1, EV_WRITE)); + tt_assert(0 == bufferevent_enable(bev2, EV_READ)); + + bufferevent_setcb(bev2, NULL, NULL, pair_flush_eventcb, &callback_what); + + bufferevent_flush(bev1, EV_WRITE, BEV_FINISHED); + + event_base_loop(data->base, EVLOOP_ONCE); + + tt_assert(callback_what == (BEV_EVENT_READING | BEV_EVENT_EOF)); + +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); +} + +struct bufferevent_filter_data_stuck { + size_t header_size; + size_t total_read; +}; + +static void +bufferevent_filter_data_stuck_readcb(struct bufferevent *bev, void *arg) +{ + struct bufferevent_filter_data_stuck *filter_data = arg; + struct evbuffer *input = bufferevent_get_input(bev); + size_t read_size = evbuffer_get_length(input); + evbuffer_drain(input, read_size); + filter_data->total_read += read_size; +} + +/** + * This filter prepends header once before forwarding data. + */ +static enum bufferevent_filter_result +bufferevent_filter_data_stuck_inputcb( + struct evbuffer *src, struct evbuffer *dst, ev_ssize_t dst_limit, + enum bufferevent_flush_mode mode, void *ctx) +{ + struct bufferevent_filter_data_stuck *filter_data = ctx; + static int header_inserted = 0; + size_t payload_size; + size_t header_size = 0; + + if (!header_inserted) { + char *header = calloc(filter_data->header_size, 1); + evbuffer_add(dst, header, filter_data->header_size); + free(header); + header_size = filter_data->header_size; + header_inserted = 1; + } + + payload_size = evbuffer_get_length(src); + if (payload_size > dst_limit - header_size) { + payload_size = dst_limit - header_size; + } + + tt_int_op(payload_size, ==, evbuffer_remove_buffer(src, dst, payload_size)); + +end: + return BEV_OK; +} + +static void +test_bufferevent_filter_data_stuck(void *arg) +{ + const size_t read_high_wm = 4096; + struct bufferevent_filter_data_stuck filter_data; + struct basic_test_data *data = arg; + struct bufferevent *pair[2]; + struct bufferevent *filter = NULL; + + int options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS; + + char payload[4096]; + int payload_size = sizeof(payload); + + memset(&filter_data, 0, sizeof(filter_data)); + filter_data.header_size = 20; + + tt_assert(bufferevent_pair_new(data->base, options, pair) == 0); + + bufferevent_setwatermark(pair[0], EV_READ, 0, read_high_wm); + bufferevent_setwatermark(pair[1], EV_READ, 0, read_high_wm); + + tt_assert( + filter = + bufferevent_filter_new(pair[1], + bufferevent_filter_data_stuck_inputcb, + NULL, + options, + NULL, + &filter_data)); + + bufferevent_setcb(filter, + bufferevent_filter_data_stuck_readcb, + NULL, + NULL, + &filter_data); + + tt_assert(bufferevent_enable(filter, EV_READ|EV_WRITE) == 0); + + bufferevent_setwatermark(filter, EV_READ, 0, read_high_wm); + + tt_assert(bufferevent_write(pair[0], payload, sizeof(payload)) == 0); + + event_base_dispatch(data->base); + + tt_int_op(filter_data.total_read, ==, payload_size + filter_data.header_size); +end: + if (pair[0]) + bufferevent_free(pair[0]); + if (filter) + bufferevent_free(filter); +} + +struct testcase_t bufferevent_testcases[] = { + + LEGACY(bufferevent, TT_ISOLATED), + LEGACY(bufferevent_pair, TT_ISOLATED), + LEGACY(bufferevent_flush_normal, TT_ISOLATED), + LEGACY(bufferevent_flush_flush, TT_ISOLATED), + LEGACY(bufferevent_flush_finished, TT_ISOLATED), + LEGACY(bufferevent_pair_flush_normal, TT_ISOLATED), + LEGACY(bufferevent_pair_flush_flush, TT_ISOLATED), + LEGACY(bufferevent_pair_flush_finished, TT_ISOLATED), +#if defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) && !defined(__SANITIZE_ADDRESS__) + { "bufferevent_pair_release_lock", test_bufferevent_pair_release_lock, + TT_FORK|TT_ISOLATED|TT_NEED_THREADS|TT_NEED_BASE|TT_LEGACY|TT_NO_LOGS, + &basic_setup, NULL }, +#endif + LEGACY(bufferevent_watermarks, TT_ISOLATED), + LEGACY(bufferevent_pair_watermarks, TT_ISOLATED), + LEGACY(bufferevent_filters, TT_ISOLATED), + LEGACY(bufferevent_pair_filters, TT_ISOLATED), + LEGACY(bufferevent_filters_disable, TT_ISOLATED), + LEGACY(bufferevent_pair_filters_disable, TT_ISOLATED), + { "bufferevent_connect", test_bufferevent_connect, TT_FORK|TT_NEED_BASE, + &basic_setup, (void*)"" }, + { "bufferevent_connect_defer", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"defer" }, + { "bufferevent_connect_lock", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, (void*)"lock" }, + { "bufferevent_connect_lock_defer", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"defer lock" }, + { "bufferevent_connect_unlocked_cbs", test_bufferevent_connect, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"lock defer unlocked" }, + { "bufferevent_connect_fail", test_bufferevent_connect_fail, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_timeout", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"" }, + { "bufferevent_timeout_pair", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"pair" }, + { "bufferevent_timeout_filter", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"filter" }, + { "bufferevent_timeout_filter_pair", test_bufferevent_timeouts, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"filter pair" }, + { "bufferevent_trigger", test_bufferevent_trigger, TT_FORK|TT_NEED_BASE, + &basic_setup, (void*)"" }, + { "bufferevent_trigger_defer", test_bufferevent_trigger, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)"defer" }, + { "bufferevent_trigger_postpone", test_bufferevent_trigger, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"postpone" }, + { "bufferevent_trigger_defer_postpone", test_bufferevent_trigger, + TT_FORK|TT_NEED_BASE|TT_NEED_THREADS, &basic_setup, + (void*)"defer postpone" }, +#ifdef EVENT__HAVE_LIBZ + LEGACY(bufferevent_zlib, TT_ISOLATED), +#else + { "bufferevent_zlib", NULL, TT_SKIP, NULL, NULL }, +#endif + + { "bufferevent_connect_fail_eventcb_defer", + test_bufferevent_connect_fail_eventcb, + TT_FORK|TT_NEED_BASE, &basic_setup, (void*)BEV_OPT_DEFER_CALLBACKS }, + { "bufferevent_connect_fail_eventcb", + test_bufferevent_connect_fail_eventcb, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + + { "bufferevent_socket_filter_inactive", + test_bufferevent_socket_filter_inactive, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_pair_flush", + test_bufferevent_pair_flush, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_filter_data_stuck", + test_bufferevent_filter_data_stuck, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + + END_OF_TESTCASES, +}; + +#define TT_IOCP (TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP) +#define TT_IOCP_LEGACY (TT_ISOLATED|TT_ENABLE_IOCP) +struct testcase_t bufferevent_iocp_testcases[] = { + LEGACY(bufferevent, TT_IOCP_LEGACY), + LEGACY(bufferevent_flush_normal, TT_ISOLATED), + LEGACY(bufferevent_flush_flush, TT_ISOLATED), + LEGACY(bufferevent_flush_finished, TT_ISOLATED), + LEGACY(bufferevent_watermarks, TT_IOCP_LEGACY), + LEGACY(bufferevent_filters, TT_IOCP_LEGACY), + LEGACY(bufferevent_filters_disable, TT_IOCP_LEGACY), + + { "bufferevent_connect", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"" }, + { "bufferevent_connect_defer", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"defer" }, + { "bufferevent_connect_lock", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"lock" }, + { "bufferevent_connect_lock_defer", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"defer lock" }, + { "bufferevent_connect_fail", test_bufferevent_connect_fail, + TT_IOCP, &basic_setup, NULL }, + { "bufferevent_connect_nonblocking", test_bufferevent_connect, + TT_IOCP, &basic_setup, (void*)"unset_connectex" }, + + { "bufferevent_connect_fail_eventcb_defer", + test_bufferevent_connect_fail_eventcb, + TT_IOCP, &basic_setup, (void*)BEV_OPT_DEFER_CALLBACKS }, + { "bufferevent_connect_fail_eventcb", + test_bufferevent_connect_fail_eventcb, TT_IOCP, &basic_setup, NULL }, + + END_OF_TESTCASES, +}; diff --git a/libevent/test/regress_dns.c b/libevent/test/regress_dns.c new file mode 100644 index 0000000..9a8bff4 --- /dev/null +++ b/libevent/test/regress_dns.c @@ -0,0 +1,2518 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <ws2tcpip.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <signal.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#endif +#ifdef EVENT__HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#ifdef EVENT__HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include "event2/dns.h" +#include "event2/dns_compat.h" +#include "event2/dns_struct.h" +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/event_struct.h" +#include "event2/util.h" +#include "event2/listener.h" +#include "event2/bufferevent.h" +#include <event2/thread.h> +#include "log-internal.h" +#include "evthread-internal.h" +#include "regress.h" +#include "regress_testutils.h" +#include "regress_thread.h" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +static int dns_ok = 0; +static int dns_got_cancel = 0; +static int dns_err = 0; + + +static void +dns_gethostbyname_cb(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + dns_ok = dns_err = 0; + + if (result == DNS_ERR_TIMEOUT) { + printf("[Timed out] "); + dns_err = result; + goto out; + } + + if (result != DNS_ERR_NONE) { + printf("[Error code %d] ", result); + goto out; + } + + TT_BLATHER(("type: %d, count: %d, ttl: %d: ", type, count, ttl)); + + switch (type) { + case DNS_IPv6_AAAA: { +#if defined(EVENT__HAVE_STRUCT_IN6_ADDR) && defined(EVENT__HAVE_INET_NTOP) && defined(INET6_ADDRSTRLEN) + struct in6_addr *in6_addrs = addresses; + char buf[INET6_ADDRSTRLEN+1]; + int i; + /* a resolution that's not valid does not help */ + if (ttl < 0) + goto out; + for (i = 0; i < count; ++i) { + const char *b = evutil_inet_ntop(AF_INET6, &in6_addrs[i], buf,sizeof(buf)); + if (b) + TT_BLATHER(("%s ", b)); + else + TT_BLATHER(("%s ", strerror(errno))); + } +#endif + break; + } + case DNS_IPv4_A: { + struct in_addr *in_addrs = addresses; + int i; + /* a resolution that's not valid does not help */ + if (ttl < 0) + goto out; + for (i = 0; i < count; ++i) + TT_BLATHER(("%s ", inet_ntoa(in_addrs[i]))); + break; + } + case DNS_PTR: + /* may get at most one PTR */ + if (count != 1) + goto out; + + TT_BLATHER(("%s ", *(char **)addresses)); + break; + default: + goto out; + } + + dns_ok = type; + +out: + if (arg == NULL) + event_loopexit(NULL); + else + event_base_loopexit((struct event_base *)arg, NULL); +} + +static void +dns_gethostbyname(void) +{ + dns_ok = 0; + evdns_resolve_ipv4("www.monkey.org", 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + tt_int_op(dns_ok, ==, DNS_IPv4_A); + test_ok = dns_ok; +end: + ; +} + +static void +dns_gethostbyname6(void) +{ + dns_ok = 0; + evdns_resolve_ipv6("www.ietf.org", 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + if (!dns_ok && dns_err == DNS_ERR_TIMEOUT) { + tt_skip(); + } + + tt_int_op(dns_ok, ==, DNS_IPv6_AAAA); + test_ok = 1; +end: + ; +} + +static void +dns_gethostbyaddr(void) +{ + struct in_addr in; + in.s_addr = htonl(0x7f000001ul); /* 127.0.0.1 */ + dns_ok = 0; + evdns_resolve_reverse(&in, 0, dns_gethostbyname_cb, NULL); + event_dispatch(); + + tt_int_op(dns_ok, ==, DNS_PTR); + test_ok = dns_ok; +end: + ; +} + +static void +dns_resolve_reverse(void *ptr) +{ + struct in_addr in; + struct event_base *base = event_base_new(); + struct evdns_base *dns = evdns_base_new(base, EVDNS_BASE_INITIALIZE_NAMESERVERS); + struct evdns_request *req = NULL; + + tt_assert(base); + tt_assert(dns); + in.s_addr = htonl(0x7f000001ul); /* 127.0.0.1 */ + dns_ok = 0; + + req = evdns_base_resolve_reverse( + dns, &in, 0, dns_gethostbyname_cb, base); + tt_assert(req); + + event_base_dispatch(base); + + tt_int_op(dns_ok, ==, DNS_PTR); + +end: + if (dns) + evdns_base_free(dns, 0); + if (base) + event_base_free(base); +} + +static int n_server_responses = 0; + +static void +dns_server_request_cb(struct evdns_server_request *req, void *data) +{ + int i, r; + const char TEST_ARPA[] = "11.11.168.192.in-addr.arpa"; + const char TEST_IN6[] = + "f.e.f.e." "0.0.0.0." "0.0.0.0." "1.1.1.1." + "a.a.a.a." "0.0.0.0." "0.0.0.0." "0.f.f.f.ip6.arpa"; + + for (i = 0; i < req->nquestions; ++i) { + const int qtype = req->questions[i]->type; + const int qclass = req->questions[i]->dns_question_class; + const char *qname = req->questions[i]->name; + + struct in_addr ans; + ans.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */ + if (qtype == EVDNS_TYPE_A && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "zz.example.com")) { + r = evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 12345); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_AAAA && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "zz.example.com")) { + char addr6[17] = "abcdefghijklmnop"; + r = evdns_server_request_add_aaaa_reply(req, + qname, 1, addr6, 123); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_PTR && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, TEST_ARPA)) { + r = evdns_server_request_add_ptr_reply(req, NULL, + qname, "ZZ.EXAMPLE.COM", 54321); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_PTR && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, TEST_IN6)){ + r = evdns_server_request_add_ptr_reply(req, NULL, + qname, + "ZZ-INET6.EXAMPLE.COM", 54322); + if (r<0) + dns_ok = 0; + } else if (qtype == EVDNS_TYPE_A && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "drop.example.com")) { + if (evdns_server_request_drop(req)<0) + dns_ok = 0; + return; + } else { + printf("Unexpected question %d %d \"%s\" ", + qtype, qclass, qname); + dns_ok = 0; + } + } + r = evdns_server_request_respond(req, 0); + if (r<0) { + printf("Couldn't send reply. "); + dns_ok = 0; + } +} + +static void +dns_server_gethostbyname_cb(int result, char type, int count, int ttl, + void *addresses, void *arg) +{ + if (result == DNS_ERR_CANCEL) { + if (arg != (void*)(char*)90909) { + printf("Unexpected cancelation"); + dns_ok = 0; + } + dns_got_cancel = 1; + goto out; + } + if (result != DNS_ERR_NONE) { + printf("Unexpected result %d. ", result); + dns_ok = 0; + goto out; + } + if (count != 1) { + printf("Unexpected answer count %d. ", count); + dns_ok = 0; + goto out; + } + switch (type) { + case DNS_IPv4_A: { + struct in_addr *in_addrs = addresses; + if (in_addrs[0].s_addr != htonl(0xc0a80b0bUL) || ttl != 12345) { + printf("Bad IPv4 response \"%s\" %d. ", + inet_ntoa(in_addrs[0]), ttl); + dns_ok = 0; + goto out; + } + break; + } + case DNS_IPv6_AAAA: { +#if defined (EVENT__HAVE_STRUCT_IN6_ADDR) && defined(EVENT__HAVE_INET_NTOP) && defined(INET6_ADDRSTRLEN) + struct in6_addr *in6_addrs = addresses; + char buf[INET6_ADDRSTRLEN+1]; + if (memcmp(&in6_addrs[0].s6_addr, "abcdefghijklmnop", 16) + || ttl != 123) { + const char *b = evutil_inet_ntop(AF_INET6, &in6_addrs[0],buf,sizeof(buf)); + printf("Bad IPv6 response \"%s\" %d. ", b, ttl); + dns_ok = 0; + goto out; + } +#endif + break; + } + case DNS_PTR: { + char **addrs = addresses; + if (arg != (void*)6) { + if (strcmp(addrs[0], "ZZ.EXAMPLE.COM") || + ttl != 54321) { + printf("Bad PTR response \"%s\" %d. ", + addrs[0], ttl); + dns_ok = 0; + goto out; + } + } else { + if (strcmp(addrs[0], "ZZ-INET6.EXAMPLE.COM") || + ttl != 54322) { + printf("Bad ipv6 PTR response \"%s\" %d. ", + addrs[0], ttl); + dns_ok = 0; + goto out; + } + } + break; + } + default: + printf("Bad response type %d. ", type); + dns_ok = 0; + } + out: + if (++n_server_responses == 3) { + event_loopexit(NULL); + } +} + +static void +dns_server(void) +{ + evutil_socket_t sock=-1; + struct sockaddr_in my_addr; + struct sockaddr_storage ss; + ev_socklen_t slen; + struct evdns_server_port *port=NULL; + struct in_addr resolve_addr; + struct in6_addr resolve_addr6; + struct evdns_base *base=NULL; + struct evdns_request *req=NULL; + + dns_ok = 1; + + base = evdns_base_new(NULL, 0); + + /* Now configure a nameserver port. */ + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock<0) { + tt_abort_perror("socket"); + } + + evutil_make_socket_nonblocking(sock); + + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sin_family = AF_INET; + my_addr.sin_port = 0; /* kernel picks */ + my_addr.sin_addr.s_addr = htonl(0x7f000001UL); + if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr)) < 0) { + tt_abort_perror("bind"); + } + slen = sizeof(ss); + if (getsockname(sock, (struct sockaddr*)&ss, &slen) < 0) { + tt_abort_perror("getsockname"); + } + + port = evdns_add_server_port(sock, 0, dns_server_request_cb, NULL); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evdns_base_nameserver_sockaddr_add(base, (struct sockaddr*)&ss, slen, 0); + tt_int_op(evdns_base_count_nameservers(base), ==, 1); + { + struct sockaddr_storage ss2; + int slen2; + + memset(&ss2, 0, sizeof(ss2)); + + slen2 = evdns_base_get_nameserver_addr(base, 0, (struct sockaddr *)&ss2, 3); + tt_int_op(slen2, ==, slen); + tt_int_op(ss2.ss_family, ==, 0); + slen2 = evdns_base_get_nameserver_addr(base, 0, (struct sockaddr *)&ss2, sizeof(ss2)); + tt_int_op(slen2, ==, slen); + tt_mem_op(&ss2, ==, &ss, slen); + + slen2 = evdns_base_get_nameserver_addr(base, 1, (struct sockaddr *)&ss2, sizeof(ss2)); + tt_int_op(-1, ==, slen2); + } + + /* Send some queries. */ + evdns_base_resolve_ipv4(base, "zz.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, NULL); + evdns_base_resolve_ipv6(base, "zz.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, NULL); + resolve_addr.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */ + evdns_base_resolve_reverse(base, &resolve_addr, 0, + dns_server_gethostbyname_cb, NULL); + memcpy(resolve_addr6.s6_addr, + "\xff\xf0\x00\x00\x00\x00\xaa\xaa" + "\x11\x11\x00\x00\x00\x00\xef\xef", 16); + evdns_base_resolve_reverse_ipv6(base, &resolve_addr6, 0, + dns_server_gethostbyname_cb, (void*)6); + + req = evdns_base_resolve_ipv4(base, + "drop.example.com", DNS_QUERY_NO_SEARCH, + dns_server_gethostbyname_cb, (void*)(char*)90909); + + evdns_cancel_request(base, req); + + event_dispatch(); + + tt_assert(dns_got_cancel); + test_ok = dns_ok; + +end: + if (port) + evdns_close_server_port(port); + if (sock >= 0) + evutil_closesocket(sock); + if (base) + evdns_base_free(base, 0); +} + +static int n_replies_left; +static struct event_base *exit_base; +static struct evdns_server_port *exit_port; + +struct generic_dns_callback_result { + int result; + char type; + int count; + int ttl; + size_t addrs_len; + void *addrs; + char addrs_buf[256]; +}; + +static void +generic_dns_callback(int result, char type, int count, int ttl, void *addresses, + void *arg) +{ + size_t len; + struct generic_dns_callback_result *res = arg; + res->result = result; + res->type = type; + res->count = count; + res->ttl = ttl; + + if (type == DNS_IPv4_A) + len = count * 4; + else if (type == DNS_IPv6_AAAA) + len = count * 16; + else if (type == DNS_PTR) + len = strlen(addresses)+1; + else { + res->addrs_len = len = 0; + res->addrs = NULL; + } + if (len) { + res->addrs_len = len; + if (len > 256) + len = 256; + memcpy(res->addrs_buf, addresses, len); + res->addrs = res->addrs_buf; + } + + --n_replies_left; + if (n_replies_left == 0) { + if (exit_port) { + evdns_close_server_port(exit_port); + exit_port = NULL; + } else + event_base_loopexit(exit_base, NULL); + } +} + +static struct regress_dns_server_table search_table[] = { + { "host.a.example.com", "err", "3", 0, 0 }, + { "host.b.example.com", "err", "3", 0, 0 }, + { "host.c.example.com", "A", "11.22.33.44", 0, 0 }, + { "host2.a.example.com", "err", "3", 0, 0 }, + { "host2.b.example.com", "A", "200.100.0.100", 0, 0 }, + { "host2.c.example.com", "err", "3", 0, 0 }, + { "hostn.a.example.com", "errsoa", "0", 0, 0 }, + { "hostn.b.example.com", "errsoa", "3", 0, 0 }, + { "hostn.c.example.com", "err", "0", 0, 0 }, + + { "host", "err", "3", 0, 0 }, + { "host2", "err", "3", 0, 0 }, + { "*", "err", "3", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; +static void +dns_search_test_impl(void *arg, int lower) +{ + struct regress_dns_server_table table[ARRAY_SIZE(search_table)]; + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r[8]; + size_t i; + + for (i = 0; i < ARRAY_SIZE(table); ++i) { + table[i] = search_table[i]; + table[i].lower = lower; + } + + tt_assert(regress_dnsserver(base, &portnum, table)); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, 0); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + evdns_base_search_add(dns, "a.example.com"); + evdns_base_search_add(dns, "b.example.com"); + evdns_base_search_add(dns, "c.example.com"); + + n_replies_left = ARRAY_SIZE(r); + exit_base = base; + + evdns_base_resolve_ipv4(dns, "host", 0, generic_dns_callback, &r[0]); + evdns_base_resolve_ipv4(dns, "host2", 0, generic_dns_callback, &r[1]); + evdns_base_resolve_ipv4(dns, "host", DNS_NO_SEARCH, generic_dns_callback, &r[2]); + evdns_base_resolve_ipv4(dns, "host2", DNS_NO_SEARCH, generic_dns_callback, &r[3]); + evdns_base_resolve_ipv4(dns, "host3", 0, generic_dns_callback, &r[4]); + evdns_base_resolve_ipv4(dns, "hostn.a.example.com", DNS_NO_SEARCH, generic_dns_callback, &r[5]); + evdns_base_resolve_ipv4(dns, "hostn.b.example.com", DNS_NO_SEARCH, generic_dns_callback, &r[6]); + evdns_base_resolve_ipv4(dns, "hostn.c.example.com", DNS_NO_SEARCH, generic_dns_callback, &r[7]); + + event_base_dispatch(base); + + tt_int_op(r[0].type, ==, DNS_IPv4_A); + tt_int_op(r[0].count, ==, 1); + tt_int_op(((ev_uint32_t*)r[0].addrs)[0], ==, htonl(0x0b16212c)); + tt_int_op(r[1].type, ==, DNS_IPv4_A); + tt_int_op(r[1].count, ==, 1); + tt_int_op(((ev_uint32_t*)r[1].addrs)[0], ==, htonl(0xc8640064)); + tt_int_op(r[2].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[3].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[4].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[5].result, ==, DNS_ERR_NODATA); + tt_int_op(r[5].ttl, ==, 42); + tt_int_op(r[6].result, ==, DNS_ERR_NOTEXIST); + tt_int_op(r[6].ttl, ==, 42); + tt_int_op(r[7].result, ==, DNS_ERR_NODATA); + tt_int_op(r[7].ttl, ==, 0); + +end: + if (dns) + evdns_base_free(dns, 0); + + regress_clean_dnsserver(); +} +static void +dns_search_empty_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + + dns = evdns_base_new(base, 0); + + evdns_base_search_add(dns, "whatever.example.com"); + + n_replies_left = 1; + exit_base = base; + + tt_ptr_op(evdns_base_resolve_ipv4(dns, "", 0, generic_dns_callback, NULL), ==, NULL); + +end: + if (dns) + evdns_base_free(dns, 0); +} +static void dns_search_test(void *arg) { dns_search_test_impl(arg, 0); } +static void dns_search_lower_test(void *arg) { dns_search_test_impl(arg, 1); } + +static int request_count = 0; +static struct evdns_request *current_req = NULL; + +static void +search_cancel_server_cb(struct evdns_server_request *req, void *data) +{ + const char *question; + + if (req->nquestions != 1) + TT_DIE(("Only handling one question at a time; got %d", + req->nquestions)); + + question = req->questions[0]->name; + + TT_BLATHER(("got question, %s", question)); + + tt_assert(request_count > 0); + tt_assert(!evdns_server_request_respond(req, 3)); + + if (!--request_count) + evdns_cancel_request(NULL, current_req); + +end: + ; +} + +static void +dns_search_cancel_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + struct evdns_server_port *port = NULL; + ev_uint16_t portnum = 0; + struct generic_dns_callback_result r1; + char buf[64]; + + port = regress_get_dnsserver(base, &portnum, NULL, + search_cancel_server_cb, NULL); + tt_assert(port); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, 0); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + evdns_base_search_add(dns, "a.example.com"); + evdns_base_search_add(dns, "b.example.com"); + evdns_base_search_add(dns, "c.example.com"); + evdns_base_search_add(dns, "d.example.com"); + + exit_base = base; + request_count = 3; + n_replies_left = 1; + + current_req = evdns_base_resolve_ipv4(dns, "host", 0, + generic_dns_callback, &r1); + event_base_dispatch(base); + + tt_int_op(r1.result, ==, DNS_ERR_CANCEL); + +end: + if (port) + evdns_close_server_port(port); + if (dns) + evdns_base_free(dns, 0); +} + +static void +fail_server_cb(struct evdns_server_request *req, void *data) +{ + const char *question; + int *count = data; + struct in_addr in; + + /* Drop the first N requests that we get. */ + if (*count > 0) { + --*count; + tt_want(! evdns_server_request_drop(req)); + return; + } + + if (req->nquestions != 1) + TT_DIE(("Only handling one question at a time; got %d", + req->nquestions)); + + question = req->questions[0]->name; + + if (!evutil_ascii_strcasecmp(question, "google.com")) { + /* Detect a probe, and get out of the loop. */ + event_base_loopexit(exit_base, NULL); + } + + tt_assert(evutil_inet_pton(AF_INET, "16.32.64.128", &in)); + evdns_server_request_add_a_reply(req, question, 1, &in.s_addr, + 100); + tt_assert(! evdns_server_request_respond(req, 0)) + return; +end: + tt_want(! evdns_server_request_drop(req)); +} + +static void +dns_retry_test_impl(void *arg, int flags) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_server_port *port = NULL; + struct evdns_base *dns = NULL; + int drop_count = 2; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r1; + + port = regress_get_dnsserver(base, &portnum, NULL, + fail_server_cb, &drop_count); + tt_assert(port); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, flags); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(! evdns_base_set_option(dns, "timeout", "0.2")); + tt_assert(! evdns_base_set_option(dns, "max-timeouts:", "10")); + tt_assert(! evdns_base_set_option(dns, "initial-probe-timeout", "0.1")); + + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + + n_replies_left = 1; + exit_base = base; + + event_base_dispatch(base); + + tt_int_op(drop_count, ==, 0); + + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0x10204080)); + + /* Now try again, but this time have the server get treated as + * failed, so we can send it a test probe. */ + drop_count = 4; + tt_assert(! evdns_base_set_option(dns, "max-timeouts:", "2")); + tt_assert(! evdns_base_set_option(dns, "attempts:", "3")); + memset(&r1, 0, sizeof(r1)); + + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + + n_replies_left = 2; + + /* This will run until it answers the "google.com" probe request. */ + event_base_dispatch(base); + + /* We'll treat the server as failed here. */ + tt_int_op(r1.result, ==, DNS_ERR_TIMEOUT); + + /* It should work this time. */ + tt_int_op(drop_count, ==, 0); + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + + event_base_dispatch(base); + tt_int_op(r1.result, ==, DNS_ERR_NONE); + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0x10204080)); + +end: + if (dns) + evdns_base_free(dns, 0); + if (port) + evdns_close_server_port(port); +} +static void +dns_retry_test(void *arg) +{ + dns_retry_test_impl(arg, 0); +} +static void +dns_retry_disable_when_inactive_test(void *arg) +{ + dns_retry_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); +} + +static struct regress_dns_server_table internal_error_table[] = { + /* Error 4 (NOTIMPL) makes us reissue the request to another server + if we can. + + XXXX we should reissue under a much wider set of circumstances! + */ + { "foof.example.com", "err", "4", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static struct regress_dns_server_table reissue_table[] = { + { "foof.example.com", "A", "240.15.240.15", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static void +dns_reissue_test_impl(void *arg, int flags) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_server_port *port1 = NULL, *port2 = NULL; + struct evdns_base *dns = NULL; + struct generic_dns_callback_result r1; + ev_uint16_t portnum1 = 0, portnum2=0; + char buf1[64], buf2[64]; + + port1 = regress_get_dnsserver(base, &portnum1, NULL, + regress_dns_server_cb, internal_error_table); + tt_assert(port1); + port2 = regress_get_dnsserver(base, &portnum2, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(port2); + evutil_snprintf(buf1, sizeof(buf1), "127.0.0.1:%d", (int)portnum1); + evutil_snprintf(buf2, sizeof(buf2), "127.0.0.1:%d", (int)portnum2); + + dns = evdns_base_new(base, flags); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf1)); + tt_assert(! evdns_base_set_option(dns, "timeout:", "0.3")); + tt_assert(! evdns_base_set_option(dns, "max-timeouts:", "2")); + tt_assert(! evdns_base_set_option(dns, "attempts:", "5")); + + memset(&r1, 0, sizeof(r1)); + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, + generic_dns_callback, &r1); + + /* Add this after, so that we are sure to get a reissue. */ + tt_assert(!evdns_base_nameserver_ip_add(dns, buf2)); + + n_replies_left = 1; + exit_base = base; + + event_base_dispatch(base); + tt_int_op(r1.result, ==, DNS_ERR_NONE); + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0xf00ff00f)); + + /* Make sure we dropped at least once. */ + tt_int_op(internal_error_table[0].seen, >, 0); + +end: + if (dns) + evdns_base_free(dns, 0); + if (port1) + evdns_close_server_port(port1); + if (port2) + evdns_close_server_port(port2); +} +static void +dns_reissue_test(void *arg) +{ + dns_reissue_test_impl(arg, 0); +} +static void +dns_reissue_disable_when_inactive_test(void *arg) +{ + dns_reissue_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); +} + +#if 0 +static void +dumb_bytes_fn(char *p, size_t n) +{ + unsigned i; + /* This gets us 6 bits of entropy per transaction ID, which means we + * will have probably have collisions and need to pick again. */ + for (i=0;i<n;++i) + p[i] = (char)(rand() & 7); +} +#endif + +static void +dns_inflight_test_impl(void *arg, int flags) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + int disable_when_inactive = flags & EVDNS_BASE_DISABLE_WHEN_INACTIVE; + + struct generic_dns_callback_result r[20]; + int i; + + dns_port = regress_get_dnsserver(base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + if (disable_when_inactive) { + exit_port = dns_port; + } + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, flags); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(! evdns_base_set_option(dns, "max-inflight:", "3")); + tt_assert(! evdns_base_set_option(dns, "randomize-case:", "0")); + + for (i=0;i<20;++i) + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, generic_dns_callback, &r[i]); + + n_replies_left = 20; + exit_base = base; + + event_base_dispatch(base); + + for (i=0;i<20;++i) { + tt_int_op(r[i].type, ==, DNS_IPv4_A); + tt_int_op(r[i].count, ==, 1); + tt_int_op(((ev_uint32_t*)r[i].addrs)[0], ==, htonl(0xf00ff00f)); + } + +end: + if (dns) + evdns_base_free(dns, 0); + if (exit_port) { + evdns_close_server_port(exit_port); + exit_port = NULL; + } else if (! disable_when_inactive) { + evdns_close_server_port(dns_port); + } +} + +static void +dns_inflight_test(void *arg) +{ + dns_inflight_test_impl(arg, 0); +} + +static void +dns_disable_when_inactive_test(void *arg) +{ + dns_inflight_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); +} + +static void +dns_disable_when_inactive_no_ns_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base, *inactive_base; + struct evdns_base *dns = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + struct generic_dns_callback_result r; + + inactive_base = event_base_new(); + tt_assert(inactive_base); + + /** Create dns server with inactive base, to avoid replying to clients */ + tt_assert(regress_dnsserver(inactive_base, &portnum, search_table)); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(! evdns_base_set_option(dns, "timeout:", "0.1")); + + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, generic_dns_callback, &r); + n_replies_left = 1; + exit_base = base; + + event_base_dispatch(base); + + tt_int_op(n_replies_left, ==, 0); + + tt_int_op(r.result, ==, DNS_ERR_TIMEOUT); + tt_int_op(r.count, ==, 0); + tt_ptr_op(r.addrs, ==, NULL); + +end: + if (dns) + evdns_base_free(dns, 0); + regress_clean_dnsserver(); + if (inactive_base) + event_base_free(inactive_base); +} + +static void +dns_initialize_nameservers_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + + dns = evdns_base_new(base, 0); + tt_assert(dns); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, -1); + evdns_base_free(dns, 0); + + dns = evdns_base_new(base, EVDNS_BASE_INITIALIZE_NAMESERVERS); + tt_assert(dns); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, sizeof(struct sockaddr)); + +end: + if (dns) + evdns_base_free(dns, 0); +} +#ifndef _WIN32 +#define RESOLV_FILE "empty-resolv.conf" +static void +dns_nameservers_no_default_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + int ok = access(RESOLV_FILE, R_OK); + + tt_assert(ok); + + dns = evdns_base_new(base, 0); + tt_assert(dns); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, -1); + + /* We cannot test + * EVDNS_BASE_INITIALIZE_NAMESERVERS|EVDNS_BASE_NAMESERVERS_NO_DEFAULT + * because we cannot mock "/etc/resolv.conf" (yet). */ + + evdns_base_resolv_conf_parse(dns, + DNS_OPTIONS_ALL|DNS_OPTION_NAMESERVERS_NO_DEFAULT, RESOLV_FILE); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, -1); + + evdns_base_resolv_conf_parse(dns, DNS_OPTIONS_ALL, RESOLV_FILE); + tt_int_op(evdns_base_get_nameserver_addr(dns, 0, NULL, 0), ==, sizeof(struct sockaddr)); + +end: + if (dns) + evdns_base_free(dns, 0); +} +#endif + +/* === Test for bufferevent_socket_connect_hostname */ + +static int total_connected_or_failed = 0; +static int total_n_accepted = 0; +static struct event_base *be_connect_hostname_base = NULL; + +/* Implements a DNS server for the connect_hostname test and the + * getaddrinfo_async test */ +static void +be_getaddrinfo_server_cb(struct evdns_server_request *req, void *data) +{ + int i; + int *n_got_p=data; + int added_any=0; + ++*n_got_p; + + for (i = 0; i < req->nquestions; ++i) { + const int qtype = req->questions[i]->type; + const int qclass = req->questions[i]->dns_question_class; + const char *qname = req->questions[i]->name; + struct in_addr ans; + struct in6_addr ans6; + memset(&ans6, 0, sizeof(ans6)); + + TT_BLATHER(("Got question about %s, type=%d", qname, qtype)); + + if (qtype == EVDNS_TYPE_A && + qclass == EVDNS_CLASS_INET && + !evutil_ascii_strcasecmp(qname, "nobodaddy.example.com")) { + ans.s_addr = htonl(0x7f000001); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "nosuchplace.example.com")) { + /* ok, just say notfound. */ + } else if (!evutil_ascii_strcasecmp(qname, + "both.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0x50502020); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x80; + ans6.s6_addr[1] = 0xff; + ans6.s6_addr[14] = 0xbb; + ans6.s6_addr[15] = 0xbb; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } + evdns_server_request_add_cname_reply(req, qname, + "both-canonical.example.com", 1000); + } else if (!evutil_ascii_strcasecmp(qname, + "v4only.example.com") || + !evutil_ascii_strcasecmp(qname, "v4assert.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0x12345678); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "v4assert.example.com")) { + TT_FAIL(("Got an AAAA request for v4assert")); + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6only.example.com") || + !evutil_ascii_strcasecmp(qname, "v6assert.example.com")) { + if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x0b; + ans6.s6_addr[1] = 0x0b; + ans6.s6_addr[14] = 0xf0; + ans6.s6_addr[15] = 0x0d; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } else if (!evutil_ascii_strcasecmp(qname, + "v6assert.example.com")) { + TT_FAIL(("Got a A request for v6assert")); + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6timeout.example.com")) { + if (qtype == EVDNS_TYPE_A) { + ans.s_addr = htonl(0xabcdef01); + evdns_server_request_add_a_reply(req, qname, + 1, &ans.s_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_AAAA) { + /* Let the v6 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "v4timeout.example.com")) { + if (qtype == EVDNS_TYPE_AAAA) { + ans6.s6_addr[0] = 0x0a; + ans6.s6_addr[1] = 0x0a; + ans6.s6_addr[14] = 0xff; + ans6.s6_addr[15] = 0x01; + evdns_server_request_add_aaaa_reply(req, qname, + 1, &ans6.s6_addr, 2000); + added_any = 1; + } else if (qtype == EVDNS_TYPE_A) { + /* Let the v4 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "v6timeout-nonexist.example.com")) { + if (qtype == EVDNS_TYPE_A) { + /* Fall through, give an nexist. */ + } else if (qtype == EVDNS_TYPE_AAAA) { + /* Let the v6 request time out.*/ + evdns_server_request_drop(req); + return; + } + } else if (!evutil_ascii_strcasecmp(qname, + "all-timeout.example.com")) { + /* drop all requests */ + evdns_server_request_drop(req); + return; + } else { + TT_GRIPE(("Got weird request for %s",qname)); + } + } + if (added_any) { + TT_BLATHER(("answering")); + evdns_server_request_respond(req, 0); + } else { + TT_BLATHER(("saying nexist.")); + evdns_server_request_respond(req, 3); + } +} + +/* Implements a listener for connect_hostname test. */ +static void +nil_accept_cb(struct evconnlistener *l, evutil_socket_t fd, struct sockaddr *s, + int socklen, void *arg) +{ + int *p = arg; + (*p)++; + ++total_n_accepted; + /* don't do anything with the socket; let it close when we exit() */ + if (total_n_accepted >= 3 && total_connected_or_failed >= 5) + event_base_loopexit(be_connect_hostname_base, + NULL); +} + +struct be_conn_hostname_result { + int dnserr; + int what; +}; + +/* Bufferevent event callback for the connect_hostname test: remembers what + * event we got. */ +static void +be_connect_hostname_event_cb(struct bufferevent *bev, short what, void *ctx) +{ + struct be_conn_hostname_result *got = ctx; + + if (got->what) { + TT_FAIL(("Two events on one bufferevent. %d,%d", + got->what, (int)what)); + } + + TT_BLATHER(("Got a bufferevent event %d", what)); + got->what = what; + + if ((what & BEV_EVENT_CONNECTED) || (what & BEV_EVENT_ERROR)) { + int expected = 3; + int r = bufferevent_socket_get_dns_error(bev); + + if (r) { + got->dnserr = r; + TT_BLATHER(("DNS error %d: %s", r, + evutil_gai_strerror(r))); + } + ++total_connected_or_failed; + TT_BLATHER(("Got %d connections or errors.", total_connected_or_failed)); + + /** emfile test */ + if (errno == EMFILE) { + expected = 0; + } + + if (total_n_accepted >= expected && total_connected_or_failed >= 5) + event_base_loopexit(be_connect_hostname_base, + NULL); + } +} + +static void +test_bufferevent_connect_hostname(void *arg) +{ + struct basic_test_data *data = arg; + struct evconnlistener *listener = NULL; + struct bufferevent *be[5]; + struct be_conn_hostname_result be_outcome[ARRAY_SIZE(be)]; + int expect_err; + struct evdns_base *dns=NULL; + struct evdns_server_port *port=NULL; + struct sockaddr_in sin; + int listener_port=-1; + ev_uint16_t dns_port=0; + int n_accept=0, n_dns=0; + char buf[128]; + int emfile = data->setup_data && !strcmp(data->setup_data, "emfile"); + unsigned i; + int ret; + + be_connect_hostname_base = data->base; + + /* Bind an address and figure out what port it's on. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sin.sin_port = 0; + listener = evconnlistener_new_bind(data->base, nil_accept_cb, + &n_accept, + LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC, + -1, (struct sockaddr *)&sin, sizeof(sin)); + tt_assert(listener); + listener_port = regress_get_socket_port( + evconnlistener_get_fd(listener)); + + port = regress_get_dnsserver(data->base, &dns_port, NULL, + be_getaddrinfo_server_cb, &n_dns); + tt_assert(port); + tt_int_op(dns_port, >=, 0); + + /* Start an evdns_base that uses the server as its resolver. */ + dns = evdns_base_new(data->base, 0); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)dns_port); + evdns_base_nameserver_ip_add(dns, buf); + +#ifdef EVENT__HAVE_SETRLIMIT + if (emfile) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct rlimit file = { fd, fd }; + + tt_int_op(fd, >=, 0); + tt_assert(!close(fd)); + + tt_assert(!setrlimit(RLIMIT_NOFILE, &file)); + } +#endif + + /* Now, finally, at long last, launch the bufferevents. One should do + * a failing lookup IP, one should do a successful lookup by IP, + * and one should do a successful lookup by hostname. */ + for (i = 0; i < ARRAY_SIZE(be); ++i) { + memset(&be_outcome[i], 0, sizeof(be_outcome[i])); + be[i] = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(be[i], NULL, NULL, be_connect_hostname_event_cb, + &be_outcome[i]); + } + + /* Use the blocking resolver. This one will fail if your resolver + * can't resolve localhost to 127.0.0.1 */ + tt_assert(!bufferevent_socket_connect_hostname(be[3], NULL, AF_INET, + "localhost", listener_port)); + /* Use the blocking resolver with a nonexistent hostname. */ + tt_assert(!bufferevent_socket_connect_hostname(be[4], NULL, AF_INET, + "nonesuch.nowhere.example.com", 80)); + { + /* The blocking resolver will use the system nameserver, which + * might tell us anything. (Yes, some twits even pretend that + * example.com is real.) Let's see what answer to expect. */ + struct evutil_addrinfo hints, *ai = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + expect_err = evutil_getaddrinfo( + "nonesuch.nowhere.example.com", "80", &hints, &ai); + } + /* Launch an async resolve that will fail. */ + tt_assert(!bufferevent_socket_connect_hostname(be[0], dns, AF_INET, + "nosuchplace.example.com", listener_port)); + /* Connect to the IP without resolving. */ + tt_assert(!bufferevent_socket_connect_hostname(be[1], dns, AF_INET, + "127.0.0.1", listener_port)); + /* Launch an async resolve that will succeed. */ + tt_assert(!bufferevent_socket_connect_hostname(be[2], dns, AF_INET, + "nobodaddy.example.com", listener_port)); + + ret = event_base_dispatch(data->base); +#ifdef __sun__ + if (emfile && !strcmp(event_base_get_method(data->base), "devpoll")) { + tt_int_op(ret, ==, -1); + /** DP_POLL failed */ + tt_skip(); + } else +#endif + { + tt_int_op(ret, ==, 0); + } + + tt_int_op(be_outcome[0].what, ==, BEV_EVENT_ERROR); + tt_int_op(be_outcome[0].dnserr, ==, EVUTIL_EAI_NONAME); + tt_int_op(be_outcome[1].what, ==, !emfile ? BEV_EVENT_CONNECTED : BEV_EVENT_ERROR); + tt_int_op(be_outcome[1].dnserr, ==, 0); + tt_int_op(be_outcome[2].what, ==, !emfile ? BEV_EVENT_CONNECTED : BEV_EVENT_ERROR); + tt_int_op(be_outcome[2].dnserr, ==, 0); + tt_int_op(be_outcome[3].what, ==, !emfile ? BEV_EVENT_CONNECTED : BEV_EVENT_ERROR); + if (!emfile) { + tt_int_op(be_outcome[3].dnserr, ==, 0); + } else { + tt_int_op(be_outcome[3].dnserr, !=, 0); + } + if (expect_err) { + tt_int_op(be_outcome[4].what, ==, BEV_EVENT_ERROR); + tt_int_op(be_outcome[4].dnserr, ==, expect_err); + } + + if (emfile) { + tt_int_op(n_accept, ==, 0); + } else { + tt_int_op(n_accept, ==, 3); + } + tt_int_op(n_dns, ==, 2); + +end: + if (listener) + evconnlistener_free(listener); + if (port) + evdns_close_server_port(port); + if (dns) + evdns_base_free(dns, 0); + for (i = 0; i < ARRAY_SIZE(be); ++i) { + if (be[i]) + bufferevent_free(be[i]); + } +} + + +struct gai_outcome { + int err; + struct evutil_addrinfo *ai; +}; + +static int n_gai_results_pending = 0; +static struct event_base *exit_base_on_no_pending_results = NULL; + +static void +gai_cb(int err, struct evutil_addrinfo *res, void *ptr) +{ + struct gai_outcome *go = ptr; + go->err = err; + go->ai = res; + if (--n_gai_results_pending <= 0 && exit_base_on_no_pending_results) + event_base_loopexit(exit_base_on_no_pending_results, NULL); + if (n_gai_results_pending < 900) + TT_BLATHER(("Got an answer; expecting %d more.", + n_gai_results_pending)); +} + +static void +cancel_gai_cb(evutil_socket_t fd, short what, void *ptr) +{ + struct evdns_getaddrinfo_request *r = ptr; + evdns_getaddrinfo_cancel(r); +} + +static void +test_getaddrinfo_async(void *arg) +{ + struct basic_test_data *data = arg; + struct evutil_addrinfo hints, *a; + struct gai_outcome local_outcome; + struct gai_outcome a_out[12]; + unsigned i; + struct evdns_getaddrinfo_request *r; + char buf[128]; + struct evdns_server_port *port = NULL; + ev_uint16_t dns_port = 0; + int n_dns_questions = 0; + struct evdns_base *dns_base; + + memset(a_out, 0, sizeof(a_out)); + memset(&local_outcome, 0, sizeof(local_outcome)); + + dns_base = evdns_base_new(data->base, 0); + tt_assert(dns_base); + + /* for localhost */ + evdns_base_load_hosts(dns_base, NULL); + + tt_assert(! evdns_base_set_option(dns_base, "timeout", "0.3")); + tt_assert(! evdns_base_set_option(dns_base, "getaddrinfo-allow-skew", "0.2")); + + n_gai_results_pending = 10000; /* don't think about exiting yet. */ + + /* 1. Try some cases that will never hit the asynchronous resolver. */ + /* 1a. Simple case with a symbolic service name */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + memset(&local_outcome, 0, sizeof(local_outcome)); + r = evdns_getaddrinfo(dns_base, "1.2.3.4", "http", + &hints, gai_cb, &local_outcome); + tt_assert(! r); + if (!local_outcome.err) { + tt_ptr_op(local_outcome.ai,!=,NULL); + test_ai_eq(local_outcome.ai, "1.2.3.4:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + } else { + TT_BLATHER(("Apparently we have no getservbyname.")); + } + + /* 1b. EVUTIL_AI_NUMERICHOST is set */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + memset(&local_outcome, 0, sizeof(local_outcome)); + r = evdns_getaddrinfo(dns_base, "www.google.com", "80", + &hints, gai_cb, &local_outcome); + tt_ptr_op(r,==,NULL); + tt_int_op(local_outcome.err,==,EVUTIL_EAI_NONAME); + tt_ptr_op(local_outcome.ai,==,NULL); + + /* 1c. We give a numeric address (ipv6) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + r = evdns_getaddrinfo(dns_base, "f::f", "8008", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + tt_ptr_op(local_outcome.ai->ai_next,==,NULL); + test_ai_eq(local_outcome.ai, "[f::f]:8008", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1d. We give a numeric address (ipv4) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "5.6.7.8", NULL, + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + a = ai_find_by_protocol(local_outcome.ai, IPPROTO_TCP); + tt_assert(a); + test_ai_eq(a, "5.6.7.8", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_protocol(local_outcome.ai, IPPROTO_UDP); + tt_assert(a); + test_ai_eq(a, "5.6.7.8", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1e. nodename is NULL (bind) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = EVUTIL_AI_PASSIVE; + r = evdns_getaddrinfo(dns_base, NULL, "9090", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 0.0.0.0... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "0.0.0.0:9090", SOCK_DGRAM, IPPROTO_UDP); + /* ... and a v6 address of ::0 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::]:9090", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1f. nodename is NULL (connect) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, NULL, "2", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 127.0.0.1 .... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "127.0.0.1:2", SOCK_STREAM, IPPROTO_TCP); + /* ... and a v6 address of ::1 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::1]:2", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1g. We find localhost immediately. (pf_unspec) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, "LOCALHOST", "80", + &hints, gai_cb, &local_outcome); + tt_assert(!r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 127.0.0.1 .... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "127.0.0.1:80", SOCK_STREAM, IPPROTO_TCP); + /* ... and a v6 address of ::1 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::1]:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1g. We find localhost immediately. (pf_inet6) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, "LOCALHOST", "9999", + &hints, gai_cb, &local_outcome); + tt_assert(! r); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + a = local_outcome.ai; + test_ai_eq(a, "[::1]:9999", SOCK_STREAM, IPPROTO_TCP); + tt_ptr_op(a->ai_next, ==, NULL); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 2. Okay, now we can actually test the asynchronous resolver. */ + /* Start a dummy local dns server... */ + port = regress_get_dnsserver(data->base, &dns_port, NULL, + be_getaddrinfo_server_cb, &n_dns_questions); + tt_assert(port); + tt_int_op(dns_port, >=, 0); + /* ... and tell the evdns_base about it. */ + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", dns_port); + evdns_base_nameserver_ip_add(dns_base, buf); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_CANONNAME; + /* 0: Request for both.example.com should return both addresses. */ + r = evdns_getaddrinfo(dns_base, "both.example.com", "8000", + &hints, gai_cb, &a_out[0]); + tt_assert(r); + + /* 1: Request for v4only.example.com should return one address. */ + r = evdns_getaddrinfo(dns_base, "v4only.example.com", "8001", + &hints, gai_cb, &a_out[1]); + tt_assert(r); + + /* 2: Request for v6only.example.com should return one address. */ + hints.ai_flags = 0; + r = evdns_getaddrinfo(dns_base, "v6only.example.com", "8002", + &hints, gai_cb, &a_out[2]); + tt_assert(r); + + /* 3: PF_INET request for v4assert.example.com should not generate a + * v6 request. The server will fail the test if it does. */ + hints.ai_family = PF_INET; + r = evdns_getaddrinfo(dns_base, "v4assert.example.com", "8003", + &hints, gai_cb, &a_out[3]); + tt_assert(r); + + /* 4: PF_INET6 request for v6assert.example.com should not generate a + * v4 request. The server will fail the test if it does. */ + hints.ai_family = PF_INET6; + r = evdns_getaddrinfo(dns_base, "v6assert.example.com", "8004", + &hints, gai_cb, &a_out[4]); + tt_assert(r); + + /* 5: PF_INET request for nosuchplace.example.com should give NEXIST. */ + hints.ai_family = PF_INET; + r = evdns_getaddrinfo(dns_base, "nosuchplace.example.com", "8005", + &hints, gai_cb, &a_out[5]); + tt_assert(r); + + /* 6: PF_UNSPEC request for nosuchplace.example.com should give NEXIST. + */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "nosuchplace.example.com", "8006", + &hints, gai_cb, &a_out[6]); + tt_assert(r); + + /* 7: PF_UNSPEC request for v6timeout.example.com should give an ipv4 + * address only. */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "v6timeout.example.com", "8007", + &hints, gai_cb, &a_out[7]); + tt_assert(r); + + /* 8: PF_UNSPEC request for v6timeout-nonexist.example.com should give + * a NEXIST */ + hints.ai_family = PF_UNSPEC; + r = evdns_getaddrinfo(dns_base, "v6timeout-nonexist.example.com", + "8008", &hints, gai_cb, &a_out[8]); + tt_assert(r); + + /* 9: AI_ADDRCONFIG should at least not crash. Can't test it more + * without knowing what kind of internet we have. */ + hints.ai_flags |= EVUTIL_AI_ADDRCONFIG; + r = evdns_getaddrinfo(dns_base, "both.example.com", + "8009", &hints, gai_cb, &a_out[9]); + tt_assert(r); + + /* 10: PF_UNSPEC for v4timeout.example.com should give an ipv6 address + * only. */ + hints.ai_family = PF_UNSPEC; + hints.ai_flags = 0; + r = evdns_getaddrinfo(dns_base, "v4timeout.example.com", "8010", + &hints, gai_cb, &a_out[10]); + tt_assert(r); + + /* 11: timeout.example.com: cancel it after 100 msec. */ + r = evdns_getaddrinfo(dns_base, "all-timeout.example.com", "8011", + &hints, gai_cb, &a_out[11]); + tt_assert(r); + { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100*1000; /* 100 msec */ + event_base_once(data->base, -1, EV_TIMEOUT, cancel_gai_cb, + r, &tv); + } + + /* XXXXX There are more tests we could do, including: + + - A test to elicit NODATA. + + */ + + n_gai_results_pending = 12; + exit_base_on_no_pending_results = data->base; + + event_base_dispatch(data->base); + + /* 0: both.example.com */ + tt_int_op(a_out[0].err, ==, 0); + tt_assert(a_out[0].ai); + tt_assert(a_out[0].ai->ai_next); + tt_assert(!a_out[0].ai->ai_next->ai_next); + a = ai_find_by_family(a_out[0].ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "80.80.32.32:8000", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_family(a_out[0].ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[80ff::bbbb]:8000", SOCK_STREAM, IPPROTO_TCP); + tt_assert(a_out[0].ai->ai_canonname); + tt_str_op(a_out[0].ai->ai_canonname, ==, "both-canonical.example.com"); + + /* 1: v4only.example.com */ + tt_int_op(a_out[1].err, ==, 0); + tt_assert(a_out[1].ai); + tt_assert(! a_out[1].ai->ai_next); + test_ai_eq(a_out[1].ai, "18.52.86.120:8001", SOCK_STREAM, IPPROTO_TCP); + tt_assert(a_out[1].ai->ai_canonname == NULL); + + + /* 2: v6only.example.com */ + tt_int_op(a_out[2].err, ==, 0); + tt_assert(a_out[2].ai); + tt_assert(! a_out[2].ai->ai_next); + test_ai_eq(a_out[2].ai, "[b0b::f00d]:8002", SOCK_STREAM, IPPROTO_TCP); + + /* 3: v4assert.example.com */ + tt_int_op(a_out[3].err, ==, 0); + tt_assert(a_out[3].ai); + tt_assert(! a_out[3].ai->ai_next); + test_ai_eq(a_out[3].ai, "18.52.86.120:8003", SOCK_STREAM, IPPROTO_TCP); + + /* 4: v6assert.example.com */ + tt_int_op(a_out[4].err, ==, 0); + tt_assert(a_out[4].ai); + tt_assert(! a_out[4].ai->ai_next); + test_ai_eq(a_out[4].ai, "[b0b::f00d]:8004", SOCK_STREAM, IPPROTO_TCP); + + /* 5: nosuchplace.example.com (inet) */ + tt_int_op(a_out[5].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[5].ai); + + /* 6: nosuchplace.example.com (unspec) */ + tt_int_op(a_out[6].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[6].ai); + + /* 7: v6timeout.example.com */ + tt_int_op(a_out[7].err, ==, 0); + tt_assert(a_out[7].ai); + tt_assert(! a_out[7].ai->ai_next); + test_ai_eq(a_out[7].ai, "171.205.239.1:8007", SOCK_STREAM, IPPROTO_TCP); + + /* 8: v6timeout-nonexist.example.com */ + tt_int_op(a_out[8].err, ==, EVUTIL_EAI_NONAME); + tt_assert(! a_out[8].ai); + + /* 9: both (ADDRCONFIG) */ + tt_int_op(a_out[9].err, ==, 0); + tt_assert(a_out[9].ai); + a = ai_find_by_family(a_out[9].ai, PF_INET); + if (a) + test_ai_eq(a, "80.80.32.32:8009", SOCK_STREAM, IPPROTO_TCP); + else + tt_assert(ai_find_by_family(a_out[9].ai, PF_INET6)); + a = ai_find_by_family(a_out[9].ai, PF_INET6); + if (a) + test_ai_eq(a, "[80ff::bbbb]:8009", SOCK_STREAM, IPPROTO_TCP); + else + tt_assert(ai_find_by_family(a_out[9].ai, PF_INET)); + + /* 10: v4timeout.example.com */ + tt_int_op(a_out[10].err, ==, 0); + tt_assert(a_out[10].ai); + tt_assert(! a_out[10].ai->ai_next); + test_ai_eq(a_out[10].ai, "[a0a::ff01]:8010", SOCK_STREAM, IPPROTO_TCP); + + /* 11: cancelled request. */ + tt_int_op(a_out[11].err, ==, EVUTIL_EAI_CANCEL); + tt_assert(a_out[11].ai == NULL); + +end: + if (local_outcome.ai) + evutil_freeaddrinfo(local_outcome.ai); + for (i = 0; i < ARRAY_SIZE(a_out); ++i) { + if (a_out[i].ai) + evutil_freeaddrinfo(a_out[i].ai); + } + if (port) + evdns_close_server_port(port); + if (dns_base) + evdns_base_free(dns_base, 0); +} + +struct gaic_request_status { + int magic; + struct event_base *base; + struct evdns_base *dns_base; + struct evdns_getaddrinfo_request *request; + struct event cancel_event; + int canceled; +}; + +#define GAIC_MAGIC 0x1234abcd + +static int gaic_pending = 0; +static int gaic_freed = 0; + +static void +gaic_cancel_request_cb(evutil_socket_t fd, short what, void *arg) +{ + struct gaic_request_status *status = arg; + + tt_assert(status->magic == GAIC_MAGIC); + status->canceled = 1; + evdns_getaddrinfo_cancel(status->request); + return; +end: + event_base_loopexit(status->base, NULL); +} + +static void +gaic_server_cb(struct evdns_server_request *req, void *arg) +{ + ev_uint32_t answer = 0x7f000001; + tt_assert(req->nquestions); + evdns_server_request_add_a_reply(req, req->questions[0]->name, 1, + &answer, 100); + evdns_server_request_respond(req, 0); + return; +end: + evdns_server_request_respond(req, DNS_ERR_REFUSED); +} + + +static void +gaic_getaddrinfo_cb(int result, struct evutil_addrinfo *res, void *arg) +{ + struct gaic_request_status *status = arg; + struct event_base *base = status->base; + tt_assert(status->magic == GAIC_MAGIC); + + if (result == EVUTIL_EAI_CANCEL) { + tt_assert(status->canceled); + } + event_del(&status->cancel_event); + + memset(status, 0xf0, sizeof(*status)); + free(status); + +end: + if (res) + { + TT_BLATHER(("evutil_freeaddrinfo(%p)", res)); + evutil_freeaddrinfo(res); + ++gaic_freed; + } + if (--gaic_pending <= 0) + event_base_loopexit(base, NULL); +} + +static void +gaic_launch(struct event_base *base, struct evdns_base *dns_base) +{ + struct gaic_request_status *status = calloc(1,sizeof(*status)); + struct timeval tv = { 0, 10000 }; + status->magic = GAIC_MAGIC; + status->base = base; + status->dns_base = dns_base; + event_assign(&status->cancel_event, base, -1, 0, gaic_cancel_request_cb, + status); + status->request = evdns_getaddrinfo(dns_base, + "foobar.bazquux.example.com", "80", NULL, gaic_getaddrinfo_cb, + status); + event_add(&status->cancel_event, &tv); + ++gaic_pending; +} + +#ifdef EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED +/* FIXME: We should move this to regress_main.c if anything else needs it.*/ + +/* Trivial replacements for malloc/free/realloc to check for memory leaks. + * Not threadsafe. */ +static int allocated_chunks = 0; + +static void * +cnt_malloc(size_t sz) +{ + allocated_chunks += 1; + return malloc(sz); +} + +static void * +cnt_realloc(void *old, size_t sz) +{ + if (!old) + allocated_chunks += 1; + if (!sz) + allocated_chunks -= 1; + return realloc(old, sz); +} + +static void +cnt_free(void *ptr) +{ + allocated_chunks -= 1; + free(ptr); +} + +struct testleak_env_t { + struct event_base *base; + struct evdns_base *dns_base; + struct evdns_request *req; + struct generic_dns_callback_result r; +}; + +static void * +testleak_setup(const struct testcase_t *testcase) +{ + struct testleak_env_t *env; + + allocated_chunks = 0; + + /* Reset allocation counter, to start allocations from the very beginning. + * (this will avoid false-positive negative numbers for allocated_chunks) + */ + libevent_global_shutdown(); + + event_set_mem_functions(cnt_malloc, cnt_realloc, cnt_free); + + event_enable_debug_mode(); + + /* not mm_calloc: we don't want to mess with the count. */ + env = calloc(1, sizeof(struct testleak_env_t)); + env->base = event_base_new(); + env->dns_base = evdns_base_new(env->base, 0); + env->req = evdns_base_resolve_ipv4( + env->dns_base, "example.com", DNS_QUERY_NO_SEARCH, + generic_dns_callback, &env->r); + return env; +} + +static int +testleak_cleanup(const struct testcase_t *testcase, void *env_) +{ + int ok = 0; + struct testleak_env_t *env = env_; + tt_assert(env); +#ifdef EVENT__DISABLE_DEBUG_MODE + tt_int_op(allocated_chunks, ==, 0); +#else + libevent_global_shutdown(); + tt_int_op(allocated_chunks, ==, 0); +#endif + ok = 1; +end: + if (env) { + if (env->dns_base) + evdns_base_free(env->dns_base, 0); + if (env->base) + event_base_free(env->base); + free(env); + } + return ok; +} + +static struct testcase_setup_t testleak_funcs = { + testleak_setup, testleak_cleanup +}; + +static void +test_dbg_leak_cancel(void *env_) +{ + /* cancel, loop, free/dns, free/base */ + struct testleak_env_t *env = env_; + int send_err_shutdown = 1; + evdns_cancel_request(env->dns_base, env->req); + env->req = 0; + + /* `req` is freed in callback, that's why one loop is required. */ + event_base_loop(env->base, EVLOOP_NONBLOCK); + + /* send_err_shutdown means nothing as soon as our request is + * already canceled */ + evdns_base_free(env->dns_base, send_err_shutdown); + env->dns_base = 0; + event_base_free(env->base); + env->base = 0; +} + +static void +dbg_leak_resume(void *env_, int cancel, int send_err_shutdown) +{ + /* cancel, loop, free/dns, free/base */ + struct testleak_env_t *env = env_; + if (cancel) { + evdns_cancel_request(env->dns_base, env->req); + tt_assert(!evdns_base_resume(env->dns_base)); + } else { + /* TODO: No nameservers, request can't be processed, must be errored */ + tt_assert(!evdns_base_resume(env->dns_base)); + } + + event_base_loop(env->base, EVLOOP_NONBLOCK); + /** + * Because we don't cancel request, and want our callback to recieve + * DNS_ERR_SHUTDOWN, we use deferred callback, and there was: + * - one extra malloc(), + * @see reply_schedule_callback() + * - and one missing free + * @see request_finished() (req->handle->pending_cb = 1) + * than we don't need to count in testleak_cleanup(), but we can clean them + * if we will run loop once again, but *after* evdns base freed. + */ + evdns_base_free(env->dns_base, send_err_shutdown); + env->dns_base = 0; + event_base_loop(env->base, EVLOOP_NONBLOCK); + +end: + event_base_free(env->base); + env->base = 0; +} + +#define IMPL_DBG_LEAK_RESUME(name, cancel, send_err_shutdown) \ + static void \ + test_dbg_leak_##name##_(void *env_) \ + { \ + dbg_leak_resume(env_, cancel, send_err_shutdown); \ + } +IMPL_DBG_LEAK_RESUME(resume, 0, 0) +IMPL_DBG_LEAK_RESUME(cancel_and_resume, 1, 0) +IMPL_DBG_LEAK_RESUME(resume_send_err, 0, 1) +IMPL_DBG_LEAK_RESUME(cancel_and_resume_send_err, 1, 1) + +static void +test_dbg_leak_shutdown(void *env_) +{ + /* free/dns, loop, free/base */ + struct testleak_env_t *env = env_; + int send_err_shutdown = 1; + + /* `req` is freed both with `send_err_shutdown` and without it, + * the only difference is `evdns_callback` call */ + env->req = 0; + + evdns_base_free(env->dns_base, send_err_shutdown); + env->dns_base = 0; + + /* `req` is freed in callback, that's why one loop is required */ + event_base_loop(env->base, EVLOOP_NONBLOCK); + event_base_free(env->base); + env->base = 0; +} +#endif + +static void +test_getaddrinfo_async_cancel_stress(void *ptr) +{ + struct event_base *base; + struct evdns_base *dns_base = NULL; + struct evdns_server_port *server = NULL; + evutil_socket_t fd = -1; + struct sockaddr_in sin; + struct sockaddr_storage ss; + ev_socklen_t slen; + unsigned i; + + base = event_base_new(); + dns_base = evdns_base_new(base, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl(0x7f000001); + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + tt_abort_perror("socket"); + } + evutil_make_socket_nonblocking(fd); + if (bind(fd, (struct sockaddr*)&sin, sizeof(sin))<0) { + tt_abort_perror("bind"); + } + server = evdns_add_server_port_with_base(base, fd, 0, gaic_server_cb, + base); + + memset(&ss, 0, sizeof(ss)); + slen = sizeof(ss); + if (getsockname(fd, (struct sockaddr*)&ss, &slen)<0) { + tt_abort_perror("getsockname"); + } + evdns_base_nameserver_sockaddr_add(dns_base, + (struct sockaddr*)&ss, slen, 0); + + for (i = 0; i < 1000; ++i) { + gaic_launch(base, dns_base); + } + + event_base_dispatch(base); + + // at least some was canceled via external event + tt_int_op(gaic_freed, !=, 1000); + +end: + if (dns_base) + evdns_base_free(dns_base, 1); + if (server) + evdns_close_server_port(server); + if (base) + event_base_free(base); + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +dns_client_fail_requests_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + int limit_inflight = data->setup_data && !strcmp(data->setup_data, "limit-inflight"); + struct evdns_base *dns = NULL; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r[20]; + unsigned i; + + dns_port = regress_get_dnsserver(base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + if (limit_inflight) + tt_assert(!evdns_base_set_option(dns, "max-inflight:", "11")); + + for (i = 0; i < 20; ++i) + evdns_base_resolve_ipv4(dns, "foof.example.com", 0, generic_dns_callback, &r[i]); + + n_replies_left = 20; + exit_base = base; + + evdns_base_free(dns, 1 /** fail requests */); + /** run defered callbacks, to trigger UAF */ + event_base_dispatch(base); + + tt_int_op(n_replies_left, ==, 0); + for (i = 0; i < 20; ++i) + tt_int_op(r[i].result, ==, DNS_ERR_SHUTDOWN); + +end: + evdns_close_server_port(dns_port); +} + +static void +getaddrinfo_cb(int err, struct evutil_addrinfo *res, void *ptr) +{ + generic_dns_callback(err, 0, 0, 0, NULL, ptr); +} +static void +dns_client_fail_requests_getaddrinfo_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_base *dns = NULL; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r[20]; + int i; + + dns_port = regress_get_dnsserver(base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + + for (i = 0; i < 20; ++i) + tt_assert(evdns_getaddrinfo(dns, "foof.example.com", "80", NULL, getaddrinfo_cb, &r[i])); + + n_replies_left = 20; + exit_base = base; + + evdns_base_free(dns, 1 /** fail requests */); + /** run defered callbacks, to trigger UAF */ + event_base_dispatch(base); + + tt_int_op(n_replies_left, ==, 0); + for (i = 0; i < 20; ++i) + tt_int_op(r[i].result, ==, EVUTIL_EAI_FAIL); + +end: + evdns_close_server_port(dns_port); +} + +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED +struct race_param +{ + void *lock; + void *reqs_cmpl_cond; + int bw_threads; + void *bw_threads_exited_cond; + volatile int stopping; + void *base; + void *dns; + + int locked; +}; +static void * +race_base_run(void *arg) +{ + struct race_param *rp = (struct race_param *)arg; + event_base_loop(rp->base, EVLOOP_NO_EXIT_ON_EMPTY); + THREAD_RETURN(); +} +static void * +race_busywait_run(void *arg) +{ + struct race_param *rp = (struct race_param *)arg; + struct sockaddr_storage ss; + while (!rp->stopping) + evdns_base_get_nameserver_addr(rp->dns, 0, (struct sockaddr *)&ss, sizeof(ss)); + EVLOCK_LOCK(rp->lock, 0); + if (--rp->bw_threads == 0) + EVTHREAD_COND_SIGNAL(rp->bw_threads_exited_cond); + EVLOCK_UNLOCK(rp->lock, 0); + THREAD_RETURN(); +} +static void +race_gai_cb(int result, struct evutil_addrinfo *res, void *arg) +{ + struct race_param *rp = arg; + (void)result; + (void)res; + + --n_replies_left; + if (n_replies_left == 0) { + EVLOCK_LOCK(rp->lock, 0); + EVTHREAD_COND_SIGNAL(rp->reqs_cmpl_cond); + EVLOCK_UNLOCK(rp->lock, 0); + } +} +static void +getaddrinfo_race_gotresolve_test(void *arg) +{ + struct race_param rp; + struct evdns_server_port *dns_port = NULL; + ev_uint16_t portnum = 0; + char buf[64]; + int i; + + // Some stress is needed to yield inside getaddrinfo between resolve_ipv4 and resolve_ipv6 + int n_reqs = 16384; +#ifdef _SC_NPROCESSORS_ONLN + int n_threads = sysconf(_SC_NPROCESSORS_ONLN) + 1; +#else + int n_threads = 17; +#endif + THREAD_T thread[n_threads]; + struct timeval tv; + + (void)arg; + + evthread_use_pthreads(); + + rp.base = event_base_new(); + tt_assert(rp.base); + if (evthread_make_base_notifiable(rp.base) < 0) + tt_abort_msg("Couldn't make base notifiable!"); + + dns_port = regress_get_dnsserver(rp.base, &portnum, NULL, + regress_dns_server_cb, reissue_table); + tt_assert(dns_port); + + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + rp.dns = evdns_base_new(rp.base, 0); + tt_assert(!evdns_base_nameserver_ip_add(rp.dns, buf)); + + n_replies_left = n_reqs; + + EVTHREAD_ALLOC_LOCK(rp.lock, 0); + EVTHREAD_ALLOC_COND(rp.reqs_cmpl_cond); + EVTHREAD_ALLOC_COND(rp.bw_threads_exited_cond); + tt_assert(rp.lock); + tt_assert(rp.reqs_cmpl_cond); + tt_assert(rp.bw_threads_exited_cond); + rp.bw_threads = 0; + rp.stopping = 0; + + // Run resolver thread + THREAD_START(thread[0], race_base_run, &rp); + // Run busy-wait threads used to force yield this thread + for (i = 1; i < n_threads; i++) { + rp.bw_threads++; + THREAD_START(thread[i], race_busywait_run, &rp); + } + + EVLOCK_LOCK(rp.lock, 0); + rp.locked = 1; + + for (i = 0; i < n_reqs; ++i) { + tt_assert(evdns_getaddrinfo(rp.dns, "foof.example.com", "80", NULL, race_gai_cb, &rp)); + // This magic along with busy-wait threads make this thread yield frequently + if (i % 100 == 0) { + tv.tv_sec = 0; + tv.tv_usec = 10000; + evutil_usleep_(&tv); + } + } + + exit_base = rp.base; + + // Wait for some time + tv.tv_sec = 5; + tv.tv_usec = 0; + EVTHREAD_COND_WAIT_TIMED(rp.reqs_cmpl_cond, rp.lock, &tv); + + // Stop busy-wait threads + tv.tv_sec = 1; + tv.tv_usec = 0; + rp.stopping = 1; + tt_assert(EVTHREAD_COND_WAIT_TIMED(rp.bw_threads_exited_cond, rp.lock, &tv) == 0); + + EVLOCK_UNLOCK(rp.lock, 0); + rp.locked = 0; + + evdns_base_free(rp.dns, 1 /** fail requests */); + + tt_int_op(n_replies_left, ==, 0); + +end: + if (rp.locked) + EVLOCK_UNLOCK(rp.lock, 0); + EVTHREAD_FREE_LOCK(rp.lock, 0); + EVTHREAD_FREE_COND(rp.reqs_cmpl_cond); + EVTHREAD_FREE_COND(rp.bw_threads_exited_cond); + evdns_close_server_port(dns_port); + event_base_loopbreak(rp.base); + event_base_free(rp.base); +} +#endif + +static void +test_set_so_rcvbuf_so_sndbuf(void *arg) +{ + struct basic_test_data *data = arg; + struct evdns_base *dns_base; + + dns_base = evdns_base_new(data->base, 0); + tt_assert(dns_base); + + tt_assert(!evdns_base_set_option(dns_base, "so-rcvbuf", "10240")); + tt_assert(!evdns_base_set_option(dns_base, "so-sndbuf", "10240")); + + /* actually check SO_RCVBUF/SO_SNDBUF not fails */ + tt_assert(!evdns_base_nameserver_ip_add(dns_base, "127.0.0.1")); + +end: + if (dns_base) + evdns_base_free(dns_base, 0); +} + +static void +test_set_option(void *arg) +{ +#define SUCCESS 0 +#define FAIL -1 + struct basic_test_data *data = arg; + struct evdns_base *dns_base; + size_t i; + /* Option names are allowed to have ':' at the end. + * So all test option names come in pairs. + */ + const char *int_options[] = { + "ndots", "ndots:", + "max-timeouts", "max-timeouts:", + "max-inflight", "max-inflight:", + "attempts", "attempts:", + "randomize-case", "randomize-case:", + "so-rcvbuf", "so-rcvbuf:", + "so-sndbuf", "so-sndbuf:", + }; + const char *timeval_options[] = { + "timeout", "timeout:", + "getaddrinfo-allow-skew", "getaddrinfo-allow-skew:", + "initial-probe-timeout", "initial-probe-timeout:", + }; + const char *addr_port_options[] = { + "bind-to", "bind-to:", + }; + + dns_base = evdns_base_new(data->base, 0); + tt_assert(dns_base); + + for (i = 0; i < ARRAY_SIZE(int_options); ++i) { + tt_assert(SUCCESS == evdns_base_set_option(dns_base, int_options[i], "0")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, int_options[i], "1")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, int_options[i], "10000")); + tt_assert(FAIL == evdns_base_set_option(dns_base, int_options[i], "foo")); + tt_assert(FAIL == evdns_base_set_option(dns_base, int_options[i], "3.14")); + } + + for (i = 0; i < ARRAY_SIZE(timeval_options); ++i) { + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "1")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "0.001")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "3.14")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, timeval_options[i], "10000")); + tt_assert(FAIL == evdns_base_set_option(dns_base, timeval_options[i], "0")); + tt_assert(FAIL == evdns_base_set_option(dns_base, timeval_options[i], "foo")); + } + + for (i = 0; i < ARRAY_SIZE(addr_port_options); ++i) { + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "8.8.8.8:80")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "1.2.3.4")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "::1:82")); + tt_assert(SUCCESS == evdns_base_set_option(dns_base, addr_port_options[i], "3::4")); + tt_assert(FAIL == evdns_base_set_option(dns_base, addr_port_options[i], "3.14")); + tt_assert(FAIL == evdns_base_set_option(dns_base, addr_port_options[i], "foo")); + } + +#undef SUCCESS +#undef FAIL +end: + if (dns_base) + evdns_base_free(dns_base, 0); +} + +#define DNS_LEGACY(name, flags) \ + { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ + dns_##name } + +struct testcase_t dns_testcases[] = { + DNS_LEGACY(server, TT_FORK|TT_NEED_BASE), + DNS_LEGACY(gethostbyname, TT_FORK|TT_NEED_BASE|TT_NEED_DNS|TT_OFF_BY_DEFAULT), + DNS_LEGACY(gethostbyname6, TT_FORK|TT_NEED_BASE|TT_NEED_DNS|TT_OFF_BY_DEFAULT), + DNS_LEGACY(gethostbyaddr, TT_FORK|TT_NEED_BASE|TT_NEED_DNS|TT_OFF_BY_DEFAULT), + { "resolve_reverse", dns_resolve_reverse, TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, + { "search_empty", dns_search_empty_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "search", dns_search_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "search_lower", dns_search_lower_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "search_cancel", dns_search_cancel_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "retry", dns_retry_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "retry_disable_when_inactive", dns_retry_disable_when_inactive_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "reissue", dns_reissue_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "reissue_disable_when_inactive", dns_reissue_disable_when_inactive_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "inflight", dns_inflight_test, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "bufferevent_connect_hostname", test_bufferevent_connect_hostname, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, +#ifdef EVENT__HAVE_SETRLIMIT + { "bufferevent_connect_hostname_emfile", test_bufferevent_connect_hostname, + TT_FORK|TT_NEED_BASE, &basic_setup, (char*)"emfile" }, +#endif + { "disable_when_inactive", dns_disable_when_inactive_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "disable_when_inactive_no_ns", dns_disable_when_inactive_no_ns_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + + { "initialize_nameservers", dns_initialize_nameservers_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, +#ifndef _WIN32 + { "nameservers_no_default", dns_nameservers_no_default_test, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, +#endif + + { "getaddrinfo_async", test_getaddrinfo_async, + TT_FORK|TT_NEED_BASE, &basic_setup, (char*)"" }, + { "getaddrinfo_cancel_stress", test_getaddrinfo_async_cancel_stress, + TT_FORK, NULL, NULL }, + +#ifdef EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED + { "leak_shutdown", test_dbg_leak_shutdown, TT_FORK, &testleak_funcs, NULL }, + { "leak_cancel", test_dbg_leak_cancel, TT_FORK, &testleak_funcs, NULL }, + + { "leak_resume", test_dbg_leak_resume_, TT_FORK, &testleak_funcs, NULL }, + { "leak_cancel_and_resume", test_dbg_leak_cancel_and_resume_, + TT_FORK, &testleak_funcs, NULL }, + { "leak_resume_send_err", test_dbg_leak_resume_send_err_, + TT_FORK, &testleak_funcs, NULL }, + { "leak_cancel_and_resume_send_err", test_dbg_leak_cancel_and_resume_send_err_, + TT_FORK, &testleak_funcs, NULL }, +#endif + + { "client_fail_requests", dns_client_fail_requests_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "client_fail_waiting_requests", dns_client_fail_requests_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, (char*)"limit-inflight" }, + { "client_fail_requests_getaddrinfo", + dns_client_fail_requests_getaddrinfo_test, + TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, +#ifdef EVTHREAD_USE_PTHREADS_IMPLEMENTED + { "getaddrinfo_race_gotresolve", + getaddrinfo_race_gotresolve_test, + TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, +#endif + + { "set_SO_RCVBUF_SO_SNDBUF", test_set_so_rcvbuf_so_sndbuf, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + { "set_options", test_set_option, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, + + END_OF_TESTCASES +}; + diff --git a/libevent/test/regress_et.c b/libevent/test/regress_et.c new file mode 100644 index 0000000..1b1f819 --- /dev/null +++ b/libevent/test/regress_et.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" +#include "event2/event-config.h" + +#ifdef _WIN32 +#include <winsock2.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <sys/time.h> +#include <unistd.h> +#endif +#include <errno.h> + +#include "event2/event.h" +#include "event2/util.h" + +#include "regress.h" + +static int was_et = 0; + +static int base_supports_et(struct event_base *base) +{ + return + (!strcmp(event_base_get_method(base), "epoll") || + !strcmp(event_base_get_method(base), "epoll (with changelist)") || + !strcmp(event_base_get_method(base), "kqueue")); +} + +static void +read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf; + int len; + + len = recv(fd, &buf, sizeof(buf), 0); + + called++; + if (event & EV_ET) + was_et = 1; + + if (!len) + event_del(arg); +} + +static void +test_edgetriggered(void *data_) +{ + struct basic_test_data *data = data_; + struct event_base *base = data->base; + evutil_socket_t *pair = data->pair; + struct event *ev = NULL; + const char *test = "test string"; + int supports_et; + + /* On Linux 3.2.1 (at least, as patched by Fedora and tested by Nick), + * doing a "recv" on an AF_UNIX socket resets the readability of the + * socket, even though there is no state change, so we don't actually + * get edge-triggered behavior. Yuck! Linux 3.1.9 didn't have this + * problem. + */ + + called = was_et = 0; + + tt_int_op(send(pair[0], test, (int)strlen(test)+1, 0), >, 0); + tt_int_op(shutdown(pair[0], EVUTIL_SHUT_WR), ==, 0); + + supports_et = base_supports_et(base); + TT_BLATHER(("Checking for edge-triggered events with %s, which should %s" + "support edge-triggering", event_base_get_method(base), + supports_et?"":"not ")); + + /* Initialize one event */ + ev = event_new(base, pair[1], EV_READ|EV_ET|EV_PERSIST, read_cb, &ev); + tt_assert(ev != NULL); + tt_int_op(event_add(ev, NULL), ==, 0); + + /* We're going to call the dispatch function twice. The first invocation + * will read a single byte from pair[1] in either case. If we're edge + * triggered, we'll only see the event once (since we only see transitions + * from no data to data), so the second invocation of event_base_loop will + * do nothing. If we're level triggered, the second invocation of + * event_base_loop will also activate the event (because there's still + * data to read). */ + tt_int_op(event_base_loop(base,EVLOOP_NONBLOCK|EVLOOP_ONCE), ==, 0); + tt_int_op(event_base_loop(base,EVLOOP_NONBLOCK|EVLOOP_ONCE), ==, 0); + + if (supports_et) { + tt_int_op(called, ==, 1); + tt_assert(was_et); + } else { + tt_int_op(called, ==, 2); + tt_assert(!was_et); + } + +end: + if (ev) { + event_del(ev); + event_free(ev); + } +} + +static void +test_edgetriggered_mix_error(void *data_) +{ + struct basic_test_data *data = data_; + struct event_base *base = NULL; + struct event *ev_et=NULL, *ev_lt=NULL; + +#ifdef EVENT__DISABLE_DEBUG_MODE + if (1) + tt_skip(); +#endif + + if (!libevent_tests_running_in_debug_mode) + event_enable_debug_mode(); + + base = event_base_new(); + + /* try mixing edge-triggered and level-triggered to make sure it fails*/ + ev_et = event_new(base, data->pair[0], EV_READ|EV_ET, read_cb, ev_et); + tt_assert(ev_et); + ev_lt = event_new(base, data->pair[0], EV_READ, read_cb, ev_lt); + tt_assert(ev_lt); + + /* Add edge-triggered, then level-triggered. Get an error. */ + tt_int_op(0, ==, event_add(ev_et, NULL)); + tt_int_op(-1, ==, event_add(ev_lt, NULL)); + tt_int_op(EV_READ, ==, event_pending(ev_et, EV_READ, NULL)); + tt_int_op(0, ==, event_pending(ev_lt, EV_READ, NULL)); + + tt_int_op(0, ==, event_del(ev_et)); + /* Add level-triggered, then edge-triggered. Get an error. */ + tt_int_op(0, ==, event_add(ev_lt, NULL)); + tt_int_op(-1, ==, event_add(ev_et, NULL)); + tt_int_op(EV_READ, ==, event_pending(ev_lt, EV_READ, NULL)); + tt_int_op(0, ==, event_pending(ev_et, EV_READ, NULL)); + +end: + if (ev_et) + event_free(ev_et); + if (ev_lt) + event_free(ev_lt); + if (base) + event_base_free(base); +} + +static int read_notification_count; +static int last_read_notification_was_et; +static void +read_notification_cb(evutil_socket_t fd, short event, void *arg) +{ + read_notification_count++; + last_read_notification_was_et = (event & EV_ET); +} + +static int write_notification_count; +static int last_write_notification_was_et; +static void +write_notification_cb(evutil_socket_t fd, short event, void *arg) +{ + write_notification_count++; + last_write_notification_was_et = (event & EV_ET); +} + +/* After two or more events have been registered for the same + * file descriptor using EV_ET, if one of the events is + * deleted, then the epoll_ctl() call issued by libevent drops + * the EPOLLET flag resulting in level triggered + * notifications. + */ +static void +test_edge_triggered_multiple_events(void *data_) +{ + struct basic_test_data *data = data_; + struct event *read_ev = NULL; + struct event *write_ev = NULL; + const char c = 'A'; + struct event_base *base = data->base; + evutil_socket_t *pair = data->pair; + + if (!base_supports_et(base)) { + tt_skip(); + return; + } + + read_notification_count = 0; + last_read_notification_was_et = 0; + write_notification_count = 0; + last_write_notification_was_et = 0; + + /* Make pair[1] readable */ + tt_int_op(send(pair[0], &c, 1, 0), >, 0); + + read_ev = event_new(base, pair[1], EV_READ|EV_ET|EV_PERSIST, + read_notification_cb, NULL); + write_ev = event_new(base, pair[1], EV_WRITE|EV_ET|EV_PERSIST, + write_notification_cb, NULL); + + event_add(read_ev, NULL); + event_add(write_ev, NULL); + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + + tt_assert(last_read_notification_was_et); + tt_int_op(read_notification_count, ==, 1); + tt_assert(last_write_notification_was_et); + tt_int_op(write_notification_count, ==, 1); + + event_del(read_ev); + + /* trigger acitivity second time for the backend that can have multiple + * events for one fd (like kqueue) */ + close(pair[0]); + pair[0] = -1; + + /* Verify that we are still edge-triggered for write notifications */ + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + event_base_loop(base, EVLOOP_NONBLOCK|EVLOOP_ONCE); + tt_assert(last_write_notification_was_et); + tt_int_op(write_notification_count, ==, 2); + +end: + if (read_ev) + event_free(read_ev); + if (write_ev) + event_free(write_ev); +} + +struct testcase_t edgetriggered_testcases[] = { + { "et", test_edgetriggered, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR, &basic_setup, NULL }, + { "et_mix_error", test_edgetriggered_mix_error, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NO_LOGS, &basic_setup, NULL }, + { "et_multiple_events", test_edge_triggered_multiple_events, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR, &basic_setup, NULL }, + END_OF_TESTCASES +}; diff --git a/libevent/test/regress_finalize.c b/libevent/test/regress_finalize.c new file mode 100644 index 0000000..9e57188 --- /dev/null +++ b/libevent/test/regress_finalize.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2013 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "event2/event-config.h" +#include "evconfig-private.h" +#include "tinytest.h" +#include "tinytest_macros.h" +#include <stdlib.h> + +#include "event2/event.h" +#include "event2/util.h" +#include "event-internal.h" +#include "defer-internal.h" + +#include "regress.h" +#include "regress_thread.h" + +static void +timer_callback(evutil_socket_t fd, short what, void *arg) +{ + int *int_arg = arg; + *int_arg += 1; + (void)fd; + (void)what; +} +static void +simple_callback(struct event_callback *evcb, void *arg) +{ + int *int_arg = arg; + *int_arg += 1; + (void)evcb; +} +static void +event_finalize_callback_1(struct event *ev, void *arg) +{ + int *int_arg = arg; + *int_arg += 100; + (void)ev; +} +static void +callback_finalize_callback_1(struct event_callback *evcb, void *arg) +{ + int *int_arg = arg; + *int_arg += 100; + (void)evcb; +} + + +static void +test_fin_cb_invoked(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *ev; + struct event ev2; + struct event_callback evcb; + int cb_called = 0; + int ev_called = 0; + + const struct timeval ten_sec = {10,0}; + + event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called); + ev = evtimer_new(base, timer_callback, &ev_called); + /* Just finalize them; don't bother adding. */ + event_free_finalize(0, ev, event_finalize_callback_1); + event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1); + + event_base_dispatch(base); + + tt_int_op(cb_called, ==, 100); + tt_int_op(ev_called, ==, 100); + + ev_called = cb_called = 0; + event_base_assert_ok_(base); + + /* Now try it when they're active. (actually, don't finalize: make + * sure activation can happen! */ + ev = evtimer_new(base, timer_callback, &ev_called); + event_deferred_cb_init_(&evcb, 0, simple_callback, &cb_called); + + event_active(ev, EV_TIMEOUT, 1); + event_callback_activate_(base, &evcb); + + event_base_dispatch(base); + tt_int_op(cb_called, ==, 1); + tt_int_op(ev_called, ==, 1); + + ev_called = cb_called = 0; + event_base_assert_ok_(base); + + /* Great, it worked. Now activate and finalize and make sure only + * finalizing happens. */ + event_active(ev, EV_TIMEOUT, 1); + event_callback_activate_(base, &evcb); + event_free_finalize(0, ev, event_finalize_callback_1); + event_callback_finalize_(base, 0, &evcb, callback_finalize_callback_1); + + event_base_dispatch(base); + tt_int_op(cb_called, ==, 100); + tt_int_op(ev_called, ==, 100); + + ev_called = 0; + + event_base_assert_ok_(base); + + /* Okay, now add but don't have it become active, and make sure *that* + * works. */ + ev = evtimer_new(base, timer_callback, &ev_called); + event_add(ev, &ten_sec); + event_free_finalize(0, ev, event_finalize_callback_1); + + event_base_dispatch(base); + tt_int_op(ev_called, ==, 100); + + ev_called = 0; + event_base_assert_ok_(base); + + /* Now try adding and deleting after finalizing. */ + ev = evtimer_new(base, timer_callback, &ev_called); + evtimer_assign(&ev2, base, timer_callback, &ev_called); + event_add(ev, &ten_sec); + event_free_finalize(0, ev, event_finalize_callback_1); + event_finalize(0, &ev2, event_finalize_callback_1); + + event_add(&ev2, &ten_sec); + event_del(ev); + event_active(&ev2, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(ev_called, ==, 200); + + event_base_assert_ok_(base); + +end: + ; +} + +#ifndef EVENT__DISABLE_MM_REPLACEMENT +static void * +tfff_malloc(size_t n) +{ + return malloc(n); +} +static void *tfff_p1=NULL, *tfff_p2=NULL; +static int tfff_p1_freed=0, tfff_p2_freed=0; +static void +tfff_free(void *p) +{ + if (! p) + return; + if (p == tfff_p1) + ++tfff_p1_freed; + if (p == tfff_p2) + ++tfff_p2_freed; + free(p); +} +static void * +tfff_realloc(void *p, size_t sz) +{ + return realloc(p,sz); +} +#endif + +static void +test_fin_free_finalize(void *arg) +{ +#ifdef EVENT__DISABLE_MM_REPLACEMENT + tinytest_set_test_skipped_(); +#else + struct event_base *base = NULL; + struct event *ev, *ev2; + int ev_called = 0; + int ev2_called = 0; + + (void)arg; + + event_set_mem_functions(tfff_malloc, tfff_realloc, tfff_free); + + base = event_base_new(); + tt_assert(base); + + ev = evtimer_new(base, timer_callback, &ev_called); + ev2 = evtimer_new(base, timer_callback, &ev2_called); + tfff_p1 = ev; + tfff_p2 = ev2; + event_free_finalize(0, ev, event_finalize_callback_1); + event_finalize(0, ev2, event_finalize_callback_1); + + event_base_dispatch(base); + + tt_int_op(ev_called, ==, 100); + tt_int_op(ev2_called, ==, 100); + + event_base_assert_ok_(base); + tt_int_op(tfff_p1_freed, ==, 1); + tt_int_op(tfff_p2_freed, ==, 0); + + event_free(ev2); + +end: + if (base) + event_base_free(base); +#endif +} + +/* For test_fin_within_cb */ +struct event_and_count { + struct event *ev; + struct event *ev2; + int count; +}; +static void +event_finalize_callback_2(struct event *ev, void *arg) +{ + struct event_and_count *evc = arg; + evc->count += 100; + event_free(ev); +} +static void +timer_callback_2(evutil_socket_t fd, short what, void *arg) +{ + struct event_and_count *evc = arg; + event_finalize(0, evc->ev, event_finalize_callback_2); + event_finalize(0, evc->ev2, event_finalize_callback_2); + ++ evc->count; + (void)fd; + (void)what; +} + +static void +test_fin_within_cb(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event_and_count evc1, evc2; + evc1.count = evc2.count = 0; + evc2.ev2 = evc1.ev = evtimer_new(base, timer_callback_2, &evc1); + evc1.ev2 = evc2.ev = evtimer_new(base, timer_callback_2, &evc2); + + /* Activate both. The first one will have its callback run, which + * will finalize both of them, preventing the second one's callback + * from running. */ + event_active(evc1.ev, EV_TIMEOUT, 1); + event_active(evc2.ev, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(evc1.count, ==, 101); + tt_int_op(evc2.count, ==, 100); + + event_base_assert_ok_(base); + /* Now try with EV_PERSIST events. */ + evc1.count = evc2.count = 0; + evc2.ev2 = evc1.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc1); + evc1.ev2 = evc2.ev = event_new(base, -1, EV_PERSIST, timer_callback_2, &evc2); + + event_active(evc1.ev, EV_TIMEOUT, 1); + event_active(evc2.ev, EV_TIMEOUT, 1); + + event_base_dispatch(base); + tt_int_op(evc1.count, ==, 101); + tt_int_op(evc2.count, ==, 100); + + event_base_assert_ok_(base); +end: + ; +} + +static void +event_finalize_callback_free(struct event *ev, void *arg) +{ + struct event_base *base = arg; + int err; + if (base) { + err = event_assign(ev, base, -1, EV_TIMEOUT, NULL, NULL); + tt_int_op(err, ==, 0); + test_ok += 1; + } else { + free(ev); + test_ok += 1; + } + +end: + ; +} +static void +test_fin_debug_use_after_free(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct event *ev; + + tt_ptr_op(ev = event_new(base, -1, EV_TIMEOUT, NULL, base), !=, NULL); + tt_int_op(event_add(ev, NULL), ==, 0); + tt_int_op(event_finalize(0, ev, event_finalize_callback_free), ==, 0); + + // Dispatch base to trigger callbacks + event_base_dispatch(base); + event_base_assert_ok_(base); + tt_int_op(test_ok, ==, 1); + + // Now add again, since we did event_assign in event_finalize_callback_free + // This used to fail in event_debug_assert_is_setup_ + tt_int_op(event_add(ev, NULL), ==, 0); + + // Finalize and dispatch again + tt_int_op(event_finalize(0, ev, event_finalize_callback_free), ==, 0); + event_base_dispatch(base); + event_base_assert_ok_(base); + tt_int_op(test_ok, ==, 2); + +end: + ; +} + +#if 0 +static void +timer_callback_3(evutil_socket_t *fd, short what, void *arg) +{ + (void)fd; + (void)what; + +} +static void +test_fin_many(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *ev1, *ev2; + struct event_callback evcb1, evcb2; + int ev1_count = 0, ev2_count = 0; + int evcb1_count = 0, evcb2_count = 0; + struct event_callback *array[4]; + + int n; + + /* First attempt: call finalize_many with no events running */ + ev1 = evtimer_new(base, timer_callback, &ev1_count); + ev1 = evtimer_new(base, timer_callback, &ev2_count); + event_deferred_cb_init_(&evcb1, 0, simple_callback, &evcb1_called); + event_deferred_cb_init_(&evcb2, 0, simple_callback, &evcb2_called); + array[0] = &ev1->ev_evcallback; + array[1] = &ev2->ev_evcallback; + array[2] = &evcb1; + array[3] = &evcb2; + + + + n = event_callback_finalize_many(base, 4, array, + callback_finalize_callback_1); + +} +#endif + + +#define TEST(name, flags) \ + { #name, test_fin_##name, (flags), &basic_setup, NULL } + +struct testcase_t finalize_testcases[] = { + + TEST(cb_invoked, TT_FORK|TT_NEED_BASE), + TEST(free_finalize, TT_FORK), + TEST(within_cb, TT_FORK|TT_NEED_BASE), + TEST(debug_use_after_free, TT_FORK|TT_NEED_BASE|TT_ENABLE_DEBUG_MODE), +// TEST(many, TT_FORK|TT_NEED_BASE), + + + END_OF_TESTCASES +}; + diff --git a/libevent/test/regress_http.c b/libevent/test/regress_http.c new file mode 100644 index 0000000..4493907 --- /dev/null +++ b/libevent/test/regress_http.c @@ -0,0 +1,4807 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <signal.h> +#include <unistd.h> +#include <netdb.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "event2/dns.h" + +#include "event2/event.h" +#include "event2/http.h" +#include "event2/buffer.h" +#include "event2/bufferevent.h" +#include "event2/bufferevent_ssl.h" +#include "event2/util.h" +#include "event2/listener.h" +#include "log-internal.h" +#include "http-internal.h" +#include "regress.h" +#include "regress_testutils.h" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +/* set if a test needs to call loopexit on a base */ +static struct event_base *exit_base; + +static char const BASIC_REQUEST_BODY[] = "This is funny"; + +static void http_basic_cb(struct evhttp_request *req, void *arg); +static void http_timeout_cb(struct evhttp_request *req, void *arg); +static void http_large_cb(struct evhttp_request *req, void *arg); +static void http_chunked_cb(struct evhttp_request *req, void *arg); +static void http_post_cb(struct evhttp_request *req, void *arg); +static void http_put_cb(struct evhttp_request *req, void *arg); +static void http_delete_cb(struct evhttp_request *req, void *arg); +static void http_delay_cb(struct evhttp_request *req, void *arg); +static void http_large_delay_cb(struct evhttp_request *req, void *arg); +static void http_badreq_cb(struct evhttp_request *req, void *arg); +static void http_dispatcher_cb(struct evhttp_request *req, void *arg); +static void http_on_complete_cb(struct evhttp_request *req, void *arg); + +#define HTTP_BIND_IPV6 1 +#define HTTP_BIND_SSL 2 +#define HTTP_SSL_FILTER 4 +static int +http_bind(struct evhttp *myhttp, ev_uint16_t *pport, int mask) +{ + int port; + struct evhttp_bound_socket *sock; + int ipv6 = mask & HTTP_BIND_IPV6; + + if (ipv6) + sock = evhttp_bind_socket_with_handle(myhttp, "::1", *pport); + else + sock = evhttp_bind_socket_with_handle(myhttp, "127.0.0.1", *pport); + + if (sock == NULL) { + if (ipv6) + return -1; + else + event_errx(1, "Could not start web server"); + } + + port = regress_get_socket_port(evhttp_bound_socket_get_fd(sock)); + if (port < 0) + return -1; + *pport = (ev_uint16_t) port; + + return 0; +} + +#ifdef EVENT__HAVE_OPENSSL +static struct bufferevent * +https_bev(struct event_base *base, void *arg) +{ + SSL *ssl = SSL_new(get_ssl_ctx()); + + SSL_use_certificate(ssl, ssl_getcert(ssl_getkey())); + SSL_use_PrivateKey(ssl, ssl_getkey()); + + return bufferevent_openssl_socket_new( + base, -1, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE); +} +#endif +static struct evhttp * +http_setup_gencb(ev_uint16_t *pport, struct event_base *base, int mask, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + struct evhttp *myhttp; + + /* Try a few different ports */ + myhttp = evhttp_new(base); + + if (http_bind(myhttp, pport, mask) < 0) + return NULL; +#ifdef EVENT__HAVE_OPENSSL + if (mask & HTTP_BIND_SSL) { + init_ssl(); + evhttp_set_bevcb(myhttp, https_bev, NULL); + } +#endif + + evhttp_set_gencb(myhttp, cb, cbarg); + + /* Register a callback for certain types of requests */ + evhttp_set_cb(myhttp, "/test", http_basic_cb, myhttp); + evhttp_set_cb(myhttp, "/test nonconformant", http_basic_cb, myhttp); + evhttp_set_cb(myhttp, "/timeout", http_timeout_cb, myhttp); + evhttp_set_cb(myhttp, "/large", http_large_cb, base); + evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, base); + evhttp_set_cb(myhttp, "/streamed", http_chunked_cb, base); + evhttp_set_cb(myhttp, "/postit", http_post_cb, base); + evhttp_set_cb(myhttp, "/putit", http_put_cb, base); + evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, base); + evhttp_set_cb(myhttp, "/delay", http_delay_cb, base); + evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, base); + evhttp_set_cb(myhttp, "/badrequest", http_badreq_cb, base); + evhttp_set_cb(myhttp, "/oncomplete", http_on_complete_cb, base); + evhttp_set_cb(myhttp, "/", http_dispatcher_cb, base); + return (myhttp); +} +static struct evhttp * +http_setup(ev_uint16_t *pport, struct event_base *base, int mask) +{ return http_setup_gencb(pport, base, mask, NULL, NULL); } + +#ifndef NI_MAXSERV +#define NI_MAXSERV 1024 +#endif + +static evutil_socket_t +http_connect(const char *address, ev_uint16_t port) +{ + /* Stupid code for connecting */ + struct evutil_addrinfo ai, *aitop; + char strport[NI_MAXSERV]; + + struct sockaddr *sa; + size_t slen; + evutil_socket_t fd; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + evutil_snprintf(strport, sizeof(strport), "%d", port); + if (evutil_getaddrinfo(address, strport, &ai, &aitop) != 0) { + event_warn("getaddrinfo"); + return (-1); + } + sa = aitop->ai_addr; + slen = aitop->ai_addrlen; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + event_err(1, "socket failed"); + + evutil_make_socket_nonblocking(fd); + if (connect(fd, sa, slen) == -1) { +#ifdef _WIN32 + int tmp_err = WSAGetLastError(); + if (tmp_err != WSAEINPROGRESS && tmp_err != WSAEINVAL && + tmp_err != WSAEWOULDBLOCK) + event_err(1, "connect failed"); +#else + if (errno != EINPROGRESS) + event_err(1, "connect failed"); +#endif + } + + evutil_freeaddrinfo(aitop); + + return (fd); +} + +/* Helper: do a strcmp on the contents of buf and the string s. */ +static int +evbuffer_datacmp(struct evbuffer *buf, const char *s) +{ + size_t b_sz = evbuffer_get_length(buf); + size_t s_sz = strlen(s); + unsigned char *d; + int r; + + if (b_sz < s_sz) + return -1; + + d = evbuffer_pullup(buf, s_sz); + if (!d) + d = (unsigned char *)""; + if ((r = memcmp(d, s, s_sz))) + return r; + + if (b_sz > s_sz) + return 1; + else + return 0; +} + +/* Helper: Return true iff buf contains s */ +static int +evbuffer_contains(struct evbuffer *buf, const char *s) +{ + struct evbuffer_ptr ptr; + ptr = evbuffer_search(buf, s, strlen(s), NULL); + return ptr.pos != -1; +} + +static void +http_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = BASIC_REQUEST_BODY; + struct event_base *my_base = arg; + + if (evbuffer_contains(bufferevent_get_input(bev), what)) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + enum message_read_status done; + + /* req->kind = EVHTTP_RESPONSE; */ + done = evhttp_parse_firstline_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + if (done == 1 && + evhttp_find_header(evhttp_request_get_input_headers(req), + "Content-Type") != NULL) + test_ok++; + + out: + evhttp_request_free(req); + bufferevent_disable(bev, EV_READ); + if (exit_base) + event_base_loopexit(exit_base, NULL); + else if (my_base) + event_base_loopexit(my_base, NULL); + else { + fprintf(stderr, "No way to exit loop!\n"); + exit(1); + } + } +} + +static void +http_writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_errorcb(struct bufferevent *bev, short what, void *arg) +{ + /** For ssl */ + if (what & BEV_EVENT_CONNECTED) + return; + test_ok = -2; + event_base_loopexit(arg, NULL); +} + +static int found_multi = 0; +static int found_multi2 = 0; + +static void +http_basic_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + struct evhttp_connection *evcon; + int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL; + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + evcon = evhttp_request_get_connection(req); + tt_assert(evhttp_connection_get_server(evcon) == arg); + + { + const struct sockaddr *sa; + char addrbuf[128]; + + sa = evhttp_connection_get_addr(evcon); + tt_assert(sa); + + if (sa->sa_family == AF_INET) { + evutil_format_sockaddr_port_((struct sockaddr *)sa, addrbuf, sizeof(addrbuf)); + tt_assert(!strncmp(addrbuf, "127.0.0.1:", strlen("127.0.0.1:"))); + } else if (sa->sa_family == AF_INET6) { + evutil_format_sockaddr_port_((struct sockaddr *)sa, addrbuf, sizeof(addrbuf)); + tt_assert(!strncmp(addrbuf, "[::1]:", strlen("[::1]:"))); + } else { + tt_fail_msg("Unsupported family"); + } + } + + /* For multi-line headers test */ + { + const char *multi = + evhttp_find_header(evhttp_request_get_input_headers(req),"X-Multi"); + if (multi) { + found_multi = !strcmp(multi,"aaaaaaaa a END"); + if (strcmp("END", multi + strlen(multi) - 3) == 0) + test_ok++; + if (evhttp_find_header(evhttp_request_get_input_headers(req), "X-Last")) + test_ok++; + } + } + { + const char *multi2 = + evhttp_find_header(evhttp_request_get_input_headers(req),"X-Multi-Extra-WS"); + if (multi2) { + found_multi2 = !strcmp(multi2,"libevent 2.1"); + } + } + + + /* injecting a bad content-length */ + if (evhttp_find_header(evhttp_request_get_input_headers(req), "X-Negative")) + evhttp_add_header(evhttp_request_get_output_headers(req), + "Content-Length", "-100"); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", + !empty ? evb : NULL); + +end: + evbuffer_free(evb); +} + +static void http_timeout_reply_cb(evutil_socket_t fd, short events, void *arg) +{ + struct evhttp_request *req = arg; + evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL); + test_ok++; +} +static void +http_timeout_cb(struct evhttp_request *req, void *arg) +{ + struct timeval when = { 0, 100 }; + event_base_once(exit_base, -1, EV_TIMEOUT, + http_timeout_reply_cb, req, &when); +} + +static void +http_large_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + int i; + + for (i = 0; i < 1<<20; ++i) { + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + } + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + evbuffer_free(evb); +} + +static char const* const CHUNKS[] = { + "This is funny", + "but not hilarious.", + "bwv 1052" +}; + +struct chunk_req_state { + struct event_base *base; + struct evhttp_request *req; + int i; +}; + +static void +http_chunked_trickle_cb(evutil_socket_t fd, short events, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + struct chunk_req_state *state = arg; + struct timeval when = { 0, 0 }; + + evbuffer_add_printf(evb, "%s", CHUNKS[state->i]); + evhttp_send_reply_chunk(state->req, evb); + evbuffer_free(evb); + + if (++state->i < (int) (sizeof(CHUNKS)/sizeof(CHUNKS[0]))) { + event_base_once(state->base, -1, EV_TIMEOUT, + http_chunked_trickle_cb, state, &when); + } else { + evhttp_send_reply_end(state->req); + free(state); + } +} + +static void +http_chunked_cb(struct evhttp_request *req, void *arg) +{ + struct timeval when = { 0, 0 }; + struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state)); + TT_BLATHER(("%s: called\n", __func__)); + + memset(state, 0, sizeof(struct chunk_req_state)); + state->req = req; + state->base = arg; + + if (strcmp(evhttp_request_get_uri(req), "/streamed") == 0) { + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Length", "39"); + } + + /* generate a chunked/streamed reply */ + evhttp_send_reply_start(req, HTTP_OK, "Everything is fine"); + + /* but trickle it across several iterations to ensure we're not + * assuming it comes all at once */ + event_base_once(arg, -1, EV_TIMEOUT, http_chunked_trickle_cb, state, &when); +} + +static struct bufferevent * +create_bev(struct event_base *base, evutil_socket_t fd, int ssl_mask, int flags_) +{ + int flags = BEV_OPT_DEFER_CALLBACKS | flags_; + struct bufferevent *bev = NULL; + + if (!ssl_mask) { + bev = bufferevent_socket_new(base, fd, flags); + } else { +#ifdef EVENT__HAVE_OPENSSL + SSL *ssl = SSL_new(get_ssl_ctx()); + if (ssl_mask & HTTP_SSL_FILTER) { + struct bufferevent *underlying = + bufferevent_socket_new(base, fd, flags); + bev = bufferevent_openssl_filter_new( + base, underlying, ssl, BUFFEREVENT_SSL_CONNECTING, flags); + } else { + bev = bufferevent_openssl_socket_new( + base, fd, ssl, BUFFEREVENT_SSL_CONNECTING, flags); + } + bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); +#endif + } + + return bev; +} + +static void +http_half_writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + if (!test_ok) { + const char http_request[] = "host\r\n" + "Connection: close\r\n" + "\r\n"; + bufferevent_write(bev, http_request, strlen(http_request)); + } + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_basic_test_impl(void *arg, int ssl, const char *request_line) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0, port2 = 0; + int server_flags = ssl ? HTTP_BIND_SSL : 0; + struct evhttp *http = http_setup(&port, data->base, server_flags); + struct evbuffer *out; + + exit_base = data->base; + + /* bind to a second socket */ + if (http_bind(http, &port2, server_flags) == -1) { + fprintf(stdout, "FAILED (bind)\n"); + exit(1); + } + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, http_readcb, http_half_writecb, + http_errorcb, data->base); + out = bufferevent_get_output(bev); + + /* first half of the http request */ + evbuffer_add_printf(out, + "%s\r\n" + "Host: some", request_line); + + test_ok = 0; + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 3); + + /* connect to the second port */ + bufferevent_free(bev); + + fd = http_connect("127.0.0.1", port2); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + out = bufferevent_get_output(bev); + + evbuffer_add_printf(out, + "%s\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n", request_line); + + test_ok = 0; + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 2); + + /* Connect to the second port again. This time, send an absolute uri. */ + bufferevent_free(bev); + + fd = http_connect("127.0.0.1", port2); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "GET http://somehost.net/test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + test_ok = 0; + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 2); + + evhttp_free(http); +end: + if (bev) + bufferevent_free(bev); +} +static void http_basic_test(void *arg)\ +{ http_basic_test_impl(arg, 0, "GET /test HTTP/1.1"); } +static void http_basic_trailing_space_test(void *arg) +{ http_basic_test_impl(arg, 0, "GET /test HTTP/1.1 "); } + + +static void +http_delay_reply(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL); + + ++test_ok; +} + +static void +http_delay_cb(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + + event_base_once(arg, -1, EV_TIMEOUT, http_delay_reply, req, &tv); +} + +static void +http_badreq_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *buf = evbuffer_new(); + + evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "text/xml; charset=UTF-8"); + evbuffer_add_printf(buf, "Hello, %s!", "127.0.0.1"); + + evhttp_send_reply(req, HTTP_OK, "OK", buf); + evbuffer_free(buf); +} + +static void +http_badreq_errorcb(struct bufferevent *bev, short what, void *arg) +{ + TT_BLATHER(("%s: called (what=%04x, arg=%p)", __func__, what, arg)); + /* ignore */ +} + +static void +http_badreq_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "Hello, 127.0.0.1"; + const char *bad_request = "400 Bad Request"; + + if (evbuffer_contains(bufferevent_get_input(bev), bad_request)) { + TT_FAIL(("%s:bad request detected", __func__)); + bufferevent_disable(bev, EV_READ); + event_base_loopexit(arg, NULL); + return; + } + + if (evbuffer_contains(bufferevent_get_input(bev), what)) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + enum message_read_status done; + + /* req->kind = EVHTTP_RESPONSE; */ + done = evhttp_parse_firstline_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + if (done == 1 && + evhttp_find_header(evhttp_request_get_input_headers(req), + "Content-Type") != NULL) + test_ok++; + + out: + evhttp_request_free(req); + evbuffer_drain(bufferevent_get_input(bev), evbuffer_get_length(bufferevent_get_input(bev))); + } + + shutdown(bufferevent_getfd(bev), EVUTIL_SHUT_WR); +} + +static void +http_badreq_successcb(evutil_socket_t fd, short what, void *arg) +{ + TT_BLATHER(("%s: called (what=%04x, arg=%p)", __func__, what, arg)); + event_base_loopexit(exit_base, NULL); +} + +static void +http_bad_request_test(void *arg) +{ + struct basic_test_data *data = arg; + struct timeval tv; + struct bufferevent *bev = NULL; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port=0, port2=0; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + exit_base = data->base; + + /* bind to a second socket */ + if (http_bind(http, &port2, 0) == -1) + TT_DIE(("Bind socket failed")); + + /* NULL request test */ + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_badreq_readcb, http_writecb, + http_badreq_errorcb, data->base); + bufferevent_enable(bev, EV_READ); + + /* real NULL request */ + http_request = ""; + + bufferevent_write(bev, http_request, strlen(http_request)); + + shutdown(fd, EVUTIL_SHUT_WR); + timerclear(&tv); + tv.tv_usec = 10000; + event_base_once(data->base, -1, EV_TIMEOUT, http_badreq_successcb, bev, &tv); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + evutil_closesocket(fd); + + if (test_ok != 0) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Second answer (BAD REQUEST) on connection close */ + + /* connect to the second port */ + fd = http_connect("127.0.0.1", port2); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_badreq_readcb, http_writecb, + http_badreq_errorcb, data->base); + bufferevent_enable(bev, EV_READ); + + /* first half of the http request */ + http_request = + "GET /badrequest HTTP/1.0\r\n" \ + "Connection: Keep-Alive\r\n" \ + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + timerclear(&tv); + tv.tv_usec = 10000; + event_base_once(data->base, -1, EV_TIMEOUT, http_badreq_successcb, bev, &tv); + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 2); + +end: + evhttp_free(http); + if (bev) + bufferevent_free(bev); + if (fd >= 0) + evutil_closesocket(fd); +} + +static struct evhttp_connection *delayed_client; + +static void +http_large_delay_cb(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_usec = 500000; + + event_base_once(arg, -1, EV_TIMEOUT, http_delay_reply, req, &tv); + evhttp_connection_fail_(delayed_client, EVREQ_HTTP_EOF); +} + +/* + * HTTP DELETE test, just piggyback on the basic test + */ + +static void +http_delete_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL; + + /* Expecting a DELETE request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_DELETE) { + fprintf(stdout, "FAILED (delete type)\n"); + exit(1); + } + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", + !empty ? evb : NULL); + + evbuffer_free(evb); +} + +static void +http_delete_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + tt_assert(http); + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "DELETE /deleteit HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + evutil_closesocket(fd); + fd = EVUTIL_INVALID_SOCKET; + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +http_sent_cb(struct evhttp_request *req, void *arg) +{ + ev_uintptr_t val = (ev_uintptr_t)arg; + struct evbuffer *b; + + if (val != 0xDEADBEEF) { + fprintf(stdout, "FAILED on_complete_cb argument\n"); + exit(1); + } + + b = evhttp_request_get_output_buffer(req); + if (evbuffer_get_length(b) != 0) { + fprintf(stdout, "FAILED on_complete_cb output buffer not written\n"); + exit(1); + } + + TT_BLATHER(("%s: called\n", __func__)); + + ++test_ok; +} + +static void +http_on_complete_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb = evbuffer_new(); + + evhttp_request_set_on_complete_cb(req, http_sent_cb, (void *)0xDEADBEEF); + + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + /* allow sending of an empty reply */ + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); + + ++test_ok; +} + +static void +http_on_complete_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_request = + "GET /oncomplete HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + + evhttp_free(http); + + tt_int_op(test_ok, ==, 4); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +http_allowed_methods_eventcb(struct bufferevent *bev, short what, void *arg) +{ + char **output = arg; + if ((what & (BEV_EVENT_ERROR|BEV_EVENT_EOF))) { + char buf[4096]; + int n; + n = evbuffer_remove(bufferevent_get_input(bev), buf, + sizeof(buf)-1); + if (n >= 0) { + buf[n]='\0'; + if (*output) + free(*output); + *output = strdup(buf); + } + event_base_loopexit(exit_base, NULL); + } +} + +static void +http_allowed_methods_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev1, *bev2, *bev3; + evutil_socket_t fd1=-1, fd2=-1, fd3=-1; + const char *http_request; + char *result1=NULL, *result2=NULL, *result3=NULL; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + fd1 = http_connect("127.0.0.1", port); + tt_assert(fd1 != EVUTIL_INVALID_SOCKET); + + /* GET is out; PATCH is in. */ + evhttp_set_allowed_methods(http, EVHTTP_REQ_PATCH); + + /* Stupid thing to send a request */ + bev1 = bufferevent_socket_new(data->base, fd1, 0); + bufferevent_enable(bev1, EV_READ|EV_WRITE); + bufferevent_setcb(bev1, NULL, NULL, + http_allowed_methods_eventcb, &result1); + + http_request = + "GET /index.html HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev1, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + fd2 = http_connect("127.0.0.1", port); + tt_assert(fd2 != EVUTIL_INVALID_SOCKET); + + bev2 = bufferevent_socket_new(data->base, fd2, 0); + bufferevent_enable(bev2, EV_READ|EV_WRITE); + bufferevent_setcb(bev2, NULL, NULL, + http_allowed_methods_eventcb, &result2); + + http_request = + "PATCH /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev2, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + fd3 = http_connect("127.0.0.1", port); + tt_assert(fd3 != EVUTIL_INVALID_SOCKET); + + bev3 = bufferevent_socket_new(data->base, fd3, 0); + bufferevent_enable(bev3, EV_READ|EV_WRITE); + bufferevent_setcb(bev3, NULL, NULL, + http_allowed_methods_eventcb, &result3); + + http_request = + "FLOOP /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev3, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev1); + bufferevent_free(bev2); + bufferevent_free(bev3); + + evhttp_free(http); + + /* Method known but disallowed */ + tt_assert(result1); + tt_assert(!strncmp(result1, "HTTP/1.1 501 ", strlen("HTTP/1.1 501 "))); + + /* Method known and allowed */ + tt_assert(result2); + tt_assert(!strncmp(result2, "HTTP/1.1 200 ", strlen("HTTP/1.1 200 "))); + + /* Method unknown */ + tt_assert(result3); + tt_assert(!strncmp(result3, "HTTP/1.1 501 ", strlen("HTTP/1.1 501 "))); + + end: + if (result1) + free(result1); + if (result2) + free(result2); + if (result3) + free(result3); + if (fd1 >= 0) + evutil_closesocket(fd1); + if (fd2 >= 0) + evutil_closesocket(fd2); + if (fd3 >= 0) + evutil_closesocket(fd3); +} + +static void http_request_no_action_done(struct evhttp_request *, void *); +static void http_request_done(struct evhttp_request *, void *); +static void http_request_empty_done(struct evhttp_request *, void *); + +static void +http_connection_test_(struct basic_test_data *data, int persistent, + const char *address, struct evdns_base *dnsbase, int ipv6, int family, + int ssl) +{ + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http; + + int mask = 0; + if (ipv6) + mask |= HTTP_BIND_IPV6; + if (ssl) + mask |= HTTP_BIND_SSL; + + http = http_setup(&port, data->base, mask); + + test_ok = 0; + if (!http && ipv6) { + tt_skip(); + } + tt_assert(http); + + if (ssl) { +#ifdef EVENT__HAVE_OPENSSL + SSL *ssl = SSL_new(get_ssl_ctx()); + struct bufferevent *bev = bufferevent_openssl_socket_new( + data->base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, BEV_OPT_DEFER_CALLBACKS); + bufferevent_openssl_set_allow_dirty_shutdown(bev, 1); + + evcon = evhttp_connection_base_bufferevent_new(data->base, dnsbase, bev, address, port); +#else + tt_skip(); +#endif + } else { + evcon = evhttp_connection_base_new(data->base, dnsbase, address, port); + } + tt_assert(evcon); + evhttp_connection_set_family(evcon, family); + + tt_assert(evhttp_connection_get_base(evcon) == data->base); + + exit_base = data->base; + + tt_assert(evhttp_connection_get_server(evcon) == NULL); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok); + + /* try to make another request over the same connection */ + test_ok = 0; + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* + * if our connections are not supposed to be persistent; request + * a close from the server. + */ + if (!persistent) + evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "close"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + event_base_dispatch(data->base); + + /* make another request: request empty reply */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_connection_test(void *arg) +{ + http_connection_test_(arg, 0, "127.0.0.1", NULL, 0, AF_UNSPEC, 0); +} +static void +http_persist_connection_test(void *arg) +{ + http_connection_test_(arg, 1, "127.0.0.1", NULL, 0, AF_UNSPEC, 0); +} + +static struct regress_dns_server_table search_table[] = { + { "localhost", "A", "127.0.0.1", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static void +http_connection_async_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evdns_base *dns_base = NULL; + ev_uint16_t portnum = 0; + char address[64]; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + tt_assert(regress_dnsserver(data->base, &portnum, search_table)); + + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, dns_base, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok); + + /* try to make another request over the same connection */ + test_ok = 0; + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* + * if our connections are not supposed to be persistent; request + * a close from the server. + */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "close"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + event_base_dispatch(data->base); + + /* make another request: request empty reply */ + test_ok = 0; + + req = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); + if (dns_base) + evdns_base_free(dns_base, 0); + regress_clean_dnsserver(); +} + +static void +http_autofree_connection_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req[2] = { NULL }; + struct evhttp *http = http_setup(&port, data->base, 0); + size_t i; + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule two request to the HTTP + * server using our make request method. + */ + req[0] = evhttp_request_new(http_request_empty_done, data->base); + req[1] = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + for (i = 0; i < ARRAY_SIZE(req); ++i) { + evhttp_add_header(evhttp_request_get_output_headers(req[i]), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req[i]), "Connection", "close"); + evhttp_add_header(evhttp_request_get_output_headers(req[i]), "Empty", "itis"); + + if (evhttp_make_request(evcon, req[i], EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + } + + /* + * Tell libevent to free the connection when the request completes + * We then set the evcon pointer to NULL since we don't want to free it + * when this function ends. + */ + evhttp_connection_free_on_completion(evcon); + evcon = NULL; + + for (i = 0; i < ARRAY_SIZE(req); ++i) + event_base_dispatch(data->base); + + /* at this point, the http server should have no connection */ + tt_assert(TAILQ_FIRST(&http->connections) == NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_request_never_call(struct evhttp_request *req, void *arg) +{ + fprintf(stdout, "FAILED\n"); + exit(1); +} +static void +http_failed_request_done(struct evhttp_request *req, void *arg) +{ + tt_assert(!req); +end: + event_base_loopexit(arg, NULL); +} +#ifndef _WIN32 +static void +http_timed_out_request_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), !=, HTTP_OK); +end: + event_base_loopexit(arg, NULL); +} +#endif + +static void +http_request_error_cb_with_cancel(enum evhttp_request_error error, void *arg) +{ + if (error != EVREQ_HTTP_REQUEST_CANCEL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + test_ok = 1; + + { + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 500 * 1000; + event_base_loopexit(exit_base, &tv); + } +} +static void +http_do_cancel(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + evhttp_cancel_request(req); + ++test_ok; +} +static void +http_no_write(struct evbuffer *buffer, const struct evbuffer_cb_info *info, void *arg) +{ + fprintf(stdout, "FAILED\n"); + exit(1); +} +static void +http_free_evcons(struct evhttp_connection **evcons) +{ + struct evhttp_connection *evcon, **orig = evcons; + + if (!evcons) + return; + + while ((evcon = *evcons++)) { + evhttp_connection_free(evcon); + } + free(orig); +} +/** fill the backlog to force server drop packages for timeouts */ +static struct evhttp_connection ** +http_fill_backlog(struct event_base *base, int port) +{ +#define BACKLOG_SIZE 256 + struct evhttp_connection **evcon = malloc(sizeof(*evcon) * (BACKLOG_SIZE + 1)); + int i; + + for (i = 0; i < BACKLOG_SIZE; ++i) { + struct evhttp_request *req; + + evcon[i] = evhttp_connection_base_new(base, NULL, "127.0.0.1", port); + tt_assert(evcon[i]); + evhttp_connection_set_timeout(evcon[i], 5); + + req = evhttp_request_new(http_request_never_call, NULL); + tt_assert(req); + tt_int_op(evhttp_make_request(evcon[i], req, EVHTTP_REQ_GET, "/delay"), !=, -1); + } + evcon[i] = NULL; + + return evcon; + end: + fprintf(stderr, "Couldn't fill the backlog"); + return NULL; +} + +enum http_cancel_test_type { + BASIC = 1, + BY_HOST = 2, + NO_NS = 4, + INACTIVE_SERVER = 8, + SERVER_TIMEOUT = 16, + NS_TIMEOUT = 32, +}; +static struct evhttp_request * +http_cancel_test_bad_request_new(enum http_cancel_test_type type, + struct event_base *base) +{ +#ifndef _WIN32 + if (!(type & NO_NS) && (type & SERVER_TIMEOUT)) + return evhttp_request_new(http_timed_out_request_done, base); + else +#endif + if ((type & INACTIVE_SERVER) || (type & NO_NS)) + return evhttp_request_new(http_failed_request_done, base); + else + return NULL; +} +static void +http_cancel_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bufev = NULL; + struct timeval tv; + struct evdns_base *dns_base = NULL; + ev_uint16_t portnum = 0; + char address[64]; + struct evhttp *inactive_http = NULL; + struct event_base *inactive_base = NULL; + struct evhttp_connection **evcons = NULL; + struct event_base *base_to_fill = data->base; + + enum http_cancel_test_type type = + (enum http_cancel_test_type)data->setup_data; + struct evhttp *http = http_setup(&port, data->base, 0); + + if (type & BY_HOST) { + const char *timeout = (type & NS_TIMEOUT) ? "6" : "3"; + + tt_assert(regress_dnsserver(data->base, &portnum, search_table)); + + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /** XXX: Hack the port to make timeout after resolving */ + if (type & NO_NS) + ++portnum; + + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + evdns_base_set_option(dns_base, "timeout:", timeout); + evdns_base_set_option(dns_base, "initial-probe-timeout:", timeout); + evdns_base_set_option(dns_base, "attempts:", "1"); + } + + exit_base = data->base; + + test_ok = 0; + + if (type & INACTIVE_SERVER) { + port = 0; + inactive_base = event_base_new(); + inactive_http = http_setup(&port, inactive_base, 0); + + base_to_fill = inactive_base; + } + + if (type & SERVER_TIMEOUT) + evcons = http_fill_backlog(base_to_fill, port); + + evcon = evhttp_connection_base_new( + data->base, dns_base, + type & BY_HOST ? "localhost" : "127.0.0.1", + port); + if (type & INACTIVE_SERVER) + evhttp_connection_set_timeout(evcon, 5); + tt_assert(evcon); + + bufev = evhttp_connection_get_bufferevent(evcon); + /* Guarantee that we stack in connect() not after waiting EV_READ after + * write() */ + if (type & SERVER_TIMEOUT) + evbuffer_add_cb(bufferevent_get_output(bufev), http_no_write, NULL); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_never_call, NULL); + evhttp_request_set_error_cb(req, http_request_error_cb_with_cancel); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/delay"), + !=, -1); + + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + + event_base_once(data->base, -1, EV_TIMEOUT, http_do_cancel, req, &tv); + + event_base_dispatch(data->base); + + if (type & NO_NS || type & INACTIVE_SERVER) + tt_int_op(test_ok, ==, 2); /** no servers responses */ + else + tt_int_op(test_ok, ==, 3); + + /* try to make another request over the same connection */ + test_ok = 0; + + http_free_evcons(evcons); + if (type & SERVER_TIMEOUT) + evcons = http_fill_backlog(base_to_fill, port); + + req = http_cancel_test_bad_request_new(type, data->base); + if (!req) + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test"), + !=, -1); + + event_base_dispatch(data->base); + + /* make another request: request empty reply */ + test_ok = 0; + + http_free_evcons(evcons); + if (type & SERVER_TIMEOUT) + evcons = http_fill_backlog(base_to_fill, port); + + req = http_cancel_test_bad_request_new(type, data->base); + if (!req) + req = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test"), + !=, -1); + + event_base_dispatch(data->base); + + end: + http_free_evcons(evcons); + if (bufev) + evbuffer_remove_cb(bufferevent_get_output(bufev), http_no_write, NULL); + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); + if (dns_base) + evdns_base_free(dns_base, 0); + regress_clean_dnsserver(); + if (inactive_http) + evhttp_free(inactive_http); + if (inactive_base) + event_base_free(inactive_base); +} + +static void +http_request_no_action_done(struct evhttp_request *req, void *arg) +{ + EVUTIL_ASSERT(exit_base); + event_base_loopexit(exit_base, NULL); +} + +static void +http_request_done(struct evhttp_request *req, void *arg) +{ + const char *what = arg; + + if (!req) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + EVUTIL_ASSERT(exit_base); + event_base_loopexit(exit_base, NULL); +} + +static void +http_request_expect_error(struct evhttp_request *req, void *arg) +{ + if (evhttp_request_get_response_code(req) == HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + EVUTIL_ASSERT(arg); + event_base_loopexit(arg, NULL); +} + +/* test virtual hosts */ +static void +http_virtual_host_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *second = NULL, *third = NULL; + evutil_socket_t fd; + struct bufferevent *bev; + const char *http_request; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + + /* virtual host */ + second = evhttp_new(NULL); + evhttp_set_cb(second, "/funnybunny", http_basic_cb, http); + third = evhttp_new(NULL); + evhttp_set_cb(third, "/blackcoffee", http_basic_cb, http); + + if (evhttp_add_virtual_host(http, "foo.com", second) == -1) { + tt_abort_msg("Couldn't add vhost"); + } + + if (evhttp_add_virtual_host(http, "bar.*.foo.com", third) == -1) { + tt_abort_msg("Couldn't add wildcarded vhost"); + } + + /* add some aliases to the vhosts */ + tt_assert(evhttp_add_server_alias(second, "manolito.info") == 0); + tt_assert(evhttp_add_server_alias(third, "bonkers.org") == 0); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* make a request with a different host and expect an error */ + req = evhttp_request_new(http_request_expect_error, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/funnybunny") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1); + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "foo.com"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/funnybunny") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1); + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "bar.magic.foo.com"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/blackcoffee") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1) + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "manolito.info"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/funnybunny") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1) + + test_ok = 0; + + /* make a request with the right host and expect a response */ + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + + /* Add the Host header. This time with the optional port. */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "bonkers.org:8000"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/blackcoffee") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1) + + test_ok = 0; + + /* Now make a raw request with an absolute URI. */ + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, NULL); + + /* The host in the URI should override the Host: header */ + http_request = + "GET http://manolito.info/funnybunny HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 2); + + bufferevent_free(bev); + evutil_closesocket(fd); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + + +/* test date header and content length */ + +static void +http_request_empty_done(struct evhttp_request *req, void *arg) +{ + if (!req) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Date") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Length") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (strcmp(evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Length"), + "0")) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + EVUTIL_ASSERT(arg); + event_base_loopexit(arg, NULL); +} + +/* + * HTTP DISPATCHER test + */ + +void +http_dispatcher_cb(struct evhttp_request *req, void *arg) +{ + + struct evbuffer *evb = evbuffer_new(); + TT_BLATHER(("%s: called\n", __func__)); + evbuffer_add_printf(evb, "DISPATCHER_TEST"); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +static void +http_dispatcher_test_done(struct evhttp_request *req, void *arg) +{ + struct event_base *base = arg; + const char *what = "DISPATCHER_TEST"; + + if (!req) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED (length %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(what)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(base, NULL); +} + +static void +http_dispatcher_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* also bind to local host */ + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_dispatcher_test_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +/* + * HTTP POST test. + */ + +void http_postrequest_done(struct evhttp_request *, void *); + +#define POST_DATA "Okay. Not really printf" + +static void +http_post_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(http_postrequest_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), POST_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + + test_ok = 0; + + req = evhttp_request_new(http_postrequest_done, data->base); + tt_assert(req); + + /* Now try with 100-continue. */ + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), POST_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + + evhttp_connection_free(evcon); + evhttp_free(http); + + end: + ; +} + +void +http_post_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb; + TT_BLATHER(("%s: called\n", __func__)); + + /* Yes, we are expecting a post request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_POST) { + fprintf(stdout, "FAILED (post type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(POST_DATA)) { + fprintf(stdout, "FAILED (length: %lu vs %lu)\n", + (unsigned long) evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long) strlen(POST_DATA)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), POST_DATA) != 0) { + fprintf(stdout, "FAILED (data)\n"); + fprintf(stdout, "Got :%s\n", evbuffer_pullup(evhttp_request_get_input_buffer(req),-1)); + fprintf(stdout, "Want:%s\n", POST_DATA); + exit(1); + } + + evb = evbuffer_new(); + evbuffer_add_printf(evb, BASIC_REQUEST_BODY); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +void +http_postrequest_done(struct evhttp_request *req, void *arg) +{ + const char *what = BASIC_REQUEST_BODY; + struct event_base *base = arg; + + if (req == NULL) { + fprintf(stderr, "FAILED (timeout)\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED (length %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(what)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(base, NULL); +} + +/* + * HTTP PUT test, basically just like POST, but ... + */ + +void http_putrequest_done(struct evhttp_request *, void *); + +#define PUT_DATA "Hi, I'm some PUT data" + +static void +http_put_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * Schedule the HTTP PUT request + */ + + req = evhttp_request_new(http_putrequest_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "someotherhost"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), PUT_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_PUT, "/putit") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + evhttp_connection_free(evcon); + evhttp_free(http); + + tt_int_op(test_ok, ==, 1); + end: + ; +} + +void +http_put_cb(struct evhttp_request *req, void *arg) +{ + struct evbuffer *evb; + TT_BLATHER(("%s: called\n", __func__)); + + /* Expecting a PUT request */ + if (evhttp_request_get_command(req) != EVHTTP_REQ_PUT) { + fprintf(stdout, "FAILED (put type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(PUT_DATA)) { + fprintf(stdout, "FAILED (length: %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(PUT_DATA)); + exit(1); + } + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), PUT_DATA) != 0) { + fprintf(stdout, "FAILED (data)\n"); + fprintf(stdout, "Got :%s\n", evbuffer_pullup(evhttp_request_get_input_buffer(req),-1)); + fprintf(stdout, "Want:%s\n", PUT_DATA); + exit(1); + } + + evb = evbuffer_new(); + evbuffer_add_printf(evb, "That ain't funny"); + + evhttp_send_reply(req, HTTP_OK, "Everything is great", evb); + + evbuffer_free(evb); +} + +void +http_putrequest_done(struct evhttp_request *req, void *arg) +{ + struct event_base *base = arg; + const char *what = "That ain't funny"; + + if (req == NULL) { + fprintf(stderr, "FAILED (timeout)\n"); + exit(1); + } + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") == NULL) { + fprintf(stderr, "FAILED (content type)\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != strlen(what)) { + fprintf(stderr, "FAILED (length %lu vs %lu)\n", + (unsigned long)evbuffer_get_length(evhttp_request_get_input_buffer(req)), (unsigned long)strlen(what)); + exit(1); + } + + + if (evbuffer_datacmp(evhttp_request_get_input_buffer(req), what) != 0) { + fprintf(stderr, "FAILED (data)\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(base, NULL); +} + +static void +http_failure_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "400 Bad Request"; + if (evbuffer_contains(bufferevent_get_input(bev), what)) { + test_ok = 2; + bufferevent_disable(bev, EV_READ); + event_base_loopexit(arg, NULL); + } +} + +/* + * Testing that the HTTP server can deal with a malformed request. + */ +static void +http_failure_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, http_failure_readcb, http_writecb, + http_errorcb, data->base); + + http_request = "illegal request\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +close_detect_done(struct evhttp_request *req, void *arg) +{ + struct timeval tv; + tt_assert(req); + tt_assert(evhttp_request_get_response_code(req) == HTTP_OK); + + test_ok = 1; + + end: + evutil_timerclear(&tv); + tv.tv_usec = 150000; + event_base_loopexit(arg, &tv); +} + +static void +close_detect_launch(evutil_socket_t fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct event_base *base = evhttp_connection_get_base(evcon); + struct evhttp_request *req; + + req = evhttp_request_new(close_detect_done, base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_fail_msg("Couldn't make request"); + } +} + +static void +close_detect_cb(struct evhttp_request *req, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct event_base *base = evhttp_connection_get_base(evcon); + struct timeval tv; + + if (req != NULL && evhttp_request_get_response_code(req) != HTTP_OK) { + tt_abort_msg("Failed"); + } + + evutil_timerclear(&tv); + tv.tv_sec = 0; /* longer than the http time out */ + tv.tv_usec = 600000; /* longer than the http time out */ + + /* launch a new request on the persistent connection in .3 seconds */ + event_base_once(base, -1, EV_TIMEOUT, close_detect_launch, evcon, &tv); + end: + ; +} + + +static void +http_close_detection_(struct basic_test_data *data, int with_delay) +{ + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + const struct timeval sec_tenth = { 0, 100000 }; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + /* .1 second timeout */ + evhttp_set_timeout_tv(http, &sec_tenth); + + evcon = evhttp_connection_base_new(data->base, NULL, + "127.0.0.1", port); + tt_assert(evcon); + evhttp_connection_set_timeout_tv(evcon, &sec_tenth); + + + tt_assert(evcon); + delayed_client = evcon; + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(close_detect_cb, evcon); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, + req, EVHTTP_REQ_GET, with_delay ? "/largedelay" : "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + event_base_dispatch(data->base); + + /* at this point, the http server should have no connection */ + tt_assert(TAILQ_FIRST(&http->connections) == NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void +http_close_detection_test(void *arg) +{ + http_close_detection_(arg, 0); +} +static void +http_close_detection_delay_test(void *arg) +{ + http_close_detection_(arg, 1); +} + +static void +http_highport_test(void *arg) +{ + struct basic_test_data *data = arg; + int i = -1; + struct evhttp *myhttp = NULL; + + /* Try a few different ports */ + for (i = 0; i < 50; ++i) { + myhttp = evhttp_new(data->base); + if (evhttp_bind_socket(myhttp, "127.0.0.1", 65535 - i) == 0) { + test_ok = 1; + evhttp_free(myhttp); + return; + } + evhttp_free(myhttp); + } + + tt_fail_msg("Couldn't get a high port"); +} + +static void +http_bad_header_test(void *ptr) +{ + struct evkeyvalq headers; + + TAILQ_INIT(&headers); + + tt_want(evhttp_add_header(&headers, "One", "Two") == 0); + tt_want(evhttp_add_header(&headers, "One", "Two\r\n Three") == 0); + tt_want(evhttp_add_header(&headers, "One\r", "Two") == -1); + tt_want(evhttp_add_header(&headers, "One\n", "Two") == -1); + tt_want(evhttp_add_header(&headers, "One", "Two\r") == -1); + tt_want(evhttp_add_header(&headers, "One", "Two\n") == -1); + + evhttp_clear_headers(&headers); +} + +static int validate_header( + const struct evkeyvalq* headers, + const char *key, const char *value) +{ + const char *real_val = evhttp_find_header(headers, key); + tt_assert(real_val != NULL); + tt_want(strcmp(real_val, value) == 0); +end: + return (0); +} + +static void +http_parse_query_test(void *ptr) +{ + struct evkeyvalq headers; + int r; + + TAILQ_INIT(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test", &headers); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test&foo=bar", &headers); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_want(validate_header(&headers, "foo", "bar") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test+foo", &headers); + tt_want(validate_header(&headers, "q", "test foo") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test%0Afoo", &headers); + tt_want(validate_header(&headers, "q", "test\nfoo") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test%0Dfoo", &headers); + tt_want(validate_header(&headers, "q", "test\rfoo") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test&&q2", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test+this", &headers); + tt_want(validate_header(&headers, "q", "test this") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=test&q2=foo", &headers); + tt_int_op(r, ==, 0); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_want(validate_header(&headers, "q2", "foo") == 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q&q2=foo", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=foo&q2", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=foo&q2&q3=x", &headers); + tt_int_op(r, ==, -1); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query("http://www.test.com/?q=&q2=&q3=", &headers); + tt_int_op(r, ==, 0); + tt_want(validate_header(&headers, "q", "") == 0); + tt_want(validate_header(&headers, "q2", "") == 0); + tt_want(validate_header(&headers, "q3", "") == 0); + evhttp_clear_headers(&headers); + +end: + evhttp_clear_headers(&headers); +} +static void +http_parse_query_str_test(void *ptr) +{ + struct evkeyvalq headers; + int r; + + TAILQ_INIT(&headers); + + r = evhttp_parse_query_str("http://www.test.com/?q=test", &headers); + tt_assert(evhttp_find_header(&headers, "q") == NULL); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + + r = evhttp_parse_query_str("q=test", &headers); + tt_want(validate_header(&headers, "q", "test") == 0); + tt_int_op(r, ==, 0); + evhttp_clear_headers(&headers); + +end: + evhttp_clear_headers(&headers); +} + +static void +http_parse_uri_test(void *ptr) +{ + const int nonconform = (ptr != NULL); + const unsigned parse_flags = + nonconform ? EVHTTP_URI_NONCONFORMANT : 0; + struct evhttp_uri *uri = NULL; + char url_tmp[4096]; +#define URI_PARSE(uri) \ + evhttp_uri_parse_with_flags((uri), parse_flags) + +#define TT_URI(want) do { \ + char *ret = evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)); \ + tt_want(ret != NULL); \ + tt_want(ret == url_tmp); \ + if (strcmp(ret,want) != 0) \ + TT_FAIL(("\"%s\" != \"%s\"",ret,want)); \ + } while(0) + + tt_want(evhttp_uri_join(NULL, 0, 0) == NULL); + tt_want(evhttp_uri_join(NULL, url_tmp, 0) == NULL); + tt_want(evhttp_uri_join(NULL, url_tmp, sizeof(url_tmp)) == NULL); + + /* bad URIs: parsing */ +#define BAD(s) do { \ + if (URI_PARSE(s) != NULL) \ + TT_FAIL(("Expected error parsing \"%s\"",s)); \ + } while(0) + /* Nonconformant URIs we can parse: parsing */ +#define NCF(s) do { \ + uri = URI_PARSE(s); \ + if (uri != NULL && !nonconform) { \ + TT_FAIL(("Expected error parsing \"%s\"",s)); \ + } else if (uri == NULL && nonconform) { \ + TT_FAIL(("Couldn't parse nonconformant URI \"%s\"", \ + s)); \ + } \ + if (uri) { \ + tt_want(evhttp_uri_join(uri, url_tmp, \ + sizeof(url_tmp))); \ + evhttp_uri_free(uri); \ + } \ + } while(0) + + NCF("http://www.test.com/ why hello"); + NCF("http://www.test.com/why-hello\x01"); + NCF("http://www.test.com/why-hello?\x01"); + NCF("http://www.test.com/why-hello#\x01"); + BAD("http://www.\x01.test.com/why-hello"); + BAD("http://www.%7test.com/why-hello"); + NCF("http://www.test.com/why-hell%7o"); + BAD("h%3ttp://www.test.com/why-hello"); + NCF("http://www.test.com/why-hello%7"); + NCF("http://www.test.com/why-hell%7o"); + NCF("http://www.test.com/foo?ba%r"); + NCF("http://www.test.com/foo#ba%r"); + BAD("99:99/foo"); + BAD("http://www.test.com:999x/"); + BAD("http://www.test.com:x/"); + BAD("http://[hello-there]/"); + BAD("http://[::1]]/"); + BAD("http://[::1/"); + BAD("http://[foob/"); + BAD("http://[/"); + BAD("http://[ffff:ffff:ffff:ffff:Ffff:ffff:ffff:" + "ffff:ffff:ffff:ffff:ffff:ffff:ffff]/"); + BAD("http://[vX.foo]/"); + BAD("http://[vX.foo]/"); + BAD("http://[v.foo]/"); + BAD("http://[v5.fo%o]/"); + BAD("http://[v5X]/"); + BAD("http://[v5]/"); + BAD("http://[]/"); + BAD("http://f\x01red@www.example.com/"); + BAD("http://f%0red@www.example.com/"); + BAD("http://www.example.com:9999999999999999999999999999999999999/"); + BAD("http://www.example.com:hihi/"); + BAD("://www.example.com/"); + + /* bad URIs: joining */ + uri = evhttp_uri_new(); + tt_want(0==evhttp_uri_set_host(uri, "www.example.com")); + tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) != NULL); + /* not enough space: */ + tt_want(evhttp_uri_join(uri, url_tmp, 3) == NULL); + /* host is set, but path doesn't start with "/": */ + tt_want(0==evhttp_uri_set_path(uri, "hi_mom")); + tt_want(evhttp_uri_join(uri, url_tmp, sizeof(url_tmp)) == NULL); + tt_want(evhttp_uri_join(uri, NULL, sizeof(url_tmp))==NULL); + tt_want(evhttp_uri_join(uri, url_tmp, 0)==NULL); + evhttp_uri_free(uri); + uri = URI_PARSE("mailto:foo@bar"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(!strcmp(evhttp_uri_get_scheme(uri), "mailto")); + tt_want(!strcmp(evhttp_uri_get_path(uri), "foo@bar")); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("mailto:foo@bar"); + evhttp_uri_free(uri); + + uri = evhttp_uri_new(); + /* Bad URI usage: setting invalid values */ + tt_want(-1 == evhttp_uri_set_scheme(uri,"")); + tt_want(-1 == evhttp_uri_set_scheme(uri,"33")); + tt_want(-1 == evhttp_uri_set_scheme(uri,"hi!")); + tt_want(-1 == evhttp_uri_set_userinfo(uri,"hello@")); + tt_want(-1 == evhttp_uri_set_host(uri,"[1.2.3.4]")); + tt_want(-1 == evhttp_uri_set_host(uri,"[")); + tt_want(-1 == evhttp_uri_set_host(uri,"www.[foo].com")); + tt_want(-1 == evhttp_uri_set_port(uri,-3)); + tt_want(-1 == evhttp_uri_set_path(uri,"hello?world")); + tt_want(-1 == evhttp_uri_set_query(uri,"hello#world")); + tt_want(-1 == evhttp_uri_set_fragment(uri,"hello#world")); + /* Valid URI usage: setting valid values */ + tt_want(0 == evhttp_uri_set_scheme(uri,"http")); + tt_want(0 == evhttp_uri_set_scheme(uri,NULL)); + tt_want(0 == evhttp_uri_set_userinfo(uri,"username:pass")); + tt_want(0 == evhttp_uri_set_userinfo(uri,NULL)); + tt_want(0 == evhttp_uri_set_host(uri,"www.example.com")); + tt_want(0 == evhttp_uri_set_host(uri,"1.2.3.4")); + tt_want(0 == evhttp_uri_set_host(uri,"[1:2:3:4::]")); + tt_want(0 == evhttp_uri_set_host(uri,"[v7.wobblewobble]")); + tt_want(0 == evhttp_uri_set_host(uri,NULL)); + tt_want(0 == evhttp_uri_set_host(uri,"")); + tt_want(0 == evhttp_uri_set_port(uri, -1)); + tt_want(0 == evhttp_uri_set_port(uri, 80)); + tt_want(0 == evhttp_uri_set_port(uri, 65535)); + tt_want(0 == evhttp_uri_set_path(uri, "")); + tt_want(0 == evhttp_uri_set_path(uri, "/documents/public/index.html")); + tt_want(0 == evhttp_uri_set_path(uri, NULL)); + tt_want(0 == evhttp_uri_set_query(uri, "key=val&key2=val2")); + tt_want(0 == evhttp_uri_set_query(uri, "keyvalblarg")); + tt_want(0 == evhttp_uri_set_query(uri, "")); + tt_want(0 == evhttp_uri_set_query(uri, NULL)); + tt_want(0 == evhttp_uri_set_fragment(uri, "")); + tt_want(0 == evhttp_uri_set_fragment(uri, "here?i?am")); + tt_want(0 == evhttp_uri_set_fragment(uri, NULL)); + evhttp_uri_free(uri); + + /* Valid parsing */ + uri = URI_PARSE("http://www.test.com/?q=t%33est"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=t%33est") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com/?q=t%33est"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://%77ww.test.com"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "%77ww.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://%77ww.test.com"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com#fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want_str_op(evhttp_uri_get_fragment(uri), ==, "fragment"); + TT_URI("http://www.test.com#fragment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://8000/"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "8000") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://8000/"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://:8000/"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == 8000); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://:8000/"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com:/"); /* empty port */ + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want_str_op(evhttp_uri_get_path(uri), ==, "/"); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com/"); + evhttp_uri_free(uri); + + uri = URI_PARSE("http://www.test.com:"); /* empty port 2 */ + tt_want(strcmp(evhttp_uri_get_scheme(uri), "http") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("http://www.test.com"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://www.test.com/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "www.test.com") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://www.test.com/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://[::1]:999/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[::1]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == 999); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://[::1]:999/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://[ff00::127.0.0.1]/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[ff00::127.0.0.1]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://[ff00::127.0.0.1]/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("ftp://[v99.not_(any:time)_soon]/?q=test"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "ftp") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "[v99.not_(any:time)_soon]") == 0); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("ftp://[v99.not_(any:time)_soon]/?q=test"); + evhttp_uri_free(uri); + + uri = URI_PARSE("scheme://user:pass@foo.com:42/?q=test&s=some+thing#fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "user:pass") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == 42); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=test&s=some+thing") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fragment") == 0); + TT_URI("scheme://user:pass@foo.com:42/?q=test&s=some+thing#fragment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("scheme://user@foo.com/#fragment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "user") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fragment") == 0); + TT_URI("scheme://user@foo.com/#fragment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("scheme://%75ser@foo.com/#frag@ment"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "scheme") == 0); + tt_want(strcmp(evhttp_uri_get_userinfo(uri), "%75ser") == 0); + tt_want(strcmp(evhttp_uri_get_host(uri), "foo.com") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "frag@ment") == 0); + TT_URI("scheme://%75ser@foo.com/#frag@ment"); + evhttp_uri_free(uri); + + uri = URI_PARSE("file:///some/path/to/the/file"); + tt_want(strcmp(evhttp_uri_get_scheme(uri), "file") == 0); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/some/path/to/the/file") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("file:///some/path/to/the/file"); + evhttp_uri_free(uri); + + uri = URI_PARSE("///some/path/to/the-file"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_host(uri), "") == 0); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/some/path/to/the-file") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("///some/path/to/the-file"); + evhttp_uri_free(uri); + + uri = URI_PARSE("/s:ome/path/to/the-file?q=99#fred"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "/s:ome/path/to/the-file") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=99") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fred") == 0); + TT_URI("/s:ome/path/to/the-file?q=99#fred"); + evhttp_uri_free(uri); + + uri = URI_PARSE("relative/path/with/co:lon"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "relative/path/with/co:lon") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(evhttp_uri_get_fragment(uri) == NULL); + TT_URI("relative/path/with/co:lon"); + evhttp_uri_free(uri); + + uri = URI_PARSE("bob?q=99&q2=q?33#fr?ed"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "bob") == 0); + tt_want(strcmp(evhttp_uri_get_query(uri), "q=99&q2=q?33") == 0); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fr?ed") == 0); + TT_URI("bob?q=99&q2=q?33#fr?ed"); + evhttp_uri_free(uri); + + uri = URI_PARSE("#fr?ed"); + tt_want(uri != NULL); + tt_want(evhttp_uri_get_scheme(uri) == NULL); + tt_want(evhttp_uri_get_userinfo(uri) == NULL); + tt_want(evhttp_uri_get_host(uri) == NULL); + tt_want(evhttp_uri_get_port(uri) == -1); + tt_want(strcmp(evhttp_uri_get_path(uri), "") == 0); + tt_want(evhttp_uri_get_query(uri) == NULL); + tt_want(strcmp(evhttp_uri_get_fragment(uri), "fr?ed") == 0); + TT_URI("#fr?ed"); + evhttp_uri_free(uri); +#undef URI_PARSE +#undef TT_URI +#undef BAD +} + +static void +http_uriencode_test(void *ptr) +{ + char *s=NULL, *s2=NULL; + size_t sz; + int bytes_decoded; + +#define ENC(from,want,plus) do { \ + s = evhttp_uriencode((from), -1, (plus)); \ + tt_assert(s); \ + tt_str_op(s,==,(want)); \ + sz = -1; \ + s2 = evhttp_uridecode((s), (plus), &sz); \ + tt_assert(s2); \ + tt_str_op(s2,==,(from)); \ + tt_int_op(sz,==,strlen(from)); \ + free(s); \ + free(s2); \ + s = s2 = NULL; \ + } while (0) + +#define DEC(from,want,dp) do { \ + s = evhttp_uridecode((from),(dp),&sz); \ + tt_assert(s); \ + tt_str_op(s,==,(want)); \ + tt_int_op(sz,==,strlen(want)); \ + free(s); \ + s = NULL; \ + } while (0) + +#define OLD_DEC(from,want) do { \ + s = evhttp_decode_uri((from)); \ + tt_assert(s); \ + tt_str_op(s,==,(want)); \ + free(s); \ + s = NULL; \ + } while (0) + + + ENC("Hello", "Hello",0); + ENC("99", "99",0); + ENC("", "",0); + ENC( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_",0); + ENC(" ", "%20",0); + ENC(" ", "+",1); + ENC("\xff\xf0\xe0", "%FF%F0%E0",0); + ENC("\x01\x19", "%01%19",1); + ENC("http://www.ietf.org/rfc/rfc3986.txt", + "http%3A%2F%2Fwww.ietf.org%2Frfc%2Frfc3986.txt",1); + + ENC("1+2=3", "1%2B2%3D3",1); + ENC("1+2=3", "1%2B2%3D3",0); + + /* Now try encoding with internal NULs. */ + s = evhttp_uriencode("hello\0world", 11, 0); + tt_assert(s); + tt_str_op(s,==,"hello%00world"); + free(s); + s = NULL; + + /* Now try decoding just part of string. */ + s = malloc(6 + 1 /* NUL byte */); + bytes_decoded = evhttp_decode_uri_internal("hello%20%20", 6, s, 0); + tt_assert(s); + tt_int_op(bytes_decoded,==,6); + tt_str_op(s,==,"hello%"); + free(s); + s = NULL; + + /* Now try out some decoding cases that we don't generate with + * encode_uri: Make sure that malformed stuff doesn't crash... */ + DEC("%%xhello th+ere \xff", + "%%xhello th+ere \xff", 0); + /* Make sure plus decoding works */ + DEC("plus+should%20work+", "plus should work ",1); + /* Try some lowercase hex */ + DEC("%f0%a0%b0", "\xf0\xa0\xb0",1); + + /* Try an internal NUL. */ + sz = 0; + s = evhttp_uridecode("%00%00x%00%00", 1, &sz); + tt_int_op(sz,==,5); + tt_assert(!memcmp(s, "\0\0x\0\0", 5)); + free(s); + s = NULL; + + /* Try with size == NULL */ + sz = 0; + s = evhttp_uridecode("%00%00x%00%00", 1, NULL); + tt_assert(!memcmp(s, "\0\0x\0\0", 5)); + free(s); + s = NULL; + + /* Test out the crazy old behavior of the deprecated + * evhttp_decode_uri */ + OLD_DEC("http://example.com/normal+path/?key=val+with+spaces", + "http://example.com/normal+path/?key=val with spaces"); + +end: + if (s) + free(s); + if (s2) + free(s2); +#undef ENC +#undef DEC +#undef OLD_DEC +} + +static void +http_base_test(void *ptr) +{ + struct event_base *base = NULL; + struct bufferevent *bev; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0; + struct evhttp *http; + + test_ok = 0; + base = event_base_new(); + tt_assert(base); + http = http_setup(&port, base, 0); + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(base, fd, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, base); + bufferevent_base_set(base, bev); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_base_dispatch(base); + + bufferevent_free(bev); + evutil_closesocket(fd); + + evhttp_free(http); + + tt_int_op(test_ok, ==, 2); + +end: + if (base) + event_base_free(base); +} + +/* + * the server is just going to close the connection if it times out during + * reading the headers. + */ + +static void +http_incomplete_readcb(struct bufferevent *bev, void *arg) +{ + test_ok = -1; + event_base_loopexit(exit_base,NULL); +} + +static void +http_incomplete_errorcb(struct bufferevent *bev, short what, void *arg) +{ + /** For ssl */ + if (what & BEV_EVENT_CONNECTED) + return; + + if (what == (BEV_EVENT_READING|BEV_EVENT_EOF)) + test_ok++; + else + test_ok = -2; + event_base_loopexit(exit_base,NULL); +} + +static void +http_incomplete_writecb(struct bufferevent *bev, void *arg) +{ + if (arg != NULL) { + evutil_socket_t fd = *(evutil_socket_t *)arg; + /* terminate the write side to simulate EOF */ + shutdown(fd, EVUTIL_SHUT_WR); + } + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_incomplete_test_(struct basic_test_data *data, int use_timeout, int ssl) +{ + struct bufferevent *bev; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0; + struct timeval tv_start, tv_end; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + evhttp_set_timeout(http, 1); + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, 0); + bufferevent_setcb(bev, + http_incomplete_readcb, http_incomplete_writecb, + http_incomplete_errorcb, use_timeout ? NULL : &fd); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + evutil_gettimeofday(&tv_start, NULL); + + event_base_dispatch(data->base); + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + + bufferevent_free(bev); + if (use_timeout) { + evutil_closesocket(fd); + fd = EVUTIL_INVALID_SOCKET; + } + + evhttp_free(http); + + if (use_timeout && tv_end.tv_sec >= 3) { + tt_abort_msg("time"); + } else if (!use_timeout && tv_end.tv_sec >= 1) { + /* we should be done immediately */ + tt_abort_msg("time"); + } + + tt_int_op(test_ok, ==, 2); + end: + if (fd >= 0) + evutil_closesocket(fd); +} +static void http_incomplete_test(void *arg) +{ http_incomplete_test_(arg, 0, 0); } +static void http_incomplete_timeout_test(void *arg) +{ http_incomplete_test_(arg, 1, 0); } + + +/* + * the server is going to reply with chunked data. + */ + +static void +http_chunked_readcb(struct bufferevent *bev, void *arg) +{ + /* nothing here */ +} + +static void +http_chunked_errorcb(struct bufferevent *bev, short what, void *arg) +{ + struct evhttp_request *req = NULL; + + /** SSL */ + if (what & BEV_EVENT_CONNECTED) + return; + + if (!test_ok) + goto out; + + test_ok = -1; + + if ((what & BEV_EVENT_EOF) != 0) { + const char *header; + enum message_read_status done; + req = evhttp_request_new(NULL, NULL); + + /* req->kind = EVHTTP_RESPONSE; */ + done = evhttp_parse_firstline_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + done = evhttp_parse_headers_(req, bufferevent_get_input(bev)); + if (done != ALL_DATA_READ) + goto out; + + header = evhttp_find_header(evhttp_request_get_input_headers(req), "Transfer-Encoding"); + if (header == NULL || strcmp(header, "chunked")) + goto out; + + header = evhttp_find_header(evhttp_request_get_input_headers(req), "Connection"); + if (header == NULL || strcmp(header, "close")) + goto out; + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 13 chars */ + if (strcmp(header, "d")) { + free((void*)header); + goto out; + } + free((void*)header); + + if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 13), + "This is funny", 13)) + goto out; + + evbuffer_drain(bufferevent_get_input(bev), 13 + 2); + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 18 chars */ + if (strcmp(header, "12")) + goto out; + free((char *)header); + + if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 18), + "but not hilarious.", 18)) + goto out; + + evbuffer_drain(bufferevent_get_input(bev), 18 + 2); + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 8 chars */ + if (strcmp(header, "8")) { + free((void*)header); + goto out; + } + free((char *)header); + + if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 8), + "bwv 1052.", 8)) + goto out; + + evbuffer_drain(bufferevent_get_input(bev), 8 + 2); + + header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF); + if (header == NULL) + goto out; + /* 0 chars */ + if (strcmp(header, "0")) { + free((void*)header); + goto out; + } + free((void *)header); + + test_ok = 2; + } + +out: + if (req) + evhttp_request_free(req); + + event_base_loopexit(arg, NULL); +} + +static void +http_chunked_writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +static void +http_chunked_request_done(struct evhttp_request *req, void *arg) +{ + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(evhttp_request_get_input_headers(req), + "Transfer-Encoding") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != 13 + 18 + 8) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (strncmp((char *)evbuffer_pullup(evhttp_request_get_input_buffer(req), 13 + 18 + 8), + "This is funnybut not hilarious.bwv 1052", + 13 + 18 + 8)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(arg, NULL); +} + +static void +http_chunk_out_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL; + evutil_socket_t fd; + const char *http_request; + ev_uint16_t port = 0; + struct timeval tv_start, tv_end; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + int i; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = create_bev(data->base, fd, ssl, BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, + http_chunked_readcb, http_chunked_writecb, + http_chunked_errorcb, data->base); + + http_request = + "GET /chunked HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + evutil_gettimeofday(&tv_start, NULL); + + event_base_dispatch(data->base); + + bufferevent_free(bev); + bev = NULL; + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + + tt_int_op(tv_end.tv_sec, <, 1); + + tt_int_op(test_ok, ==, 2); + + /* now try again with the regular connection object */ + bev = create_bev(data->base, -1, ssl, BEV_OPT_CLOSE_ON_FREE); + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "127.0.0.1", port); + tt_assert(evcon); + + /* make two requests to check the keepalive behavior */ + for (i = 0; i < 2; i++) { + test_ok = 0; + req = evhttp_request_new(http_chunked_request_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/chunked") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_assert(test_ok == 1); + } + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void http_chunk_out_test(void *arg) +{ http_chunk_out_test_impl(arg, 0); } + +static void +http_stream_out_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bev; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + test_ok = 0; + exit_base = data->base; + + bev = create_bev(data->base, -1, ssl, 0); + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, + (void *)"This is funnybut not hilarious.bwv 1052"); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/streamed") + == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void http_stream_out_test(void *arg) +{ http_stream_out_test_impl(arg, 0); } + +static void +http_stream_in_chunk(struct evhttp_request *req, void *arg) +{ + struct evbuffer *reply = arg; + + if (evhttp_request_get_response_code(req) != HTTP_OK) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + evbuffer_add_buffer(reply, evhttp_request_get_input_buffer(req)); +} + +static void +http_stream_in_done(struct evhttp_request *req, void *arg) +{ + if (evbuffer_get_length(evhttp_request_get_input_buffer(req)) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + event_base_loopexit(exit_base, NULL); +} + +/** + * Makes a request and reads the response in chunks. + */ +static void +http_stream_in_test_(struct basic_test_data *data, char const *url, + size_t expected_len, char const *expected) +{ + struct evhttp_connection *evcon; + struct evbuffer *reply = evbuffer_new(); + struct evhttp_request *req = NULL; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + + evcon = evhttp_connection_base_new(data->base, NULL,"127.0.0.1", port); + tt_assert(evcon); + + req = evhttp_request_new(http_stream_in_done, reply); + evhttp_request_set_chunked_cb(req, http_stream_in_chunk); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, url) == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + if (evbuffer_get_length(reply) != expected_len) { + TT_DIE(("reply length %lu; expected %lu; FAILED (%s)\n", + (unsigned long)evbuffer_get_length(reply), + (unsigned long)expected_len, + (char*)evbuffer_pullup(reply, -1))); + } + + if (memcmp(evbuffer_pullup(reply, -1), expected, expected_len) != 0) { + tt_abort_msg("Memory mismatch"); + } + + test_ok = 1; + end: + if (reply) + evbuffer_free(reply); + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_stream_in_test(void *arg) +{ + http_stream_in_test_(arg, "/chunked", 13 + 18 + 8, + "This is funnybut not hilarious.bwv 1052"); + + http_stream_in_test_(arg, "/test", strlen(BASIC_REQUEST_BODY), + BASIC_REQUEST_BODY); +} + +static void +http_stream_in_cancel_chunk(struct evhttp_request *req, void *arg) +{ + tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_OK); + + end: + evhttp_cancel_request(req); + event_base_loopexit(arg, NULL); +} + +static void +http_stream_in_cancel_done(struct evhttp_request *req, void *arg) +{ + /* should never be called */ + tt_fail_msg("In cancel done"); +} + +static void +http_stream_in_cancel_test(void *arg) +{ + struct basic_test_data *data = arg; + struct evhttp_connection *evcon; + struct evhttp_request *req = NULL; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + req = evhttp_request_new(http_stream_in_cancel_done, data->base); + evhttp_request_set_chunked_cb(req, http_stream_in_cancel_chunk); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/chunked") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + test_ok = 1; + end: + evhttp_connection_free(evcon); + evhttp_free(http); + +} + +static void +http_connection_fail_done(struct evhttp_request *req, void *arg) +{ + struct evhttp_connection *evcon = arg; + struct event_base *base = evhttp_connection_get_base(evcon); + + /* An ENETUNREACH error results in an unrecoverable + * evhttp_connection error (see evhttp_connection_fail_()). The + * connection will be reset, and the user will be notified with a NULL + * req parameter. */ + tt_assert(!req); + + evhttp_connection_free(evcon); + + test_ok = 1; + + end: + event_base_loopexit(base, NULL); +} + +/* Test unrecoverable evhttp_connection errors by generating an ENETUNREACH + * error on connection. */ +static void +http_connection_fail_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bev; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + /* auto detect a port */ + evhttp_free(http); + + bev = create_bev(data->base, -1, ssl, 0); + /* Pick an unroutable address. This administratively scoped multicast + * address should do when working with TCP. */ + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "239.10.20.30", 80); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_connection_fail_done, evcon); + tt_assert(req); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + tt_int_op(test_ok, ==, 1); + + end: + ; +} +static void http_connection_fail_test(void *arg) +{ http_connection_fail_test_impl(arg, 0); } + +static void +http_connection_retry_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), !=, HTTP_OK); + if (evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type") != NULL) { + tt_abort_msg("(content type)\n"); + } + + tt_uint_op(evbuffer_get_length(evhttp_request_get_input_buffer(req)), ==, 0); + + test_ok = 1; + end: + event_base_loopexit(arg,NULL); +} + +struct http_server +{ + ev_uint16_t port; + int ssl; + struct evhttp *http; +}; +static struct event_base *http_make_web_server_base=NULL; +static void +http_make_web_server(evutil_socket_t fd, short what, void *arg) +{ + struct http_server *hs = (struct http_server *)arg; + hs->http = http_setup(&hs->port, http_make_web_server_base, hs->ssl ? HTTP_BIND_SSL : 0); +} + +static void +http_simple_test_impl(void *arg, int ssl, int dirty, const char *uri) +{ + struct basic_test_data *data = arg; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct bufferevent *bev; + struct http_server hs = { 0, ssl, NULL, }; + struct evhttp *http = http_setup(&hs.port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + bev = create_bev(data->base, -1, ssl, 0); +#ifdef EVENT__HAVE_OPENSSL + bufferevent_openssl_set_allow_dirty_shutdown(bev, dirty); +#endif + + evcon = evhttp_connection_base_bufferevent_new( + data->base, NULL, bev, "127.0.0.1", hs.port); + tt_assert(evcon); + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY); + tt_assert(req); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, uri) == -1) + tt_abort_msg("Couldn't make request"); + + event_base_dispatch(data->base); + tt_int_op(test_ok, ==, 1); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} +static void http_simple_test(void *arg) +{ http_simple_test_impl(arg, 0, 0, "/test"); } +static void http_simple_nonconformant_test(void *arg) +{ http_simple_test_impl(arg, 0, 0, "/test nonconformant"); } + +static void +http_connection_retry_test_basic(void *arg, const char *addr, struct evdns_base *dns_base, int ssl) +{ + struct basic_test_data *data = arg; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct timeval tv, tv_start, tv_end; + struct bufferevent *bev; + struct http_server hs = { 0, ssl, NULL, }; + struct evhttp *http = http_setup(&hs.port, data->base, ssl ? HTTP_BIND_SSL : 0); + + exit_base = data->base; + test_ok = 0; + + /* auto detect a port */ + evhttp_free(http); + + bev = create_bev(data->base, -1, ssl, 0); + evcon = evhttp_connection_base_bufferevent_new(data->base, dns_base, bev, addr, hs.port); + tt_assert(evcon); + if (dns_base) + tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_REUSE_CONNECTED_ADDR)); + + evhttp_connection_set_timeout(evcon, 1); + /* also bind to local host */ + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_connection_retry_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(data->base); + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + tt_int_op(tv_end.tv_sec, <, 1); + + tt_int_op(test_ok, ==, 1); + + /* + * now test the same but with retries + */ + test_ok = 0; + /** Shutdown dns server, to test conn_address reusing */ + if (dns_base) + regress_clean_dnsserver(); + + { + const struct timeval tv_timeout = { 0, 500000 }; + const struct timeval tv_retry = { 0, 500000 }; + evhttp_connection_set_timeout_tv(evcon, &tv_timeout); + evhttp_connection_set_initial_retry_tv(evcon, &tv_retry); + } + evhttp_connection_set_retries(evcon, 1); + + req = evhttp_request_new(http_connection_retry_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(data->base); + evutil_gettimeofday(&tv_end, NULL); + + /* fails fast, .5 sec to wait to retry, fails fast again. */ + test_timeval_diff_leq(&tv_start, &tv_end, 500, 200); + + tt_assert(test_ok == 1); + + /* + * now test the same but with retries and give it a web server + * at the end + */ + test_ok = 0; + + evhttp_connection_set_timeout(evcon, 1); + evhttp_connection_set_retries(evcon, 3); + + req = evhttp_request_new(http_dispatcher_test_done, data->base); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, + "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + + /* start up a web server .2 seconds after the connection tried + * to send a request + */ + evutil_timerclear(&tv); + tv.tv_usec = 200000; + http_make_web_server_base = data->base; + event_base_once(data->base, -1, EV_TIMEOUT, http_make_web_server, &hs, &tv); + + evutil_gettimeofday(&tv_start, NULL); + event_base_dispatch(data->base); + evutil_gettimeofday(&tv_end, NULL); + /* We'll wait twice as long as we did last time. */ + test_timeval_diff_leq(&tv_start, &tv_end, 1000, 400); + + tt_int_op(test_ok, ==, 1); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(hs.http); +} + +static void +http_connection_retry_conn_address_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t portnum = 0; + struct evdns_base *dns_base = NULL; + char address[64]; + + tt_assert(regress_dnsserver(data->base, &portnum, search_table)); + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + http_connection_retry_test_basic(arg, "localhost", dns_base, ssl); + + end: + if (dns_base) + evdns_base_free(dns_base, 0); + /** dnsserver will be cleaned in http_connection_retry_test_basic() */ +} +static void http_connection_retry_conn_address_test(void *arg) +{ http_connection_retry_conn_address_test_impl(arg, 0); } + +static void +http_connection_retry_test_impl(void *arg, int ssl) +{ + http_connection_retry_test_basic(arg, "127.0.0.1", NULL, ssl); +} +static void +http_connection_retry_test(void *arg) +{ http_connection_retry_test_impl(arg, 0); } + +static void +http_primitives(void *ptr) +{ + char *escaped = NULL; + struct evhttp *http = NULL; + + escaped = evhttp_htmlescape("<script>"); + tt_assert(escaped); + tt_str_op(escaped, ==, "<script>"); + free(escaped); + + escaped = evhttp_htmlescape("\"\'&"); + tt_assert(escaped); + tt_str_op(escaped, ==, ""'&"); + + http = evhttp_new(NULL); + tt_assert(http); + tt_int_op(evhttp_set_cb(http, "/test", http_basic_cb, http), ==, 0); + tt_int_op(evhttp_set_cb(http, "/test", http_basic_cb, http), ==, -1); + tt_int_op(evhttp_del_cb(http, "/test"), ==, 0); + tt_int_op(evhttp_del_cb(http, "/test"), ==, -1); + tt_int_op(evhttp_set_cb(http, "/test", http_basic_cb, http), ==, 0); + + end: + if (escaped) + free(escaped); + if (http) + evhttp_free(http); +} + +static void +http_multi_line_header_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev= NULL; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + const char *http_start_request; + ev_uint16_t port = 0; + struct evhttp *http = http_setup(&port, data->base, 0); + + exit_base = data->base; + test_ok = 0; + + tt_ptr_op(http, !=, NULL); + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + tt_ptr_op(bev, !=, NULL); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, data->base); + + http_start_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "X-Multi-Extra-WS: libevent \r\n" + "\t\t\t2.1 \r\n" + "X-Multi: aaaaaaaa\r\n" + " a\r\n" + "\tEND\r\n" + "X-Last: last\r\n" + "\r\n"; + + bufferevent_write(bev, http_start_request, strlen(http_start_request)); + found_multi = found_multi2 = 0; + + event_base_dispatch(data->base); + + tt_int_op(found_multi, ==, 1); + tt_int_op(found_multi2, ==, 1); + tt_int_op(test_ok, ==, 4); + end: + if (bev) + bufferevent_free(bev); + if (fd >= 0) + evutil_closesocket(fd); + if (http) + evhttp_free(http); +} + +static void +http_request_bad(struct evhttp_request *req, void *arg) +{ + if (req != NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_base_loopexit(arg, NULL); +} + +static void +http_negative_content_length_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_bad, data->base); + + /* Cause the response to have a negative content-length */ + evhttp_add_header(evhttp_request_get_output_headers(req), "X-Negative", "makeitso"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + + +static void +http_data_length_constraints_test_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_BADREQUEST); +end: + event_base_loopexit(arg, NULL); +} +static void +http_large_entity_test_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_ENTITYTOOLARGE); +end: + event_base_loopexit(arg, NULL); +} +static void +http_expectation_failed_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(evhttp_request_get_response_code(req), ==, HTTP_EXPECTATIONFAILED); +end: + event_base_loopexit(arg, NULL); +} + +static void +http_data_length_constraints_test_impl(void *arg, int read_on_write_error) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + char *long_str = NULL; + const size_t continue_size = 1<<20; + const size_t size = (1<<20) * 3; + void (*cb)(struct evhttp_request *, void *); + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + cb = http_failed_request_done; + if (read_on_write_error) + cb = http_data_length_constraints_test_done; + + tt_assert(continue_size < size); + + long_str = malloc(size); + memset(long_str, 'a', size); + long_str[size - 1] = '\0'; + + TT_BLATHER(("Creating connection to :%i", port)); + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + if (read_on_write_error) + tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_READ_ON_WRITE_ERROR)); + + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + evhttp_set_max_headers_size(http, size - 1); + TT_BLATHER(("Set max header size %zu", size - 1)); + + req = evhttp_request_new(http_data_length_constraints_test_done, data->base); + tt_assert(req); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Longheader", long_str); + TT_BLATHER(("GET /?arg=val")); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + req = evhttp_request_new(http_data_length_constraints_test_done, data->base); + tt_assert(req); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + /* GET /?arg=verylongvalue HTTP/1.1 */ + TT_BLATHER(("GET %s", long_str)); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, long_str) == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + evhttp_set_max_body_size(http, size - 2); + TT_BLATHER(("Set body header size %zu", size - 2)); + + if (read_on_write_error) + cb = http_large_entity_test_done; + req = evhttp_request_new(cb, data->base); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + TT_BLATHER(("POST /")); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + req = evhttp_request_new(http_large_entity_test_done, data->base); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + TT_BLATHER(("POST / (Expect: 100-continue, http_large_entity_test_done)")); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + long_str[continue_size] = '\0'; + + req = evhttp_request_new(http_dispatcher_test_done, data->base); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "100-continue"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + TT_BLATHER(("POST / (Expect: 100-continue, http_dispatcher_test_done)")); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + if (read_on_write_error) + cb = http_expectation_failed_done; + req = evhttp_request_new(cb, data->base); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req), "Expect", "101-continue"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + TT_BLATHER(("POST / (Expect: 101-continue)")); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + test_ok = 1; + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); + if (long_str) + free(long_str); +} +static void http_data_length_constraints_test(void *arg) +{ http_data_length_constraints_test_impl(arg, 0); } +static void http_read_on_write_error_test(void *arg) +{ http_data_length_constraints_test_impl(arg, 1); } + +static void +http_lingering_close_test_impl(void *arg, int lingering) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + char *long_str = NULL; + size_t size = (1<<20) * 3; + void (*cb)(struct evhttp_request *, void *); + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + if (lingering) + tt_assert(!evhttp_set_flags(http, EVHTTP_SERVER_LINGERING_CLOSE)); + evhttp_set_max_body_size(http, size / 2); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + long_str = malloc(size); + memset(long_str, 'a', size); + long_str[size - 1] = '\0'; + + if (lingering) + cb = http_large_entity_test_done; + else + cb = http_failed_request_done; + req = evhttp_request_new(cb, data->base); + tt_assert(req); + evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost"); + evbuffer_add_printf(evhttp_request_get_output_buffer(req), "%s", long_str); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_base_dispatch(data->base); + + test_ok = 1; + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); + if (long_str) + free(long_str); +} +static void http_non_lingering_close_test(void *arg) +{ http_lingering_close_test_impl(arg, 0); } +static void http_lingering_close_test(void *arg) +{ http_lingering_close_test_impl(arg, 1); } + +/* + * Testing client reset of server chunked connections + */ + +struct terminate_state { + struct event_base *base; + struct evhttp_request *req; + struct bufferevent *bev; + evutil_socket_t fd; + int gotclosecb: 1; + int oneshot: 1; +}; + +static void +terminate_chunked_trickle_cb(evutil_socket_t fd, short events, void *arg) +{ + struct terminate_state *state = arg; + struct evbuffer *evb; + + if (!state->req) { + return; + } + + if (evhttp_request_get_connection(state->req) == NULL) { + test_ok = 1; + evhttp_request_free(state->req); + event_base_loopexit(state->base,NULL); + return; + } + + evb = evbuffer_new(); + evbuffer_add_printf(evb, "%p", evb); + evhttp_send_reply_chunk(state->req, evb); + evbuffer_free(evb); + + if (!state->oneshot) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 3000; + EVUTIL_ASSERT(state); + EVUTIL_ASSERT(state->base); + event_base_once(state->base, -1, EV_TIMEOUT, terminate_chunked_trickle_cb, arg, &tv); + } +} + +static void +terminate_chunked_close_cb(struct evhttp_connection *evcon, void *arg) +{ + struct terminate_state *state = arg; + state->gotclosecb = 1; + + /** TODO: though we can do this unconditionally */ + if (state->oneshot) { + evhttp_request_free(state->req); + state->req = NULL; + event_base_loopexit(state->base,NULL); + } +} + +static void +terminate_chunked_cb(struct evhttp_request *req, void *arg) +{ + struct terminate_state *state = arg; + struct timeval tv; + + /* we want to know if this connection closes on us */ + evhttp_connection_set_closecb( + evhttp_request_get_connection(req), + terminate_chunked_close_cb, arg); + + state->req = req; + + evhttp_send_reply_start(req, HTTP_OK, "OK"); + + tv.tv_sec = 0; + tv.tv_usec = 3000; + event_base_once(state->base, -1, EV_TIMEOUT, terminate_chunked_trickle_cb, arg, &tv); +} + +static void +terminate_chunked_client(evutil_socket_t fd, short event, void *arg) +{ + struct terminate_state *state = arg; + bufferevent_free(state->bev); + evutil_closesocket(state->fd); +} + +static void +terminate_readcb(struct bufferevent *bev, void *arg) +{ + /* just drop the data */ + evbuffer_drain(bufferevent_get_input(bev), -1); +} + + +static void +http_terminate_chunked_test_impl(void *arg, int oneshot) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL; + struct timeval tv; + const char *http_request; + ev_uint16_t port = 0; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + struct terminate_state terminate_state; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + + evhttp_del_cb(http, "/test"); + tt_assert(evhttp_set_cb(http, "/test", + terminate_chunked_cb, &terminate_state) == 0); + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = bufferevent_socket_new(data->base, fd, 0); + bufferevent_setcb(bev, terminate_readcb, http_writecb, + http_errorcb, data->base); + + memset(&terminate_state, 0, sizeof(terminate_state)); + terminate_state.base = data->base; + terminate_state.fd = fd; + terminate_state.bev = bev; + terminate_state.gotclosecb = 0; + terminate_state.oneshot = oneshot; + + /* first half of the http request */ + http_request = + "GET /test HTTP/1.1\r\n" + "Host: some\r\n\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + evutil_timerclear(&tv); + tv.tv_usec = 10000; + event_base_once(data->base, -1, EV_TIMEOUT, terminate_chunked_client, &terminate_state, + &tv); + + event_base_dispatch(data->base); + + if (terminate_state.gotclosecb == 0) + test_ok = 0; + + end: + if (fd >= 0) + evutil_closesocket(fd); + if (http) + evhttp_free(http); +} +static void +http_terminate_chunked_test(void *arg) +{ + http_terminate_chunked_test_impl(arg, 0); +} +static void +http_terminate_chunked_oneshot_test(void *arg) +{ + http_terminate_chunked_test_impl(arg, 1); +} + +static struct regress_dns_server_table ipv6_search_table[] = { + { "localhost", "AAAA", "::1", 0, 0 }, + { NULL, NULL, NULL, 0, 0 } +}; + +static void +http_ipv6_for_domain_test_impl(void *arg, int family) +{ + struct basic_test_data *data = arg; + struct evdns_base *dns_base = NULL; + ev_uint16_t portnum = 0; + char address[64]; + + tt_assert(regress_dnsserver(data->base, &portnum, ipv6_search_table)); + + dns_base = evdns_base_new(data->base, 0/* init name servers */); + tt_assert(dns_base); + + /* Add ourself as the only nameserver, and make sure we really are + * the only nameserver. */ + evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum); + evdns_base_nameserver_ip_add(dns_base, address); + + http_connection_test_(arg, 0 /* not persistent */, "localhost", dns_base, + 1 /* ipv6 */, family, 0); + + end: + if (dns_base) + evdns_base_free(dns_base, 0); + regress_clean_dnsserver(); +} +static void +http_ipv6_for_domain_test(void *arg) +{ + http_ipv6_for_domain_test_impl(arg, AF_UNSPEC); +} + +static void +http_request_get_addr_on_close(struct evhttp_connection *evcon, void *arg) +{ + const struct sockaddr *storage; + char addrbuf[128]; + char local[] = "127.0.0.1:"; + + test_ok = 0; + tt_assert(evcon); + + storage = evhttp_connection_get_addr(evcon); + tt_assert(storage); + + evutil_format_sockaddr_port_((struct sockaddr *)storage, addrbuf, sizeof(addrbuf)); + tt_assert(!strncmp(addrbuf, local, sizeof(local) - 1)); + + test_ok = 1; + return; + +end: + test_ok = 0; +} + +static void +http_get_addr_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + exit_base = data->base; + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + evhttp_connection_set_closecb(evcon, http_request_get_addr_on_close, arg); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + + req = evhttp_request_new(http_request_done, (void *)BASIC_REQUEST_BODY); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + + event_base_dispatch(data->base); + + http_request_get_addr_on_close(evcon, NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + +static void +http_set_family_test(void *arg) +{ + http_connection_test_(arg, 0, "127.0.0.1", NULL, 0, AF_UNSPEC, 0); +} +static void +http_set_family_ipv4_test(void *arg) +{ + http_connection_test_(arg, 0, "127.0.0.1", NULL, 0, AF_INET, 0); +} +static void +http_set_family_ipv6_test(void *arg) +{ + http_ipv6_for_domain_test_impl(arg, AF_INET6); +} + +static void +http_write_during_read(evutil_socket_t fd, short what, void *arg) +{ + struct bufferevent *bev = arg; + struct timeval tv; + + bufferevent_write(bev, "foobar", strlen("foobar")); + + evutil_timerclear(&tv); + tv.tv_sec = 1; + event_base_loopexit(exit_base, &tv); +} +static void +http_write_during_read_test_impl(void *arg, int ssl) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct bufferevent *bev = NULL; + struct timeval tv; + evutil_socket_t fd; + const char *http_request; + struct evhttp *http = http_setup(&port, data->base, ssl ? HTTP_BIND_SSL : 0); + + test_ok = 0; + exit_base = data->base; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + bev = create_bev(data->base, fd, 0, 0); + bufferevent_setcb(bev, NULL, NULL, NULL, data->base); + bufferevent_disable(bev, EV_READ); + + http_request = + "GET /large HTTP/1.1\r\n" + "Host: somehost\r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + evutil_timerclear(&tv); + tv.tv_usec = 10000; + event_base_once(data->base, -1, EV_TIMEOUT, http_write_during_read, bev, &tv); + + event_base_dispatch(data->base); + +end: + if (bev) + bufferevent_free(bev); + if (http) + evhttp_free(http); +} +static void http_write_during_read_test(void *arg) +{ http_write_during_read_test_impl(arg, 0); } + +static void +http_request_own_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct evhttp *http = http_setup(&port, data->base, 0); + + test_ok = 0; + exit_base = data->base; + + evhttp_free(http); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + req = evhttp_request_new(http_request_no_action_done, NULL); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("Couldn't make request"); + } + evhttp_request_own(req); + + event_base_dispatch(data->base); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (req) + evhttp_request_free(req); + + test_ok = 1; +} + +static void http_run_bev_request(struct event_base *base, int port, + const char *fmt, ...) +{ + struct bufferevent *bev = NULL; + va_list ap; + evutil_socket_t fd; + struct evbuffer *out; + + fd = http_connect("127.0.0.1", port); + tt_assert(fd != EVUTIL_INVALID_SOCKET); + + /* Stupid thing to send a request */ + bev = create_bev(base, fd, 0, 0); + bufferevent_setcb(bev, http_readcb, http_writecb, + http_errorcb, base); + out = bufferevent_get_output(bev); + + va_start(ap, fmt); + evbuffer_add_vprintf(out, fmt, ap); + va_end(ap); + + event_base_dispatch(base); + +end: + if (bev) + bufferevent_free(bev); +} +static void +http_request_extra_body_test(void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev = NULL; + ev_uint16_t port = 0; + int i; + struct evhttp *http = + http_setup_gencb(&port, data->base, 0, http_timeout_cb, NULL); + struct evbuffer *body = NULL; + + exit_base = data->base; + test_ok = 0; + + body = evbuffer_new(); + for (i = 0; i < 10000; ++i) + evbuffer_add_printf(body, "this is the body that HEAD should not have"); + + http_run_bev_request(data->base, port, + "HEAD /timeout HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "Content-Length: %i\r\n" + "\r\n%s", + (int)evbuffer_get_length(body), + evbuffer_pullup(body, -1) + ); + tt_assert(test_ok == -2); + + http_run_bev_request(data->base, port, + "HEAD /__gencb__ HTTP/1.1\r\n" + "Host: somehost\r\n" + "Connection: close\r\n" + "Content-Length: %i\r\n" + "\r\n%s", + (int)evbuffer_get_length(body), + evbuffer_pullup(body, -1) + ); + tt_assert(test_ok == -2); + + end: + evhttp_free(http); + if (bev) + bufferevent_free(bev); + if (body) + evbuffer_free(body); +} + +#define HTTP_LEGACY(name) \ + { #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \ + http_##name##_test } + +#define HTTP_CAST_ARG(a) ((void *)(a)) +#define HTTP_OFF_N(title, name, arg) \ + { #title, http_##name##_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, HTTP_CAST_ARG(arg) } +#define HTTP_RET_N(title, name, test_opts, arg) \ + { #title, http_##name##_test, TT_ISOLATED|TT_RETRIABLE|test_opts, &basic_setup, HTTP_CAST_ARG(arg) } +#define HTTP_N(title, name, test_opts, arg) \ + { #title, http_##name##_test, TT_ISOLATED|test_opts, &basic_setup, HTTP_CAST_ARG(arg) } +#define HTTP(name) HTTP_N(name, name, 0, NULL) +#define HTTPS(name) \ + { "https_" #name, https_##name##_test, TT_ISOLATED, &basic_setup, NULL } + +#ifdef EVENT__HAVE_OPENSSL +static void https_basic_test(void *arg) +{ http_basic_test_impl(arg, 1, "GET /test HTTP/1.1"); } +static void https_filter_basic_test(void *arg) +{ http_basic_test_impl(arg, 1 | HTTP_SSL_FILTER, "GET /test HTTP/1.1"); } +static void https_incomplete_test(void *arg) +{ http_incomplete_test_(arg, 0, 1); } +static void https_incomplete_timeout_test(void *arg) +{ http_incomplete_test_(arg, 1, 1); } +static void https_simple_test(void *arg) +{ http_simple_test_impl(arg, 1, 0, "/test"); } +static void https_simple_dirty_test(void *arg) +{ http_simple_test_impl(arg, 1, 1, "/test"); } +static void https_connection_retry_conn_address_test(void *arg) +{ http_connection_retry_conn_address_test_impl(arg, 1); } +static void https_connection_retry_test(void *arg) +{ http_connection_retry_test_impl(arg, 1); } +static void https_chunk_out_test(void *arg) +{ http_chunk_out_test_impl(arg, 1); } +static void https_filter_chunk_out_test(void *arg) +{ http_chunk_out_test_impl(arg, 1 | HTTP_SSL_FILTER); } +static void https_stream_out_test(void *arg) +{ http_stream_out_test_impl(arg, 1); } +static void https_connection_fail_test(void *arg) +{ http_connection_fail_test_impl(arg, 1); } +static void https_write_during_read_test(void *arg) +{ http_write_during_read_test_impl(arg, 1); } +static void https_connection_test(void *arg) +{ http_connection_test_(arg, 0, "127.0.0.1", NULL, 0, AF_UNSPEC, 1); } +static void https_persist_connection_test(void *arg) +{ http_connection_test_(arg, 1, "127.0.0.1", NULL, 0, AF_UNSPEC, 1); } +#endif + +struct testcase_t http_testcases[] = { + { "primitives", http_primitives, 0, NULL, NULL }, + { "base", http_base_test, TT_FORK, NULL, NULL }, + { "bad_headers", http_bad_header_test, 0, NULL, NULL }, + { "parse_query", http_parse_query_test, 0, NULL, NULL }, + { "parse_query_str", http_parse_query_str_test, 0, NULL, NULL }, + { "parse_uri", http_parse_uri_test, 0, NULL, NULL }, + { "parse_uri_nc", http_parse_uri_test, 0, &basic_setup, (void*)"nc" }, + { "uriencode", http_uriencode_test, 0, NULL, NULL }, + HTTP(basic), + HTTP(basic_trailing_space), + HTTP(simple), + HTTP(simple_nonconformant), + + HTTP_N(cancel, cancel, 0, BASIC), + HTTP_RET_N(cancel_by_host, cancel, 0, BY_HOST), + HTTP_RET_N(cancel_by_host_inactive_server, cancel, TT_NO_LOGS, BY_HOST | INACTIVE_SERVER), + HTTP_RET_N(cancel_by_host_no_ns, cancel, TT_NO_LOGS, BY_HOST | NO_NS), + HTTP_N(cancel_inactive_server, cancel, 0, INACTIVE_SERVER), + HTTP_N(cancel_by_host_no_ns_inactive_server, cancel, TT_NO_LOGS, BY_HOST | NO_NS | INACTIVE_SERVER), + HTTP_OFF_N(cancel_by_host_server_timeout, cancel, BY_HOST | INACTIVE_SERVER | SERVER_TIMEOUT), + HTTP_OFF_N(cancel_server_timeout, cancel, INACTIVE_SERVER | SERVER_TIMEOUT), + HTTP_OFF_N(cancel_by_host_no_ns_server_timeout, cancel, BY_HOST | NO_NS | INACTIVE_SERVER | SERVER_TIMEOUT), + HTTP_OFF_N(cancel_by_host_ns_timeout_server_timeout, cancel, BY_HOST | NO_NS | NS_TIMEOUT | INACTIVE_SERVER | SERVER_TIMEOUT), + HTTP_RET_N(cancel_by_host_ns_timeout, cancel, TT_NO_LOGS, BY_HOST | NO_NS | NS_TIMEOUT), + HTTP_RET_N(cancel_by_host_ns_timeout_inactive_server, cancel, TT_NO_LOGS, BY_HOST | NO_NS | NS_TIMEOUT | INACTIVE_SERVER), + + HTTP(virtual_host), + HTTP(post), + HTTP(put), + HTTP(delete), + HTTP(allowed_methods), + HTTP(failure), + HTTP(connection), + HTTP(persist_connection), + HTTP(autofree_connection), + HTTP(connection_async), + HTTP(close_detection), + HTTP(close_detection_delay), + HTTP(bad_request), + HTTP(incomplete), + HTTP(incomplete_timeout), + HTTP(terminate_chunked), + HTTP(terminate_chunked_oneshot), + HTTP(on_complete), + + HTTP(highport), + HTTP(dispatcher), + HTTP(multi_line_header), + HTTP(negative_content_length), + HTTP(chunk_out), + HTTP(stream_out), + + HTTP(stream_in), + HTTP(stream_in_cancel), + + HTTP(connection_fail), + { "connection_retry", http_connection_retry_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, + { "connection_retry_conn_address", http_connection_retry_conn_address_test, + TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, + + HTTP(data_length_constraints), + HTTP(read_on_write_error), + HTTP(non_lingering_close), + HTTP(lingering_close), + + HTTP(ipv6_for_domain), + HTTP(get_addr), + + HTTP(set_family), + HTTP(set_family_ipv4), + HTTP(set_family_ipv6), + + HTTP(write_during_read), + HTTP(request_own), + + HTTP(request_extra_body), + +#ifdef EVENT__HAVE_OPENSSL + HTTPS(basic), + HTTPS(filter_basic), + HTTPS(simple), + HTTPS(simple_dirty), + HTTPS(incomplete), + HTTPS(incomplete_timeout), + { "https_connection_retry", https_connection_retry_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, + { "https_connection_retry_conn_address", https_connection_retry_conn_address_test, + TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, + HTTPS(chunk_out), + HTTPS(filter_chunk_out), + HTTPS(stream_out), + HTTPS(connection_fail), + HTTPS(write_during_read), + HTTPS(connection), + HTTPS(persist_connection), +#endif + + END_OF_TESTCASES +}; + +struct testcase_t http_iocp_testcases[] = { + { "simple", http_simple_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL }, +#ifdef EVENT__HAVE_OPENSSL + { "https_simple", https_simple_test, TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL }, +#endif + END_OF_TESTCASES +}; diff --git a/libevent/test/regress_iocp.c b/libevent/test/regress_iocp.c new file mode 100644 index 0000000..17b3852 --- /dev/null +++ b/libevent/test/regress_iocp.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> +#include "event2/event.h" +#include "event2/thread.h" +#include "event2/buffer.h" +#include "event2/buffer_compat.h" +#include "event2/bufferevent.h" + +#include <winsock2.h> +#include <ws2tcpip.h> + +#include "regress.h" +#include "tinytest.h" +#include "tinytest_macros.h" + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <winsock2.h> +#undef WIN32_LEAN_AND_MEAN + +#include "iocp-internal.h" +#include "evbuffer-internal.h" +#include "evthread-internal.h" + +/* FIXME remove these ones */ +#include <sys/queue.h> +#include "event2/event_struct.h" +#include "event-internal.h" + +#define MAX_CALLS 16 + +static void *count_lock = NULL, *count_cond = NULL; +static int count = 0; + +static void +count_init(void) +{ + EVTHREAD_ALLOC_LOCK(count_lock, 0); + EVTHREAD_ALLOC_COND(count_cond); + + tt_assert(count_lock); + tt_assert(count_cond); + +end: + ; +} + +static void +count_free(void) +{ + EVTHREAD_FREE_LOCK(count_lock, 0); + EVTHREAD_FREE_COND(count_cond); +} + +static void +count_incr(void) +{ + EVLOCK_LOCK(count_lock, 0); + count++; + EVTHREAD_COND_BROADCAST(count_cond); + EVLOCK_UNLOCK(count_lock, 0); +} + +static int +count_wait_for(int i, int ms) +{ + struct timeval tv; + DWORD elapsed; + int rv = -1; + + EVLOCK_LOCK(count_lock, 0); + while (ms > 0 && count != i) { + tv.tv_sec = 0; + tv.tv_usec = ms * 1000; + elapsed = GetTickCount(); + EVTHREAD_COND_WAIT_TIMED(count_cond, count_lock, &tv); + elapsed = GetTickCount() - elapsed; + ms -= elapsed; + } + if (count == i) + rv = 0; + EVLOCK_UNLOCK(count_lock, 0); + + return rv; +} + +struct dummy_overlapped { + struct event_overlapped eo; + void *lock; + int call_count; + uintptr_t keys[MAX_CALLS]; + ev_ssize_t sizes[MAX_CALLS]; +}; + +static void +dummy_cb(struct event_overlapped *o, uintptr_t key, ev_ssize_t n, int ok) +{ + struct dummy_overlapped *d_o = + EVUTIL_UPCAST(o, struct dummy_overlapped, eo); + + EVLOCK_LOCK(d_o->lock, 0); + if (d_o->call_count < MAX_CALLS) { + d_o->keys[d_o->call_count] = key; + d_o->sizes[d_o->call_count] = n; + } + d_o->call_count++; + EVLOCK_UNLOCK(d_o->lock, 0); + + count_incr(); +} + +static int +pair_is_in(struct dummy_overlapped *o, uintptr_t key, ev_ssize_t n) +{ + int i; + int result = 0; + EVLOCK_LOCK(o->lock, 0); + for (i=0; i < o->call_count; ++i) { + if (o->keys[i] == key && o->sizes[i] == n) { + result = 1; + break; + } + } + EVLOCK_UNLOCK(o->lock, 0); + return result; +} + +static void +test_iocp_port(void *ptr) +{ + struct event_iocp_port *port = NULL; + struct dummy_overlapped o1, o2; + + memset(&o1, 0, sizeof(o1)); + memset(&o2, 0, sizeof(o2)); + + count_init(); + EVTHREAD_ALLOC_LOCK(o1.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + EVTHREAD_ALLOC_LOCK(o2.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + + tt_assert(o1.lock); + tt_assert(o2.lock); + + event_overlapped_init_(&o1.eo, dummy_cb); + event_overlapped_init_(&o2.eo, dummy_cb); + + port = event_iocp_port_launch_(0); + tt_assert(port); + + tt_assert(!event_iocp_activate_overlapped_(port, &o1.eo, 10, 100)); + tt_assert(!event_iocp_activate_overlapped_(port, &o2.eo, 20, 200)); + + tt_assert(!event_iocp_activate_overlapped_(port, &o1.eo, 11, 101)); + tt_assert(!event_iocp_activate_overlapped_(port, &o2.eo, 21, 201)); + + tt_assert(!event_iocp_activate_overlapped_(port, &o1.eo, 12, 102)); + tt_assert(!event_iocp_activate_overlapped_(port, &o2.eo, 22, 202)); + + tt_assert(!event_iocp_activate_overlapped_(port, &o1.eo, 13, 103)); + tt_assert(!event_iocp_activate_overlapped_(port, &o2.eo, 23, 203)); + + tt_int_op(count_wait_for(8, 2000), ==, 0); + + tt_want(!event_iocp_shutdown_(port, 2000)); + + tt_int_op(o1.call_count, ==, 4); + tt_int_op(o2.call_count, ==, 4); + + tt_want(pair_is_in(&o1, 10, 100)); + tt_want(pair_is_in(&o1, 11, 101)); + tt_want(pair_is_in(&o1, 12, 102)); + tt_want(pair_is_in(&o1, 13, 103)); + + tt_want(pair_is_in(&o2, 20, 200)); + tt_want(pair_is_in(&o2, 21, 201)); + tt_want(pair_is_in(&o2, 22, 202)); + tt_want(pair_is_in(&o2, 23, 203)); + +end: + EVTHREAD_FREE_LOCK(o1.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + EVTHREAD_FREE_LOCK(o2.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + count_free(); +} + +static struct evbuffer *rbuf = NULL, *wbuf = NULL; + +static void +read_complete(struct event_overlapped *eo, uintptr_t key, + ev_ssize_t nbytes, int ok) +{ + tt_assert(ok); + evbuffer_commit_read_(rbuf, nbytes); + count_incr(); +end: + ; +} + +static void +write_complete(struct event_overlapped *eo, uintptr_t key, + ev_ssize_t nbytes, int ok) +{ + tt_assert(ok); + evbuffer_commit_write_(wbuf, nbytes); + count_incr(); +end: + ; +} + +static void +test_iocp_evbuffer(void *ptr) +{ + struct event_overlapped rol, wol; + struct basic_test_data *data = ptr; + struct event_iocp_port *port = NULL; + struct evbuffer *buf=NULL; + struct evbuffer_chain *chain; + char junk[1024]; + int i; + + count_init(); + event_overlapped_init_(&rol, read_complete); + event_overlapped_init_(&wol, write_complete); + + for (i = 0; i < (int)sizeof(junk); ++i) + junk[i] = (char)(i); + + rbuf = evbuffer_overlapped_new_(data->pair[0]); + wbuf = evbuffer_overlapped_new_(data->pair[1]); + evbuffer_enable_locking(rbuf, NULL); + evbuffer_enable_locking(wbuf, NULL); + + port = event_iocp_port_launch_(0); + tt_assert(port); + tt_assert(rbuf); + tt_assert(wbuf); + + tt_assert(!event_iocp_port_associate_(port, data->pair[0], 100)); + tt_assert(!event_iocp_port_associate_(port, data->pair[1], 100)); + + for (i=0;i<10;++i) + evbuffer_add(wbuf, junk, sizeof(junk)); + + buf = evbuffer_new(); + tt_assert(buf != NULL); + evbuffer_add(rbuf, junk, sizeof(junk)); + tt_assert(!evbuffer_launch_read_(rbuf, 2048, &rol)); + evbuffer_add_buffer(buf, rbuf); + tt_int_op(evbuffer_get_length(buf), ==, sizeof(junk)); + for (chain = buf->first; chain; chain = chain->next) + tt_int_op(chain->flags & EVBUFFER_MEM_PINNED_ANY, ==, 0); + tt_assert(!evbuffer_get_length(rbuf)); + tt_assert(!evbuffer_launch_write_(wbuf, 512, &wol)); + + tt_int_op(count_wait_for(2, 2000), ==, 0); + + tt_int_op(evbuffer_get_length(rbuf),==,512); + + /* FIXME Actually test some stuff here. */ + + tt_want(!event_iocp_shutdown_(port, 2000)); +end: + count_free(); + evbuffer_free(rbuf); + evbuffer_free(wbuf); + if (buf) evbuffer_free(buf); +} + +static int got_readcb = 0; + +static void +async_readcb(struct bufferevent *bev, void *arg) +{ + /* Disabling read should cause the loop to quit */ + bufferevent_disable(bev, EV_READ); + got_readcb++; +} + +static void +test_iocp_bufferevent_async(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event_iocp_port *port = NULL; + struct bufferevent *bea1=NULL, *bea2=NULL; + char buf[128]; + size_t n; + + event_base_start_iocp_(data->base, 0); + port = event_base_get_iocp_(data->base); + tt_assert(port); + + bea1 = bufferevent_async_new_(data->base, data->pair[0], + BEV_OPT_DEFER_CALLBACKS); + bea2 = bufferevent_async_new_(data->base, data->pair[1], + BEV_OPT_DEFER_CALLBACKS); + tt_assert(bea1); + tt_assert(bea2); + + bufferevent_setcb(bea2, async_readcb, NULL, NULL, NULL); + bufferevent_enable(bea1, EV_WRITE); + bufferevent_enable(bea2, EV_READ); + + bufferevent_write(bea1, "Hello world", strlen("Hello world")+1); + + event_base_dispatch(data->base); + + tt_int_op(got_readcb, ==, 1); + n = bufferevent_read(bea2, buf, sizeof(buf)-1); + buf[n]='\0'; + tt_str_op(buf, ==, "Hello world"); + +end: + bufferevent_free(bea1); + bufferevent_free(bea2); +} + + +struct testcase_t iocp_testcases[] = { + { "port", test_iocp_port, TT_FORK|TT_NEED_THREADS, &basic_setup, NULL }, + { "evbuffer", test_iocp_evbuffer, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_THREADS, + &basic_setup, NULL }, + { "bufferevent_async", test_iocp_bufferevent_async, + TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_THREADS|TT_NEED_BASE, + &basic_setup, NULL }, + END_OF_TESTCASES +}; diff --git a/libevent/test/regress_listener.c b/libevent/test/regress_listener.c new file mode 100644 index 0000000..871da4c --- /dev/null +++ b/libevent/test/regress_listener.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include <sys/types.h> + +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +# ifdef _XOPEN_SOURCE_EXTENDED +# include <arpa/inet.h> +# endif +#include <unistd.h> +#endif +#ifdef EVENT__HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include <string.h> + +#include "event2/listener.h" +#include "event2/event.h" +#include "event2/util.h" + +#include "regress.h" +#include "tinytest.h" +#include "tinytest_macros.h" + +static void +acceptcb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *addr, int socklen, void *arg) +{ + int *ptr = arg; + --*ptr; + TT_BLATHER(("Got one for %p", ptr)); + evutil_closesocket(fd); + + if (! *ptr) + evconnlistener_disable(listener); +} + +static void +regress_pick_a_port(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evconnlistener *listener1 = NULL, *listener2 = NULL; + struct sockaddr_in sin; + int count1 = 2, count2 = 1; + struct sockaddr_storage ss1, ss2; + struct sockaddr_in *sin1, *sin2; + ev_socklen_t slen1 = sizeof(ss1), slen2 = sizeof(ss2); + unsigned int flags = + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC; + evutil_socket_t fd1, fd2, fd3; + + fd1 = fd2 = fd3 = EVUTIL_INVALID_SOCKET; + + if (data->setup_data && strstr((char*)data->setup_data, "ts")) { + flags |= LEV_OPT_THREADSAFE; + } + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sin.sin_port = 0; /* "You pick!" */ + + listener1 = evconnlistener_new_bind(base, acceptcb, &count1, + flags, -1, (struct sockaddr *)&sin, sizeof(sin)); + tt_assert(listener1); + listener2 = evconnlistener_new_bind(base, acceptcb, &count2, + flags, -1, (struct sockaddr *)&sin, sizeof(sin)); + tt_assert(listener2); + + tt_assert(evconnlistener_get_fd(listener1) != EVUTIL_INVALID_SOCKET); + tt_assert(evconnlistener_get_fd(listener2) != EVUTIL_INVALID_SOCKET); + tt_assert(getsockname(evconnlistener_get_fd(listener1), + (struct sockaddr*)&ss1, &slen1) == 0); + tt_assert(getsockname(evconnlistener_get_fd(listener2), + (struct sockaddr*)&ss2, &slen2) == 0); + tt_int_op(ss1.ss_family, ==, AF_INET); + tt_int_op(ss2.ss_family, ==, AF_INET); + + sin1 = (struct sockaddr_in*)&ss1; + sin2 = (struct sockaddr_in*)&ss2; + tt_int_op(ntohl(sin1->sin_addr.s_addr), ==, 0x7f000001); + tt_int_op(ntohl(sin2->sin_addr.s_addr), ==, 0x7f000001); + tt_int_op(sin1->sin_port, !=, sin2->sin_port); + + tt_ptr_op(evconnlistener_get_base(listener1), ==, base); + tt_ptr_op(evconnlistener_get_base(listener2), ==, base); + + fd1 = fd2 = fd3 = EVUTIL_INVALID_SOCKET; + evutil_socket_connect_(&fd1, (struct sockaddr*)&ss1, slen1); + evutil_socket_connect_(&fd2, (struct sockaddr*)&ss1, slen1); + evutil_socket_connect_(&fd3, (struct sockaddr*)&ss2, slen2); + +#ifdef _WIN32 + Sleep(100); /* XXXX this is a stupid stopgap. */ +#endif + event_base_dispatch(base); + + tt_int_op(count1, ==, 0); + tt_int_op(count2, ==, 0); + +end: + if (fd1>=0) + EVUTIL_CLOSESOCKET(fd1); + if (fd2>=0) + EVUTIL_CLOSESOCKET(fd2); + if (fd3>=0) + EVUTIL_CLOSESOCKET(fd3); + if (listener1) + evconnlistener_free(listener1); + if (listener2) + evconnlistener_free(listener2); +} + +static void +errorcb(struct evconnlistener *lis, void *data_) +{ + int *data = data_; + *data = 1000; + evconnlistener_disable(lis); +} + +static void +regress_listener_error(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evconnlistener *listener = NULL; + int count = 1; + unsigned int flags = LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE; + + if (data->setup_data && strstr((char*)data->setup_data, "ts")) { + flags |= LEV_OPT_THREADSAFE; + } + + /* send, so that pair[0] will look 'readable'*/ + tt_int_op(send(data->pair[1], "hello", 5, 0), >, 0); + + /* Start a listener with a bogus socket. */ + listener = evconnlistener_new(base, acceptcb, &count, + flags, 0, + data->pair[0]); + tt_assert(listener); + + evconnlistener_set_error_cb(listener, errorcb); + + tt_assert(listener); + + event_base_dispatch(base); + tt_int_op(count,==,1000); /* set by error cb */ + +end: + if (listener) + evconnlistener_free(listener); +} + +static void +acceptcb_free(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *addr, int socklen, void *arg) +{ + int *ptr = arg; + --*ptr; + TT_BLATHER(("Got one for %p", ptr)); + evutil_closesocket(fd); + + if (! *ptr) + evconnlistener_free(listener); +} +static void +regress_listener_close_accepted_fd(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evconnlistener *listener = NULL; + struct sockaddr_in sin; + struct sockaddr_storage ss; + ev_socklen_t slen = sizeof(ss); + int count = 1; + unsigned int flags = LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE; + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sin.sin_port = 0; /* "You pick!" */ + + /* Start a listener with a bogus socket. */ + listener = evconnlistener_new_bind(base, acceptcb_free, &count, + flags, -1, (struct sockaddr *)&sin, sizeof(sin)); + tt_assert(listener); + + tt_assert(getsockname(evconnlistener_get_fd(listener), + (struct sockaddr*)&ss, &slen) == 0); + evutil_socket_connect_(&fd, (struct sockaddr*)&ss, slen); + + event_base_dispatch(base); + +end: + ; +} + +static void +regress_listener_immediate_close(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evconnlistener *listener = NULL; + struct sockaddr_in sin; + struct sockaddr_storage ss; + ev_socklen_t slen = sizeof(ss); + int count = 1; + unsigned int flags = LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE; + evutil_socket_t fd1 = EVUTIL_INVALID_SOCKET, fd2 = EVUTIL_INVALID_SOCKET; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sin.sin_port = 0; /* "You pick!" */ + + /* Start a listener with a bogus socket. */ + listener = evconnlistener_new_bind(base, acceptcb, &count, + flags, -1, (struct sockaddr *)&sin, sizeof(sin)); + tt_assert(listener); + + tt_assert(getsockname(evconnlistener_get_fd(listener), + (struct sockaddr*)&ss, &slen) == 0); + + evutil_socket_connect_(&fd1, (struct sockaddr*)&ss, slen); + evutil_socket_connect_(&fd2, (struct sockaddr*)&ss, slen); + + event_base_dispatch(base); + + tt_int_op(count, ==, 0); + +end: + if (listener) + evconnlistener_free(listener); +} + +#ifdef EVENT__HAVE_SETRLIMIT +static void +regress_listener_error_unlock(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evconnlistener *listener = NULL; + unsigned int flags = + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_THREADSAFE; + + tt_int_op(send(data->pair[1], "hello", 5, 0), >, 0); + + /* Start a listener with a bogus socket. */ + listener = evconnlistener_new(base, acceptcb, NULL, flags, 0, data->pair[0]); + tt_assert(listener); + + /** accept() must errored out with EMFILE */ + { + struct rlimit rl; + rl.rlim_cur = rl.rlim_max = data->pair[1]; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + TT_DIE(("Can't change RLIMIT_NOFILE")); + } + } + + event_base_loop(base, EVLOOP_ONCE); + + /** with lock debugging, can fail on lock->count assertion */ + +end: + if (listener) + evconnlistener_free(listener); +} +#endif + +struct testcase_t listener_testcases[] = { + + { "randport", regress_pick_a_port, TT_FORK|TT_NEED_BASE, + &basic_setup, NULL}, + + { "randport_ts", regress_pick_a_port, TT_FORK|TT_NEED_BASE, + &basic_setup, (char*)"ts"}, + +#ifdef EVENT__HAVE_SETRLIMIT + { "error_unlock", regress_listener_error_unlock, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR|TT_NO_LOGS, + &basic_setup, NULL}, +#endif + + { "error", regress_listener_error, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR, + &basic_setup, NULL}, + + { "error_ts", regress_listener_error, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR, + &basic_setup, (char*)"ts"}, + + { "close_accepted_fd", regress_listener_close_accepted_fd, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL, }, + + { "immediate_close", regress_listener_immediate_close, + TT_FORK|TT_NEED_BASE, &basic_setup, NULL, }, + + END_OF_TESTCASES, +}; + +struct testcase_t listener_iocp_testcases[] = { + { "randport", regress_pick_a_port, + TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, + &basic_setup, NULL}, + + { "error", regress_listener_error, + TT_FORK|TT_NEED_BASE|TT_NEED_SOCKETPAIR|TT_ENABLE_IOCP, + &basic_setup, NULL}, + + END_OF_TESTCASES, +}; diff --git a/libevent/test/regress_main.c b/libevent/test/regress_main.c new file mode 100644 index 0000000..2665612 --- /dev/null +++ b/libevent/test/regress_main.c @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <io.h> +#include <fcntl.h> +#endif + +/* move_pthread_to_realtime_scheduling_class() */ +#ifdef EVENT__HAVE_MACH_MACH_H +#include <mach/mach.h> +#endif +#ifdef EVENT__HAVE_MACH_MACH_TIME_H +#include <mach/mach_time.h> +#endif + +#if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) +#if (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 && \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070) +#define FORK_BREAKS_GCOV +#include <vproc.h> +#endif +#endif + +#include "event2/event-config.h" + +#if 0 +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#include <signal.h> +#include <errno.h> +#endif + +#include <sys/types.h> +#ifdef EVENT__HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifndef _WIN32 +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <netdb.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include "event2/util.h" +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/dns.h" +#include "event2/dns_compat.h" +#include "event2/thread.h" + +#include "event2/event-config.h" +#include "regress.h" +#include "regress_thread.h" +#include "tinytest.h" +#include "tinytest_macros.h" +#include "../iocp-internal.h" +#include "../event-internal.h" +#include "../evthread-internal.h" + +struct evutil_weakrand_state test_weakrand_state; + +long +timeval_msec_diff(const struct timeval *start, const struct timeval *end) +{ + long ms = end->tv_sec - start->tv_sec; + ms *= 1000; + ms += ((end->tv_usec - start->tv_usec)+500) / 1000; + return ms; +} + +/* ============================================================ */ +/* Code to wrap up old legacy test cases that used setup() and cleanup(). + * + * Not all of the tests designated "legacy" are ones that used setup() and + * cleanup(), of course. A test is legacy it it uses setup()/cleanup(), OR + * if it wants to find its event base/socketpair in global variables (ugh), + * OR if it wants to communicate success/failure through test_ok. + */ + +/* This is set to true if we're inside a legacy test wrapper. It lets the + setup() and cleanup() functions in regress.c know they're not needed. + */ +int in_legacy_test_wrapper = 0; + +static void dnslogcb(int w, const char *m) +{ + TT_BLATHER(("%s", m)); +} + +/* creates a temporary file with the data in it. If *filename_out gets set, + * the caller should try to unlink it. */ +int +regress_make_tmpfile(const void *data, size_t datalen, char **filename_out) +{ +#ifndef _WIN32 + char tmpfilename[32]; + int fd; + *filename_out = NULL; + strcpy(tmpfilename, "/tmp/eventtmp.XXXXXX"); +#ifdef EVENT__HAVE_UMASK + umask(0077); +#endif + fd = mkstemp(tmpfilename); + if (fd == -1) + return (-1); + if (write(fd, data, datalen) != (int)datalen) { + close(fd); + return (-1); + } + lseek(fd, 0, SEEK_SET); + /* remove it from the file system */ + unlink(tmpfilename); + return (fd); +#else + /* XXXX actually delete the file later */ + char tmpfilepath[MAX_PATH]; + char tmpfilename[MAX_PATH]; + DWORD r, written; + int tries = 16; + HANDLE h; + r = GetTempPathA(MAX_PATH, tmpfilepath); + if (r > MAX_PATH || r == 0) + return (-1); + for (; tries > 0; --tries) { + r = GetTempFileNameA(tmpfilepath, "LIBEVENT", 0, tmpfilename); + if (r == 0) + return (-1); + h = CreateFileA(tmpfilename, GENERIC_READ|GENERIC_WRITE, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (h != INVALID_HANDLE_VALUE) + break; + } + if (tries == 0) + return (-1); + written = 0; + *filename_out = strdup(tmpfilename); + WriteFile(h, data, (DWORD)datalen, &written, NULL); + /* Closing the fd returned by this function will indeed close h. */ + return _open_osfhandle((intptr_t)h,_O_RDONLY); +#endif +} + +#ifndef _WIN32 +pid_t +regress_fork(void) +{ + pid_t pid = fork(); +#ifdef FORK_BREAKS_GCOV + vproc_transaction_begin(0); +#endif + return pid; +} +#endif + +static void +ignore_log_cb(int s, const char *msg) +{ +} + +/** + * Put into the real time scheduling class for better timers latency. + * https://developer.apple.com/library/archive/technotes/tn2169/_index.html#//apple_ref/doc/uid/DTS40013172-CH1-TNTAG6000 + */ +#if defined(__APPLE__) +static void move_pthread_to_realtime_scheduling_class(pthread_t pthread) +{ + mach_timebase_info_data_t info; + mach_timebase_info(&info); + + const uint64_t NANOS_PER_MSEC = 1000000ULL; + double clock2abs = + ((double)info.denom / (double)info.numer) * NANOS_PER_MSEC; + + thread_time_constraint_policy_data_t policy; + policy.period = 0; + policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work + policy.constraint = (uint32_t)(10 * clock2abs); + policy.preemptible = FALSE; + + int kr = thread_policy_set(pthread_mach_thread_np(pthread), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (kr != KERN_SUCCESS) { + mach_error("thread_policy_set:", kr); + exit(1); + } +} + +void thread_setup(THREAD_T pthread) +{ + move_pthread_to_realtime_scheduling_class(pthread); +} +#else /** \__APPLE__ */ +void thread_setup(THREAD_T pthread) {} +#endif /** \!__APPLE__ */ + + +void * +basic_test_setup(const struct testcase_t *testcase) +{ + struct event_base *base = NULL; + evutil_socket_t spair[2] = { -1, -1 }; + struct basic_test_data *data = NULL; + + thread_setup(THREAD_SELF()); + +#ifndef _WIN32 + if (testcase->flags & TT_ENABLE_IOCP_FLAG) + return (void*)TT_SKIP; +#endif + + if (testcase->flags & TT_ENABLE_DEBUG_MODE && + !libevent_tests_running_in_debug_mode) { + event_enable_debug_mode(); + libevent_tests_running_in_debug_mode = 1; + } + + if (testcase->flags & TT_NEED_THREADS) { + if (!(testcase->flags & TT_FORK)) + return NULL; +#if defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) + if (evthread_use_pthreads()) + exit(1); +#elif defined(EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED) + if (evthread_use_windows_threads()) + exit(1); +#else + return (void*)TT_SKIP; +#endif + } + + if (testcase->flags & TT_NEED_SOCKETPAIR) { + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, spair) == -1) { + fprintf(stderr, "%s: socketpair\n", __func__); + exit(1); + } + + if (evutil_make_socket_nonblocking(spair[0]) == -1) { + fprintf(stderr, "fcntl(O_NONBLOCK)"); + exit(1); + } + + if (evutil_make_socket_nonblocking(spair[1]) == -1) { + fprintf(stderr, "fcntl(O_NONBLOCK)"); + exit(1); + } + } + if (testcase->flags & TT_NEED_BASE) { + if (testcase->flags & TT_LEGACY) + base = event_init(); + else + base = event_base_new(); + if (!base) + exit(1); + } + if (testcase->flags & TT_ENABLE_IOCP_FLAG) { + if (event_base_start_iocp_(base, 0)<0) { + event_base_free(base); + return (void*)TT_SKIP; + } + } + + if (testcase->flags & TT_NEED_DNS) { + evdns_set_log_fn(dnslogcb); + if (evdns_init()) + return NULL; /* fast failure */ /*XXX asserts. */ + } + + if (testcase->flags & TT_NO_LOGS) + event_set_log_callback(ignore_log_cb); + + data = calloc(1, sizeof(*data)); + if (!data) + exit(1); + data->base = base; + data->pair[0] = spair[0]; + data->pair[1] = spair[1]; + data->setup_data = testcase->setup_data; + return data; +} + +int +basic_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + struct basic_test_data *data = ptr; + + if (testcase->flags & TT_NO_LOGS) + event_set_log_callback(NULL); + + if (testcase->flags & TT_NEED_SOCKETPAIR) { + if (data->pair[0] != -1) + evutil_closesocket(data->pair[0]); + if (data->pair[1] != -1) + evutil_closesocket(data->pair[1]); + } + + if (testcase->flags & TT_NEED_DNS) { + evdns_shutdown(0); + } + + if (testcase->flags & TT_NEED_BASE) { + if (data->base) { + event_base_assert_ok_(data->base); + event_base_free(data->base); + } + } + + if (testcase->flags & TT_FORK) + libevent_global_shutdown(); + + free(data); + + return 1; +} + +const struct testcase_setup_t basic_setup = { + basic_test_setup, basic_test_cleanup +}; + +/* The "data" for a legacy test is just a pointer to the void fn(void) + function implementing the test case. We need to set up some globals, + though, since that's where legacy tests expect to find a socketpair + (sometimes) and a global event_base (sometimes). + */ +static void * +legacy_test_setup(const struct testcase_t *testcase) +{ + struct basic_test_data *data = basic_test_setup(testcase); + if (data == (void*)TT_SKIP || data == NULL) + return data; + global_base = data->base; + pair[0] = data->pair[0]; + pair[1] = data->pair[1]; + data->legacy_test_fn = testcase->setup_data; + return data; +} + +/* This function is the implementation of every legacy test case. It + sets test_ok to 0, invokes the test function, and tells tinytest that + the test failed if the test didn't set test_ok to 1. + */ +void +run_legacy_test_fn(void *ptr) +{ + struct basic_test_data *data = ptr; + test_ok = called = 0; + + in_legacy_test_wrapper = 1; + data->legacy_test_fn(); /* This part actually calls the test */ + in_legacy_test_wrapper = 0; + + if (!test_ok) + tt_abort_msg("Legacy unit test failed"); + +end: + test_ok = 0; +} + +/* This function doesn't have to clean up ptr (which is just a pointer + to the test function), but it may need to close the socketpair or + free the event_base. + */ +static int +legacy_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + int r = basic_test_cleanup(testcase, ptr); + pair[0] = pair[1] = -1; + global_base = NULL; + return r; +} + +const struct testcase_setup_t legacy_setup = { + legacy_test_setup, legacy_test_cleanup +}; + +/* ============================================================ */ + +#if (!defined(EVENT__HAVE_PTHREADS) && !defined(_WIN32)) || defined(EVENT__DISABLE_THREAD_SUPPORT) +struct testcase_t thread_testcases[] = { + { "basic", NULL, TT_SKIP, NULL, NULL }, + END_OF_TESTCASES +}; +#endif + +struct testgroup_t testgroups[] = { + { "main/", main_testcases }, + { "heap/", minheap_testcases }, + { "et/", edgetriggered_testcases }, + { "finalize/", finalize_testcases }, + { "evbuffer/", evbuffer_testcases }, + { "signal/", signal_testcases }, + { "util/", util_testcases }, + { "bufferevent/", bufferevent_testcases }, + { "http/", http_testcases }, + { "dns/", dns_testcases }, + { "evtag/", evtag_testcases }, + { "rpc/", rpc_testcases }, + { "thread/", thread_testcases }, + { "listener/", listener_testcases }, +#ifdef _WIN32 + { "iocp/", iocp_testcases }, + { "iocp/bufferevent/", bufferevent_iocp_testcases }, + { "iocp/listener/", listener_iocp_testcases }, + { "iocp/http/", http_iocp_testcases }, +#endif +#ifdef EVENT__HAVE_OPENSSL + { "ssl/", ssl_testcases }, +#endif + END_OF_GROUPS +}; + +const char *alltests[] = { "+..", NULL }; +const char *livenettests[] = { + "+util/getaddrinfo_live", + "+dns/gethostby..", + "+dns/resolve_reverse", + NULL +}; +const char *finetimetests[] = { + "+util/monotonic_res_precise", + "+util/monotonic_res_fallback", + "+thread/deferred_cb_skew", + "+http/connection_retry", + "+http/https_connection_retry", + NULL +}; +struct testlist_alias_t testaliases[] = { + { "all", alltests }, + { "live_net", livenettests }, + { "fine_timing", finetimetests }, + END_OF_ALIASES +}; + +int libevent_tests_running_in_debug_mode = 0; + +int +main(int argc, const char **argv) +{ +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + +#ifndef _WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return 1; +#endif + +#ifdef _WIN32 + tinytest_skip(testgroups, "http/connection_retry"); + tinytest_skip(testgroups, "http/https_connection_retry"); + tinytest_skip(testgroups, "http/read_on_write_error"); +#endif + +#ifndef EVENT__DISABLE_THREAD_SUPPORT + if (!getenv("EVENT_NO_DEBUG_LOCKS")) + evthread_enable_lock_debugging(); +#endif + + if (getenv("EVENT_DEBUG_MODE")) { + event_enable_debug_mode(); + libevent_tests_running_in_debug_mode = 1; + } + if (getenv("EVENT_DEBUG_LOGGING_ALL")) { + event_enable_debug_logging(EVENT_DBG_ALL); + } + + tinytest_set_aliases(testaliases); + + evutil_weakrand_seed_(&test_weakrand_state, 0); + + if (getenv("EVENT_NO_FILE_BUFFERING")) { + setbuf(stdout, NULL); + setbuf(stderr, NULL); + } + + if (tinytest_main(argc,argv,testgroups)) + return 1; + + libevent_global_shutdown(); + + return 0; +} + diff --git a/libevent/test/regress_minheap.c b/libevent/test/regress_minheap.c new file mode 100644 index 0000000..05db32e --- /dev/null +++ b/libevent/test/regress_minheap.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../minheap-internal.h" + +#include <stdlib.h> +#include "event2/event_struct.h" + +#include "tinytest.h" +#include "tinytest_macros.h" +#include "regress.h" + +static void +set_random_timeout(struct event *ev) +{ + ev->ev_timeout.tv_sec = test_weakrand(); + ev->ev_timeout.tv_usec = test_weakrand() & 0xfffff; + ev->ev_timeout_pos.min_heap_idx = -1; +} + +static void +check_heap(struct min_heap *heap) +{ + unsigned i; + for (i = 1; i < heap->n; ++i) { + unsigned parent_idx = (i-1)/2; + tt_want(evutil_timercmp(&heap->p[i]->ev_timeout, + &heap->p[parent_idx]->ev_timeout, >=)); + } +} + +static void +test_heap_randomized(void *ptr) +{ + struct min_heap heap; + struct event *inserted[1024]; + struct event *e, *last_e; + int i; + + min_heap_ctor_(&heap); + + for (i = 0; i < 1024; ++i) { + inserted[i] = malloc(sizeof(struct event)); + set_random_timeout(inserted[i]); + min_heap_push_(&heap, inserted[i]); + } + check_heap(&heap); + + tt_assert(min_heap_size_(&heap) == 1024); + + for (i = 0; i < 512; ++i) { + min_heap_erase_(&heap, inserted[i]); + if (0 == (i % 32)) + check_heap(&heap); + } + tt_assert(min_heap_size_(&heap) == 512); + + last_e = min_heap_pop_(&heap); + while (1) { + e = min_heap_pop_(&heap); + if (!e) + break; + tt_want(evutil_timercmp(&last_e->ev_timeout, + &e->ev_timeout, <=)); + } + tt_assert(min_heap_size_(&heap) == 0); +end: + for (i = 0; i < 1024; ++i) + free(inserted[i]); + + min_heap_dtor_(&heap); +} + +struct testcase_t minheap_testcases[] = { + { "randomized", test_heap_randomized, 0, NULL, NULL }, + END_OF_TESTCASES +}; diff --git a/libevent/test/regress_rpc.c b/libevent/test/regress_rpc.c new file mode 100644 index 0000000..87a7efa --- /dev/null +++ b/libevent/test/regress_rpc.c @@ -0,0 +1,952 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* The old tests here need assertions to work. */ +#undef NDEBUG + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <signal.h> +#include <unistd.h> +#include <netdb.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "event2/buffer.h" +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/http.h" +#include "event2/http_compat.h" +#include "event2/http_struct.h" +#include "event2/rpc.h" +#include "event2/rpc_struct.h" +#include "event2/tag.h" +#include "log-internal.h" + +#include "regress.gen.h" + +#include "regress.h" +#include "regress_testutils.h" + +#ifndef NO_PYTHON_EXISTS + +static struct evhttp * +http_setup(ev_uint16_t *pport) +{ + struct evhttp *myhttp; + ev_uint16_t port; + struct evhttp_bound_socket *sock; + + myhttp = evhttp_new(NULL); + if (!myhttp) + event_errx(1, "Could not start web server"); + + /* Try a few different ports */ + sock = evhttp_bind_socket_with_handle(myhttp, "127.0.0.1", 0); + if (!sock) + event_errx(1, "Couldn't open web port"); + + port = regress_get_socket_port(evhttp_bound_socket_get_fd(sock)); + + *pport = port; + return (myhttp); +} + +EVRPC_HEADER(Message, msg, kill) +EVRPC_HEADER(NeverReply, msg, kill) + +EVRPC_GENERATE(Message, msg, kill) +EVRPC_GENERATE(NeverReply, msg, kill) + +static int need_input_hook = 0; +static int need_output_hook = 0; + +static void +MessageCb(EVRPC_STRUCT(Message)* rpc, void *arg) +{ + struct kill* kill_reply = rpc->reply; + + if (need_input_hook) { + struct evhttp_request* req = EVRPC_REQUEST_HTTP(rpc); + const char *header = evhttp_find_header( + req->input_headers, "X-Hook"); + assert(header); + assert(strcmp(header, "input") == 0); + } + + /* we just want to fill in some non-sense */ + EVTAG_ASSIGN(kill_reply, weapon, "dagger"); + EVTAG_ASSIGN(kill_reply, action, "wave around like an idiot"); + + /* no reply to the RPC */ + EVRPC_REQUEST_DONE(rpc); +} + +static EVRPC_STRUCT(NeverReply) *saved_rpc; + +static void +NeverReplyCb(EVRPC_STRUCT(NeverReply)* rpc, void *arg) +{ + test_ok += 1; + saved_rpc = rpc; +} + +static void +rpc_setup(struct evhttp **phttp, ev_uint16_t *pport, struct evrpc_base **pbase) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + + http = http_setup(&port); + base = evrpc_init(http); + + EVRPC_REGISTER(base, Message, msg, kill, MessageCb, NULL); + EVRPC_REGISTER(base, NeverReply, msg, kill, NeverReplyCb, NULL); + + *phttp = http; + *pport = port; + *pbase = base; + + need_input_hook = 0; + need_output_hook = 0; +} + +static void +rpc_teardown(struct evrpc_base *base) +{ + assert(EVRPC_UNREGISTER(base, Message) == 0); + assert(EVRPC_UNREGISTER(base, NeverReply) == 0); + + evrpc_free(base); +} + +static void +rpc_postrequest_failure(struct evhttp_request *req, void *arg) +{ + if (req->response_code != HTTP_SERVUNAVAIL) { + + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +/* + * Test a malformed payload submitted as an RPC + */ + +static void +rpc_basic_test(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + rpc_setup(&http, &port, &base); + + evcon = evhttp_connection_new("127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(rpc_postrequest_failure, NULL); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + evbuffer_add_printf(req->output_buffer, "Some Nonsense"); + + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_POST, + "/.rpc.Message") == -1) { + tt_abort(); + } + + test_ok = 0; + + event_dispatch(); + + evhttp_connection_free(evcon); + + rpc_teardown(base); + + tt_assert(test_ok == 1); + +end: + evhttp_free(http); +} + +static void +rpc_postrequest_done(struct evhttp_request *req, void *arg) +{ + struct kill* kill_reply = NULL; + + if (req->response_code != HTTP_OK) { + fprintf(stderr, "FAILED (response code)\n"); + exit(1); + } + + kill_reply = kill_new(); + + if ((kill_unmarshal(kill_reply, req->input_buffer)) == -1) { + fprintf(stderr, "FAILED (unmarshal)\n"); + exit(1); + } + + kill_free(kill_reply); + + test_ok = 1; + event_loopexit(NULL); +} + +static void +rpc_basic_message(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + struct msg *msg; + + rpc_setup(&http, &port, &base); + + evcon = evhttp_connection_new("127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(rpc_postrequest_done, NULL); + if (req == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* set up the basic message */ + msg = msg_new(); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + msg_marshal(req->output_buffer, msg); + msg_free(msg); + + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_POST, + "/.rpc.Message") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + test_ok = 0; + + event_dispatch(); + + evhttp_connection_free(evcon); + + rpc_teardown(base); + +end: + evhttp_free(http); +} + +static struct evrpc_pool * +rpc_pool_with_connection(ev_uint16_t port) +{ + struct evhttp_connection *evcon; + struct evrpc_pool *pool; + + pool = evrpc_pool_new(NULL); + assert(pool != NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + assert(evcon != NULL); + + evrpc_pool_add_connection(pool, evcon); + + return (pool); +} + +static void +GotKillCb(struct evrpc_status *status, + struct msg *msg, struct kill *kill, void *arg) +{ + char *weapon; + char *action; + + if (need_output_hook) { + struct evhttp_request *req = status->http_req; + const char *header = evhttp_find_header( + req->input_headers, "X-Pool-Hook"); + assert(header); + assert(strcmp(header, "ran") == 0); + } + + if (status->error != EVRPC_STATUS_ERR_NONE) + goto done; + + if (EVTAG_GET(kill, weapon, &weapon) == -1) { + fprintf(stderr, "get weapon\n"); + goto done; + } + if (EVTAG_GET(kill, action, &action) == -1) { + fprintf(stderr, "get action\n"); + goto done; + } + + if (strcmp(weapon, "dagger")) + goto done; + + if (strcmp(action, "wave around like an idiot")) + goto done; + + test_ok += 1; + +done: + event_loopexit(NULL); +} + +static void +GotKillCbTwo(struct evrpc_status *status, + struct msg *msg, struct kill *kill, void *arg) +{ + char *weapon; + char *action; + + if (status->error != EVRPC_STATUS_ERR_NONE) + goto done; + + if (EVTAG_GET(kill, weapon, &weapon) == -1) { + fprintf(stderr, "get weapon\n"); + goto done; + } + if (EVTAG_GET(kill, action, &action) == -1) { + fprintf(stderr, "get action\n"); + goto done; + } + + if (strcmp(weapon, "dagger")) + goto done; + + if (strcmp(action, "wave around like an idiot")) + goto done; + + test_ok += 1; + +done: + if (test_ok == 2) + event_loopexit(NULL); +} + +static int +rpc_hook_add_header(void *ctx, struct evhttp_request *req, + struct evbuffer *evbuf, void *arg) +{ + const char *hook_type = arg; + if (strcmp("input", hook_type) == 0) + evhttp_add_header(req->input_headers, "X-Hook", hook_type); + else + evhttp_add_header(req->output_headers, "X-Hook", hook_type); + + assert(evrpc_hook_get_connection(ctx) != NULL); + + return (EVRPC_CONTINUE); +} + +static int +rpc_hook_add_meta(void *ctx, struct evhttp_request *req, + struct evbuffer *evbuf, void *arg) +{ + evrpc_hook_add_meta(ctx, "meta", "test", 5); + + assert(evrpc_hook_get_connection(ctx) != NULL); + + return (EVRPC_CONTINUE); +} + +static int +rpc_hook_remove_header(void *ctx, struct evhttp_request *req, + struct evbuffer *evbuf, void *arg) +{ + const char *header = evhttp_find_header(req->input_headers, "X-Hook"); + void *data = NULL; + size_t data_len = 0; + + assert(header != NULL); + assert(strcmp(header, arg) == 0); + + evhttp_remove_header(req->input_headers, "X-Hook"); + evhttp_add_header(req->input_headers, "X-Pool-Hook", "ran"); + + assert(evrpc_hook_find_meta(ctx, "meta", &data, &data_len) == 0); + assert(data != NULL); + assert(data_len == 5); + + assert(evrpc_hook_get_connection(ctx) != NULL); + + return (EVRPC_CONTINUE); +} + +static void +rpc_basic_client(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg = NULL; + struct kill *kill = NULL; + + rpc_setup(&http, &port, &base); + + need_input_hook = 1; + need_output_hook = 1; + + assert(evrpc_add_hook(base, EVRPC_INPUT, rpc_hook_add_header, (void*)"input") + != NULL); + assert(evrpc_add_hook(base, EVRPC_OUTPUT, rpc_hook_add_header, (void*)"output") + != NULL); + + pool = rpc_pool_with_connection(port); + tt_assert(pool); + + assert(evrpc_add_hook(pool, EVRPC_OUTPUT, rpc_hook_add_meta, NULL)); + assert(evrpc_add_hook(pool, EVRPC_INPUT, rpc_hook_remove_header, (void*)"output")); + + /* set up the basic message */ + msg = msg_new(); + tt_assert(msg); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill = kill_new(); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill, GotKillCb, NULL); + + test_ok = 0; + + event_dispatch(); + + tt_assert(test_ok == 1); + + /* we do it twice to make sure that reuse works correctly */ + kill_clear(kill); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill, GotKillCb, NULL); + + event_dispatch(); + + tt_assert(test_ok == 2); + + /* we do it trice to make sure other stuff works, too */ + kill_clear(kill); + + { + struct evrpc_request_wrapper *ctx = + EVRPC_MAKE_CTX(Message, msg, kill, + pool, msg, kill, GotKillCb, NULL); + evrpc_make_request(ctx); + } + + event_dispatch(); + + rpc_teardown(base); + + tt_assert(test_ok == 3); + +end: + if (msg) + msg_free(msg); + if (kill) + kill_free(kill); + + if (pool) + evrpc_pool_free(pool); + if (http) + evhttp_free(http); + + need_input_hook = 0; + need_output_hook = 0; +} + +/* + * We are testing that the second requests gets send over the same + * connection after the first RPCs completes. + */ +static void +rpc_basic_queued_client(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg=NULL; + struct kill *kill_one=NULL, *kill_two=NULL; + + rpc_setup(&http, &port, &base); + + pool = rpc_pool_with_connection(port); + tt_assert(pool); + + /* set up the basic message */ + msg = msg_new(); + tt_assert(msg); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill_one = kill_new(); + kill_two = kill_new(); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill_one, GotKillCbTwo, NULL); + EVRPC_MAKE_REQUEST(Message, pool, msg, kill_two, GotKillCb, NULL); + + test_ok = 0; + + event_dispatch(); + + rpc_teardown(base); + + tt_assert(test_ok == 2); + +end: + if (msg) + msg_free(msg); + if (kill_one) + kill_free(kill_one); + if (kill_two) + kill_free(kill_two); + + if (pool) + evrpc_pool_free(pool); + if (http) + evhttp_free(http); +} + +static void +GotErrorCb(struct evrpc_status *status, + struct msg *msg, struct kill *kill, void *arg) +{ + if (status->error != EVRPC_STATUS_ERR_TIMEOUT) + goto done; + + /* should never be complete but just to check */ + if (kill_complete(kill) == 0) + goto done; + + test_ok += 1; + +done: + event_loopexit(NULL); +} + +/* we just pause the rpc and continue it in the next callback */ + +struct rpc_hook_ctx_ { + void *vbase; + void *ctx; +}; + +static int hook_pause_cb_called=0; + +static void +rpc_hook_pause_cb(evutil_socket_t fd, short what, void *arg) +{ + struct rpc_hook_ctx_ *ctx = arg; + ++hook_pause_cb_called; + evrpc_resume_request(ctx->vbase, ctx->ctx, EVRPC_CONTINUE); + free(arg); +} + +static int +rpc_hook_pause(void *ctx, struct evhttp_request *req, struct evbuffer *evbuf, + void *arg) +{ + struct rpc_hook_ctx_ *tmp = malloc(sizeof(*tmp)); + struct timeval tv; + + assert(tmp != NULL); + tmp->vbase = arg; + tmp->ctx = ctx; + + memset(&tv, 0, sizeof(tv)); + event_once(-1, EV_TIMEOUT, rpc_hook_pause_cb, tmp, &tv); + return EVRPC_PAUSE; +} + +static void +rpc_basic_client_with_pause(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg = NULL; + struct kill *kill= NULL; + + rpc_setup(&http, &port, &base); + + assert(evrpc_add_hook(base, EVRPC_INPUT, rpc_hook_pause, base)); + assert(evrpc_add_hook(base, EVRPC_OUTPUT, rpc_hook_pause, base)); + + pool = rpc_pool_with_connection(port); + tt_assert(pool); + assert(evrpc_add_hook(pool, EVRPC_INPUT, rpc_hook_pause, pool)); + assert(evrpc_add_hook(pool, EVRPC_OUTPUT, rpc_hook_pause, pool)); + + /* set up the basic message */ + msg = msg_new(); + tt_assert(msg); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill = kill_new(); + + EVRPC_MAKE_REQUEST(Message, pool, msg, kill, GotKillCb, NULL); + + test_ok = 0; + + event_dispatch(); + + tt_int_op(test_ok, ==, 1); + tt_int_op(hook_pause_cb_called, ==, 4); + +end: + if (base) + rpc_teardown(base); + + if (msg) + msg_free(msg); + if (kill) + kill_free(kill); + + if (pool) + evrpc_pool_free(pool); + if (http) + evhttp_free(http); +} + +static void +rpc_client_timeout(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evrpc_pool *pool = NULL; + struct msg *msg = NULL; + struct kill *kill = NULL; + + rpc_setup(&http, &port, &base); + + pool = rpc_pool_with_connection(port); + tt_assert(pool); + + /* set the timeout to 1 second. */ + evrpc_pool_set_timeout(pool, 1); + + /* set up the basic message */ + msg = msg_new(); + tt_assert(msg); + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "tester"); + + kill = kill_new(); + + EVRPC_MAKE_REQUEST(NeverReply, pool, msg, kill, GotErrorCb, NULL); + + test_ok = 0; + + event_dispatch(); + + /* free the saved RPC structure up */ + EVRPC_REQUEST_DONE(saved_rpc); + + rpc_teardown(base); + + tt_assert(test_ok == 2); + +end: + if (msg) + msg_free(msg); + if (kill) + kill_free(kill); + + if (pool) + evrpc_pool_free(pool); + if (http) + evhttp_free(http); +} + +static void +rpc_test(void) +{ + struct msg *msg = NULL, *msg2 = NULL; + struct kill *attack = NULL; + struct run *run = NULL; + struct evbuffer *tmp = evbuffer_new(); + struct timeval tv_start, tv_end; + ev_uint32_t tag; + int i; + + msg = msg_new(); + + tt_assert(msg); + + EVTAG_ASSIGN(msg, from_name, "niels"); + EVTAG_ASSIGN(msg, to_name, "phoenix"); + + if (EVTAG_GET(msg, attack, &attack) == -1) { + tt_abort_msg("Failed to set kill message."); + } + + EVTAG_ASSIGN(attack, weapon, "feather"); + EVTAG_ASSIGN(attack, action, "tickle"); + for (i = 0; i < 3; ++i) { + if (EVTAG_ARRAY_ADD_VALUE(attack, how_often, i) == NULL) { + tt_abort_msg("Failed to add how_often."); + } + } + + evutil_gettimeofday(&tv_start, NULL); + for (i = 0; i < 1000; ++i) { + run = EVTAG_ARRAY_ADD(msg, run); + if (run == NULL) { + tt_abort_msg("Failed to add run message."); + } + EVTAG_ASSIGN(run, how, "very fast but with some data in it"); + EVTAG_ASSIGN(run, fixed_bytes, + (ev_uint8_t*)"012345678901234567890123"); + + if (EVTAG_ARRAY_ADD_VALUE( + run, notes, "this is my note") == NULL) { + tt_abort_msg("Failed to add note."); + } + if (EVTAG_ARRAY_ADD_VALUE(run, notes, "pps") == NULL) { + tt_abort_msg("Failed to add note"); + } + + EVTAG_ASSIGN(run, large_number, 0xdead0a0bcafebeefLL); + EVTAG_ARRAY_ADD_VALUE(run, other_numbers, 0xdead0a0b); + EVTAG_ARRAY_ADD_VALUE(run, other_numbers, 0xbeefcafe); + } + + if (msg_complete(msg) == -1) + tt_abort_msg("Failed to make complete message."); + + evtag_marshal_msg(tmp, 0xdeaf, msg); + + if (evtag_peek(tmp, &tag) == -1) + tt_abort_msg("Failed to peak tag."); + + if (tag != 0xdeaf) + TT_DIE(("Got incorrect tag: %0x.", (unsigned)tag)); + + msg2 = msg_new(); + if (evtag_unmarshal_msg(tmp, 0xdeaf, msg2) == -1) + tt_abort_msg("Failed to unmarshal message."); + + evutil_gettimeofday(&tv_end, NULL); + evutil_timersub(&tv_end, &tv_start, &tv_end); + TT_BLATHER(("(%.1f us/add) ", + (float)tv_end.tv_sec/(float)i * 1000000.0 + + tv_end.tv_usec / (float)i)); + + if (!EVTAG_HAS(msg2, from_name) || + !EVTAG_HAS(msg2, to_name) || + !EVTAG_HAS(msg2, attack)) { + tt_abort_msg("Missing data structures."); + } + + if (EVTAG_GET(msg2, attack, &attack) == -1) { + tt_abort_msg("Could not get attack."); + } + + if (EVTAG_ARRAY_LEN(msg2, run) != i) { + tt_abort_msg("Wrong number of run messages."); + } + + /* get the very first run message */ + if (EVTAG_ARRAY_GET(msg2, run, 0, &run) == -1) { + tt_abort_msg("Failed to get run msg."); + } else { + /* verify the notes */ + char *note_one, *note_two; + ev_uint64_t large_number; + ev_uint32_t short_number; + + if (EVTAG_ARRAY_LEN(run, notes) != 2) { + tt_abort_msg("Wrong number of note strings."); + } + + if (EVTAG_ARRAY_GET(run, notes, 0, ¬e_one) == -1 || + EVTAG_ARRAY_GET(run, notes, 1, ¬e_two) == -1) { + tt_abort_msg("Could not get note strings."); + } + + if (strcmp(note_one, "this is my note") || + strcmp(note_two, "pps")) { + tt_abort_msg("Incorrect note strings encoded."); + } + + if (EVTAG_GET(run, large_number, &large_number) == -1 || + large_number != 0xdead0a0bcafebeefLL) { + tt_abort_msg("Incorrrect large_number."); + } + + if (EVTAG_ARRAY_LEN(run, other_numbers) != 2) { + tt_abort_msg("Wrong number of other_numbers."); + } + + if (EVTAG_ARRAY_GET( + run, other_numbers, 0, &short_number) == -1) { + tt_abort_msg("Could not get short number."); + } + tt_uint_op(short_number, ==, 0xdead0a0b); + + } + tt_int_op(EVTAG_ARRAY_LEN(attack, how_often), ==, 3); + + for (i = 0; i < 3; ++i) { + ev_uint32_t res; + if (EVTAG_ARRAY_GET(attack, how_often, i, &res) == -1) { + TT_DIE(("Cannot get %dth how_often msg.", i)); + } + if ((int)res != i) { + TT_DIE(("Wrong message encoded %d != %d", i, res)); + } + } + + test_ok = 1; +end: + if (msg) + msg_free(msg); + if (msg2) + msg_free(msg2); + if (tmp) + evbuffer_free(tmp); +} + +static void +rpc_invalid_type(void) +{ + ev_uint16_t port; + struct evhttp *http = NULL; + struct evrpc_base *base = NULL; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + + rpc_setup(&http, &port, &base); + + evcon = evhttp_connection_new("127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(rpc_postrequest_failure, NULL); + tt_assert(req); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + evbuffer_add_printf(req->output_buffer, "Some Nonsense"); + + if (evhttp_make_request(evcon, req, + EVHTTP_REQ_GET, + "/.rpc.Message") == -1) { + tt_abort(); + } + + test_ok = 0; + + event_dispatch(); + + evhttp_connection_free(evcon); + + rpc_teardown(base); + + tt_assert(test_ok == 1); + +end: + evhttp_free(http); +} + + +#define RPC_LEGACY(name) \ + { #name, run_legacy_test_fn, TT_FORK|TT_NEED_BASE|TT_LEGACY, \ + &legacy_setup, \ + rpc_##name } +#else +/* NO_PYTHON_EXISTS */ + +#define RPC_LEGACY(name) \ + { #name, NULL, TT_SKIP, NULL, NULL } + +#endif + +struct testcase_t rpc_testcases[] = { + RPC_LEGACY(basic_test), + RPC_LEGACY(basic_message), + RPC_LEGACY(basic_client), + RPC_LEGACY(basic_queued_client), + RPC_LEGACY(basic_client_with_pause), + RPC_LEGACY(invalid_type), + RPC_LEGACY(client_timeout), + RPC_LEGACY(test), + + END_OF_TESTCASES, +}; diff --git a/libevent/test/regress_ssl.c b/libevent/test/regress_ssl.c new file mode 100644 index 0000000..37dc334 --- /dev/null +++ b/libevent/test/regress_ssl.c @@ -0,0 +1,1078 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Get rid of OSX 10.7 and greater deprecation warnings. +#if defined(__APPLE__) && defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include "util-internal.h" + +#ifndef _WIN32 +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +#include "event2/util.h" +#include "event2/event.h" +#include "event2/bufferevent_ssl.h" +#include "event2/bufferevent_struct.h" +#include "event2/buffer.h" +#include "event2/listener.h" + +#include "regress.h" +#include "tinytest.h" +#include "tinytest_macros.h" + +#include <openssl/err.h> +#include <openssl/pem.h> +#include "openssl-compat.h" + +#include <string.h> +#ifdef _WIN32 +#include <io.h> +#define read _read +#define write _write +#else +#include <unistd.h> +#endif + +/* A pre-generated key, to save the cost of doing an RSA key generation step + * during the unit tests. It is published in this file, so you would have to + * be very foolish to consider using it in your own code. */ +static const char KEY[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEogIBAAKCAQEAtK07Ili0dkJb79m/sFmHoVJTWyLoveXex2yX/BtUzzcvZEOu\n" + "QLon/++5YOA48kzZm5K9mIwZkZhui1ZgJ5Bjq0LGAWTZGIn+NXjLFshPYvTKpOCW\n" + "uzL0Ir0LXMsBLYJQ5A4FomLNxs4I3H/dhDSGy/rSiJB1B4w2xNiwPK08/VL3zZqk\n" + "V+GsSvGIIkzhTMbqPJy9K8pqyjwOU2pgORS794yXciTGxWYjTDzJPgQ35YMDATaG\n" + "jr4HHo1zxU/Lj0pndSUK5rKLYxYQ3Uc8B3AVYDl9CP/GbOoQ4LBzS68JjcAUyp6i\n" + "6NfXlc2D9S9XgqVqwI+JqgJs0eW/+zPY2UEDWwIDAQABAoIBAD2HzV66FOM9YDAD\n" + "2RtGskEHV2nvLpIVadRCsFPkPvK+2X3s6rgSbbLkwh4y3lHuSCGKTNVZyQ9jeSos\n" + "xVxT+Q2HFQW+gYyw2gj91TQyDY8mzKhv8AVaqff2p5r3a7RC8CdqexK9UVUGL9Bg\n" + "H2F5vfpTtkVZ5PEoGDLblNFlMiMW/t1SobUeBVx+Msco/xqk9lFv1A9nnepGy0Gi\n" + "D+i6YNGTBsX22YhoCZl/ICxCL8lgqPei4FvBr9dBVh/jQgjuUBm2jz55p2r7+7Aw\n" + "khmXHReejoVokQ2+htgSgZNKlKuDy710ZpBqnDi8ynQi82Y2qCpyg/p/xcER54B6\n" + "hSftaiECgYEA2RkSoxU+nWk+BClQEUZRi88QK5W/M8oo1DvUs36hvPFkw3Jk/gz0\n" + "fgd5bnA+MXj0Fc0QHvbddPjIkyoI/evq9GPV+JYIuH5zabrlI3Jvya8q9QpAcEDO\n" + "KkL/O09qXVEW52S6l05nh4PLejyI7aTyTIN5nbVLac/+M8MY/qOjZksCgYEA1Q1o\n" + "L8kjSavU2xhQmSgZb9W62Do60sa3e73ljrDPoiyvbExldpSdziFYxHBD/Rep0ePf\n" + "eVSGS3VSwevt9/jSGo2Oa83TYYns9agBm03oR/Go/DukESdI792NsEM+PRFypVNy\n" + "AohWRLj0UU6DV+zLKp0VBavtx0ATeLFX0eN17TECgYBI2O/3Bz7uhQ0JSm+SjFz6\n" + "o+2SInp5P2G57aWu4VQWWY3tQ2p+EQzNaWam10UXRrXoxtmc+ktPX9e2AgnoYoyB\n" + "myqGcpnUhqHlnZAb999o9r1cYidDQ4uqhLauSTSwwXAFDzjJYsa8o03Y440y6QFh\n" + "CVD6yYXXqLJs3g96CqDexwKBgAHxq1+0QCQt8zVElYewO/svQhMzBNJjic0RQIT6\n" + "zAo4yij80XgxhvcYiszQEW6/xobpw2JCCS+rFGQ8mOFIXfJsFD6blDAxp/3d2JXo\n" + "MhRl+hrDGI4ng5zcsqxHEMxR2m/zwPiQ8eiSn3gWdVBaEsiCwmxY00ScKxFQ3PJH\n" + "Vw4hAoGAdZLd8KfjjG6lg7hfpVqavstqVi9LOgkHeCfdjn7JP+76kYrgLk/XdkrP\n" + "N/BHhtFVFjOi/mTQfQ5YfZImkm/1ePBy7437DT8BDkOxspa50kK4HPggHnU64h1w\n" + "lhdEOj7mAgHwGwwVZWOgs9Lq6vfztnSuhqjha1daESY6kDscPIQ=\n" + "-----END RSA PRIVATE KEY-----\n"; + +EVP_PKEY * +ssl_getkey(void) +{ + EVP_PKEY *key; + BIO *bio; + + /* new read-only BIO backed by KEY. */ + bio = BIO_new_mem_buf((char*)KEY, -1); + tt_assert(bio); + + key = PEM_read_bio_PrivateKey(bio,NULL,NULL,NULL); + BIO_free(bio); + tt_assert(key); + + return key; +end: + return NULL; +} + +X509 * +ssl_getcert(EVP_PKEY *key) +{ + /* Dummy code to make a quick-and-dirty valid certificate with + OpenSSL. Don't copy this code into your own program! It does a + number of things in a stupid and insecure way. */ + X509 *x509 = NULL; + X509_NAME *name = NULL; + int nid; + time_t now = time(NULL); + + tt_assert(key); + + x509 = X509_new(); + tt_assert(x509); + tt_assert(0 != X509_set_version(x509, 2)); + tt_assert(0 != ASN1_INTEGER_set(X509_get_serialNumber(x509), + (long)now)); + + name = X509_NAME_new(); + tt_assert(name); + nid = OBJ_txt2nid("commonName"); + tt_assert(NID_undef != nid); + tt_assert(0 != X509_NAME_add_entry_by_NID( + name, nid, MBSTRING_ASC, (unsigned char*)"example.com", + -1, -1, 0)); + + X509_set_subject_name(x509, name); + X509_set_issuer_name(x509, name); + X509_NAME_free(name); + + X509_time_adj(X509_getm_notBefore(x509), 0, &now); + now += 3600; + X509_time_adj(X509_getm_notAfter(x509), 0, &now); + X509_set_pubkey(x509, key); + tt_assert(0 != X509_sign(x509, key, EVP_sha1())); + + return x509; +end: + X509_free(x509); + X509_NAME_free(name); + return NULL; +} + +static int disable_tls_11_and_12 = 0; +static SSL_CTX *the_ssl_ctx = NULL; + +SSL_CTX * +get_ssl_ctx(void) +{ + if (the_ssl_ctx) + return the_ssl_ctx; + the_ssl_ctx = SSL_CTX_new(SSLv23_method()); + if (!the_ssl_ctx) + return NULL; + if (disable_tls_11_and_12) { +#ifdef SSL_OP_NO_TLSv1_2 + SSL_CTX_set_options(the_ssl_ctx, SSL_OP_NO_TLSv1_2); +#endif +#ifdef SSL_OP_NO_TLSv1_1 + SSL_CTX_set_options(the_ssl_ctx, SSL_OP_NO_TLSv1_1); +#endif + } + return the_ssl_ctx; +} + +static int test_is_done; +static int n_connected; +static int got_close; +static int got_error; +static int got_timeout; +static int renegotiate_at = -1; +static int stop_when_connected; +static int pending_connect_events; +static struct event_base *exit_base; +static X509 *the_cert; +EVP_PKEY *the_key; + +void +init_ssl(void) +{ +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) + SSL_library_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + if (SSLeay() != OPENSSL_VERSION_NUMBER) { + TT_DECLARE("WARN", + ("Version mismatch for openssl: compiled with %lx but running with %lx", + (unsigned long)OPENSSL_VERSION_NUMBER, (unsigned long)SSLeay())); + } +#endif +} + +static void * +ssl_test_setup(const struct testcase_t *testcase) +{ + init_ssl(); + + the_key = ssl_getkey(); + EVUTIL_ASSERT(the_key); + + the_cert = ssl_getcert(the_key); + EVUTIL_ASSERT(the_cert); + + disable_tls_11_and_12 = 0; + + return basic_test_setup(testcase); +} +static int +ssl_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + int ret = basic_test_cleanup(testcase, ptr); + if (!ret) { + return ret; + } + + test_is_done = 0; + n_connected = 0; + got_close = 0; + got_error = 0; + got_timeout = 0; + renegotiate_at = -1; + stop_when_connected = 0; + pending_connect_events = 0; + exit_base = NULL; + + X509_free(the_cert); + EVP_PKEY_free(the_key); + + SSL_CTX_free(the_ssl_ctx); + the_ssl_ctx = NULL; + + return 1; +} +const struct testcase_setup_t ssl_setup = { + ssl_test_setup, ssl_test_cleanup +}; + + +/* ==================== + Here's a simple test: we read a number from the input, increment it, and + reply, until we get to 1001. +*/ + +enum regress_openssl_type +{ + REGRESS_OPENSSL_SOCKETPAIR = 1, + REGRESS_OPENSSL_FILTER = 2, + REGRESS_OPENSSL_RENEGOTIATE = 4, + REGRESS_OPENSSL_OPEN = 8, + REGRESS_OPENSSL_DIRTY_SHUTDOWN = 16, + REGRESS_OPENSSL_FD = 32, + + REGRESS_OPENSSL_CLIENT = 64, + REGRESS_OPENSSL_SERVER = 128, + + REGRESS_OPENSSL_FREED = 256, + REGRESS_OPENSSL_TIMEOUT = 512, + REGRESS_OPENSSL_SLEEP = 1024, + + REGRESS_OPENSSL_CLIENT_WRITE = 2048, + + REGRESS_DEFERRED_CALLBACKS = 4096, +}; + +static void +bufferevent_openssl_check_fd(struct bufferevent *bev, int filter) +{ + tt_fd_op(bufferevent_getfd(bev), !=, EVUTIL_INVALID_SOCKET); + tt_fd_op(bufferevent_setfd(bev, EVUTIL_INVALID_SOCKET), ==, 0); + if (filter) { + tt_fd_op(bufferevent_getfd(bev), !=, EVUTIL_INVALID_SOCKET); + } else { + tt_fd_op(bufferevent_getfd(bev), ==, EVUTIL_INVALID_SOCKET); + } + +end: + ; +} +static void +bufferevent_openssl_check_freed(struct bufferevent *bev) +{ + tt_int_op(event_pending(&bev->ev_read, EVLIST_ALL, NULL), ==, 0); + tt_int_op(event_pending(&bev->ev_write, EVLIST_ALL, NULL), ==, 0); + +end: + ; +} + +static void +free_on_cb(struct bufferevent *bev, void *ctx) +{ + TT_BLATHER(("free_on_cb: %p", bev)); + bufferevent_free(bev); +} + +static void +respond_to_number(struct bufferevent *bev, void *ctx) +{ + struct evbuffer *b = bufferevent_get_input(bev); + char *line; + int n; + + enum regress_openssl_type type; + type = (enum regress_openssl_type)ctx; + + line = evbuffer_readln(b, NULL, EVBUFFER_EOL_LF); + if (! line) + return; + n = atoi(line); + if (n <= 0) + TT_FAIL(("Bad number: %s", line)); + free(line); + TT_BLATHER(("The number was %d", n)); + if (n == 1001) { + ++test_is_done; + bufferevent_free(bev); /* Should trigger close on other side. */ + return; + } + if ((type & REGRESS_OPENSSL_CLIENT) && n == renegotiate_at) { + SSL_renegotiate(bufferevent_openssl_get_ssl(bev)); + } + ++n; + evbuffer_add_printf(bufferevent_get_output(bev), + "%d\n", n); + TT_BLATHER(("Done reading; now writing.")); + bufferevent_enable(bev, EV_WRITE); + bufferevent_disable(bev, EV_READ); +} + +static void +done_writing_cb(struct bufferevent *bev, void *ctx) +{ + struct evbuffer *b = bufferevent_get_output(bev); + if (evbuffer_get_length(b)) + return; + TT_BLATHER(("Done writing.")); + bufferevent_disable(bev, EV_WRITE); + bufferevent_enable(bev, EV_READ); +} + +static void +eventcb(struct bufferevent *bev, short what, void *ctx) +{ + X509 *peer_cert = NULL; + enum regress_openssl_type type; + + type = (enum regress_openssl_type)ctx; + + TT_BLATHER(("Got event %d", (int)what)); + if (what & BEV_EVENT_CONNECTED) { + SSL *ssl; + ++n_connected; + ssl = bufferevent_openssl_get_ssl(bev); + tt_assert(ssl); + peer_cert = SSL_get_peer_certificate(ssl); + if (type & REGRESS_OPENSSL_SERVER) { + tt_assert(peer_cert == NULL); + } else { + tt_assert(peer_cert != NULL); + } + if (stop_when_connected) { + if (--pending_connect_events == 0) + event_base_loopexit(exit_base, NULL); + } + + if ((type & REGRESS_OPENSSL_CLIENT_WRITE) && (type & REGRESS_OPENSSL_CLIENT)) + evbuffer_add_printf(bufferevent_get_output(bev), "1\n"); + } else if (what & BEV_EVENT_EOF) { + TT_BLATHER(("Got a good EOF")); + ++got_close; + if (type & REGRESS_OPENSSL_FD) { + bufferevent_openssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); + } + if (type & REGRESS_OPENSSL_FREED) { + bufferevent_openssl_check_freed(bev); + } + bufferevent_free(bev); + } else if (what & BEV_EVENT_ERROR) { + TT_BLATHER(("Got an error.")); + ++got_error; + if (type & REGRESS_OPENSSL_FD) { + bufferevent_openssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); + } + if (type & REGRESS_OPENSSL_FREED) { + bufferevent_openssl_check_freed(bev); + } + bufferevent_free(bev); + } else if (what & BEV_EVENT_TIMEOUT) { + TT_BLATHER(("Got timeout.")); + ++got_timeout; + if (type & REGRESS_OPENSSL_FD) { + bufferevent_openssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); + } + if (type & REGRESS_OPENSSL_FREED) { + bufferevent_openssl_check_freed(bev); + } + bufferevent_free(bev); + } + +end: + if (peer_cert) + X509_free(peer_cert); +} + +static void +open_ssl_bufevs(struct bufferevent **bev1_out, struct bufferevent **bev2_out, + struct event_base *base, int is_open, int flags, SSL *ssl1, SSL *ssl2, + evutil_socket_t *fd_pair, struct bufferevent **underlying_pair, + enum regress_openssl_type type) +{ + int state1 = is_open ? BUFFEREVENT_SSL_OPEN :BUFFEREVENT_SSL_CONNECTING; + int state2 = is_open ? BUFFEREVENT_SSL_OPEN :BUFFEREVENT_SSL_ACCEPTING; + int dirty_shutdown = type & REGRESS_OPENSSL_DIRTY_SHUTDOWN; + if (fd_pair) { + *bev1_out = bufferevent_openssl_socket_new( + base, fd_pair[0], ssl1, state1, flags); + *bev2_out = bufferevent_openssl_socket_new( + base, fd_pair[1], ssl2, state2, flags); + } else { + *bev1_out = bufferevent_openssl_filter_new( + base, underlying_pair[0], ssl1, state1, flags); + *bev2_out = bufferevent_openssl_filter_new( + base, underlying_pair[1], ssl2, state2, flags); + + } + bufferevent_setcb(*bev1_out, respond_to_number, done_writing_cb, + eventcb, (void*)(REGRESS_OPENSSL_CLIENT | (long)type)); + bufferevent_setcb(*bev2_out, respond_to_number, done_writing_cb, + eventcb, (void*)(REGRESS_OPENSSL_SERVER | (long)type)); + + bufferevent_openssl_set_allow_dirty_shutdown(*bev1_out, dirty_shutdown); + bufferevent_openssl_set_allow_dirty_shutdown(*bev2_out, dirty_shutdown); +} + +static void +regress_bufferevent_openssl(void *arg) +{ + struct basic_test_data *data = arg; + + struct bufferevent *bev1, *bev2; + SSL *ssl1, *ssl2; + int flags = BEV_OPT_DEFER_CALLBACKS; + struct bufferevent *bev_ll[2] = { NULL, NULL }; + evutil_socket_t *fd_pair = NULL; + + enum regress_openssl_type type; + type = (enum regress_openssl_type)data->setup_data; + + if (type & REGRESS_OPENSSL_RENEGOTIATE) { + if (OPENSSL_VERSION_NUMBER >= 0x10001000 && + OPENSSL_VERSION_NUMBER < 0x1000104f) { + /* 1.0.1 up to 1.0.1c has a bug where TLS1.1 and 1.2 + * can't renegotiate with themselves. Disable. */ + disable_tls_11_and_12 = 1; + } + renegotiate_at = 600; + } + + ssl1 = SSL_new(get_ssl_ctx()); + ssl2 = SSL_new(get_ssl_ctx()); + + SSL_use_certificate(ssl2, the_cert); + SSL_use_PrivateKey(ssl2, the_key); + + if (!(type & REGRESS_OPENSSL_OPEN)) + flags |= BEV_OPT_CLOSE_ON_FREE; + + if (!(type & REGRESS_OPENSSL_FILTER)) { + tt_assert(type & REGRESS_OPENSSL_SOCKETPAIR); + fd_pair = data->pair; + } else { + bev_ll[0] = bufferevent_socket_new(data->base, data->pair[0], + BEV_OPT_CLOSE_ON_FREE); + bev_ll[1] = bufferevent_socket_new(data->base, data->pair[1], + BEV_OPT_CLOSE_ON_FREE); + } + + open_ssl_bufevs(&bev1, &bev2, data->base, 0, flags, ssl1, ssl2, + fd_pair, bev_ll, type); + + if (!(type & REGRESS_OPENSSL_FILTER)) { + tt_fd_op(bufferevent_getfd(bev1), ==, data->pair[0]); + } else { + tt_ptr_op(bufferevent_get_underlying(bev1), ==, bev_ll[0]); + } + + if (type & REGRESS_OPENSSL_OPEN) { + pending_connect_events = 2; + stop_when_connected = 1; + exit_base = data->base; + event_base_dispatch(data->base); + /* Okay, now the renegotiation is done. Make new + * bufferevents to test opening in BUFFEREVENT_SSL_OPEN */ + flags |= BEV_OPT_CLOSE_ON_FREE; + bufferevent_free(bev1); + bufferevent_free(bev2); + bev1 = bev2 = NULL; + open_ssl_bufevs(&bev1, &bev2, data->base, 1, flags, ssl1, ssl2, + fd_pair, bev_ll, type); + } + + if (!(type & REGRESS_OPENSSL_TIMEOUT)) { + bufferevent_enable(bev1, EV_READ|EV_WRITE); + bufferevent_enable(bev2, EV_READ|EV_WRITE); + + if (!(type & REGRESS_OPENSSL_CLIENT_WRITE)) + evbuffer_add_printf(bufferevent_get_output(bev1), "1\n"); + + event_base_dispatch(data->base); + + tt_assert(test_is_done == 1); + tt_assert(n_connected == 2); + + /* We don't handle shutdown properly yet */ + if (type & REGRESS_OPENSSL_DIRTY_SHUTDOWN) { + tt_int_op(got_close, ==, 1); + tt_int_op(got_error, ==, 0); + } else { + tt_int_op(got_error, ==, 1); + } + tt_int_op(got_timeout, ==, 0); + } else { + struct timeval t = { 2, 0 }; + + bufferevent_enable(bev1, EV_READ|EV_WRITE); + bufferevent_disable(bev2, EV_READ|EV_WRITE); + + bufferevent_set_timeouts(bev1, &t, &t); + + if (!(type & REGRESS_OPENSSL_CLIENT_WRITE)) + evbuffer_add_printf(bufferevent_get_output(bev1), "1\n"); + + event_base_dispatch(data->base); + + tt_assert(test_is_done == 0); + tt_assert(n_connected == 0); + + tt_int_op(got_close, ==, 0); + tt_int_op(got_error, ==, 0); + tt_int_op(got_timeout, ==, 1); + + bufferevent_free(bev2); + } + +end: + return; +} + +static void +acceptcb_deferred(evutil_socket_t fd, short events, void *arg) +{ + struct bufferevent *bev = arg; + bufferevent_enable(bev, EV_READ|EV_WRITE); +} +static void +acceptcb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *addr, int socklen, void *arg) +{ + struct basic_test_data *data = arg; + struct bufferevent *bev; + enum regress_openssl_type type; + SSL *ssl = SSL_new(get_ssl_ctx()); + + type = (enum regress_openssl_type)data->setup_data; + + SSL_use_certificate(ssl, the_cert); + SSL_use_PrivateKey(ssl, the_key); + + bev = bufferevent_openssl_socket_new( + data->base, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + tt_assert(bev); + + bufferevent_setcb(bev, respond_to_number, NULL, eventcb, + (void*)(REGRESS_OPENSSL_SERVER)); + + if (type & REGRESS_OPENSSL_SLEEP) { + struct timeval when = { 1, 0 }; + event_base_once(data->base, -1, EV_TIMEOUT, + acceptcb_deferred, bev, &when); + bufferevent_disable(bev, EV_READ|EV_WRITE); + } else { + bufferevent_enable(bev, EV_READ|EV_WRITE); + } + + /* Only accept once, then disable ourself. */ + evconnlistener_disable(listener); + +end: + ; +} + +struct rwcount +{ + evutil_socket_t fd; + size_t read; + size_t write; +}; +static int +bio_rwcount_new(BIO *b) +{ + BIO_set_init(b, 0); + BIO_set_data(b, NULL); + return 1; +} +static int +bio_rwcount_free(BIO *b) +{ + TT_BLATHER(("bio_rwcount_free: %p", b)); + if (!b) + return 0; + if (BIO_get_shutdown(b)) { + BIO_set_init(b, 0); + BIO_set_data(b, NULL); + } + return 1; +} +static int +bio_rwcount_read(BIO *b, char *out, int outlen) +{ + struct rwcount *rw = BIO_get_data(b); + ev_ssize_t ret = recv(rw->fd, out, outlen, 0); + ++rw->read; + if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { + BIO_set_retry_read(b); + } + return ret; +} +static int +bio_rwcount_write(BIO *b, const char *in, int inlen) +{ + struct rwcount *rw = BIO_get_data(b); + ev_ssize_t ret = send(rw->fd, in, inlen, 0); + ++rw->write; + if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { + BIO_set_retry_write(b); + } + return ret; +} +static long +bio_rwcount_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + struct rwcount *rw = BIO_get_data(b); + long ret = 0; + switch (cmd) { + case BIO_C_GET_FD: + ret = rw->fd; + break; + case BIO_CTRL_GET_CLOSE: + ret = BIO_get_shutdown(b); + break; + case BIO_CTRL_SET_CLOSE: + BIO_set_shutdown(b, (int)num); + break; + case BIO_CTRL_PENDING: + ret = 0; + break; + case BIO_CTRL_WPENDING: + ret = 0; + break; + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + } + return ret; +} +static int +bio_rwcount_puts(BIO *b, const char *s) +{ + return bio_rwcount_write(b, s, strlen(s)); +} +#define BIO_TYPE_LIBEVENT_RWCOUNT 0xff1 +static BIO_METHOD *methods_rwcount; + +static BIO_METHOD * +BIO_s_rwcount(void) +{ + if (methods_rwcount == NULL) { + methods_rwcount = BIO_meth_new(BIO_TYPE_LIBEVENT_RWCOUNT, "rwcount"); + if (methods_rwcount == NULL) + return NULL; + BIO_meth_set_write(methods_rwcount, bio_rwcount_write); + BIO_meth_set_read(methods_rwcount, bio_rwcount_read); + BIO_meth_set_puts(methods_rwcount, bio_rwcount_puts); + BIO_meth_set_ctrl(methods_rwcount, bio_rwcount_ctrl); + BIO_meth_set_create(methods_rwcount, bio_rwcount_new); + BIO_meth_set_destroy(methods_rwcount, bio_rwcount_free); + } + return methods_rwcount; +} +static BIO * +BIO_new_rwcount(int close_flag) +{ + BIO *result; + if (!(result = BIO_new(BIO_s_rwcount()))) + return NULL; + BIO_set_init(result, 1); + BIO_set_data(result, NULL); + BIO_set_shutdown(result, !!close_flag); + return result; +} + +static void +regress_bufferevent_openssl_connect(void *arg) +{ + struct basic_test_data *data = arg; + + struct event_base *base = data->base; + + struct evconnlistener *listener; + struct bufferevent *bev; + struct sockaddr_in sin; + struct sockaddr_storage ss; + ev_socklen_t slen; + SSL *ssl; + struct rwcount rw = { -1, 0, 0 }; + enum regress_openssl_type type; + + type = (enum regress_openssl_type)data->setup_data; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); + + memset(&ss, 0, sizeof(ss)); + slen = sizeof(ss); + + listener = evconnlistener_new_bind(base, acceptcb, data, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + -1, (struct sockaddr *)&sin, sizeof(sin)); + + tt_assert(listener); + tt_assert(evconnlistener_get_fd(listener) >= 0); + + ssl = SSL_new(get_ssl_ctx()); + tt_assert(ssl); + + bev = bufferevent_openssl_socket_new( + data->base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); + tt_assert(bev); + + bufferevent_setcb(bev, respond_to_number, free_on_cb, eventcb, + (void*)(REGRESS_OPENSSL_CLIENT)); + + tt_assert(getsockname(evconnlistener_get_fd(listener), + (struct sockaddr*)&ss, &slen) == 0); + tt_assert(slen == sizeof(struct sockaddr_in)); + tt_int_op(((struct sockaddr*)&ss)->sa_family, ==, AF_INET); + + tt_assert(0 == + bufferevent_socket_connect(bev, (struct sockaddr*)&ss, slen)); + /* Possible only when we have fd, since be_openssl can and will overwrite + * bio otherwise before */ + if (type & REGRESS_OPENSSL_SLEEP) { + BIO *bio; + + rw.fd = bufferevent_getfd(bev); + bio = BIO_new_rwcount(0); + tt_assert(bio); + BIO_set_data(bio, &rw); + SSL_set_bio(ssl, bio, bio); + } + evbuffer_add_printf(bufferevent_get_output(bev), "1\n"); + bufferevent_enable(bev, EV_READ|EV_WRITE); + + event_base_dispatch(base); + + tt_int_op(rw.read, <=, 100); + tt_int_op(rw.write, <=, 100); +end: + evconnlistener_free(listener); +} + +struct wm_context +{ + int server; + int flags; + struct evbuffer *data; + size_t to_read; + size_t wm_high; + size_t limit; + size_t get; + struct bufferevent *bev; + struct wm_context *neighbour; +}; +static void +wm_transfer(struct bufferevent *bev, void *arg) +{ + struct wm_context *ctx = arg; + struct evbuffer *in = bufferevent_get_input(bev); + struct evbuffer *out = bufferevent_get_output(bev); + size_t len = evbuffer_get_length(in); + size_t drain = len < ctx->to_read ? len : ctx->to_read; + + if (ctx->get >= ctx->limit) { + TT_BLATHER(("wm_transfer-%s(%p): break", + ctx->server ? "server" : "client", bev)); + bufferevent_setcb(bev, NULL, NULL, NULL, NULL); + bufferevent_disable(bev, EV_READ); + if (ctx->neighbour->get >= ctx->neighbour->limit) { + event_base_loopbreak(bufferevent_get_base(bev)); + } + } else { + ctx->get += drain; + evbuffer_drain(in, drain); + } + + TT_BLATHER(("wm_transfer-%s(%p): " + "in: " EV_SIZE_FMT ", " + "out: " EV_SIZE_FMT ", " + "got: " EV_SIZE_FMT "", + ctx->server ? "server" : "client", bev, + evbuffer_get_length(in), + evbuffer_get_length(out), + ctx->get)); + + evbuffer_add_buffer_reference(out, ctx->data); +} +static void +wm_eventcb(struct bufferevent *bev, short what, void *arg) +{ + struct wm_context *ctx = arg; + TT_BLATHER(("wm_eventcb-%s(%p): %i", + ctx->server ? "server" : "client", bev, what)); + if (what & BEV_EVENT_CONNECTED) { + } else { + ctx->get = 0; + } +} +static void +wm_acceptcb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *addr, int socklen, void *arg) +{ + struct wm_context *ctx = arg; + struct bufferevent *bev; + struct event_base *base = evconnlistener_get_base(listener); + SSL *ssl = SSL_new(get_ssl_ctx()); + + SSL_use_certificate(ssl, the_cert); + SSL_use_PrivateKey(ssl, the_key); + + bev = bufferevent_openssl_socket_new( + base, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, ctx->flags); + + TT_BLATHER(("wm_transfer-%s(%p): accept", + ctx->server ? "server" : "client", bev)); + + bufferevent_setwatermark(bev, EV_READ, 0, ctx->wm_high); + bufferevent_setcb(bev, wm_transfer, NULL, wm_eventcb, ctx); + bufferevent_enable(bev, EV_READ|EV_WRITE); + ctx->bev = bev; + + /* Only accept once, then disable ourself. */ + evconnlistener_disable(listener); +} +static void +regress_bufferevent_openssl_wm(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct evconnlistener *listener; + struct bufferevent *bev; + struct sockaddr_in sin; + struct sockaddr_storage ss; + enum regress_openssl_type type = + (enum regress_openssl_type)data->setup_data; + int bev_flags = BEV_OPT_CLOSE_ON_FREE; + ev_socklen_t slen; + SSL *ssl; + struct wm_context client, server; + char *payload; + size_t payload_len = 1<<10; + size_t wm_high = 5<<10; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); + + memset(&ss, 0, sizeof(ss)); + slen = sizeof(ss); + + if (type & REGRESS_DEFERRED_CALLBACKS) + bev_flags |= BEV_OPT_DEFER_CALLBACKS; + + memset(&client, 0, sizeof(client)); + memset(&server, 0, sizeof(server)); + client.server = 0; + server.server = 1; + client.flags = server.flags = bev_flags; + client.data = evbuffer_new(); + server.data = evbuffer_new(); + payload = calloc(1, payload_len); + memset(payload, 'A', payload_len); + evbuffer_add(server.data, payload, payload_len); + evbuffer_add(client.data, payload, payload_len); + client.wm_high = server.wm_high = wm_high; + client.limit = server.limit = wm_high<<3; + client.to_read = server.to_read = payload_len>>1; + + TT_BLATHER(("openssl_wm: " + "payload_len = " EV_SIZE_FMT ", " + "wm_high = " EV_SIZE_FMT ", " + "limit = " EV_SIZE_FMT ", " + "to_read: " EV_SIZE_FMT "", + payload_len, + wm_high, + server.limit, + server.to_read)); + + listener = evconnlistener_new_bind(base, wm_acceptcb, &server, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + -1, (struct sockaddr *)&sin, sizeof(sin)); + + tt_assert(listener); + tt_assert(evconnlistener_get_fd(listener) >= 0); + + ssl = SSL_new(get_ssl_ctx()); + tt_assert(ssl); + + if (type & REGRESS_OPENSSL_FILTER) { + bev = bufferevent_socket_new(data->base, -1, client.flags); + tt_assert(bev); + bev = bufferevent_openssl_filter_new( + base, bev, ssl, BUFFEREVENT_SSL_CONNECTING, client.flags); + } else { + bev = bufferevent_openssl_socket_new( + data->base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, + client.flags); + } + tt_assert(bev); + client.bev = bev; + + server.neighbour = &client; + client.neighbour = &server; + + bufferevent_setwatermark(bev, EV_READ, 0, client.wm_high); + bufferevent_setcb(bev, wm_transfer, NULL, wm_eventcb, &client); + + tt_assert(getsockname(evconnlistener_get_fd(listener), + (struct sockaddr*)&ss, &slen) == 0); + + tt_assert(!bufferevent_socket_connect(bev, (struct sockaddr*)&ss, slen)); + tt_assert(!evbuffer_add_buffer_reference(bufferevent_get_output(bev), client.data)); + tt_assert(!bufferevent_enable(bev, EV_READ|EV_WRITE)); + + event_base_dispatch(base); + + tt_int_op(client.get, ==, client.limit); + tt_int_op(server.get, ==, server.limit); + +end: + free(payload); + evbuffer_free(client.data); + evbuffer_free(server.data); + evconnlistener_free(listener); + bufferevent_free(client.bev); + bufferevent_free(server.bev); + + /* XXX: by some reason otherise there is a leak */ + if (!(type & REGRESS_OPENSSL_FILTER)) + event_base_loop(base, EVLOOP_ONCE); +} + +struct testcase_t ssl_testcases[] = { +#define T(a) ((void *)(a)) + { "bufferevent_socketpair", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, T(REGRESS_OPENSSL_SOCKETPAIR) }, + { "bufferevent_socketpair_write_after_connect", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR|REGRESS_OPENSSL_CLIENT_WRITE) }, + { "bufferevent_filter", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, T(REGRESS_OPENSSL_FILTER) }, + { "bufferevent_filter_write_after_connect", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_FILTER|REGRESS_OPENSSL_CLIENT_WRITE) }, + { "bufferevent_renegotiate_socketpair", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_RENEGOTIATE) }, + { "bufferevent_renegotiate_filter", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_RENEGOTIATE) }, + { "bufferevent_socketpair_startopen", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_OPEN) }, + { "bufferevent_filter_startopen", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_OPEN) }, + + { "bufferevent_socketpair_dirty_shutdown", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_DIRTY_SHUTDOWN) }, + { "bufferevent_filter_dirty_shutdown", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_DIRTY_SHUTDOWN) }, + { "bufferevent_renegotiate_socketpair_dirty_shutdown", + regress_bufferevent_openssl, + TT_ISOLATED, + &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_RENEGOTIATE | REGRESS_OPENSSL_DIRTY_SHUTDOWN) }, + { "bufferevent_renegotiate_filter_dirty_shutdown", + regress_bufferevent_openssl, + TT_ISOLATED, + &ssl_setup, + T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_RENEGOTIATE | REGRESS_OPENSSL_DIRTY_SHUTDOWN) }, + { "bufferevent_socketpair_startopen_dirty_shutdown", + regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_OPEN | REGRESS_OPENSSL_DIRTY_SHUTDOWN) }, + { "bufferevent_filter_startopen_dirty_shutdown", + regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_OPEN | REGRESS_OPENSSL_DIRTY_SHUTDOWN) }, + + { "bufferevent_socketpair_fd", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_FD) }, + { "bufferevent_socketpair_freed", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_FREED) }, + { "bufferevent_socketpair_freed_fd", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_FREED | REGRESS_OPENSSL_FD) }, + { "bufferevent_filter_freed_fd", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_FREED | REGRESS_OPENSSL_FD) }, + + { "bufferevent_socketpair_timeout", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_TIMEOUT) }, + { "bufferevent_socketpair_timeout_freed_fd", regress_bufferevent_openssl, + TT_ISOLATED, &ssl_setup, + T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_TIMEOUT | REGRESS_OPENSSL_FREED | REGRESS_OPENSSL_FD) }, + + { "bufferevent_connect", regress_bufferevent_openssl_connect, + TT_FORK|TT_NEED_BASE, &ssl_setup, NULL }, + { "bufferevent_connect_sleep", regress_bufferevent_openssl_connect, + TT_FORK|TT_NEED_BASE, &ssl_setup, T(REGRESS_OPENSSL_SLEEP) }, + + { "bufferevent_wm", regress_bufferevent_openssl_wm, + TT_FORK|TT_NEED_BASE, &ssl_setup, NULL }, + { "bufferevent_wm_filter", regress_bufferevent_openssl_wm, + TT_FORK|TT_NEED_BASE, &ssl_setup, T(REGRESS_OPENSSL_FILTER) }, + { "bufferevent_wm_defer", regress_bufferevent_openssl_wm, + TT_FORK|TT_NEED_BASE, &ssl_setup, T(REGRESS_DEFERRED_CALLBACKS) }, + { "bufferevent_wm_filter_defer", regress_bufferevent_openssl_wm, + TT_FORK|TT_NEED_BASE, &ssl_setup, T(REGRESS_OPENSSL_FILTER|REGRESS_DEFERRED_CALLBACKS) }, + +#undef T + + END_OF_TESTCASES, +}; diff --git a/libevent/test/regress_testutils.c b/libevent/test/regress_testutils.c new file mode 100644 index 0000000..b0ce7db --- /dev/null +++ b/libevent/test/regress_testutils.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2010-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <ws2tcpip.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <sys/queue.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <signal.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#endif +#ifdef EVENT__HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "event2/dns.h" +#include "event2/dns_struct.h" +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/util.h" +#include "event2/listener.h" +#include "event2/bufferevent.h" +#include "log-internal.h" +#include "regress.h" +#include "regress_testutils.h" + +/* globals */ +static struct evdns_server_port *dns_port; +evutil_socket_t dns_sock = -1; + +/* Helper: return the port that a socket is bound on, in host order. */ +int +regress_get_socket_port(evutil_socket_t fd) +{ + struct sockaddr_storage ss; + ev_socklen_t socklen = sizeof(ss); + if (getsockname(fd, (struct sockaddr*)&ss, &socklen) != 0) + return -1; + if (ss.ss_family == AF_INET) + return ntohs( ((struct sockaddr_in*)&ss)->sin_port); + else if (ss.ss_family == AF_INET6) + return ntohs( ((struct sockaddr_in6*)&ss)->sin6_port); + else + return -1; +} + +struct evdns_server_port * +regress_get_dnsserver(struct event_base *base, + ev_uint16_t *portnum, + evutil_socket_t *psock, + evdns_request_callback_fn_type cb, + void *arg) +{ + struct evdns_server_port *port = NULL; + evutil_socket_t sock; + struct sockaddr_in my_addr; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + tt_abort_perror("socket"); + } + + evutil_make_socket_nonblocking(sock); + + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(*portnum); + my_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + if (bind(sock, (struct sockaddr*)&my_addr, sizeof(my_addr)) < 0) { + evutil_closesocket(sock); + tt_abort_perror("bind"); + } + port = evdns_add_server_port_with_base(base, sock, 0, cb, arg); + if (!*portnum) + *portnum = regress_get_socket_port(sock); + if (psock) + *psock = sock; + + return port; +end: + return NULL; +} + +void +regress_clean_dnsserver(void) +{ + if (dns_port) { + evdns_close_server_port(dns_port); + dns_port = NULL; + } + if (dns_sock >= 0) { + evutil_closesocket(dns_sock); + dns_sock = -1; + } +} + +static void strtolower(char *s) +{ + while (*s) { + *s = EVUTIL_TOLOWER_(*s); + ++s; + } +} +void +regress_dns_server_cb(struct evdns_server_request *req, void *data) +{ + struct regress_dns_server_table *tab = data; + char *question; + + if (req->nquestions != 1) + TT_DIE(("Only handling one question at a time; got %d", + req->nquestions)); + + question = req->questions[0]->name; + + while (tab->q && evutil_ascii_strcasecmp(question, tab->q) && + strcmp("*", tab->q)) + ++tab; + if (tab->q == NULL) + TT_DIE(("Unexpected question: '%s'", question)); + + ++tab->seen; + + if (tab->lower) + strtolower(question); + + if (!strcmp(tab->anstype, "err")) { + int err = atoi(tab->ans); + tt_assert(! evdns_server_request_respond(req, err)); + return; + } else if (!strcmp(tab->anstype, "errsoa")) { + int err = atoi(tab->ans); + char soa_record[] = + "\x04" "dns1" "\x05" "icann" "\x03" "org" "\0" + "\x0a" "hostmaster" "\x05" "icann" "\x03" "org" "\0" + "\x77\xde\x5e\xba" /* serial */ + "\x00\x00\x1c\x20" /* refreshtime = 2h */ + "\x00\x00\x0e\x10" /* retry = 1h */ + "\x00\x12\x75\x00" /* expiration = 14d */ + "\x00\x00\x0e\x10" /* min.ttl = 1h */ + ; + evdns_server_request_add_reply( + req, EVDNS_AUTHORITY_SECTION, + "example.com", EVDNS_TYPE_SOA, EVDNS_CLASS_INET, + 42, sizeof(soa_record) - 1, 0, soa_record); + tt_assert(! evdns_server_request_respond(req, err)); + return; + } else if (!strcmp(tab->anstype, "A")) { + struct in_addr in; + if (!evutil_inet_pton(AF_INET, tab->ans, &in)) { + TT_DIE(("Bad A value %s in table", tab->ans)); + } + evdns_server_request_add_a_reply(req, question, 1, &in.s_addr, + 100); + } else if (!strcmp(tab->anstype, "AAAA")) { + struct in6_addr in6; + if (!evutil_inet_pton(AF_INET6, tab->ans, &in6)) { + TT_DIE(("Bad AAAA value %s in table", tab->ans)); + } + evdns_server_request_add_aaaa_reply(req, + question, 1, &in6.s6_addr, 100); + } else { + TT_DIE(("Weird table entry with type '%s'", tab->anstype)); + } + tt_assert(! evdns_server_request_respond(req, 0)) + return; +end: + tt_want(! evdns_server_request_drop(req)); +} + +int +regress_dnsserver(struct event_base *base, ev_uint16_t *port, + struct regress_dns_server_table *search_table) +{ + dns_port = regress_get_dnsserver(base, port, &dns_sock, + regress_dns_server_cb, search_table); + return dns_port != NULL; +} + +int +regress_get_listener_addr(struct evconnlistener *lev, + struct sockaddr *sa, ev_socklen_t *socklen) +{ + evutil_socket_t s = evconnlistener_get_fd(lev); + if (s <= 0) + return -1; + return getsockname(s, sa, socklen); +} diff --git a/libevent/test/regress_testutils.h b/libevent/test/regress_testutils.h new file mode 100644 index 0000000..040516a --- /dev/null +++ b/libevent/test/regress_testutils.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef REGRESS_TESTUTILS_H_INCLUDED_ +#define REGRESS_TESTUTILS_H_INCLUDED_ + +#include "event2/dns.h" + +struct regress_dns_server_table { + const char *q; + const char *anstype; + const char *ans; + int seen; + int lower; +}; + +struct evdns_server_port * +regress_get_dnsserver(struct event_base *base, + ev_uint16_t *portnum, + evutil_socket_t *psock, + evdns_request_callback_fn_type cb, + void *arg); + +/* Helper: return the port that a socket is bound on, in host order. */ +int regress_get_socket_port(evutil_socket_t fd); + +/* used to look up pre-canned responses in a search table */ +void regress_dns_server_cb( + struct evdns_server_request *req, void *data); + +/* globally allocates a dns server that serves from a search table */ +int regress_dnsserver(struct event_base *base, ev_uint16_t *port, + struct regress_dns_server_table *seach_table); + +/* clean up the global dns server resources */ +void regress_clean_dnsserver(void); + +struct evconnlistener; +struct sockaddr; +int regress_get_listener_addr(struct evconnlistener *lev, + struct sockaddr *sa, ev_socklen_t *socklen); + +#endif /* REGRESS_TESTUTILS_H_INCLUDED_ */ + diff --git a/libevent/test/regress_thread.c b/libevent/test/regress_thread.c new file mode 100644 index 0000000..1e0ce41 --- /dev/null +++ b/libevent/test/regress_thread.c @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" + +/* The old tests here need assertions to work. */ +#undef NDEBUG + +#include "event2/event-config.h" + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef EVENT__HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef EVENT__HAVE_PTHREADS +#include <pthread.h> +#elif defined(_WIN32) +#include <process.h> +#endif +#include <assert.h> +#ifdef EVENT__HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <time.h> + +#include "sys/queue.h" + +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/thread.h" +#include "event2/util.h" +#include "evthread-internal.h" +#include "event-internal.h" +#include "defer-internal.h" +#include "regress.h" +#include "tinytest_macros.h" +#include "time-internal.h" +#include "regress_thread.h" + +struct cond_wait { + void *lock; + void *cond; +}; + +static void +wake_all_timeout(evutil_socket_t fd, short what, void *arg) +{ + struct cond_wait *cw = arg; + EVLOCK_LOCK(cw->lock, 0); + EVTHREAD_COND_BROADCAST(cw->cond); + EVLOCK_UNLOCK(cw->lock, 0); + +} + +static void +wake_one_timeout(evutil_socket_t fd, short what, void *arg) +{ + struct cond_wait *cw = arg; + EVLOCK_LOCK(cw->lock, 0); + EVTHREAD_COND_SIGNAL(cw->cond); + EVLOCK_UNLOCK(cw->lock, 0); +} + +#define NUM_THREADS 100 +#define NUM_ITERATIONS 100 +void *count_lock; +static int count; + +static THREAD_FN +basic_thread(void *arg) +{ + struct cond_wait cw; + struct event_base *base = arg; + struct event ev; + int i = 0; + + EVTHREAD_ALLOC_LOCK(cw.lock, 0); + EVTHREAD_ALLOC_COND(cw.cond); + assert(cw.lock); + assert(cw.cond); + + evtimer_assign(&ev, base, wake_all_timeout, &cw); + for (i = 0; i < NUM_ITERATIONS; i++) { + struct timeval tv; + evutil_timerclear(&tv); + tv.tv_sec = 0; + tv.tv_usec = 3000; + + EVLOCK_LOCK(cw.lock, 0); + /* we need to make sure that event does not happen before + * we get to wait on the conditional variable */ + assert(evtimer_add(&ev, &tv) == 0); + + assert(EVTHREAD_COND_WAIT(cw.cond, cw.lock) == 0); + EVLOCK_UNLOCK(cw.lock, 0); + + EVLOCK_LOCK(count_lock, 0); + ++count; + EVLOCK_UNLOCK(count_lock, 0); + } + + /* exit the loop only if all threads fired all timeouts */ + EVLOCK_LOCK(count_lock, 0); + if (count >= NUM_THREADS * NUM_ITERATIONS) + event_base_loopexit(base, NULL); + EVLOCK_UNLOCK(count_lock, 0); + + EVTHREAD_FREE_LOCK(cw.lock, 0); + EVTHREAD_FREE_COND(cw.cond); + + THREAD_RETURN(); +} + +static int notification_fd_used = 0; +#ifndef _WIN32 +static int got_sigchld = 0; +static void +sigchld_cb(evutil_socket_t fd, short event, void *arg) +{ + struct timeval tv; + struct event_base *base = arg; + + got_sigchld++; + tv.tv_usec = 100000; + tv.tv_sec = 0; + event_base_loopexit(base, &tv); +} + + +static void +notify_fd_cb(evutil_socket_t fd, short event, void *arg) +{ + ++notification_fd_used; +} +#endif + +static void +thread_basic(void *arg) +{ + THREAD_T threads[NUM_THREADS]; + struct event ev; + struct timeval tv; + int i; + struct basic_test_data *data = arg; + struct event_base *base = data->base; + + struct event *notification_event = NULL; + struct event *sigchld_event = NULL; + + EVTHREAD_ALLOC_LOCK(count_lock, 0); + tt_assert(count_lock); + + tt_assert(base); + if (evthread_make_base_notifiable(base)<0) { + tt_abort_msg("Couldn't make base notifiable!"); + } + +#ifndef _WIN32 + if (data->setup_data && !strcmp(data->setup_data, "forking")) { + pid_t pid; + int status; + sigchld_event = evsignal_new(base, SIGCHLD, sigchld_cb, base); + /* This piggybacks on the th_notify_fd weirdly, and looks + * inside libevent internals. Not a good idea in non-testing + * code! */ + notification_event = event_new(base, + base->th_notify_fd[0], EV_READ|EV_PERSIST, notify_fd_cb, + NULL); + event_add(sigchld_event, NULL); + event_add(notification_event, NULL); + + if ((pid = fork()) == 0) { + event_del(notification_event); + if (event_reinit(base) < 0) { + TT_FAIL(("reinit")); + exit(1); + } + event_assign(notification_event, base, + base->th_notify_fd[0], EV_READ|EV_PERSIST, + notify_fd_cb, NULL); + event_add(notification_event, NULL); + goto child; + } + + event_base_dispatch(base); + + if (waitpid(pid, &status, 0) == -1) + tt_abort_perror("waitpid"); + TT_BLATHER(("Waitpid okay\n")); + + tt_assert(got_sigchld); + tt_int_op(notification_fd_used, ==, 0); + + goto end; + } + +child: +#endif + for (i = 0; i < NUM_THREADS; ++i) + THREAD_START(threads[i], basic_thread, base); + + evtimer_assign(&ev, base, NULL, NULL); + evutil_timerclear(&tv); + tv.tv_sec = 1000; + event_add(&ev, &tv); + + event_base_dispatch(base); + + for (i = 0; i < NUM_THREADS; ++i) + THREAD_JOIN(threads[i]); + + event_del(&ev); + + tt_int_op(count, ==, NUM_THREADS * NUM_ITERATIONS); + + EVTHREAD_FREE_LOCK(count_lock, 0); + + TT_BLATHER(("notifiations==%d", notification_fd_used)); + +end: + + if (notification_event) + event_free(notification_event); + if (sigchld_event) + event_free(sigchld_event); +} + +#undef NUM_THREADS +#define NUM_THREADS 10 + +struct alerted_record { + struct cond_wait *cond; + struct timeval delay; + struct timeval alerted_at; + int timed_out; +}; + +static THREAD_FN +wait_for_condition(void *arg) +{ + struct alerted_record *rec = arg; + int r; + + EVLOCK_LOCK(rec->cond->lock, 0); + if (rec->delay.tv_sec || rec->delay.tv_usec) { + r = EVTHREAD_COND_WAIT_TIMED(rec->cond->cond, rec->cond->lock, + &rec->delay); + } else { + r = EVTHREAD_COND_WAIT(rec->cond->cond, rec->cond->lock); + } + EVLOCK_UNLOCK(rec->cond->lock, 0); + + evutil_gettimeofday(&rec->alerted_at, NULL); + if (r == 1) + rec->timed_out = 1; + + THREAD_RETURN(); +} + +static void +thread_conditions_simple(void *arg) +{ + struct timeval tv_signal, tv_timeout, tv_broadcast; + struct alerted_record alerted[NUM_THREADS]; + THREAD_T threads[NUM_THREADS]; + struct cond_wait cond; + int i; + struct timeval launched_at; + struct event wake_one; + struct event wake_all; + struct basic_test_data *data = arg; + struct event_base *base = data->base; + int n_timed_out=0, n_signal=0, n_broadcast=0; + + tv_signal.tv_sec = tv_timeout.tv_sec = tv_broadcast.tv_sec = 0; + tv_signal.tv_usec = 30*1000; + tv_timeout.tv_usec = 150*1000; + tv_broadcast.tv_usec = 500*1000; + + EVTHREAD_ALLOC_LOCK(cond.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + EVTHREAD_ALLOC_COND(cond.cond); + tt_assert(cond.lock); + tt_assert(cond.cond); + for (i = 0; i < NUM_THREADS; ++i) { + memset(&alerted[i], 0, sizeof(struct alerted_record)); + alerted[i].cond = &cond; + } + + /* Threads 5 and 6 will be allowed to time out */ + memcpy(&alerted[5].delay, &tv_timeout, sizeof(tv_timeout)); + memcpy(&alerted[6].delay, &tv_timeout, sizeof(tv_timeout)); + + evtimer_assign(&wake_one, base, wake_one_timeout, &cond); + evtimer_assign(&wake_all, base, wake_all_timeout, &cond); + + evutil_gettimeofday(&launched_at, NULL); + + /* Launch the threads... */ + for (i = 0; i < NUM_THREADS; ++i) { + THREAD_START(threads[i], wait_for_condition, &alerted[i]); + } + + /* Start the timers... */ + tt_int_op(event_add(&wake_one, &tv_signal), ==, 0); + tt_int_op(event_add(&wake_all, &tv_broadcast), ==, 0); + + /* And run for a bit... */ + event_base_dispatch(base); + + /* And wait till the threads are done. */ + for (i = 0; i < NUM_THREADS; ++i) + THREAD_JOIN(threads[i]); + + /* Now, let's see what happened. At least one of 5 or 6 should + * have timed out. */ + n_timed_out = alerted[5].timed_out + alerted[6].timed_out; + tt_int_op(n_timed_out, >=, 1); + tt_int_op(n_timed_out, <=, 2); + + for (i = 0; i < NUM_THREADS; ++i) { + const struct timeval *target_delay; + struct timeval target_time, actual_delay; + if (alerted[i].timed_out) { + TT_BLATHER(("%d looks like a timeout\n", i)); + target_delay = &tv_timeout; + tt_assert(i == 5 || i == 6); + } else if (evutil_timerisset(&alerted[i].alerted_at)) { + long diff1,diff2; + evutil_timersub(&alerted[i].alerted_at, + &launched_at, &actual_delay); + diff1 = timeval_msec_diff(&actual_delay, + &tv_signal); + diff2 = timeval_msec_diff(&actual_delay, + &tv_broadcast); + if (labs(diff1) < labs(diff2)) { + TT_BLATHER(("%d looks like a signal\n", i)); + target_delay = &tv_signal; + ++n_signal; + } else { + TT_BLATHER(("%d looks like a broadcast\n", i)); + target_delay = &tv_broadcast; + ++n_broadcast; + } + } else { + TT_FAIL(("Thread %d never got woken", i)); + continue; + } + evutil_timeradd(target_delay, &launched_at, &target_time); + test_timeval_diff_leq(&target_time, &alerted[i].alerted_at, + 0, 200); + } + tt_int_op(n_broadcast + n_signal + n_timed_out, ==, NUM_THREADS); + tt_int_op(n_signal, ==, 1); + +end: + EVTHREAD_FREE_LOCK(cond.lock, EVTHREAD_LOCKTYPE_RECURSIVE); + EVTHREAD_FREE_COND(cond.cond); +} + +#define CB_COUNT 128 +#define QUEUE_THREAD_COUNT 8 + +static void +SLEEP_MS(int ms) +{ + struct timeval tv; + tv.tv_sec = ms/1000; + tv.tv_usec = (ms%1000)*1000; + evutil_usleep_(&tv); +} + +struct deferred_test_data { + struct event_callback cbs[CB_COUNT]; + struct event_base *queue; +}; + +static struct timeval timer_start = {0,0}; +static struct timeval timer_end = {0,0}; +static unsigned callback_count = 0; +static THREAD_T load_threads[QUEUE_THREAD_COUNT]; +static struct deferred_test_data deferred_data[QUEUE_THREAD_COUNT]; + +static void +deferred_callback(struct event_callback *cb, void *arg) +{ + SLEEP_MS(1); + callback_count += 1; +} + +static THREAD_FN +load_deferred_queue(void *arg) +{ + struct deferred_test_data *data = arg; + size_t i; + + for (i = 0; i < CB_COUNT; ++i) { + event_deferred_cb_init_(&data->cbs[i], 0, deferred_callback, + NULL); + event_deferred_cb_schedule_(data->queue, &data->cbs[i]); + SLEEP_MS(1); + } + + THREAD_RETURN(); +} + +static void +timer_callback(evutil_socket_t fd, short what, void *arg) +{ + evutil_gettimeofday(&timer_end, NULL); +} + +static void +start_threads_callback(evutil_socket_t fd, short what, void *arg) +{ + int i; + + for (i = 0; i < QUEUE_THREAD_COUNT; ++i) { + THREAD_START(load_threads[i], load_deferred_queue, + &deferred_data[i]); + } +} + +static void +thread_deferred_cb_skew(void *arg) +{ + struct timeval tv_timer = {1, 0}; + struct event_base *base = NULL; + struct event_config *cfg = NULL; + struct timeval elapsed; + int elapsed_usec; + int i; + + cfg = event_config_new(); + tt_assert(cfg); + event_config_set_max_dispatch_interval(cfg, NULL, 16, 0); + + base = event_base_new_with_config(cfg); + tt_assert(base); + + for (i = 0; i < QUEUE_THREAD_COUNT; ++i) + deferred_data[i].queue = base; + + evutil_gettimeofday(&timer_start, NULL); + event_base_once(base, -1, EV_TIMEOUT, timer_callback, NULL, + &tv_timer); + event_base_once(base, -1, EV_TIMEOUT, start_threads_callback, + NULL, NULL); + event_base_dispatch(base); + + evutil_timersub(&timer_end, &timer_start, &elapsed); + TT_BLATHER(("callback count, %u", callback_count)); + elapsed_usec = + (unsigned)(elapsed.tv_sec*1000000 + elapsed.tv_usec); + TT_BLATHER(("elapsed time, %u usec", elapsed_usec)); + + /* XXX be more intelligent here. just make sure skew is + * within .4 seconds for now. */ + tt_assert(elapsed_usec >= 600000 && elapsed_usec <= 1400000); + +end: + for (i = 0; i < QUEUE_THREAD_COUNT; ++i) + THREAD_JOIN(load_threads[i]); + if (base) + event_base_free(base); + if (cfg) + event_config_free(cfg); +} + +static struct event time_events[5]; +static struct timeval times[5]; +static struct event_base *exit_base = NULL; +static void +note_time_cb(evutil_socket_t fd, short what, void *arg) +{ + evutil_gettimeofday(arg, NULL); + if (arg == ×[4]) { + event_base_loopbreak(exit_base); + } +} +static THREAD_FN +register_events_subthread(void *arg) +{ + struct timeval tv = {0,0}; + SLEEP_MS(100); + event_active(&time_events[0], EV_TIMEOUT, 1); + SLEEP_MS(100); + event_active(&time_events[1], EV_TIMEOUT, 1); + SLEEP_MS(100); + tv.tv_usec = 100*1000; + event_add(&time_events[2], &tv); + tv.tv_usec = 150*1000; + event_add(&time_events[3], &tv); + SLEEP_MS(200); + event_active(&time_events[4], EV_TIMEOUT, 1); + + THREAD_RETURN(); +} + +static void +thread_no_events(void *arg) +{ + THREAD_T thread; + struct basic_test_data *data = arg; + struct timeval starttime, endtime; + int i; + exit_base = data->base; + + memset(times,0,sizeof(times)); + for (i=0;i<5;++i) { + event_assign(&time_events[i], data->base, + -1, 0, note_time_cb, ×[i]); + } + + evutil_gettimeofday(&starttime, NULL); + THREAD_START(thread, register_events_subthread, data->base); + event_base_loop(data->base, EVLOOP_NO_EXIT_ON_EMPTY); + evutil_gettimeofday(&endtime, NULL); + tt_assert(event_base_got_break(data->base)); + THREAD_JOIN(thread); + for (i=0; i<5; ++i) { + struct timeval diff; + double sec; + evutil_timersub(×[i], &starttime, &diff); + sec = diff.tv_sec + diff.tv_usec/1.0e6; + TT_BLATHER(("event %d at %.4f seconds", i, sec)); + } + test_timeval_diff_eq(&starttime, ×[0], 100); + test_timeval_diff_eq(&starttime, ×[1], 200); + test_timeval_diff_eq(&starttime, ×[2], 400); + test_timeval_diff_eq(&starttime, ×[3], 450); + test_timeval_diff_eq(&starttime, ×[4], 500); + test_timeval_diff_eq(&starttime, &endtime, 500); + +end: + ; +} + +#define TEST(name, f) \ + { #name, thread_##name, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE|(f), \ + &basic_setup, NULL } + +struct testcase_t thread_testcases[] = { + { "basic", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE, + &basic_setup, NULL }, +#ifndef _WIN32 + { "forking", thread_basic, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE, + &basic_setup, (char*)"forking" }, +#endif + TEST(conditions_simple, TT_RETRIABLE), + { "deferred_cb_skew", thread_deferred_cb_skew, + TT_FORK|TT_NEED_THREADS|TT_OFF_BY_DEFAULT, + &basic_setup, NULL }, +#ifndef _WIN32 + /****** XXX TODO FIXME windows seems to be having some timing trouble, + * looking into it now. / ellzey + ******/ + TEST(no_events, TT_RETRIABLE), +#endif + END_OF_TESTCASES +}; + diff --git a/libevent/test/regress_thread.h b/libevent/test/regress_thread.h new file mode 100644 index 0000000..a954fef --- /dev/null +++ b/libevent/test/regress_thread.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef REGRESS_THREAD_H_INCLUDED_ +#define REGRESS_THREAD_H_INCLUDED_ + +#if defined(_WIN32) /** _WIN32 */ +#define THREAD_T void * /* HANDLE */ +#define THREAD_FN unsigned __stdcall +#define THREAD_RETURN() return (0) +#define THREAD_SELF() GetCurrentThreadId() +#define THREAD_START(threadvar, fn, arg) do { \ + uintptr_t threadhandle = _beginthreadex(NULL,0,fn,(arg),0,NULL); \ + (threadvar) = (THREAD_T)threadhandle; \ + thread_setup(threadvar); \ +} while (0) +#define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE) +#else /* !_WIN32 */ +#include <pthread.h> +#define THREAD_T pthread_t +#define THREAD_FN void * +#define THREAD_RETURN() return (NULL) +#define THREAD_SELF() pthread_self() +#define THREAD_START(threadvar, fn, arg) do { \ + if (!pthread_create(&(threadvar), NULL, fn, arg)) \ + thread_setup(threadvar); \ +} while (0) +#define THREAD_JOIN(th) pthread_join(th, NULL) +#endif /* \!_WIN32 */ + +void thread_setup(THREAD_T pthread); + +#endif diff --git a/libevent/test/regress_util.c b/libevent/test/regress_util.c new file mode 100644 index 0000000..45caa27 --- /dev/null +++ b/libevent/test/regress_util.c @@ -0,0 +1,1681 @@ +/* + * Copyright (c) 2009-2012 Nick Mathewson and Niels Provos + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** For event_debug() usage/coverage */ +#define EVENT_VISIBILITY_WANT_DLLIMPORT + +#include "../util-internal.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <ws2tcpip.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> + +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#endif +#ifdef EVENT__HAVE_NETINET_IN6_H +#include <netinet/in6.h> +#endif +#ifdef EVENT__HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event2/event.h" +#include "event2/util.h" +#include "../ipv6-internal.h" +#include "../log-internal.h" +#include "../strlcpy-internal.h" +#include "../mm-internal.h" +#include "../time-internal.h" + +#include "regress.h" + +enum entry_status { NORMAL, CANONICAL, BAD }; + +/* This is a big table of results we expect from generating and parsing */ +static struct ipv4_entry { + const char *addr; + ev_uint32_t res; + enum entry_status status; +} ipv4_entries[] = { + { "1.2.3.4", 0x01020304u, CANONICAL }, + { "255.255.255.255", 0xffffffffu, CANONICAL }, + { "256.0.0.0", 0, BAD }, + { "ABC", 0, BAD }, + { "1.2.3.4.5", 0, BAD }, + { "176.192.208.244", 0xb0c0d0f4, CANONICAL }, + { NULL, 0, BAD }, +}; + +static struct ipv6_entry { + const char *addr; + ev_uint32_t res[4]; + enum entry_status status; +} ipv6_entries[] = { + { "::", { 0, 0, 0, 0, }, CANONICAL }, + { "0:0:0:0:0:0:0:0", { 0, 0, 0, 0, }, NORMAL }, + { "::1", { 0, 0, 0, 1, }, CANONICAL }, + { "::1.2.3.4", { 0, 0, 0, 0x01020304, }, CANONICAL }, + { "ffff:1::", { 0xffff0001u, 0, 0, 0, }, CANONICAL }, + { "ffff:0000::", { 0xffff0000u, 0, 0, 0, }, NORMAL }, + { "ffff::1234", { 0xffff0000u, 0, 0, 0x1234, }, CANONICAL }, + { "0102::1.2.3.4", {0x01020000u, 0, 0, 0x01020304u }, NORMAL }, + { "::9:c0a8:1:1", { 0, 0, 0x0009c0a8u, 0x00010001u }, CANONICAL }, + { "::ffff:1.2.3.4", { 0, 0, 0x000ffffu, 0x01020304u }, CANONICAL }, + { "FFFF::", { 0xffff0000u, 0, 0, 0 }, NORMAL }, + { "foobar.", { 0, 0, 0, 0 }, BAD }, + { "foobar", { 0, 0, 0, 0 }, BAD }, + { "fo:obar", { 0, 0, 0, 0 }, BAD }, + { "ffff", { 0, 0, 0, 0 }, BAD }, + { "fffff::", { 0, 0, 0, 0 }, BAD }, + { "fffff::", { 0, 0, 0, 0 }, BAD }, + { "::1.0.1.1000", { 0, 0, 0, 0 }, BAD }, + { "1:2:33333:4::", { 0, 0, 0, 0 }, BAD }, + { "1:2:3:4:5:6:7:8:9", { 0, 0, 0, 0 }, BAD }, + { "1::2::3", { 0, 0, 0, 0 }, BAD }, + { ":::1", { 0, 0, 0, 0 }, BAD }, + { NULL, { 0, 0, 0, 0, }, BAD }, +}; + +static void +regress_ipv4_parse(void *ptr) +{ + int i; + for (i = 0; ipv4_entries[i].addr; ++i) { + char written[128]; + struct ipv4_entry *ent = &ipv4_entries[i]; + struct in_addr in; + int r; + r = evutil_inet_pton(AF_INET, ent->addr, &in); + if (r == 0) { + if (ent->status != BAD) { + TT_FAIL(("%s did not parse, but it's a good address!", + ent->addr)); + } + continue; + } + if (ent->status == BAD) { + TT_FAIL(("%s parsed, but we expected an error", ent->addr)); + continue; + } + if (ntohl(in.s_addr) != ent->res) { + TT_FAIL(("%s parsed to %lx, but we expected %lx", ent->addr, + (unsigned long)ntohl(in.s_addr), + (unsigned long)ent->res)); + continue; + } + if (ent->status == CANONICAL) { + const char *w = evutil_inet_ntop(AF_INET, &in, written, + sizeof(written)); + if (!w) { + TT_FAIL(("Tried to write out %s; got NULL.", ent->addr)); + continue; + } + if (strcmp(written, ent->addr)) { + TT_FAIL(("Tried to write out %s; got %s", + ent->addr, written)); + continue; + } + } + + } + +} + +static void +regress_ipv6_parse(void *ptr) +{ +#ifdef AF_INET6 + int i, j; + + for (i = 0; ipv6_entries[i].addr; ++i) { + char written[128]; + struct ipv6_entry *ent = &ipv6_entries[i]; + struct in6_addr in6; + int r; + r = evutil_inet_pton(AF_INET6, ent->addr, &in6); + if (r == 0) { + if (ent->status != BAD) + TT_FAIL(("%s did not parse, but it's a good address!", + ent->addr)); + continue; + } + if (ent->status == BAD) { + TT_FAIL(("%s parsed, but we expected an error", ent->addr)); + continue; + } + for (j = 0; j < 4; ++j) { + /* Can't use s6_addr32 here; some don't have it. */ + ev_uint32_t u = + ((ev_uint32_t)in6.s6_addr[j*4 ] << 24) | + ((ev_uint32_t)in6.s6_addr[j*4+1] << 16) | + ((ev_uint32_t)in6.s6_addr[j*4+2] << 8) | + ((ev_uint32_t)in6.s6_addr[j*4+3]); + if (u != ent->res[j]) { + TT_FAIL(("%s did not parse as expected.", ent->addr)); + continue; + } + } + if (ent->status == CANONICAL) { + const char *w = evutil_inet_ntop(AF_INET6, &in6, written, + sizeof(written)); + if (!w) { + TT_FAIL(("Tried to write out %s; got NULL.", ent->addr)); + continue; + } + if (strcmp(written, ent->addr)) { + TT_FAIL(("Tried to write out %s; got %s", ent->addr, written)); + continue; + } + } + + } +#else + TT_BLATHER(("Skipping IPv6 address parsing.")); +#endif +} + +static struct ipv6_entry_scope { + const char *addr; + ev_uint32_t res[4]; + unsigned scope; + enum entry_status status; +} ipv6_entries_scope[] = { + { "2001:DB8::", { 0x20010db8, 0, 0 }, 0, NORMAL }, + { "2001:DB8::%0", { 0x20010db8, 0, 0, 0 }, 0, NORMAL }, + { "2001:DB8::%1", { 0x20010db8, 0, 0, 0 }, 1, NORMAL }, + { "foobar.", { 0, 0, 0, 0 }, 0, BAD }, + { "2001:DB8::%does-not-exist", { 0, 0, 0, 0 }, 0, BAD }, + { NULL, { 0, 0, 0, 0, }, 0, BAD }, +}; +static void +regress_ipv6_parse_scope(void *ptr) +{ +#ifdef AF_INET6 + int i, j; + unsigned if_scope; + + for (i = 0; ipv6_entries_scope[i].addr; ++i) { + struct ipv6_entry_scope *ent = &ipv6_entries_scope[i]; + struct in6_addr in6; + int r; + r = evutil_inet_pton_scope(AF_INET6, ent->addr, &in6, + &if_scope); + if (r == 0) { + if (ent->status != BAD) + TT_FAIL(("%s did not parse, but it's a good address!", + ent->addr)); + continue; + } + if (ent->status == BAD) { + TT_FAIL(("%s parsed, but we expected an error", ent->addr)); + continue; + } + for (j = 0; j < 4; ++j) { + /* Can't use s6_addr32 here; some don't have it. */ + ev_uint32_t u = + ((ev_uint32_t)in6.s6_addr[j*4 ] << 24) | + ((ev_uint32_t)in6.s6_addr[j*4+1] << 16) | + ((ev_uint32_t)in6.s6_addr[j*4+2] << 8) | + ((ev_uint32_t)in6.s6_addr[j*4+3]); + if (u != ent->res[j]) { + TT_FAIL(("%s did not parse as expected.", ent->addr)); + continue; + } + } + if (if_scope != ent->scope) { + TT_FAIL(("%s did not parse as expected.", ent->addr)); + continue; + } + } +#else + TT_BLATHER(("Skipping IPv6 address parsing.")); +#endif +} + + +static struct sa_port_ent { + const char *parse; + int safamily; + const char *addr; + int port; +} sa_port_ents[] = { + { "[ffff::1]:1000", AF_INET6, "ffff::1", 1000 }, + { "[ffff::1]", AF_INET6, "ffff::1", 0 }, + { "[ffff::1", 0, NULL, 0 }, + { "[ffff::1]:65599", 0, NULL, 0 }, + { "[ffff::1]:0", 0, NULL, 0 }, + { "[ffff::1]:-1", 0, NULL, 0 }, + { "::1", AF_INET6, "::1", 0 }, + { "1:2::1", AF_INET6, "1:2::1", 0 }, + { "192.168.0.1:50", AF_INET, "192.168.0.1", 50 }, + { "1.2.3.4", AF_INET, "1.2.3.4", 0 }, + { NULL, 0, NULL, 0 }, +}; + +static void +regress_sockaddr_port_parse(void *ptr) +{ + struct sockaddr_storage ss; + int i, r; + + for (i = 0; sa_port_ents[i].parse; ++i) { + struct sa_port_ent *ent = &sa_port_ents[i]; + int len = sizeof(ss); + memset(&ss, 0, sizeof(ss)); + r = evutil_parse_sockaddr_port(ent->parse, (struct sockaddr*)&ss, &len); + if (r < 0) { + if (ent->safamily) + TT_FAIL(("Couldn't parse %s!", ent->parse)); + continue; + } else if (! ent->safamily) { + TT_FAIL(("Shouldn't have been able to parse %s!", ent->parse)); + continue; + } + if (ent->safamily == AF_INET) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); +#ifdef EVENT__HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin.sin_len = sizeof(sin); +#endif + sin.sin_family = AF_INET; + sin.sin_port = htons(ent->port); + r = evutil_inet_pton(AF_INET, ent->addr, &sin.sin_addr); + if (1 != r) { + TT_FAIL(("Couldn't parse ipv4 target %s.", ent->addr)); + } else if (memcmp(&sin, &ss, sizeof(sin))) { + TT_FAIL(("Parse for %s was not as expected.", ent->parse)); + } else if (len != sizeof(sin)) { + TT_FAIL(("Length for %s not as expected.",ent->parse)); + } + } else { + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); +#ifdef EVENT__HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6.sin6_len = sizeof(sin6); +#endif + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(ent->port); + r = evutil_inet_pton(AF_INET6, ent->addr, &sin6.sin6_addr); + if (1 != r) { + TT_FAIL(("Couldn't parse ipv6 target %s.", ent->addr)); + } else if (memcmp(&sin6, &ss, sizeof(sin6))) { + TT_FAIL(("Parse for %s was not as expected.", ent->parse)); + } else if (len != sizeof(sin6)) { + TT_FAIL(("Length for %s not as expected.",ent->parse)); + } + } + } +} + + +static void +regress_sockaddr_port_format(void *ptr) +{ + struct sockaddr_storage ss; + int len; + const char *cp; + char cbuf[128]; + int r; + + len = sizeof(ss); + r = evutil_parse_sockaddr_port("192.168.1.1:80", + (struct sockaddr*)&ss, &len); + tt_int_op(r,==,0); + cp = evutil_format_sockaddr_port_( + (struct sockaddr*)&ss, cbuf, sizeof(cbuf)); + tt_ptr_op(cp,==,cbuf); + tt_str_op(cp,==,"192.168.1.1:80"); + + len = sizeof(ss); + r = evutil_parse_sockaddr_port("[ff00::8010]:999", + (struct sockaddr*)&ss, &len); + tt_int_op(r,==,0); + cp = evutil_format_sockaddr_port_( + (struct sockaddr*)&ss, cbuf, sizeof(cbuf)); + tt_ptr_op(cp,==,cbuf); + tt_str_op(cp,==,"[ff00::8010]:999"); + + ss.ss_family=99; + cp = evutil_format_sockaddr_port_( + (struct sockaddr*)&ss, cbuf, sizeof(cbuf)); + tt_ptr_op(cp,==,cbuf); + tt_str_op(cp,==,"<addr with socktype 99>"); +end: + ; +} + +static struct sa_pred_ent { + const char *parse; + + int is_loopback; +} sa_pred_entries[] = { + { "127.0.0.1", 1 }, + { "127.0.3.2", 1 }, + { "128.1.2.3", 0 }, + { "18.0.0.1", 0 }, + { "129.168.1.1", 0 }, + + { "::1", 1 }, + { "::0", 0 }, + { "f::1", 0 }, + { "::501", 0 }, + { NULL, 0 }, + +}; + +static void +test_evutil_sockaddr_predicates(void *ptr) +{ + struct sockaddr_storage ss; + int r, i; + + for (i=0; sa_pred_entries[i].parse; ++i) { + struct sa_pred_ent *ent = &sa_pred_entries[i]; + int len = sizeof(ss); + + r = evutil_parse_sockaddr_port(ent->parse, (struct sockaddr*)&ss, &len); + + if (r<0) { + TT_FAIL(("Couldn't parse %s!", ent->parse)); + continue; + } + + /* sockaddr_is_loopback */ + if (ent->is_loopback != evutil_sockaddr_is_loopback_((struct sockaddr*)&ss)) { + TT_FAIL(("evutil_sockaddr_loopback(%s) not as expected", + ent->parse)); + } + } +} + +static void +test_evutil_strtoll(void *ptr) +{ + const char *s; + char *endptr; + + tt_want(evutil_strtoll("5000000000", NULL, 10) == + ((ev_int64_t)5000000)*1000); + tt_want(evutil_strtoll("-5000000000", NULL, 10) == + ((ev_int64_t)5000000)*-1000); + s = " 99999stuff"; + tt_want(evutil_strtoll(s, &endptr, 10) == (ev_int64_t)99999); + tt_want(endptr == s+6); + tt_want(evutil_strtoll("foo", NULL, 10) == 0); + } + +static void +test_evutil_snprintf(void *ptr) +{ + char buf[16]; + int r; + ev_uint64_t u64 = ((ev_uint64_t)1000000000)*200; + ev_int64_t i64 = -1 * (ev_int64_t) u64; + size_t size = 8000; + ev_ssize_t ssize = -9000; + + r = evutil_snprintf(buf, sizeof(buf), "%d %d", 50, 100); + tt_str_op(buf, ==, "50 100"); + tt_int_op(r, ==, 6); + + r = evutil_snprintf(buf, sizeof(buf), "longish %d", 1234567890); + tt_str_op(buf, ==, "longish 1234567"); + tt_int_op(r, ==, 18); + + r = evutil_snprintf(buf, sizeof(buf), EV_U64_FMT, EV_U64_ARG(u64)); + tt_str_op(buf, ==, "200000000000"); + tt_int_op(r, ==, 12); + + r = evutil_snprintf(buf, sizeof(buf), EV_I64_FMT, EV_I64_ARG(i64)); + tt_str_op(buf, ==, "-200000000000"); + tt_int_op(r, ==, 13); + + r = evutil_snprintf(buf, sizeof(buf), EV_SIZE_FMT" "EV_SSIZE_FMT, + EV_SIZE_ARG(size), EV_SSIZE_ARG(ssize)); + tt_str_op(buf, ==, "8000 -9000"); + tt_int_op(r, ==, 10); + + end: + ; +} + +static void +test_evutil_casecmp(void *ptr) +{ + tt_int_op(evutil_ascii_strcasecmp("ABC", "ABC"), ==, 0); + tt_int_op(evutil_ascii_strcasecmp("ABC", "abc"), ==, 0); + tt_int_op(evutil_ascii_strcasecmp("ABC", "abcd"), <, 0); + tt_int_op(evutil_ascii_strcasecmp("ABC", "abb"), >, 0); + tt_int_op(evutil_ascii_strcasecmp("ABCd", "abc"), >, 0); + + tt_int_op(evutil_ascii_strncasecmp("Libevent", "LibEvEnT", 100), ==, 0); + tt_int_op(evutil_ascii_strncasecmp("Libevent", "LibEvEnT", 4), ==, 0); + tt_int_op(evutil_ascii_strncasecmp("Libevent", "LibEXXXX", 4), ==, 0); + tt_int_op(evutil_ascii_strncasecmp("Libevent", "LibE", 4), ==, 0); + tt_int_op(evutil_ascii_strncasecmp("Libe", "LibEvEnT", 4), ==, 0); + tt_int_op(evutil_ascii_strncasecmp("Lib", "LibEvEnT", 4), <, 0); + tt_int_op(evutil_ascii_strncasecmp("abc", "def", 99), <, 0); + tt_int_op(evutil_ascii_strncasecmp("Z", "qrst", 1), >, 0); +end: + ; +} + +static void +test_evutil_rtrim(void *ptr) +{ +#define TEST_TRIM(s, result) \ + do { \ + if (cp) mm_free(cp); \ + cp = mm_strdup(s); \ + tt_assert(cp); \ + evutil_rtrim_lws_(cp); \ + tt_str_op(cp, ==, result); \ + } while(0) + + char *cp = NULL; + (void) ptr; + + TEST_TRIM("", ""); + TEST_TRIM("a", "a"); + TEST_TRIM("abcdef ghi", "abcdef ghi"); + + TEST_TRIM(" ", ""); + TEST_TRIM(" ", ""); + TEST_TRIM("a ", "a"); + TEST_TRIM("abcdef gH ", "abcdef gH"); + + TEST_TRIM("\t\t", ""); + TEST_TRIM(" \t", ""); + TEST_TRIM("\t", ""); + TEST_TRIM("a \t", "a"); + TEST_TRIM("a\t ", "a"); + TEST_TRIM("a\t", "a"); + TEST_TRIM("abcdef gH \t ", "abcdef gH"); + +end: + if (cp) + mm_free(cp); +} + +static int logsev = 0; +static char *logmsg = NULL; + +static void +logfn(int severity, const char *msg) +{ + logsev = severity; + tt_want(msg); + if (msg) { + if (logmsg) + free(logmsg); + logmsg = strdup(msg); + } +} + +static int fatal_want_severity = 0; +static const char *fatal_want_message = NULL; +static void +fatalfn(int exitcode) +{ + if (logsev != fatal_want_severity || + !logmsg || + strcmp(logmsg, fatal_want_message)) + exit(0); + else + exit(exitcode); +} + +#ifndef _WIN32 +#define CAN_CHECK_ERR +static void +check_error_logging(void (*fn)(void), int wantexitcode, + int wantseverity, const char *wantmsg) +{ + pid_t pid; + int status = 0, exitcode; + fatal_want_severity = wantseverity; + fatal_want_message = wantmsg; + if ((pid = regress_fork()) == 0) { + /* child process */ + fn(); + exit(0); /* should be unreachable. */ + } else { + wait(&status); + exitcode = WEXITSTATUS(status); + tt_int_op(wantexitcode, ==, exitcode); + } +end: + ; +} + +static void +errx_fn(void) +{ + event_errx(2, "Fatal error; too many kumquats (%d)", 5); +} + +static void +err_fn(void) +{ + errno = ENOENT; + event_err(5,"Couldn't open %s", "/very/bad/file"); +} + +static void +sock_err_fn(void) +{ + evutil_socket_t fd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef _WIN32 + EVUTIL_SET_SOCKET_ERROR(WSAEWOULDBLOCK); +#else + errno = EAGAIN; +#endif + event_sock_err(20, fd, "Unhappy socket"); +} +#endif + +static void +test_evutil_log(void *ptr) +{ + evutil_socket_t fd = -1; + char buf[128]; + + event_set_log_callback(logfn); + event_set_fatal_callback(fatalfn); +#define RESET() do { \ + logsev = 0; \ + if (logmsg) free(logmsg); \ + logmsg = NULL; \ + } while (0) +#define LOGEQ(sev,msg) do { \ + tt_int_op(logsev,==,sev); \ + tt_assert(logmsg != NULL); \ + tt_str_op(logmsg,==,msg); \ + } while (0) + +#ifdef CAN_CHECK_ERR + /* We need to disable these tests for now. Previously, the logging + * module didn't enforce the requirement that a fatal callback + * actually exit. Now, it exits no matter what, so if we wan to + * reinstate these tests, we'll need to fork for each one. */ + check_error_logging(errx_fn, 2, EVENT_LOG_ERR, + "Fatal error; too many kumquats (5)"); + RESET(); +#endif + + event_warnx("Far too many %s (%d)", "wombats", 99); + LOGEQ(EVENT_LOG_WARN, "Far too many wombats (99)"); + RESET(); + + event_msgx("Connecting lime to coconut"); + LOGEQ(EVENT_LOG_MSG, "Connecting lime to coconut"); + RESET(); + + event_debug(("A millisecond passed! We should log that!")); +#ifdef USE_DEBUG + LOGEQ(EVENT_LOG_DEBUG, "A millisecond passed! We should log that!"); +#else + tt_int_op(logsev,==,0); + tt_ptr_op(logmsg,==,NULL); +#endif + RESET(); + + /* Try with an errno. */ + errno = ENOENT; + event_warn("Couldn't open %s", "/bad/file"); + evutil_snprintf(buf, sizeof(buf), + "Couldn't open /bad/file: %s",strerror(ENOENT)); + LOGEQ(EVENT_LOG_WARN,buf); + RESET(); + +#ifdef CAN_CHECK_ERR + evutil_snprintf(buf, sizeof(buf), + "Couldn't open /very/bad/file: %s",strerror(ENOENT)); + check_error_logging(err_fn, 5, EVENT_LOG_ERR, buf); + RESET(); +#endif + + /* Try with a socket errno. */ + fd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef _WIN32 + evutil_snprintf(buf, sizeof(buf), + "Unhappy socket: %s", + evutil_socket_error_to_string(WSAEWOULDBLOCK)); + EVUTIL_SET_SOCKET_ERROR(WSAEWOULDBLOCK); +#else + evutil_snprintf(buf, sizeof(buf), + "Unhappy socket: %s", strerror(EAGAIN)); + errno = EAGAIN; +#endif + event_sock_warn(fd, "Unhappy socket"); + LOGEQ(EVENT_LOG_WARN, buf); + RESET(); + +#ifdef CAN_CHECK_ERR + check_error_logging(sock_err_fn, 20, EVENT_LOG_ERR, buf); + RESET(); +#endif + +#undef RESET +#undef LOGEQ +end: + if (logmsg) + free(logmsg); + if (fd >= 0) + evutil_closesocket(fd); +} + +static void +test_evutil_strlcpy(void *arg) +{ + char buf[8]; + + /* Successful case. */ + tt_int_op(5, ==, strlcpy(buf, "Hello", sizeof(buf))); + tt_str_op(buf, ==, "Hello"); + + /* Overflow by a lot. */ + tt_int_op(13, ==, strlcpy(buf, "pentasyllabic", sizeof(buf))); + tt_str_op(buf, ==, "pentasy"); + + /* Overflow by exactly one. */ + tt_int_op(8, ==, strlcpy(buf, "overlong", sizeof(buf))); + tt_str_op(buf, ==, "overlon"); +end: + ; +} + +struct example_struct { + const char *a; + const char *b; + long c; +}; + +static void +test_evutil_upcast(void *arg) +{ + struct example_struct es1; + const char **cp; + es1.a = "World"; + es1.b = "Hello"; + es1.c = -99; + + tt_int_op(evutil_offsetof(struct example_struct, b), ==, sizeof(char*)); + + cp = &es1.b; + tt_ptr_op(EVUTIL_UPCAST(cp, struct example_struct, b), ==, &es1); + +end: + ; +} + +static void +test_evutil_integers(void *arg) +{ + ev_int64_t i64; + ev_uint64_t u64; + ev_int32_t i32; + ev_uint32_t u32; + ev_int16_t i16; + ev_uint16_t u16; + ev_int8_t i8; + ev_uint8_t u8; + + void *ptr; + ev_intptr_t iptr; + ev_uintptr_t uptr; + + ev_ssize_t ssize; + + tt_int_op(sizeof(u64), ==, 8); + tt_int_op(sizeof(i64), ==, 8); + tt_int_op(sizeof(u32), ==, 4); + tt_int_op(sizeof(i32), ==, 4); + tt_int_op(sizeof(u16), ==, 2); + tt_int_op(sizeof(i16), ==, 2); + tt_int_op(sizeof(u8), ==, 1); + tt_int_op(sizeof(i8), ==, 1); + + tt_int_op(sizeof(ev_ssize_t), ==, sizeof(size_t)); + tt_int_op(sizeof(ev_intptr_t), >=, sizeof(void *)); + tt_int_op(sizeof(ev_uintptr_t), ==, sizeof(intptr_t)); + + u64 = 1000000000; + u64 *= 1000000000; + tt_assert(u64 / 1000000000 == 1000000000); + i64 = -1000000000; + i64 *= 1000000000; + tt_assert(i64 / 1000000000 == -1000000000); + + u64 = EV_UINT64_MAX; + i64 = EV_INT64_MAX; + tt_assert(u64 > 0); + tt_assert(i64 > 0); + u64++; +/* i64++; */ + tt_assert(u64 == 0); +/* tt_assert(i64 == EV_INT64_MIN); */ +/* tt_assert(i64 < 0); */ + + u32 = EV_UINT32_MAX; + i32 = EV_INT32_MAX; + tt_assert(u32 > 0); + tt_assert(i32 > 0); + u32++; +/* i32++; */ + tt_assert(u32 == 0); +/* tt_assert(i32 == EV_INT32_MIN); */ +/* tt_assert(i32 < 0); */ + + u16 = EV_UINT16_MAX; + i16 = EV_INT16_MAX; + tt_assert(u16 > 0); + tt_assert(i16 > 0); + u16++; +/* i16++; */ + tt_assert(u16 == 0); +/* tt_assert(i16 == EV_INT16_MIN); */ +/* tt_assert(i16 < 0); */ + + u8 = EV_UINT8_MAX; + i8 = EV_INT8_MAX; + tt_assert(u8 > 0); + tt_assert(i8 > 0); + u8++; +/* i8++;*/ + tt_assert(u8 == 0); +/* tt_assert(i8 == EV_INT8_MIN); */ +/* tt_assert(i8 < 0); */ + +/* + ssize = EV_SSIZE_MAX; + tt_assert(ssize > 0); + ssize++; + tt_assert(ssize < 0); + tt_assert(ssize == EV_SSIZE_MIN); +*/ + + ptr = &ssize; + iptr = (ev_intptr_t)ptr; + uptr = (ev_uintptr_t)ptr; + ptr = (void *)iptr; + tt_assert(ptr == &ssize); + ptr = (void *)uptr; + tt_assert(ptr == &ssize); + + iptr = -1; + tt_assert(iptr < 0); +end: + ; +} + +struct evutil_addrinfo * +ai_find_by_family(struct evutil_addrinfo *ai, int family) +{ + while (ai) { + if (ai->ai_family == family) + return ai; + ai = ai->ai_next; + } + return NULL; +} + +struct evutil_addrinfo * +ai_find_by_protocol(struct evutil_addrinfo *ai, int protocol) +{ + while (ai) { + if (ai->ai_protocol == protocol) + return ai; + ai = ai->ai_next; + } + return NULL; +} + + +int +test_ai_eq_(const struct evutil_addrinfo *ai, const char *sockaddr_port, + int socktype, int protocol, int line) +{ + struct sockaddr_storage ss; + int slen = sizeof(ss); + int gotport; + char buf[128]; + memset(&ss, 0, sizeof(ss)); + if (socktype > 0) + tt_int_op(ai->ai_socktype, ==, socktype); + if (protocol > 0) + tt_int_op(ai->ai_protocol, ==, protocol); + + if (evutil_parse_sockaddr_port( + sockaddr_port, (struct sockaddr*)&ss, &slen)<0) { + TT_FAIL(("Couldn't parse expected address %s on line %d", + sockaddr_port, line)); + return -1; + } + if (ai->ai_family != ss.ss_family) { + TT_FAIL(("Address family %d did not match %d on line %d", + ai->ai_family, ss.ss_family, line)); + return -1; + } + if (ai->ai_addr->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)ai->ai_addr; + evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)); + gotport = ntohs(sin->sin_port); + if (ai->ai_addrlen != sizeof(struct sockaddr_in)) { + TT_FAIL(("Addr size mismatch on line %d", line)); + return -1; + } + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)ai->ai_addr; + evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf)); + gotport = ntohs(sin6->sin6_port); + if (ai->ai_addrlen != sizeof(struct sockaddr_in6)) { + TT_FAIL(("Addr size mismatch on line %d", line)); + return -1; + } + } + if (evutil_sockaddr_cmp(ai->ai_addr, (struct sockaddr*)&ss, 1)) { + TT_FAIL(("Wanted %s, got %s:%d on line %d", sockaddr_port, + buf, gotport, line)); + return -1; + } else { + TT_BLATHER(("Wanted %s, got %s:%d on line %d", sockaddr_port, + buf, gotport, line)); + } + return 0; +end: + TT_FAIL(("Test failed on line %d", line)); + return -1; +} + +static void +test_evutil_rand(void *arg) +{ + char buf1[32]; + char buf2[32]; + int counts[256]; + int i, j, k, n=0; + struct evutil_weakrand_state seed = { 12346789U }; + + memset(buf2, 0, sizeof(buf2)); + memset(counts, 0, sizeof(counts)); + + for (k=0;k<32;++k) { + /* Try a few different start and end points; try to catch + * the various misaligned cases of arc4random_buf */ + int startpoint = evutil_weakrand_(&seed) % 4; + int endpoint = 32 - (evutil_weakrand_(&seed) % 4); + + memset(buf2, 0, sizeof(buf2)); + + /* Do 6 runs over buf1, or-ing the result into buf2 each + * time, to make sure we're setting each byte that we mean + * to set. */ + for (i=0;i<8;++i) { + memset(buf1, 0, sizeof(buf1)); + evutil_secure_rng_get_bytes(buf1 + startpoint, + endpoint-startpoint); + n += endpoint - startpoint; + for (j=0; j<32; ++j) { + if (j >= startpoint && j < endpoint) { + buf2[j] |= buf1[j]; + ++counts[(unsigned char)buf1[j]]; + } else { + tt_assert(buf1[j] == 0); + tt_int_op(buf1[j], ==, 0); + + } + } + } + + /* This will give a false positive with P=(256**8)==(2**64) + * for each character. */ + for (j=startpoint;j<endpoint;++j) { + tt_int_op(buf2[j], !=, 0); + } + } + + evutil_weakrand_seed_(&seed, 0); + for (i = 0; i < 10000; ++i) { + ev_int32_t r = evutil_weakrand_range_(&seed, 9999); + tt_int_op(0, <=, r); + tt_int_op(r, <, 9999); + } + + /* for (i=0;i<256;++i) { printf("%3d %2d\n", i, counts[i]); } */ +end: + ; +} + +static void +test_EVUTIL_IS_(void *arg) +{ + tt_int_op(EVUTIL_ISDIGIT_('0'), ==, 1); + tt_int_op(EVUTIL_ISDIGIT_('a'), ==, 0); + tt_int_op(EVUTIL_ISDIGIT_('\xff'), ==, 0); +end: + ; +} + +static void +test_evutil_getaddrinfo(void *arg) +{ + struct evutil_addrinfo *ai = NULL, *a; + struct evutil_addrinfo hints; + int r; + + /* Try using it as a pton. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evutil_getaddrinfo("1.2.3.4", "8080", &hints, &ai); + tt_int_op(r, ==, 0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); /* no ambiguity */ + test_ai_eq(ai, "1.2.3.4:8080", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_UDP; + r = evutil_getaddrinfo("1001:b0b::f00f", "4321", &hints, &ai); + tt_int_op(r, ==, 0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); /* no ambiguity */ + test_ai_eq(ai, "[1001:b0b::f00f]:4321", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Try out the behavior of nodename=NULL */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = EVUTIL_AI_PASSIVE; /* as if for bind */ + r = evutil_getaddrinfo(NULL, "9999", &hints, &ai); + tt_int_op(r,==,0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "0.0.0.0:9999", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + hints.ai_flags = 0; /* as if for connect */ + r = evutil_getaddrinfo(NULL, "9998", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + test_ai_eq(ai, "127.0.0.1:9998", SOCK_STREAM, IPPROTO_TCP); + tt_ptr_op(ai->ai_next, ==, NULL); + evutil_freeaddrinfo(ai); + ai = NULL; + + hints.ai_flags = 0; /* as if for connect */ + hints.ai_family = PF_INET6; + r = evutil_getaddrinfo(NULL, "9997", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "[::1]:9997", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + hints.ai_flags = EVUTIL_AI_PASSIVE; /* as if for bind. */ + hints.ai_family = PF_INET6; + r = evutil_getaddrinfo(NULL, "9996", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "[::]:9996", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Now try an unspec one. We should get a v6 and a v4. */ + hints.ai_family = PF_UNSPEC; + r = evutil_getaddrinfo(NULL, "9996", &hints, &ai); + tt_assert(ai); + tt_int_op(r,==,0); + a = ai_find_by_family(ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::]:9996", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_family(ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "0.0.0.0:9996", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Try out AI_NUMERICHOST: successful case. Also try + * multiprotocol. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + r = evutil_getaddrinfo("1.2.3.4", NULL, &hints, &ai); + tt_int_op(r, ==, 0); + a = ai_find_by_protocol(ai, IPPROTO_TCP); + tt_assert(a); + test_ai_eq(a, "1.2.3.4", SOCK_STREAM, IPPROTO_TCP); + a = ai_find_by_protocol(ai, IPPROTO_UDP); + tt_assert(a); + test_ai_eq(a, "1.2.3.4", SOCK_DGRAM, IPPROTO_UDP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* Try the failing case of AI_NUMERICHOST */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = EVUTIL_AI_NUMERICHOST; + r = evutil_getaddrinfo("www.google.com", "80", &hints, &ai); + tt_int_op(r, ==, EVUTIL_EAI_NONAME); + tt_ptr_op(ai, ==, NULL); + + /* Try symbolic service names wit AI_NUMERICSERV */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_NUMERICSERV; + r = evutil_getaddrinfo("1.2.3.4", "http", &hints, &ai); + tt_int_op(r,==,EVUTIL_EAI_NONAME); + + /* Try symbolic service names */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evutil_getaddrinfo("1.2.3.4", "http", &hints, &ai); + if (r!=0) { + TT_DECLARE("SKIP", ("Symbolic service names seem broken.")); + } else { + tt_assert(ai); + test_ai_eq(ai, "1.2.3.4:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + } + +end: + if (ai) + evutil_freeaddrinfo(ai); +} + +static void +test_evutil_getaddrinfo_live(void *arg) +{ + struct evutil_addrinfo *ai = NULL; + struct evutil_addrinfo hints; + + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + char buf[128]; + const char *cp; + int r; + + /* Now do some actual lookups. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_socktype = SOCK_STREAM; + r = evutil_getaddrinfo("www.google.com", "80", &hints, &ai); + if (r != 0) { + TT_DECLARE("SKIP", ("Couldn't resolve www.google.com")); + } else { + tt_assert(ai); + tt_int_op(ai->ai_family, ==, PF_INET); + tt_int_op(ai->ai_protocol, ==, IPPROTO_TCP); + tt_int_op(ai->ai_socktype, ==, SOCK_STREAM); + tt_int_op(ai->ai_addrlen, ==, sizeof(struct sockaddr_in)); + sin = (struct sockaddr_in*)ai->ai_addr; + tt_int_op(sin->sin_family, ==, AF_INET); + tt_int_op(sin->sin_port, ==, htons(80)); + tt_int_op(sin->sin_addr.s_addr, !=, 0xffffffff); + + cp = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)); + TT_BLATHER(("www.google.com resolved to %s", + cp?cp:"<unwriteable>")); + evutil_freeaddrinfo(ai); + ai = NULL; + } + + hints.ai_family = PF_INET6; + r = evutil_getaddrinfo("ipv6.google.com", "80", &hints, &ai); + if (r != 0) { + TT_BLATHER(("Couldn't do an ipv6 lookup for ipv6.google.com")); + } else { + tt_assert(ai); + tt_int_op(ai->ai_family, ==, PF_INET6); + tt_int_op(ai->ai_addrlen, ==, sizeof(struct sockaddr_in6)); + sin6 = (struct sockaddr_in6*)ai->ai_addr; + tt_int_op(sin6->sin6_port, ==, htons(80)); + + cp = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, + sizeof(buf)); + TT_BLATHER(("ipv6.google.com resolved to %s", + cp?cp:"<unwriteable>")); + } + +end: + if (ai) + evutil_freeaddrinfo(ai); +} + +static void +test_evutil_getaddrinfo_AI_ADDRCONFIG(void *arg) +{ + struct evutil_addrinfo *ai = NULL; + struct evutil_addrinfo hints; + int r; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_PASSIVE|EVUTIL_AI_ADDRCONFIG; + + /* IPv4 */ + r = evutil_getaddrinfo("127.0.0.1", "80", &hints, &ai); + tt_int_op(r, ==, 0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "127.0.0.1:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + + /* IPv6 */ + r = evutil_getaddrinfo("::1", "80", &hints, &ai); + tt_int_op(r, ==, 0); + tt_assert(ai); + tt_ptr_op(ai->ai_next, ==, NULL); + test_ai_eq(ai, "[::1]:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(ai); + ai = NULL; + +end: + if (ai) + evutil_freeaddrinfo(ai); +} + +#ifdef _WIN32 +static void +test_evutil_loadsyslib(void *arg) +{ + HMODULE h=NULL; + + h = evutil_load_windows_system_library_(TEXT("kernel32.dll")); + tt_assert(h); + +end: + if (h) + CloseHandle(h); + +} +#endif + +/** Test mm_malloc(). */ +static void +test_event_malloc(void *arg) +{ + void *p = NULL; + (void)arg; + + /* mm_malloc(0) should simply return NULL. */ +#ifndef EVENT__DISABLE_MM_REPLACEMENT + errno = 0; + p = mm_malloc(0); + tt_assert(p == NULL); + tt_int_op(errno, ==, 0); +#endif + + /* Trivial case. */ + errno = 0; + p = mm_malloc(8); + tt_assert(p != NULL); + tt_int_op(errno, ==, 0); + mm_free(p); + + end: + errno = 0; + return; +} + +static void +test_event_calloc(void *arg) +{ + void *p = NULL; + (void)arg; + +#ifndef EVENT__DISABLE_MM_REPLACEMENT + /* mm_calloc() should simply return NULL + * if either argument is zero. */ + errno = 0; + p = mm_calloc(0, 0); + tt_assert(p == NULL); + tt_int_op(errno, ==, 0); + errno = 0; + p = mm_calloc(0, 1); + tt_assert(p == NULL); + tt_int_op(errno, ==, 0); + errno = 0; + p = mm_calloc(1, 0); + tt_assert(p == NULL); + tt_int_op(errno, ==, 0); +#endif + + /* Trivial case. */ + errno = 0; + p = mm_calloc(8, 8); + tt_assert(p != NULL); + tt_int_op(errno, ==, 0); + mm_free(p); + p = NULL; + + /* mm_calloc() should set errno = ENOMEM and return NULL + * in case of potential overflow. */ + errno = 0; + p = mm_calloc(EV_SIZE_MAX/2, EV_SIZE_MAX/2 + 8); + tt_assert(p == NULL); + tt_int_op(errno, ==, ENOMEM); + + end: + errno = 0; + if (p) + mm_free(p); + + return; +} + +static void +test_event_strdup(void *arg) +{ + void *p = NULL; + (void)arg; + +#ifndef EVENT__DISABLE_MM_REPLACEMENT + /* mm_strdup(NULL) should set errno = EINVAL and return NULL. */ + errno = 0; + p = mm_strdup(NULL); + tt_assert(p == NULL); + tt_int_op(errno, ==, EINVAL); +#endif + + /* Trivial cases. */ + + errno = 0; + p = mm_strdup(""); + tt_assert(p != NULL); + tt_int_op(errno, ==, 0); + tt_str_op(p, ==, ""); + mm_free(p); + + errno = 0; + p = mm_strdup("foo"); + tt_assert(p != NULL); + tt_int_op(errno, ==, 0); + tt_str_op(p, ==, "foo"); + mm_free(p); + + /* XXX + * mm_strdup(str) where str is a string of length EV_SIZE_MAX + * should set errno = ENOMEM and return NULL. */ + + end: + errno = 0; + return; +} + +static void +test_evutil_usleep(void *arg) +{ + struct timeval tv1, tv2, tv3, diff1, diff2; + const struct timeval quarter_sec = {0, 250*1000}; + const struct timeval tenth_sec = {0, 100*1000}; + long usec1, usec2; + + evutil_gettimeofday(&tv1, NULL); + evutil_usleep_(&quarter_sec); + evutil_gettimeofday(&tv2, NULL); + evutil_usleep_(&tenth_sec); + evutil_gettimeofday(&tv3, NULL); + + evutil_timersub(&tv2, &tv1, &diff1); + evutil_timersub(&tv3, &tv2, &diff2); + usec1 = diff1.tv_sec * 1000000 + diff1.tv_usec; + usec2 = diff2.tv_sec * 1000000 + diff2.tv_usec; + + tt_int_op(usec1, >, 200000); + tt_int_op(usec1, <, 300000); + tt_int_op(usec2, >, 80000); + tt_int_op(usec2, <, 120000); + +end: + ; +} + +static void +test_evutil_monotonic_res(void *data_) +{ + /* Basic santity-test for monotonic timers. What we'd really like + * to do is make sure that they can't go backwards even when the + * system clock goes backwards. But we haven't got a good way to + * move the system clock backwards. + */ + struct basic_test_data *data = data_; + struct evutil_monotonic_timer timer; + const int precise = strstr(data->setup_data, "precise") != NULL; + const int fallback = strstr(data->setup_data, "fallback") != NULL; + struct timeval tv[10], delay; + int total_diff = 0; + + int flags = 0, wantres, acceptdiff, i; + if (precise) + flags |= EV_MONOT_PRECISE; + if (fallback) + flags |= EV_MONOT_FALLBACK; + if (precise || fallback) { +#ifdef _WIN32 + wantres = 10*1000; + acceptdiff = 1000; +#else + wantres = 1000; + acceptdiff = 300; +#endif + } else { + wantres = 40*1000; + acceptdiff = 20*1000; + } + + TT_BLATHER(("Precise = %d", precise)); + TT_BLATHER(("Fallback = %d", fallback)); + + /* First, make sure we match up with usleep. */ + + delay.tv_sec = 0; + delay.tv_usec = wantres; + + tt_int_op(evutil_configure_monotonic_time_(&timer, flags), ==, 0); + + for (i = 0; i < 10; ++i) { + evutil_gettime_monotonic_(&timer, &tv[i]); + evutil_usleep_(&delay); + } + + for (i = 0; i < 9; ++i) { + struct timeval diff; + tt_assert(evutil_timercmp(&tv[i], &tv[i+1], <)); + evutil_timersub(&tv[i+1], &tv[i], &diff); + tt_int_op(diff.tv_sec, ==, 0); + total_diff += diff.tv_usec; + TT_BLATHER(("Difference = %d", (int)diff.tv_usec)); + } + tt_int_op(abs(total_diff/9 - wantres), <, acceptdiff); + +end: + ; +} + +static void +test_evutil_monotonic_prc(void *data_) +{ + struct basic_test_data *data = data_; + struct evutil_monotonic_timer timer; + const int precise = strstr(data->setup_data, "precise") != NULL; + const int fallback = strstr(data->setup_data, "fallback") != NULL; + struct timeval tv[10]; + int total_diff = 0; + int i, maxstep = 25*1000,flags=0; + if (precise) + maxstep = 500; + if (precise) + flags |= EV_MONOT_PRECISE; + if (fallback) + flags |= EV_MONOT_FALLBACK; + tt_int_op(evutil_configure_monotonic_time_(&timer, flags), ==, 0); + + /* find out what precision we actually see. */ + + evutil_gettime_monotonic_(&timer, &tv[0]); + for (i = 1; i < 10; ++i) { + do { + evutil_gettime_monotonic_(&timer, &tv[i]); + } while (evutil_timercmp(&tv[i-1], &tv[i], ==)); + } + + total_diff = 0; + for (i = 0; i < 9; ++i) { + struct timeval diff; + tt_assert(evutil_timercmp(&tv[i], &tv[i+1], <)); + evutil_timersub(&tv[i+1], &tv[i], &diff); + tt_int_op(diff.tv_sec, ==, 0); + total_diff += diff.tv_usec; + TT_BLATHER(("Step difference = %d", (int)diff.tv_usec)); + } + TT_BLATHER(("Average step difference = %d", total_diff / 9)); + tt_int_op(total_diff/9, <, maxstep); + +end: + ; +} + +static void +create_tm_from_unix_epoch(struct tm *cur_p, const time_t t) +{ +#ifdef _WIN32 + struct tm *tmp = gmtime(&t); + if (!tmp) { + fprintf(stderr, "gmtime: %s (%i)", strerror(errno), (int)t); + exit(1); + } + *cur_p = *tmp; +#else + gmtime_r(&t, cur_p); +#endif +} + +static struct date_rfc1123_case { + time_t t; + char date[30]; +} date_rfc1123_cases[] = { + { 0, "Thu, 01 Jan 1970 00:00:00 GMT"} /* UNIX time of zero */, + { 946684799, "Fri, 31 Dec 1999 23:59:59 GMT"} /* the last moment of the 20th century */, + { 946684800, "Sat, 01 Jan 2000 00:00:00 GMT"} /* the first moment of the 21st century */, + { 981072000, "Fri, 02 Feb 2001 00:00:00 GMT"}, + { 1015113600, "Sun, 03 Mar 2002 00:00:00 GMT"}, + { 1049414400, "Fri, 04 Apr 2003 00:00:00 GMT"}, + { 1083715200, "Wed, 05 May 2004 00:00:00 GMT"}, + { 1118016000, "Mon, 06 Jun 2005 00:00:00 GMT"}, + { 1152230400, "Fri, 07 Jul 2006 00:00:00 GMT"}, + { 1186531200, "Wed, 08 Aug 2007 00:00:00 GMT"}, + { 1220918400, "Tue, 09 Sep 2008 00:00:00 GMT"}, + { 1255132800, "Sat, 10 Oct 2009 00:00:00 GMT"}, + { 1289433600, "Thu, 11 Nov 2010 00:00:00 GMT"}, + { 1323648000, "Mon, 12 Dec 2011 00:00:00 GMT"}, +#ifndef _WIN32 +#if EVENT__SIZEOF_TIME_T > 4 + /** In win32 case we have max "23:59:59 January 18, 2038, UTC" for time32 */ + { 4294967296, "Sun, 07 Feb 2106 06:28:16 GMT"} /* 2^32 */, + /** In win32 case we have max "23:59:59, December 31, 3000, UTC" for time64 */ + {253402300799, "Fri, 31 Dec 9999 23:59:59 GMT"} /* long long future no one can imagine */, +#endif /* time_t != 32bit */ + { 1456704000, "Mon, 29 Feb 2016 00:00:00 GMT"} /* leap year */, +#endif + { 1435708800, "Wed, 01 Jul 2015 00:00:00 GMT"} /* leap second */, + { 1481866376, "Fri, 16 Dec 2016 05:32:56 GMT"} /* the time this test case is generated */, + {0, ""} /* end of test cases. */ +}; + +static void +test_evutil_date_rfc1123(void *arg) +{ + struct tm query; + char result[30]; + size_t i = 0; + + /* Checks if too small buffers are safely accepted. */ + { + create_tm_from_unix_epoch(&query, 0); + evutil_date_rfc1123(result, 8, &query); + tt_str_op(result, ==, "Thu, 01"); + } + + /* Checks for testcases. */ + for (i = 0; ; i++) { + struct date_rfc1123_case c = date_rfc1123_cases[i]; + + if (strlen(c.date) == 0) + break; + + create_tm_from_unix_epoch(&query, c.t); + evutil_date_rfc1123(result, sizeof(result), &query); + tt_str_op(result, ==, c.date); + } + +end: + ; +} + +static void +test_evutil_v4addr_is_local(void *arg) +{ + struct sockaddr_in sin; + sin.sin_family = AF_INET; + + /* we use evutil_inet_pton() here to fill in network-byte order */ +#define LOCAL(str, yes) do { \ + tt_int_op(evutil_inet_pton(AF_INET, str, &sin.sin_addr), ==, 1); \ + tt_int_op(evutil_v4addr_is_local_(&sin.sin_addr), ==, yes); \ +} while (0) + + /** any */ + sin.sin_addr.s_addr = INADDR_ANY; + tt_int_op(evutil_v4addr_is_local_(&sin.sin_addr), ==, 1); + + /** loopback */ + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + tt_int_op(evutil_v4addr_is_local_(&sin.sin_addr), ==, 1); + LOCAL("127.0.0.1", 1); + LOCAL("127.255.255.255", 1); + LOCAL("121.0.0.1", 0); + + /** link-local */ + LOCAL("169.254.0.1", 1); + LOCAL("169.254.255.255", 1); + LOCAL("170.0.0.0", 0); + + /** Multicast */ + LOCAL("224.0.0.0", 1); + LOCAL("239.255.255.255", 1); + LOCAL("240.0.0.0", 0); +end: + ; +} + +static void +test_evutil_v6addr_is_local(void *arg) +{ + struct sockaddr_in6 sin6; + struct in6_addr anyaddr = IN6ADDR_ANY_INIT; + struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT; + + sin6.sin6_family = AF_INET6; +#define LOCAL6(str, yes) do { \ + tt_int_op(evutil_inet_pton(AF_INET6, str, &sin6.sin6_addr), ==, 1);\ + tt_int_op(evutil_v6addr_is_local_(&sin6.sin6_addr), ==, yes); \ +} while (0) + + /** any */ + tt_int_op(evutil_v6addr_is_local_(&anyaddr), ==, 1); + LOCAL6("::0", 1); + + /** loopback */ + tt_int_op(evutil_v6addr_is_local_(&loopback), ==, 1); + LOCAL6("::1", 1); + + /** IPV4 mapped */ + LOCAL6("::ffff:0:0", 1); + /** IPv4 translated */ + LOCAL6("::ffff:0:0:0", 1); + /** IPv4/IPv6 translation */ + LOCAL6("64:ff9b::", 0); + /** Link-local */ + LOCAL6("fe80::", 1); + /** Multicast */ + LOCAL6("ff00::", 1); + /** Unspecified */ + LOCAL6("::", 1); + + /** Global Internet */ + LOCAL6("2001::", 0); + LOCAL6("2001:4860:4802:32::1b", 0); +end: + ; +} + +struct testcase_t util_testcases[] = { + { "ipv4_parse", regress_ipv4_parse, 0, NULL, NULL }, + { "ipv6_parse", regress_ipv6_parse, 0, NULL, NULL }, + { "ipv6_parse_scope", regress_ipv6_parse_scope, 0, NULL, NULL }, + { "sockaddr_port_parse", regress_sockaddr_port_parse, 0, NULL, NULL }, + { "sockaddr_port_format", regress_sockaddr_port_format, 0, NULL, NULL }, + { "sockaddr_predicates", test_evutil_sockaddr_predicates, 0,NULL,NULL }, + { "evutil_snprintf", test_evutil_snprintf, 0, NULL, NULL }, + { "evutil_strtoll", test_evutil_strtoll, 0, NULL, NULL }, + { "evutil_casecmp", test_evutil_casecmp, 0, NULL, NULL }, + { "evutil_rtrim", test_evutil_rtrim, 0, NULL, NULL }, + { "strlcpy", test_evutil_strlcpy, 0, NULL, NULL }, + { "log", test_evutil_log, TT_FORK, NULL, NULL }, + { "upcast", test_evutil_upcast, 0, NULL, NULL }, + { "integers", test_evutil_integers, 0, NULL, NULL }, + { "rand", test_evutil_rand, TT_FORK, NULL, NULL }, + { "EVUTIL_IS_", test_EVUTIL_IS_, 0, NULL, NULL }, + { "getaddrinfo", test_evutil_getaddrinfo, TT_FORK, NULL, NULL }, + { "getaddrinfo_live", test_evutil_getaddrinfo_live, TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, + { "getaddrinfo_AI_ADDRCONFIG", test_evutil_getaddrinfo_AI_ADDRCONFIG, TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, +#ifdef _WIN32 + { "loadsyslib", test_evutil_loadsyslib, TT_FORK, NULL, NULL }, +#endif + { "mm_malloc", test_event_malloc, 0, NULL, NULL }, + { "mm_calloc", test_event_calloc, 0, NULL, NULL }, + { "mm_strdup", test_event_strdup, 0, NULL, NULL }, + { "usleep", test_evutil_usleep, TT_RETRIABLE, NULL, NULL }, + { "monotonic_res", test_evutil_monotonic_res, 0, &basic_setup, (void*)"" }, + { "monotonic_res_precise", test_evutil_monotonic_res, TT_OFF_BY_DEFAULT, &basic_setup, (void*)"precise" }, + { "monotonic_res_fallback", test_evutil_monotonic_res, TT_OFF_BY_DEFAULT, &basic_setup, (void*)"fallback" }, + { "monotonic_prc", test_evutil_monotonic_prc, 0, &basic_setup, (void*)"" }, + { "monotonic_prc_precise", test_evutil_monotonic_prc, TT_RETRIABLE, &basic_setup, (void*)"precise" }, + { "monotonic_prc_fallback", test_evutil_monotonic_prc, 0, &basic_setup, (void*)"fallback" }, + { "date_rfc1123", test_evutil_date_rfc1123, 0, NULL, NULL }, + { "evutil_v4addr_is_local", test_evutil_v4addr_is_local, 0, NULL, NULL }, + { "evutil_v6addr_is_local", test_evutil_v6addr_is_local, 0, NULL, NULL }, + END_OF_TESTCASES, +}; + diff --git a/libevent/test/regress_zlib.c b/libevent/test/regress_zlib.c new file mode 100644 index 0000000..5fe7749 --- /dev/null +++ b/libevent/test/regress_zlib.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2008-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* The old tests here need assertions to work. */ +#undef NDEBUG + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#endif + +#include "event2/event-config.h" + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <netdb.h> +#endif +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <assert.h> +#include <errno.h> + +#include "event2/util.h" +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/buffer.h" +#include "event2/bufferevent.h" + +#include "regress.h" +#include "mm-internal.h" + +/* zlib 1.2.4 and 1.2.5 do some "clever" things with macros. Instead of + saying "(defined(FOO) ? FOO : 0)" they like to say "FOO-0", on the theory + that nobody will care if the compile outputs a no-such-identifier warning. + + Sorry, but we like -Werror over here, so I guess we need to define these. + I hope that zlib 1.2.6 doesn't break these too. +*/ +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE 0 +#endif +#ifndef _LFS64_LARGEFILE +#define _LFS64_LARGEFILE 0 +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 0 +#endif +#ifndef off64_t +#define off64_t ev_int64_t +#endif + +#include <zlib.h> + +static int infilter_calls; +static int outfilter_calls; +static int readcb_finished; +static int writecb_finished; +static int errorcb_invoked; + +/* + * Zlib filters + */ + +static void +zlib_deflate_free(void *ctx) +{ + z_streamp p = ctx; + + assert(deflateEnd(p) == Z_OK); + mm_free(p); +} + +static void +zlib_inflate_free(void *ctx) +{ + z_streamp p = ctx; + + assert(inflateEnd(p) == Z_OK); + mm_free(p); +} + +static int +getstate(enum bufferevent_flush_mode state) +{ + switch (state) { + case BEV_FINISHED: + return Z_FINISH; + case BEV_FLUSH: + return Z_SYNC_FLUSH; + case BEV_NORMAL: + default: + return Z_NO_FLUSH; + } +} + +/* + * The input filter is triggered only on new input read from the network. + * That means all input data needs to be consumed or the filter needs to + * initiate its own triggering via a timeout. + */ +static enum bufferevent_filter_result +zlib_input_filter(struct evbuffer *src, struct evbuffer *dst, + ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx) +{ + struct evbuffer_iovec v_in[1]; + struct evbuffer_iovec v_out[1]; + int nread, nwrite; + int res, n; + + z_streamp p = ctx; + + do { + /* let's do some decompression */ + n = evbuffer_peek(src, -1, NULL, v_in, 1); + if (n) { + p->avail_in = v_in[0].iov_len; + p->next_in = (unsigned char *)v_in[0].iov_base; + } else { + p->avail_in = 0; + p->next_in = 0; + } + + evbuffer_reserve_space(dst, 4096, v_out, 1); + p->next_out = (unsigned char *)v_out[0].iov_base; + p->avail_out = v_out[0].iov_len; + + /* we need to flush zlib if we got a flush */ + res = inflate(p, getstate(state)); + + /* let's figure out how much was compressed */ + nread = v_in[0].iov_len - p->avail_in; + nwrite = v_out[0].iov_len - p->avail_out; + + evbuffer_drain(src, nread); + v_out[0].iov_len = nwrite; + evbuffer_commit_space(dst, v_out, 1); + + if (res==Z_BUF_ERROR) { + /* We're out of space, or out of decodeable input. + Only if nwrite == 0 assume the latter. + */ + if (nwrite == 0) + return BEV_NEED_MORE; + } else { + assert(res == Z_OK || res == Z_STREAM_END); + } + + } while (evbuffer_get_length(src) > 0); + + ++infilter_calls; + + return (BEV_OK); +} + +static enum bufferevent_filter_result +zlib_output_filter(struct evbuffer *src, struct evbuffer *dst, + ev_ssize_t lim, enum bufferevent_flush_mode state, void *ctx) +{ + struct evbuffer_iovec v_in[1]; + struct evbuffer_iovec v_out[1]; + int nread, nwrite; + int res, n; + + z_streamp p = ctx; + + do { + /* let's do some compression */ + n = evbuffer_peek(src, -1, NULL, v_in, 1); + if (n) { + p->avail_in = v_in[0].iov_len; + p->next_in = (unsigned char *)v_in[0].iov_base; + } else { + p->avail_in = 0; + p->next_in = 0; + } + + evbuffer_reserve_space(dst, 4096, v_out, 1); + p->next_out = (unsigned char *)v_out[0].iov_base; + p->avail_out = v_out[0].iov_len; + + /* we need to flush zlib if we got a flush */ + res = deflate(p, getstate(state)); + + /* let's figure out how much was decompressed */ + nread = v_in[0].iov_len - p->avail_in; + nwrite = v_out[0].iov_len - p->avail_out; + + evbuffer_drain(src, nread); + v_out[0].iov_len = nwrite; + evbuffer_commit_space(dst, v_out, 1); + + if (res==Z_BUF_ERROR) { + /* We're out of space, or out of decodeable input. + Only if nwrite == 0 assume the latter. + */ + if (nwrite == 0) + return BEV_NEED_MORE; + } else { + assert(res == Z_OK || res == Z_STREAM_END); + } + + } while (evbuffer_get_length(src) > 0); + + ++outfilter_calls; + + return (BEV_OK); +} + +/* + * simple bufferevent test (over transparent zlib treatment) + */ + +static void +readcb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_input(bev)) == 8333) { + struct evbuffer *evbuf = evbuffer_new(); + assert(evbuf != NULL); + + /* gratuitous test of bufferevent_read_buffer */ + bufferevent_read_buffer(bev, evbuf); + + bufferevent_disable(bev, EV_READ); + + if (evbuffer_get_length(evbuf) == 8333) { + ++readcb_finished; + } + + evbuffer_free(evbuf); + } +} + +static void +writecb(struct bufferevent *bev, void *arg) +{ + if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + ++writecb_finished; + } +} + +static void +errorcb(struct bufferevent *bev, short what, void *arg) +{ + errorcb_invoked = 1; +} + +void +test_bufferevent_zlib(void *arg) +{ + struct bufferevent *bev1=NULL, *bev2=NULL; + char buffer[8333]; + z_stream *z_input, *z_output; + int i, r; + evutil_socket_t pair[2] = {-1, -1}; + (void)arg; + + infilter_calls = outfilter_calls = readcb_finished = writecb_finished + = errorcb_invoked = 0; + + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { + tt_abort_perror("socketpair"); + } + + evutil_make_socket_nonblocking(pair[0]); + evutil_make_socket_nonblocking(pair[1]); + + bev1 = bufferevent_socket_new(NULL, pair[0], 0); + bev2 = bufferevent_socket_new(NULL, pair[1], 0); + + z_output = mm_calloc(sizeof(*z_output), 1); + r = deflateInit(z_output, Z_DEFAULT_COMPRESSION); + tt_int_op(r, ==, Z_OK); + z_input = mm_calloc(sizeof(*z_input), 1); + r = inflateInit(z_input); + tt_int_op(r, ==, Z_OK); + + /* initialize filters */ + bev1 = bufferevent_filter_new(bev1, NULL, zlib_output_filter, + BEV_OPT_CLOSE_ON_FREE, zlib_deflate_free, z_output); + bev2 = bufferevent_filter_new(bev2, zlib_input_filter, + NULL, BEV_OPT_CLOSE_ON_FREE, zlib_inflate_free, z_input); + bufferevent_setcb(bev1, readcb, writecb, errorcb, NULL); + bufferevent_setcb(bev2, readcb, writecb, errorcb, NULL); + + bufferevent_disable(bev1, EV_READ); + bufferevent_enable(bev1, EV_WRITE); + + bufferevent_enable(bev2, EV_READ); + + for (i = 0; i < (int)sizeof(buffer); i++) + buffer[i] = i; + + /* break it up into multiple buffer chains */ + bufferevent_write(bev1, buffer, 1800); + bufferevent_write(bev1, buffer + 1800, sizeof(buffer) - 1800); + + /* we are done writing - we need to flush everything */ + bufferevent_flush(bev1, EV_WRITE, BEV_FINISHED); + + event_dispatch(); + + tt_want(infilter_calls); + tt_want(outfilter_calls); + tt_want(readcb_finished); + tt_want(writecb_finished); + tt_want(!errorcb_invoked); + + test_ok = 1; +end: + if (bev1) + bufferevent_free(bev1); + if (bev2) + bufferevent_free(bev2); + + if (pair[0] >= 0) + evutil_closesocket(pair[0]); + if (pair[1] >= 0) + evutil_closesocket(pair[1]); +} diff --git a/libevent/test/rpcgen_wrapper.sh b/libevent/test/rpcgen_wrapper.sh new file mode 100755 index 0000000..fe582d5 --- /dev/null +++ b/libevent/test/rpcgen_wrapper.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# libevent rpcgen_wrapper.sh +# Transforms event_rpcgen.py failure into success for make, only if +# regress.gen.c and regress.gen.h already exist in $srcdir. This +# is needed for "make distcheck" to pass the read-only $srcdir build, +# as with read-only sources fresh from tarball, regress.gen.[ch] will +# be correct in $srcdir but unwritable. This previously triggered +# Makefile.am to create stub regress.gen.c and regress.gen.h in the +# distcheck _build directory, which were then detected as leftover +# files in the build tree after distclean, breaking distcheck. +# Note that regress.gen.[ch] are not in fresh git clones, making +# working Python a requirement for make distcheck of a git tree. + +exit_updated() { +# echo "Updated ${srcdir}/regress.gen.c and ${srcdir}/regress.gen.h" + exit 0 +} + +exit_reuse() { + echo "event_rpcgen.py failed, ${srcdir}/regress.gen.\[ch\] will be reused." >&2 + exit 0 +} + +exit_failed() { + echo "Could not generate regress.gen.\[ch\] using event_rpcgen.sh" >&2 + exit 1 +} +srcdir=$1 +srcdir=${srcdir:-.} + +${srcdir}/../event_rpcgen.py --quiet ${srcdir}/regress.rpc \ + test/regress.gen.h test/regress.gen.c + +case "$?" in + 0) + exit_updated + ;; + *) + test -r ${srcdir}/regress.gen.c -a -r ${srcdir}/regress.gen.h && \ + exit_reuse + exit_failed + ;; +esac diff --git a/libevent/test/test-changelist.c b/libevent/test/test-changelist.c new file mode 100644 index 0000000..fd1a17f --- /dev/null +++ b/libevent/test/test-changelist.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "event2/event-config.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#else +#include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "event2/event.h" +#include "event2/util.h" +#include <time.h> + +struct cpu_usage_timer { +#ifdef _WIN32 + HANDLE thread; + FILETIME usertimeBegin; + FILETIME kerneltimeBegin; +#else + clock_t ticksBegin; +#endif + struct timeval timeBegin; +}; +static void +start_cpu_usage_timer(struct cpu_usage_timer *timer) +{ +#ifdef _WIN32 + int r; + FILETIME createtime, exittime; + timer->thread = GetCurrentThread(); + r = GetThreadTimes(timer->thread, &createtime, &exittime, + &timer->usertimeBegin, &timer->kerneltimeBegin); + if (r==0) printf("GetThreadTimes failed."); +#else + timer->ticksBegin = clock(); +#endif + + evutil_gettimeofday(&timer->timeBegin, NULL); +} +#ifdef _WIN32 +static ev_int64_t +filetime_to_100nsec(const FILETIME *ft) +{ + /* Number of 100-nanosecond units */ + ev_int64_t n = ft->dwHighDateTime; + n <<= 32; + n += ft->dwLowDateTime; + return n; +} +static double +filetime_diff(const FILETIME *ftStart, const FILETIME *ftEnd) +{ + ev_int64_t s, e, diff; + double r; + s = filetime_to_100nsec(ftStart); + e = filetime_to_100nsec(ftEnd); + diff = e - s; + r = (double) diff; + return r / 1.0e7; +} +#endif + +static void +get_cpu_usage(struct cpu_usage_timer *timer, double *secElapsedOut, + double *secUsedOut, double *usageOut) +{ +#ifdef _WIN32 + double usertime_seconds, kerneltime_seconds; + FILETIME createtime, exittime, usertimeEnd, kerneltimeEnd; + int r; +#else + clock_t ticksEnd; +#endif + struct timeval timeEnd, timeDiff; + double secondsPassed, secondsUsed; + +#ifdef _WIN32 + r = GetThreadTimes(timer->thread, &createtime, &exittime, + &usertimeEnd, &kerneltimeEnd); + if (r==0) printf("GetThreadTimes failed."); + usertime_seconds = filetime_diff(&timer->usertimeBegin, &usertimeEnd); + kerneltime_seconds = filetime_diff(&timer->kerneltimeBegin, &kerneltimeEnd); + secondsUsed = kerneltime_seconds + usertime_seconds; +#else + ticksEnd = clock(); + secondsUsed = (ticksEnd - timer->ticksBegin) / (double)CLOCKS_PER_SEC; +#endif + evutil_gettimeofday(&timeEnd, NULL); + evutil_timersub(&timeEnd, &timer->timeBegin, &timeDiff); + secondsPassed = timeDiff.tv_sec + (timeDiff.tv_usec / 1.0e6); + + *secElapsedOut = secondsPassed; + *secUsedOut = secondsUsed; + *usageOut = secondsUsed / secondsPassed; +} + +static void +write_cb(evutil_socket_t fd, short event, void *arg) +{ + printf("write callback. should only see this once\n"); + + /* got what we want remove the event */ + event_del(*(struct event**)arg); + + /* opps changed my mind add it back again */ + event_add(*(struct event**)arg,NULL); + + /* not a good day for decisiveness, I really didn't want it after all */ + event_del(*(struct event**)arg); + +} + +static void +timeout_cb(evutil_socket_t fd, short event, void *arg) +{ + printf("timeout fired, time to end test\n"); + event_del(*(struct event**)arg); + return; +} + +int +main(int argc, char **argv) +{ + struct event* ev; + struct event* timeout; + struct event_base* base; + + evutil_socket_t pair[2]; + struct timeval tv; + struct cpu_usage_timer timer; + + double usage, secPassed, secUsed; + +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + /* Initialize the event library */ + if (!(base = event_base_new())) + return (1); + + /* Initialize a timeout to terminate the test */ + timeout = evtimer_new(base,timeout_cb,&timeout); + /* and watch for writability on one end of the pipe */ + ev = event_new(base,pair[1],EV_WRITE | EV_PERSIST, write_cb, &ev); + + tv.tv_sec = 1; + tv.tv_usec = 500*1000; + + evtimer_add(timeout, &tv); + + event_add(ev, NULL); + + start_cpu_usage_timer(&timer); + + event_base_dispatch(base); + + event_free(ev); + event_free(timeout); + event_base_free(base); + + get_cpu_usage(&timer, &secPassed, &secUsed, &usage); + + /* attempt to calculate our cpu usage over the test should be + virtually nil */ + + printf("usec used=%d, usec passed=%d, cpu usage=%.2f%%\n", + (int)(secUsed*1e6), + (int)(secPassed*1e6), + usage*100); + + if (usage > 50.0) /* way too high */ + return 1; + + return 0; +} + diff --git a/libevent/test/test-closed.c b/libevent/test/test-closed.c new file mode 100644 index 0000000..9e60504 --- /dev/null +++ b/libevent/test/test-closed.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2002-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2013 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" +#include "event2/event-config.h" + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <event.h> +#include <evutil.h> + +struct timeval timeout = {3, 0}; + +static void +closed_cb(evutil_socket_t fd, short event, void *arg) +{ + if (EV_TIMEOUT & event) { + printf("%s: Timeout!\n", __func__); + exit(1); + } + + if (EV_CLOSED & event) { + printf("%s: detected socket close with success\n", __func__); + return; + } + + printf("%s: unable to detect socket close\n", __func__); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct event_base *base; + struct event_config *cfg; + struct event *ev; + const char *test = "test string"; + evutil_socket_t pair[2]; + + /* Initialize the library and check if the backend + supports EV_FEATURE_EARLY_CLOSE + */ + cfg = event_config_new(); + event_config_require_features(cfg, EV_FEATURE_EARLY_CLOSE); + base = event_base_new_with_config(cfg); + event_config_free(cfg); + if (!base) { + /* Backend doesn't support EV_FEATURE_EARLY_CLOSE */ + return 0; + } + + /* Create a pair of sockets */ + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + /* Send some data on socket 0 and immediately close it */ + if (send(pair[0], test, (int)strlen(test)+1, 0) < 0) + return (1); + shutdown(pair[0], EVUTIL_SHUT_WR); + + /* Dispatch */ + ev = event_new(base, pair[1], EV_CLOSED | EV_TIMEOUT, closed_cb, event_self_cbarg()); + event_add(ev, &timeout); + event_base_dispatch(base); + + /* Finalize library */ + event_free(ev); + event_base_free(base); + return 0; +} + diff --git a/libevent/test/test-dumpevents.c b/libevent/test/test-dumpevents.c new file mode 100644 index 0000000..1c272d4 --- /dev/null +++ b/libevent/test/test-dumpevents.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util-internal.h" +#include "event2/event-config.h" + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#else +#include <unistd.h> +#endif + +#include <stdio.h> +#include <event2/event.h> +#include <signal.h> + +static void +sock_perror(const char *s) +{ +#ifdef _WIN32 + const char *err = evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()); + fprintf(stderr, "%s: %s\n", s, err); +#else + perror(s); +#endif +} + +static void +callback1(evutil_socket_t fd, short events, void *arg) +{ +} +static void +callback2(evutil_socket_t fd, short events, void *arg) +{ +} + +/* Testing code for event_base_dump_events(). + + Notes that just because we have code to exercise this function, + doesn't mean that *ANYTHING* about the output format is guaranteed to + remain in the future. + */ +int +main(int argc, char **argv) +{ +#define N_EVENTS 13 + int i; + struct event *ev[N_EVENTS]; + evutil_socket_t pair1[2]; + evutil_socket_t pair2[2]; + struct timeval tv_onesec = {1,0}; + struct timeval tv_two5sec = {2,500*1000}; + const struct timeval *tv_onesec_common; + const struct timeval *tv_two5sec_common; + struct event_base *base; + struct timeval now; + +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + WSAStartup(wVersionRequested, &wsaData); +#endif + +#ifdef _WIN32 +#define LOCAL_SOCKETPAIR_AF AF_INET +#else +#define LOCAL_SOCKETPAIR_AF AF_UNIX +#endif + + if (evutil_make_internal_pipe_(pair1) < 0 || + evutil_make_internal_pipe_(pair2) < 0) { + sock_perror("evutil_make_internal_pipe_"); + return 1; + } + + if (!(base = event_base_new())) { + fprintf(stderr,"Couldn't make event_base\n"); + return 2; + } + + tv_onesec_common = event_base_init_common_timeout(base, &tv_onesec); + tv_two5sec_common = event_base_init_common_timeout(base, &tv_two5sec); + + ev[0] = event_new(base, pair1[0], EV_WRITE, callback1, NULL); + ev[1] = event_new(base, pair1[1], EV_READ|EV_PERSIST, callback1, NULL); + ev[2] = event_new(base, pair2[0], EV_WRITE|EV_PERSIST, callback2, NULL); + ev[3] = event_new(base, pair2[1], EV_READ, callback2, NULL); + + /* For timers */ + ev[4] = evtimer_new(base, callback1, NULL); + ev[5] = evtimer_new(base, callback1, NULL); + ev[6] = evtimer_new(base, callback1, NULL); + ev[7] = event_new(base, -1, EV_PERSIST, callback2, NULL); + ev[8] = event_new(base, -1, EV_PERSIST, callback2, NULL); + ev[9] = event_new(base, -1, EV_PERSIST, callback2, NULL); + + /* To activate */ + ev[10] = event_new(base, -1, 0, callback1, NULL); + ev[11] = event_new(base, -1, 0, callback2, NULL); + + /* Signals */ + ev[12] = evsignal_new(base, SIGINT, callback2, NULL); + + event_add(ev[0], NULL); + event_add(ev[1], &tv_onesec); + event_add(ev[2], tv_onesec_common); + event_add(ev[3], tv_two5sec_common); + + event_add(ev[4], tv_onesec_common); + event_add(ev[5], tv_onesec_common); + event_add(ev[6], &tv_onesec); + event_add(ev[7], tv_two5sec_common); + event_add(ev[8], tv_onesec_common); + event_add(ev[9], &tv_two5sec); + + event_active(ev[10], EV_READ, 1); + event_active(ev[11], EV_READ|EV_WRITE|EV_TIMEOUT, 1); + event_active(ev[1], EV_READ, 1); + + event_add(ev[12], NULL); + + evutil_gettimeofday(&now,NULL); + puts("=====expected"); + printf("Now= %ld.%06d\n",(long)now.tv_sec,(int)now.tv_usec); + puts("Inserted:"); + printf(" %p [fd %ld] Write\n",ev[0],(long)pair1[0]); + printf(" %p [fd %ld] Read Persist Timeout=T+1\n",ev[1],(long)pair1[1]); + printf(" %p [fd %ld] Write Persist Timeout=T+1\n",ev[2],(long)pair2[0]); + printf(" %p [fd %ld] Read Timeout=T+2.5\n",ev[3],(long)pair2[1]); + printf(" %p [fd -1] Timeout=T+1\n",ev[4]); + printf(" %p [fd -1] Timeout=T+1\n",ev[5]); + printf(" %p [fd -1] Timeout=T+1\n",ev[6]); + printf(" %p [fd -1] Persist Timeout=T+2.5\n",ev[7]); + printf(" %p [fd -1] Persist Timeout=T+1\n",ev[8]); + printf(" %p [fd -1] Persist Timeout=T+2.5\n",ev[9]); + printf(" %p [sig %d] Signal Persist\n", ev[12], (int)SIGINT); + + puts("Active:"); + printf(" %p [fd -1, priority=0] Read active\n", ev[10]); + printf(" %p [fd -1, priority=0] Read Write Timeout active\n", ev[11]); + printf(" %p [fd %ld, priority=0] Read active\n", ev[1], (long)pair1[1]); + + puts("======received"); + event_base_dump_events(base, stdout); + + for (i = 0; i < N_EVENTS; ++i) { + event_free(ev[i]); + } + event_base_free(base); + + return 0; +} + diff --git a/libevent/test/test-eof.c b/libevent/test/test-eof.c new file mode 100644 index 0000000..de2fd88 --- /dev/null +++ b/libevent/test/test-eof.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2002-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" +#include "event2/event-config.h" + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include <event.h> +#include <evutil.h> + +int test_okay = 1; +int called = 0; +struct timeval timeout = {60, 0}; + +static void +read_cb(evutil_socket_t fd, short event, void *arg) +{ + char buf[256]; + int len; + + if (EV_TIMEOUT & event) { + printf("%s: Timeout!\n", __func__); + exit(1); + } + + len = recv(fd, buf, sizeof(buf), 0); + + printf("%s: read %d%s\n", __func__, + len, len ? "" : " - means EOF"); + + if (len) { + if (!called) + event_add(arg, &timeout); + } else if (called == 1) + test_okay = 0; + + called++; +} + +int +main(int argc, char **argv) +{ + struct event ev; + const char *test = "test string"; + evutil_socket_t pair[2]; + +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + + if (send(pair[0], test, (int)strlen(test)+1, 0) < 0) + return (1); + shutdown(pair[0], EVUTIL_SHUT_WR); + + /* Initialize the event library */ + event_init(); + + /* Initialize one event */ + event_set(&ev, pair[1], EV_READ | EV_TIMEOUT, read_cb, &ev); + + event_add(&ev, &timeout); + + event_dispatch(); + + return (test_okay); +} + diff --git a/libevent/test/test-fdleak.c b/libevent/test/test-fdleak.c new file mode 100644 index 0000000..a1fb4ed --- /dev/null +++ b/libevent/test/test-fdleak.c @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2012 Ross Lagerwall <rosslagerwall@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "event2/event-config.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef EVENT__HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif +#ifdef EVENT__HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include "event2/event.h" +#include "event2/bufferevent.h" +#include "event2/buffer.h" +#include "event2/listener.h" + +/* Number of requests to make. Setting this too high might result in the machine + running out of ephemeral ports */ +#ifdef _WIN32 +#define MAX_REQUESTS 1000 +#else +#define MAX_REQUESTS 4000 +#endif + +/* Provide storage for the address, both for the server & the clients */ +static struct sockaddr_in saddr; + +/* Number of sucessful requests so far */ +static int num_requests; + +static void start_client(struct event_base *base); + +static void +my_perror(const char *s) +{ + fprintf(stderr, "%s: %s", + s, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); +} + +/* +=============================================== +Server functions +=============================================== +*/ + +/* Read a byte from the client and write it back */ +static void +server_read_cb(struct bufferevent *bev, void *ctx) +{ + while (evbuffer_get_length(bufferevent_get_input(bev))) { + unsigned char tmp; + bufferevent_read(bev, &tmp, 1); + bufferevent_write(bev, &tmp, 1); + } +} + +/* Wait for an EOF and then free the bufferevent */ +static void +server_event_cb(struct bufferevent *bev, short events, void *ctx) +{ + if (events & BEV_EVENT_ERROR) { + my_perror("Error from bufferevent"); + exit(1); + } else if (events & BEV_EVENT_EOF) { + bufferevent_free(bev); + if (num_requests == MAX_REQUESTS) { + event_base_loopbreak(bufferevent_get_base(bev)); + } + } +} + +/* Accept a client socket and set it up to for reading & writing */ +static void +listener_accept_cb(struct evconnlistener *listener, evutil_socket_t sock, + struct sockaddr *addr, int len, void *ptr) +{ + struct event_base *base = evconnlistener_get_base(listener); + struct bufferevent *bev = bufferevent_socket_new(base, sock, + BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, server_read_cb, NULL, server_event_cb, NULL); + bufferevent_enable(bev, EV_READ|EV_WRITE); +} + +/* Start the server listening on a random port and start the first client. */ +static void +start_loop(void) +{ + struct event_base *base; + struct evconnlistener *listener; + struct sockaddr_storage ss; + ev_socklen_t socklen = sizeof(ss); + evutil_socket_t fd; + + base = event_base_new(); + if (base == NULL) { + puts("Could not open event base!"); + exit(1); + } + + listener = evconnlistener_new_bind(base, listener_accept_cb, NULL, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + -1, (struct sockaddr *)&saddr, sizeof(saddr)); + if (listener == NULL) { + my_perror("Could not create listener!"); + exit(1); + } + fd = evconnlistener_get_fd(listener); + if (fd < 0) { + puts("Couldn't get fd from listener"); + exit(1); + } + if (getsockname(fd, (struct sockaddr *)&ss, &socklen) < 0) { + my_perror("getsockname()"); + exit(1); + } + memcpy(&saddr, &ss, sizeof(saddr)); + if (saddr.sin_family != AF_INET) { + puts("AF mismatch from getsockname()."); + exit(1); + } + + start_client(base); + + event_base_dispatch(base); + + evconnlistener_free(listener); + event_base_free(base); +} + +/* +=============================================== +Client functions +=============================================== +*/ + +/* Check that the server sends back the same byte that the client sent. + If MAX_REQUESTS have been reached, exit. Otherwise, start another client. */ +static void +client_read_cb(struct bufferevent *bev, void *ctx) +{ + unsigned char tmp; + struct event_base *base = bufferevent_get_base(bev); + + bufferevent_read(bev, &tmp, 1); + if (tmp != 'A') { + puts("Incorrect data received!"); + exit(2); + } + bufferevent_free(bev); + + num_requests++; + if (++num_requests < MAX_REQUESTS) { + start_client(base); + } +} + +/* Send a byte to the server. */ +static void +client_event_cb(struct bufferevent *bev, short events, void *ctx) +{ + if (events & BEV_EVENT_CONNECTED) { + unsigned char tmp = 'A'; + bufferevent_write(bev, &tmp, 1); + } else if (events & BEV_EVENT_ERROR) { + puts("Client socket got error!"); + exit(2); + } + + bufferevent_enable(bev, EV_READ); +} + +/* Open a client socket to connect to localhost on sin */ +static void +start_client(struct event_base *base) +{ + struct bufferevent *bev = bufferevent_socket_new(base, -1, + BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, client_read_cb, NULL, client_event_cb, NULL); + + if (bufferevent_socket_connect(bev, (struct sockaddr *)&saddr, + sizeof(saddr)) < 0) { + my_perror("Could not connect!"); + bufferevent_free(bev); + exit(2); + } +} + +int +main(int argc, char **argv) +{ +#ifdef EVENT__HAVE_SETRLIMIT + /* Set the fd limit to a low value so that any fd leak is caught without + making many requests. */ + struct rlimit rl; + rl.rlim_cur = rl.rlim_max = 20; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { + my_perror("setrlimit"); + exit(3); + } +#endif + +#ifdef _WIN32 + WSADATA WSAData; + WSAStartup(0x101, &WSAData); +#endif + + /* Set up an address, used by both client & server. */ + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(0x7f000001); + saddr.sin_port = 0; /* Tell the implementation to pick a port. */ + + start_loop(); + + return 0; +} + +/* XXX why does this test cause so much latency sometimes (OSX 10.5)? */ diff --git a/libevent/test/test-init.c b/libevent/test/test-init.c new file mode 100644 index 0000000..aea49ee --- /dev/null +++ b/libevent/test/test-init.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "event2/event-config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <unistd.h> +#endif +#include <errno.h> + +#include <event.h> + +int +main(int argc, char **argv) +{ +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + + /* Initialize the event library */ + event_init(); + + return (0); +} + diff --git a/libevent/test/test-ratelim.c b/libevent/test/test-ratelim.c new file mode 100644 index 0000000..34112e3 --- /dev/null +++ b/libevent/test/test-ratelim.c @@ -0,0 +1,688 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <math.h> + +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +# ifdef _XOPEN_SOURCE_EXTENDED +# include <arpa/inet.h> +# endif +#endif +#include <signal.h> + +#include "event2/bufferevent.h" +#include "event2/buffer.h" +#include "event2/event.h" +#include "event2/util.h" +#include "event2/listener.h" +#include "event2/thread.h" + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +static struct evutil_weakrand_state weakrand_state; + +static int cfg_verbose = 0; +static int cfg_help = 0; + +static int cfg_n_connections = 30; +static int cfg_duration = 5; +static int cfg_connlimit = 0; +static int cfg_grouplimit = 0; +static int cfg_tick_msec = 1000; +static int cfg_min_share = -1; +static int cfg_group_drain = 0; + +static int cfg_connlimit_tolerance = -1; +static int cfg_grouplimit_tolerance = -1; +static int cfg_stddev_tolerance = -1; + +#ifdef _WIN32 +static int cfg_enable_iocp = 0; +#endif + +static struct timeval cfg_tick = { 0, 500*1000 }; + +static struct ev_token_bucket_cfg *conn_bucket_cfg = NULL; +static struct ev_token_bucket_cfg *group_bucket_cfg = NULL; +struct bufferevent_rate_limit_group *ratelim_group = NULL; +static double seconds_per_tick = 0.0; + +struct client_state { + size_t queued; + ev_uint64_t received; + +}; +static const struct timeval *ms100_common=NULL; + +/* Timers bias for slow CPUs, affects: + * - cfg_connlimit_tolerance (--check-connlimit) + * - cfg_grouplimit_tolerance (--check-grouplimit) + * - cfg_stddev_tolerance (--check-stddev) + */ +static int timer_bias_events; +static struct timeval timer_bias_start; +double timer_bias_spend; +/* Real cost is less (approximately ~5 usec), + * this macros adjusted to make the bias less */ +#define TIMER_MAX_COST_USEC 10 + +/* info from check_bucket_levels_cb */ +static int total_n_bev_checks = 0; +static ev_int64_t total_rbucket_level=0; +static ev_int64_t total_wbucket_level=0; +static ev_int64_t total_max_to_read=0; +static ev_int64_t total_max_to_write=0; +static ev_int64_t max_bucket_level=EV_INT64_MIN; +static ev_int64_t min_bucket_level=EV_INT64_MAX; + +/* from check_group_bucket_levels_cb */ +static int total_n_group_bev_checks = 0; +static ev_int64_t total_group_rbucket_level = 0; +static ev_int64_t total_group_wbucket_level = 0; + +static int n_echo_conns_open = 0; + +/* Info on the open connections */ +struct bufferevent **bevs; +struct client_state *states; +struct bufferevent_rate_limit_group *group = NULL; + +static void check_bucket_levels_cb(evutil_socket_t fd, short events, void *arg); + +static void +loud_writecb(struct bufferevent *bev, void *ctx) +{ + struct client_state *cs = ctx; + struct evbuffer *output = bufferevent_get_output(bev); + char buf[1024]; + int r = evutil_weakrand_(&weakrand_state); + memset(buf, r, sizeof(buf)); + while (evbuffer_get_length(output) < 8192) { + evbuffer_add(output, buf, sizeof(buf)); + cs->queued += sizeof(buf); + } +} + +static void +discard_readcb(struct bufferevent *bev, void *ctx) +{ + struct client_state *cs = ctx; + struct evbuffer *input = bufferevent_get_input(bev); + size_t len = evbuffer_get_length(input); + evbuffer_drain(input, len); + cs->received += len; +} + +static void +write_on_connectedcb(struct bufferevent *bev, short what, void *ctx) +{ + if (what & BEV_EVENT_CONNECTED) { + loud_writecb(bev, ctx); + /* XXXX this shouldn't be needed. */ + bufferevent_enable(bev, EV_READ|EV_WRITE); + } +} + +static void +echo_readcb(struct bufferevent *bev, void *ctx) +{ + struct evbuffer *input = bufferevent_get_input(bev); + struct evbuffer *output = bufferevent_get_output(bev); + + evbuffer_add_buffer(output, input); + if (evbuffer_get_length(output) > 1024000) + bufferevent_disable(bev, EV_READ); +} + +static void +echo_writecb(struct bufferevent *bev, void *ctx) +{ + struct evbuffer *output = bufferevent_get_output(bev); + if (evbuffer_get_length(output) < 512000) + bufferevent_enable(bev, EV_READ); +} + +static void +echo_eventcb(struct bufferevent *bev, short what, void *ctx) +{ + if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { + --n_echo_conns_open; + bufferevent_free(bev); + } +} + +static void +echo_listenercb(struct evconnlistener *listener, evutil_socket_t newsock, + struct sockaddr *sourceaddr, int socklen, void *ctx) +{ + struct event_base *base = ctx; + int flags = BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE; + struct bufferevent *bev; + + bev = bufferevent_socket_new(base, newsock, flags); + bufferevent_setcb(bev, echo_readcb, echo_writecb, echo_eventcb, NULL); + if (conn_bucket_cfg) { + struct event *check_event = + event_new(base, -1, EV_PERSIST, check_bucket_levels_cb, bev); + bufferevent_set_rate_limit(bev, conn_bucket_cfg); + + assert(bufferevent_get_token_bucket_cfg(bev) != NULL); + event_add(check_event, ms100_common); + } + if (ratelim_group) + bufferevent_add_to_rate_limit_group(bev, ratelim_group); + ++n_echo_conns_open; + bufferevent_enable(bev, EV_READ|EV_WRITE); +} + +/* Called periodically to check up on how full the buckets are */ +static void +check_bucket_levels_cb(evutil_socket_t fd, short events, void *arg) +{ + struct bufferevent *bev = arg; + + ev_ssize_t r = bufferevent_get_read_limit(bev); + ev_ssize_t w = bufferevent_get_write_limit(bev); + ev_ssize_t rm = bufferevent_get_max_to_read(bev); + ev_ssize_t wm = bufferevent_get_max_to_write(bev); + /* XXXX check that no value is above the cofigured burst + * limit */ + total_rbucket_level += r; + total_wbucket_level += w; + total_max_to_read += rm; + total_max_to_write += wm; +#define B(x) \ + if ((x) > max_bucket_level) \ + max_bucket_level = (x); \ + if ((x) < min_bucket_level) \ + min_bucket_level = (x) + B(r); + B(w); +#undef B + + total_n_bev_checks++; + if (total_n_bev_checks >= .8 * ((double)cfg_duration / cfg_tick_msec) * cfg_n_connections) { + event_free(event_base_get_running_event(bufferevent_get_base(bev))); + } +} + +static void +check_group_bucket_levels_cb(evutil_socket_t fd, short events, void *arg) +{ + if (ratelim_group) { + ev_ssize_t r = bufferevent_rate_limit_group_get_read_limit(ratelim_group); + ev_ssize_t w = bufferevent_rate_limit_group_get_write_limit(ratelim_group); + total_group_rbucket_level += r; + total_group_wbucket_level += w; + } + ++total_n_group_bev_checks; +} + +static void +group_drain_cb(evutil_socket_t fd, short events, void *arg) +{ + bufferevent_rate_limit_group_decrement_read(ratelim_group, cfg_group_drain); + bufferevent_rate_limit_group_decrement_write(ratelim_group, cfg_group_drain); +} + +static void +timer_bias_cb(evutil_socket_t fd, short events, void *arg) +{ + struct event *event = arg; + struct timeval end; + struct timeval diff; + + /** XXX: use rdtsc? (portability issues?) */ + evutil_gettimeofday(&end, NULL); + evutil_timersub(&end, &timer_bias_start, &diff); + timer_bias_spend += diff.tv_sec + diff.tv_usec * 1e6; + timer_bias_start = end; + + if (++timer_bias_events == 100) + event_del(event); +} +static double +timer_bias_calculate(void) +{ + struct event_config *cfg = NULL; + struct event_base *base = NULL; + struct event *timer = NULL; + struct timeval tv = { 0, 1 }; + int done = 0; + + cfg = event_config_new(); + if (!cfg) + goto err; + if (event_config_set_flag(cfg, EVENT_BASE_FLAG_PRECISE_TIMER)) + goto err; + base = event_base_new_with_config(cfg); + if (!base) + goto err; + + timer = event_new(base, -1, EV_PERSIST, timer_bias_cb, event_self_cbarg()); + if (!timer || event_add(timer, &tv)) { + goto err; + } + + evutil_gettimeofday(&timer_bias_start, NULL); + event_base_dispatch(base); + done = 1; + +err: + if (cfg) + event_config_free(cfg); + if (timer) + event_free(timer); + if (base) + event_base_free(base); + + if (done) + return MIN(timer_bias_spend / 1e6 / timer_bias_events / TIMER_MAX_COST_USEC, 5); + + fprintf(stderr, "Couldn't create event for CPU cycle counter bias\n"); + return -1; +} + +static int +test_ratelimiting(void) +{ + struct event_base *base; + struct sockaddr_in sin; + struct evconnlistener *listener; + + struct sockaddr_storage ss; + ev_socklen_t slen; + + int i; + + struct timeval tv; + + ev_uint64_t total_received; + double total_sq_persec, total_persec; + double variance; + double expected_total_persec = -1.0, expected_avg_persec = -1.0; + int ok = 1; + struct event_config *base_cfg; + struct event *periodic_level_check; + struct event *group_drain_event=NULL; + double timer_bias; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sin.sin_port = 0; /* unspecified port */ + + if (0) + event_enable_debug_mode(); + + timer_bias = timer_bias_calculate(); + if (timer_bias > 1) { + fprintf(stderr, "CPU is slow, timers bias is %f\n", timer_bias); + cfg_connlimit_tolerance *= timer_bias; + cfg_grouplimit_tolerance *= timer_bias; + cfg_stddev_tolerance *= timer_bias; + } else { + printf("CPU is fast enough, timers bias is %f\n", timer_bias); + } + + base_cfg = event_config_new(); + +#ifdef _WIN32 + if (cfg_enable_iocp) { +#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED + evthread_use_windows_threads(); +#endif + event_config_set_flag(base_cfg, EVENT_BASE_FLAG_STARTUP_IOCP); + } +#endif + + base = event_base_new_with_config(base_cfg); + event_config_free(base_cfg); + if (! base) { + fprintf(stderr, "Couldn't create event_base"); + return 1; + } + + listener = evconnlistener_new_bind(base, echo_listenercb, base, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1, + (struct sockaddr *)&sin, sizeof(sin)); + if (! listener) { + fprintf(stderr, "Couldn't create listener"); + return 1; + } + + slen = sizeof(ss); + if (getsockname(evconnlistener_get_fd(listener), (struct sockaddr *)&ss, + &slen) < 0) { + perror("getsockname"); + return 1; + } + + if (cfg_connlimit > 0) { + conn_bucket_cfg = ev_token_bucket_cfg_new( + cfg_connlimit, cfg_connlimit * 4, + cfg_connlimit, cfg_connlimit * 4, + &cfg_tick); + assert(conn_bucket_cfg); + } + + if (cfg_grouplimit > 0) { + group_bucket_cfg = ev_token_bucket_cfg_new( + cfg_grouplimit, cfg_grouplimit * 4, + cfg_grouplimit, cfg_grouplimit * 4, + &cfg_tick); + group = ratelim_group = bufferevent_rate_limit_group_new( + base, group_bucket_cfg); + expected_total_persec = cfg_grouplimit - (cfg_group_drain / seconds_per_tick); + expected_avg_persec = cfg_grouplimit / cfg_n_connections; + if (cfg_connlimit > 0 && expected_avg_persec > cfg_connlimit) + expected_avg_persec = cfg_connlimit; + if (cfg_min_share >= 0) + bufferevent_rate_limit_group_set_min_share( + ratelim_group, cfg_min_share); + } + + if (expected_avg_persec < 0 && cfg_connlimit > 0) + expected_avg_persec = cfg_connlimit; + + if (expected_avg_persec > 0) + expected_avg_persec /= seconds_per_tick; + if (expected_total_persec > 0) + expected_total_persec /= seconds_per_tick; + + bevs = calloc(cfg_n_connections, sizeof(struct bufferevent *)); + states = calloc(cfg_n_connections, sizeof(struct client_state)); + + for (i = 0; i < cfg_n_connections; ++i) { + bevs[i] = bufferevent_socket_new(base, -1, + BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE); + assert(bevs[i]); + bufferevent_setcb(bevs[i], discard_readcb, loud_writecb, + write_on_connectedcb, &states[i]); + bufferevent_enable(bevs[i], EV_READ|EV_WRITE); + bufferevent_socket_connect(bevs[i], (struct sockaddr *)&ss, + slen); + } + + tv.tv_sec = cfg_duration - 1; + tv.tv_usec = 995000; + + event_base_loopexit(base, &tv); + + tv.tv_sec = 0; + tv.tv_usec = 100*1000; + ms100_common = event_base_init_common_timeout(base, &tv); + + periodic_level_check = event_new(base, -1, EV_PERSIST, check_group_bucket_levels_cb, NULL); + event_add(periodic_level_check, ms100_common); + + if (cfg_group_drain && ratelim_group) { + group_drain_event = event_new(base, -1, EV_PERSIST, group_drain_cb, NULL); + event_add(group_drain_event, &cfg_tick); + } + + event_base_dispatch(base); + + ratelim_group = NULL; /* So no more responders get added */ + event_free(periodic_level_check); + if (group_drain_event) + event_free(group_drain_event); + + for (i = 0; i < cfg_n_connections; ++i) { + bufferevent_free(bevs[i]); + } + evconnlistener_free(listener); + + /* Make sure no new echo_conns get added to the group. */ + ratelim_group = NULL; + + /* This should get _everybody_ freed */ + while (n_echo_conns_open) { + printf("waiting for %d conns\n", n_echo_conns_open); + tv.tv_sec = 0; + tv.tv_usec = 300000; + event_base_loopexit(base, &tv); + event_base_dispatch(base); + } + + if (group) + bufferevent_rate_limit_group_free(group); + + if (total_n_bev_checks) { + printf("Average read bucket level: %f\n", + (double)total_rbucket_level/total_n_bev_checks); + printf("Average write bucket level: %f\n", + (double)total_wbucket_level/total_n_bev_checks); + printf("Highest read bucket level: %f\n", + (double)max_bucket_level); + printf("Highest write bucket level: %f\n", + (double)min_bucket_level); + printf("Average max-to-read: %f\n", + ((double)total_max_to_read)/total_n_bev_checks); + printf("Average max-to-write: %f\n", + ((double)total_max_to_write)/total_n_bev_checks); + } + if (total_n_group_bev_checks) { + printf("Average group read bucket level: %f\n", + ((double)total_group_rbucket_level)/total_n_group_bev_checks); + printf("Average group write bucket level: %f\n", + ((double)total_group_wbucket_level)/total_n_group_bev_checks); + } + + total_received = 0; + total_persec = 0.0; + total_sq_persec = 0.0; + for (i=0; i < cfg_n_connections; ++i) { + double persec = states[i].received; + persec /= cfg_duration; + total_received += states[i].received; + total_persec += persec; + total_sq_persec += persec*persec; + printf("%d: %f per second\n", i+1, persec); + } + printf(" total: %f per second\n", + ((double)total_received)/cfg_duration); + if (expected_total_persec > 0) { + double diff = expected_total_persec - + ((double)total_received/cfg_duration); + printf(" [Off by %lf]\n", diff); + if (cfg_grouplimit_tolerance > 0 && + fabs(diff) > cfg_grouplimit_tolerance) { + fprintf(stderr, "Group bandwidth out of bounds\n"); + ok = 0; + } + } + + printf(" average: %f per second\n", + (((double)total_received)/cfg_duration)/cfg_n_connections); + if (expected_avg_persec > 0) { + double diff = expected_avg_persec - (((double)total_received)/cfg_duration)/cfg_n_connections; + printf(" [Off by %lf]\n", diff); + if (cfg_connlimit_tolerance > 0 && + fabs(diff) > cfg_connlimit_tolerance) { + fprintf(stderr, "Connection bandwidth out of bounds\n"); + ok = 0; + } + } + + variance = total_sq_persec/cfg_n_connections - total_persec*total_persec/(cfg_n_connections*cfg_n_connections); + + printf(" stddev: %f per second\n", sqrt(variance)); + if (cfg_stddev_tolerance > 0 && + sqrt(variance) > cfg_stddev_tolerance) { + fprintf(stderr, "Connection variance out of bounds\n"); + ok = 0; + } + + event_base_free(base); + free(bevs); + free(states); + + return ok ? 0 : 1; +} + +static struct option { + const char *name; int *ptr; int min; int isbool; +} options[] = { + { "-v", &cfg_verbose, 0, 1 }, + { "-h", &cfg_help, 0, 1 }, + { "-n", &cfg_n_connections, 1, 0 }, + { "-d", &cfg_duration, 1, 0 }, + { "-c", &cfg_connlimit, 0, 0 }, + { "-g", &cfg_grouplimit, 0, 0 }, + { "-G", &cfg_group_drain, -100000, 0 }, + { "-t", &cfg_tick_msec, 10, 0 }, + { "--min-share", &cfg_min_share, 0, 0 }, + { "--check-connlimit", &cfg_connlimit_tolerance, 0, 0 }, + { "--check-grouplimit", &cfg_grouplimit_tolerance, 0, 0 }, + { "--check-stddev", &cfg_stddev_tolerance, 0, 0 }, +#ifdef _WIN32 + { "--iocp", &cfg_enable_iocp, 0, 1 }, +#endif + { NULL, NULL, -1, 0 }, +}; + +static int +handle_option(int argc, char **argv, int *i, const struct option *opt) +{ + long val; + char *endptr = NULL; + if (opt->isbool) { + *opt->ptr = 1; + return 0; + } + if (*i + 1 == argc) { + fprintf(stderr, "Too few arguments to '%s'\n",argv[*i]); + return -1; + } + val = strtol(argv[*i+1], &endptr, 10); + if (*argv[*i+1] == '\0' || !endptr || *endptr != '\0') { + fprintf(stderr, "Couldn't parse numeric value '%s'\n", + argv[*i+1]); + return -1; + } + if (val < opt->min || val > 0x7fffffff) { + fprintf(stderr, "Value '%s' is out-of-range'\n", + argv[*i+1]); + return -1; + } + *opt->ptr = (int)val; + ++*i; + return 0; +} + +static void +usage(void) +{ + fprintf(stderr, +"test-ratelim [-v] [-n INT] [-d INT] [-c INT] [-g INT] [-t INT]\n\n" +"Pushes bytes through a number of possibly rate-limited connections, and\n" +"displays average throughput.\n\n" +" -n INT: Number of connections to open (default: 30)\n" +" -d INT: Duration of the test in seconds (default: 5 sec)\n"); + fprintf(stderr, +" -c INT: Connection-rate limit applied to each connection in bytes per second\n" +" (default: None.)\n" +" -g INT: Group-rate limit applied to sum of all usage in bytes per second\n" +" (default: None.)\n" +" -G INT: drain INT bytes from the group limit every tick. (default: 0)\n" +" -t INT: Granularity of timing, in milliseconds (default: 1000 msec)\n"); +} + +int +main(int argc, char **argv) +{ + int i,j; + double ratio; + +#ifdef _WIN32 + WORD wVersionRequested = MAKEWORD(2,2); + WSADATA wsaData; + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + + evutil_weakrand_seed_(&weakrand_state, 0); + +#ifndef _WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return 1; +#endif + for (i = 1; i < argc; ++i) { + for (j = 0; options[j].name; ++j) { + if (!strcmp(argv[i],options[j].name)) { + if (handle_option(argc,argv,&i,&options[j])<0) + return 1; + goto again; + } + } + fprintf(stderr, "Unknown option '%s'\n", argv[i]); + usage(); + return 1; + again: + ; + } + if (cfg_help) { + usage(); + return 0; + } + + cfg_tick.tv_sec = cfg_tick_msec / 1000; + cfg_tick.tv_usec = (cfg_tick_msec % 1000)*1000; + + seconds_per_tick = ratio = cfg_tick_msec / 1000.0; + + cfg_connlimit *= ratio; + cfg_grouplimit *= ratio; + + { + struct timeval tv; + evutil_gettimeofday(&tv, NULL); +#ifdef _WIN32 + srand(tv.tv_usec); +#else + srandom(tv.tv_usec); +#endif + } + +#ifndef EVENT__DISABLE_THREAD_SUPPORT + evthread_enable_lock_debugging(); +#endif + + return test_ratelimiting(); +} diff --git a/libevent/test/test-time.c b/libevent/test/test-time.c new file mode 100644 index 0000000..a8b3846 --- /dev/null +++ b/libevent/test/test-time.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2002-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "event2/event-config.h" +#include "util-internal.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +#include <unistd.h> +#include <sys/time.h> +#endif +#include <errno.h> + +#include "event2/event.h" +#include "event2/event_compat.h" +#include "event2/event_struct.h" + +int called = 0; + +#define NEVENT 20000 + +struct event *ev[NEVENT]; + +struct evutil_weakrand_state weakrand_state; + +static int +rand_int(int n) +{ + return evutil_weakrand_(&weakrand_state) % n; +} + +static void +time_cb(evutil_socket_t fd, short event, void *arg) +{ + struct timeval tv; + int i, j; + + called++; + + if (called < 10*NEVENT) { + for (i = 0; i < 10; i++) { + j = rand_int(NEVENT); + tv.tv_sec = 0; + tv.tv_usec = rand_int(50000); + if (tv.tv_usec % 2 || called < NEVENT) + evtimer_add(ev[j], &tv); + else + evtimer_del(ev[j]); + } + } +} + +int +main(int argc, char **argv) +{ + struct event_base *base; + struct timeval tv; + int i; + +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + + evutil_weakrand_seed_(&weakrand_state, 0); + + if (getenv("EVENT_DEBUG_LOGGING_ALL")) { + event_enable_debug_logging(EVENT_DBG_ALL); + } + + base = event_base_new(); + + for (i = 0; i < NEVENT; i++) { + ev[i] = evtimer_new(base, time_cb, event_self_cbarg()); + tv.tv_sec = 0; + tv.tv_usec = rand_int(50000); + evtimer_add(ev[i], &tv); + } + + i = event_base_dispatch(base); + + printf("event_base_dispatch=%d, called=%d, EVENT=%d\n", + i, called, NEVENT); + + if (i == 1 && called >= NEVENT) { + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +} + diff --git a/libevent/test/test-weof.c b/libevent/test/test-weof.c new file mode 100644 index 0000000..68e7cd4 --- /dev/null +++ b/libevent/test/test-weof.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2002-2007 Niels Provos <provos@citi.umich.edu> + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "../util-internal.h" +#include "event2/event-config.h" + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#ifdef EVENT__HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef EVENT__HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <errno.h> + +#include "event2/event.h" +#include "event2/event_struct.h" +#include "event2/event_compat.h" +#include "event2/util.h" + +evutil_socket_t pair[2]; +int test_okay = 1; +int called = 0; + +static void +write_cb(evutil_socket_t fd, short event, void *arg) +{ + const char *test = "test string"; + int len; + + len = send(fd, test, (int)strlen(test) + 1, 0); + + printf("%s: write %d%s\n", __func__, + len, len ? "" : " - means EOF"); + + if (len > 0) { + if (!called) + event_add(arg, NULL); + evutil_closesocket(pair[0]); + } else if (called == 1) + test_okay = 0; + + called++; +} + +int +main(int argc, char **argv) +{ + struct event ev; + +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + + (void) WSAStartup(wVersionRequested, &wsaData); +#endif + +#ifndef _WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return (1); +#endif + + if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + /* Initialize the event library */ + event_init(); + + /* Initialize one event */ + event_set(&ev, pair[1], EV_WRITE, write_cb, &ev); + + event_add(&ev, NULL); + + event_dispatch(); + + return (test_okay); +} + diff --git a/libevent/test/test.sh b/libevent/test/test.sh new file mode 100755 index 0000000..f91a8b0 --- /dev/null +++ b/libevent/test/test.sh @@ -0,0 +1,188 @@ +#!/bin/sh + +BACKENDS="EVPORT KQUEUE EPOLL DEVPOLL POLL SELECT WIN32" +TESTS="test-eof test-closed test-weof test-time test-changelist test-fdleak" +FAILED=no +TEST_OUTPUT_FILE=${TEST_OUTPUT_FILE:-/dev/null} +REGRESS_ARGS=${REGRESS_ARGS:-} + +# /bin/echo is a little more likely to support -n than sh's builtin echo, +# printf is even more likely +if test "`printf %s hello 2>&1`" = "hello" +then + ECHO_N="printf %s" +else + if test -x /bin/echo + then + ECHO_N="/bin/echo -n" + else + ECHO_N="echo -n" + fi +fi + +if test "$TEST_OUTPUT_FILE" != "/dev/null" +then + touch "$TEST_OUTPUT_FILE" || exit 1 +fi + +TEST_DIR=. +TEST_SRC_DIR=. + +T=`echo "$0" | sed -e 's/test.sh$//'` +if test -x "$T/test-init" +then + TEST_DIR="$T" +elif test -x "./test/test-init" +then + TEST_DIR="./test" +fi +if test -f "$T/check-dumpevents.py" +then + TEST_SRC_DIR="$T" +elif test -f "./test/check-dumpevents.py" +then + TEST_SRC_DIR="./test" +fi + +setup () { + for i in $BACKENDS; do + eval "EVENT_NO$i=yes; export EVENT_NO$i" + done + unset EVENT_EPOLL_USE_CHANGELIST + unset EVENT_PRECISE_TIMER +} + +announce () { + echo "$@" + echo "$@" >>"$TEST_OUTPUT_FILE" +} + +announce_n () { + $ECHO_N "$@" + echo "$@" >>"$TEST_OUTPUT_FILE" +} + + +run_tests () { + if $TEST_DIR/test-init 2>>"$TEST_OUTPUT_FILE" ; + then + true + else + announce Skipping test + return + fi + for i in $TESTS; do + announce_n " $i: " + if $TEST_DIR/$i >>"$TEST_OUTPUT_FILE" ; + then + announce OKAY ; + else + announce FAILED ; + FAILED=yes + fi + done + announce_n " test-dumpevents: " + if python -c 'import sys; assert(sys.version_info >= (2, 4))' 2>/dev/null && test -f $TEST_SRC_DIR/check-dumpevents.py; then + if $TEST_DIR/test-dumpevents | $TEST_SRC_DIR/check-dumpevents.py >> "$TEST_OUTPUT_FILE" ; + then + announce OKAY ; + else + announce FAILED ; + fi + else + # no python + if $TEST_DIR/test-dumpevents >/dev/null; then + announce "OKAY (output not checked)" ; + else + announce "FAILED (output not checked)" ; + fi + fi + + test -x $TEST_DIR/regress || return + announce_n " regress: " + if test "$TEST_OUTPUT_FILE" = "/dev/null" ; + then + $TEST_DIR/regress --quiet $REGRESS_ARGS + else + $TEST_DIR/regress $REGRESS_ARGS >>"$TEST_OUTPUT_FILE" + fi + if test "$?" = "0" ; + then + announce OKAY ; + else + announce FAILED ; + FAILED=yes + fi + + announce_n " regress_debug: " + if test "$TEST_OUTPUT_FILE" = "/dev/null" ; + then + EVENT_DEBUG_MODE=1 $TEST_DIR/regress --quiet $REGRESS_ARGS + else + EVENT_DEBUG_MODE=1 $TEST_DIR/regress $REGRESS_ARGS >>"$TEST_OUTPUT_FILE" + fi + if test "$?" = "0" ; + then + announce OKAY ; + else + announce FAILED ; + FAILED=yes + fi +} + +do_test() { + setup + announce "$1 $2" + unset EVENT_NO$1 + if test "$2" = "(changelist)" ; then + EVENT_EPOLL_USE_CHANGELIST=yes; export EVENT_EPOLL_USE_CHANGELIST + elif test "$2" = "(timerfd)" ; then + EVENT_PRECISE_TIMER=1; export EVENT_PRECISE_TIMER + elif test "$2" = "(timerfd+changelist)" ; then + EVENT_EPOLL_USE_CHANGELIST=yes; export EVENT_EPOLL_USE_CHANGELIST + EVENT_PRECISE_TIMER=1; export EVENT_PRECISE_TIMER + fi + + run_tests +} + +usage() +{ + cat <<EOL + -b - specify backends + -t - run timerfd test + -c - run changelist test + -T - run timerfd+changelist test +EOL +} +main() +{ + backends=$BACKENDS + timerfd=0 + changelist=0 + timerfd_changelist=0 + + while getopts "b:tcT" c; do + case "$c" in + b) backends="$OPTARG";; + t) timerfd=1;; + c) changelist=1;; + T) timerfd_changelist=1;; + ?*) usage && exit 1;; + esac + done + + announce "Running tests:" + + [ $timerfd -eq 0 ] || do_test EPOLL "(timerfd)" + [ $changelist -eq 0 ] || do_test EPOLL "(changelist)" + [ $timerfd_changelist -eq 0 ] || do_test EPOLL "(timerfd+changelist)" + for i in $backends; do + do_test $i + done + + if test "$FAILED" = "yes"; then + exit 1 + fi +} +main "$@" diff --git a/libevent/test/tinytest.c b/libevent/test/tinytest.c new file mode 100644 index 0000000..85dfe74 --- /dev/null +++ b/libevent/test/tinytest.c @@ -0,0 +1,610 @@ +/* tinytest.c -- Copyright 2009-2012 Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef TINYTEST_LOCAL +#include "tinytest_local.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#ifndef NO_FORKING + +#ifdef _WIN32 +#include <windows.h> +#else +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#endif + +#if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) +#if (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 && \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070) +/* Workaround for a stupid bug in OSX 10.6 */ +#define FORK_BREAKS_GCOV +#include <vproc.h> +#endif +#endif + +#endif /* !NO_FORKING */ + +#ifndef __GNUC__ +#define __attribute__(x) +#endif + +#include "tinytest.h" +#include "tinytest_macros.h" + +#define LONGEST_TEST_NAME 16384 +#define DEFAULT_TESTCASE_TIMEOUT 30U +#define MAGIC_EXITCODE 42 + +static int in_tinytest_main = 0; /**< true if we're in tinytest_main().*/ +static int n_ok = 0; /**< Number of tests that have passed */ +static int n_bad = 0; /**< Number of tests that have failed. */ +static int n_skipped = 0; /**< Number of tests that have been skipped. */ + +static int opt_forked = 0; /**< True iff we're called from inside a win32 fork*/ +static int opt_nofork = 0; /**< Suppress calls to fork() for debugging. */ +static int opt_verbosity = 1; /**< -==quiet,0==terse,1==normal,2==verbose */ +static unsigned int opt_timeout = DEFAULT_TESTCASE_TIMEOUT; /**< Timeout for every test (using alarm()) */ +const char *verbosity_flag = ""; + +const struct testlist_alias_t *cfg_aliases=NULL; + +enum outcome { SKIP=2, OK=1, FAIL=0 }; +static enum outcome cur_test_outcome = 0; +const char *cur_test_prefix = NULL; /**< prefix of the current test group */ +/** Name of the current test, if we haven't logged is yet. Used for --quiet */ +const char *cur_test_name = NULL; + +static void usage(struct testgroup_t *groups, int list_groups) + __attribute__((noreturn)); +static int process_test_option(struct testgroup_t *groups, const char *test); + +#ifdef _WIN32 +/* Copy of argv[0] for win32. */ +static char commandname[MAX_PATH+1]; + +struct timeout_thread_args { + const testcase_fn *fn; + void *env; +}; + +static DWORD WINAPI +timeout_thread_proc_(LPVOID arg) +{ + struct timeout_thread_args *args = arg; + (*(args->fn))(args->env); + ExitThread(cur_test_outcome == FAIL ? 1 : 0); +} + +static enum outcome +testcase_run_in_thread_(const struct testcase_t *testcase, void *env) +{ + /* We will never run testcase in a new thread when the + timeout is set to zero */ + assert(opt_timeout); + DWORD ret, tid; + HANDLE handle; + struct timeout_thread_args args = { + &(testcase->fn), + env + }; + + handle =CreateThread(NULL, 0, timeout_thread_proc_, + (LPVOID)&args, 0, &tid); + ret = WaitForSingleObject(handle, opt_timeout * 1000U); + if (ret == WAIT_OBJECT_0) { + ret = 0; + if (!GetExitCodeThread(handle, &ret)) { + printf("GetExitCodeThread failed\n"); + ret = 1; + } + } else if (ret == WAIT_TIMEOUT) { + printf("timeout\n"); + } else { + printf("Wait failed\n"); + } + CloseHandle(handle); + if (ret == 0) + return OK; + else if (ret == MAGIC_EXITCODE) + return SKIP; + else + return FAIL; +} +#else +static unsigned int testcase_set_timeout_(void) +{ + return alarm(opt_timeout); +} + +static unsigned int testcase_reset_timeout_(void) +{ + return alarm(0); +} +#endif + +static enum outcome +testcase_run_bare_(const struct testcase_t *testcase) +{ + void *env = NULL; + int outcome; + if (testcase->setup) { + env = testcase->setup->setup_fn(testcase); + if (!env) + return FAIL; + else if (env == (void*)TT_SKIP) + return SKIP; + } + + cur_test_outcome = OK; + { + if (opt_timeout) { +#ifdef _WIN32 + cur_test_outcome = testcase_run_in_thread_(testcase, env); +#else + testcase_set_timeout_(); + testcase->fn(env); + testcase_reset_timeout_(); +#endif + } else { + testcase->fn(env); + } + } + outcome = cur_test_outcome; + + if (testcase->setup) { + if (testcase->setup->cleanup_fn(testcase, env) == 0) + outcome = FAIL; + } + + return outcome; +} + + +#ifndef NO_FORKING + +static enum outcome +testcase_run_forked_(const struct testgroup_t *group, + const struct testcase_t *testcase) +{ +#ifdef _WIN32 + /* Fork? On Win32? How primitive! We'll do what the smart kids do: + we'll invoke our own exe (whose name we recall from the command + line) with a command line that tells it to run just the test we + want, and this time without forking. + + (No, threads aren't an option. The whole point of forking is to + share no state between tests.) + */ + int ok; + char buffer[LONGEST_TEST_NAME+256]; + STARTUPINFOA si; + PROCESS_INFORMATION info; + DWORD ret; + + if (!in_tinytest_main) { + printf("\nERROR. On Windows, testcase_run_forked_ must be" + " called from within tinytest_main.\n"); + abort(); + } + if (opt_verbosity>0) + printf("[forking] "); + + snprintf(buffer, sizeof(buffer), "%s --RUNNING-FORKED %s --timeout 0 %s%s", + commandname, verbosity_flag, group->prefix, testcase->name); + + memset(&si, 0, sizeof(si)); + memset(&info, 0, sizeof(info)); + si.cb = sizeof(si); + + ok = CreateProcessA(commandname, buffer, NULL, NULL, 0, + 0, NULL, NULL, &si, &info); + if (!ok) { + printf("CreateProcess failed!\n"); + return FAIL; + } + ret = WaitForSingleObject(info.hProcess, + (opt_timeout ? opt_timeout * 1000U : INFINITE)); + + if (ret == WAIT_OBJECT_0) { + GetExitCodeProcess(info.hProcess, &ret); + } else if (ret == WAIT_TIMEOUT) { + printf("timeout\n"); + } else { + printf("Wait failed\n"); + } + CloseHandle(info.hProcess); + CloseHandle(info.hThread); + if (ret == 0) + return OK; + else if (ret == MAGIC_EXITCODE) + return SKIP; + else + return FAIL; +#else + int outcome_pipe[2]; + pid_t pid; + (void)group; + + if (pipe(outcome_pipe)) + perror("opening pipe"); + + if (opt_verbosity>0) + printf("[forking] "); + pid = fork(); +#ifdef FORK_BREAKS_GCOV + vproc_transaction_begin(0); +#endif + if (!pid) { + /* child. */ + int test_r, write_r; + char b[1]; + close(outcome_pipe[0]); + test_r = testcase_run_bare_(testcase); + assert(0<=(int)test_r && (int)test_r<=2); + b[0] = "NYS"[test_r]; + write_r = (int)write(outcome_pipe[1], b, 1); + if (write_r != 1) { + perror("write outcome to pipe"); + exit(1); + } + exit(0); + return FAIL; /* unreachable */ + } else { + /* parent */ + int status, r, exitcode; + char b[1]; + /* Close this now, so that if the other side closes it, + * our read fails. */ + close(outcome_pipe[1]); + r = (int)read(outcome_pipe[0], b, 1); + if (r == 0) { + printf("[Lost connection!] "); + return FAIL; + } else if (r != 1) { + perror("read outcome from pipe"); + } + waitpid(pid, &status, 0); + exitcode = WEXITSTATUS(status); + close(outcome_pipe[0]); + if (opt_verbosity>1) + printf("%s%s: exited with %i (%i)\n", group->prefix, testcase->name, exitcode, status); + if (exitcode != 0) + { + printf("[atexit failure!] "); + return FAIL; + } + return b[0]=='Y' ? OK : (b[0]=='S' ? SKIP : FAIL); + } +#endif +} + +#endif /* !NO_FORKING */ + +int +testcase_run_one(const struct testgroup_t *group, + const struct testcase_t *testcase) +{ + enum outcome outcome; + + if (testcase->flags & (TT_SKIP|TT_OFF_BY_DEFAULT)) { + if (opt_verbosity>0) + printf("%s%s: %s\n", + group->prefix, testcase->name, + (testcase->flags & TT_SKIP) ? "SKIPPED" : "DISABLED"); + ++n_skipped; + return SKIP; + } + + if (opt_verbosity>0 && !opt_forked) { + printf("%s%s: ", group->prefix, testcase->name); + } else { + if (opt_verbosity==0) printf("."); + cur_test_prefix = group->prefix; + cur_test_name = testcase->name; + } + +#ifndef NO_FORKING + if ((testcase->flags & TT_FORK) && !(opt_forked||opt_nofork)) { + outcome = testcase_run_forked_(group, testcase); + } else { +#else + { +#endif + outcome = testcase_run_bare_(testcase); + } + + if (outcome == OK) { + if (opt_verbosity>0 && !opt_forked) + puts(opt_verbosity==1?"OK":""); + } else if (outcome == SKIP) { + if (opt_verbosity>0 && !opt_forked) + puts("SKIPPED"); + } else { + if (!opt_forked) + printf("\n [%s FAILED]\n", testcase->name); + } + + if (opt_forked) { + exit(outcome==OK ? 0 : (outcome==SKIP?MAGIC_EXITCODE : 1)); + return 1; /* unreachable */ + } else { + return (int)outcome; + } +} + +int +tinytest_set_flag_(struct testgroup_t *groups, const char *arg, int set, unsigned long flag) +{ + int i, j; + size_t length = LONGEST_TEST_NAME; + char fullname[LONGEST_TEST_NAME]; + int found=0; + if (strstr(arg, "..")) + length = strstr(arg,"..")-arg; + for (i=0; groups[i].prefix; ++i) { + for (j=0; groups[i].cases[j].name; ++j) { + struct testcase_t *testcase = &groups[i].cases[j]; + snprintf(fullname, sizeof(fullname), "%s%s", + groups[i].prefix, testcase->name); + if (!flag) { /* Hack! */ + printf(" %s", fullname); + if (testcase->flags & TT_OFF_BY_DEFAULT) + puts(" (Off by default)"); + else if (testcase->flags & TT_SKIP) + puts(" (DISABLED)"); + else + puts(""); + } + if (!strncmp(fullname, arg, length)) { + if (set) + testcase->flags |= flag; + else + testcase->flags &= ~flag; + ++found; + } + } + } + return found; +} + +static void +usage(struct testgroup_t *groups, int list_groups) +{ + puts("Options are: [--verbose|--quiet|--terse] [--no-fork] [--timeout <sec>]"); + puts(" Specify tests by name, or using a prefix ending with '..'"); + puts(" To skip a test, prefix its name with a colon."); + puts(" To enable a disabled test, prefix its name with a plus."); + puts(" Use --list-tests for a list of tests."); + if (list_groups) { + puts("Known tests are:"); + tinytest_set_flag_(groups, "..", 1, 0); + } + exit(0); +} + +static int +process_test_alias(struct testgroup_t *groups, const char *test) +{ + int i, j, n, r; + for (i=0; cfg_aliases && cfg_aliases[i].name; ++i) { + if (!strcmp(cfg_aliases[i].name, test)) { + n = 0; + for (j = 0; cfg_aliases[i].tests[j]; ++j) { + r = process_test_option(groups, cfg_aliases[i].tests[j]); + if (r<0) + return -1; + n += r; + } + return n; + } + } + printf("No such test alias as @%s!",test); + return -1; +} + +static int +process_test_option(struct testgroup_t *groups, const char *test) +{ + int flag = TT_ENABLED_; + int n = 0; + if (test[0] == '@') { + return process_test_alias(groups, test + 1); + } else if (test[0] == ':') { + ++test; + flag = TT_SKIP; + } else if (test[0] == '+') { + ++test; + ++n; + if (!tinytest_set_flag_(groups, test, 0, TT_OFF_BY_DEFAULT)) { + printf("No such test as %s!\n", test); + return -1; + } + } else { + ++n; + } + if (!tinytest_set_flag_(groups, test, 1, flag)) { + printf("No such test as %s!\n", test); + return -1; + } + return n; +} + +void +tinytest_set_aliases(const struct testlist_alias_t *aliases) +{ + cfg_aliases = aliases; +} + +int +tinytest_main(int c, const char **v, struct testgroup_t *groups) +{ + int i, j, n=0; + +#ifdef _WIN32 + const char *sp = strrchr(v[0], '.'); + const char *extension = ""; + if (!sp || stricmp(sp, ".exe")) + extension = ".exe"; /* Add an exe so CreateProcess will work */ + snprintf(commandname, sizeof(commandname), "%s%s", v[0], extension); + commandname[MAX_PATH]='\0'; +#endif + for (i=1; i<c; ++i) { + if (v[i][0] == '-') { + if (!strcmp(v[i], "--RUNNING-FORKED")) { + opt_forked = 1; + } else if (!strcmp(v[i], "--no-fork")) { + opt_nofork = 1; + } else if (!strcmp(v[i], "--quiet")) { + opt_verbosity = -1; + verbosity_flag = "--quiet"; + } else if (!strcmp(v[i], "--verbose")) { + opt_verbosity = 2; + verbosity_flag = "--verbose"; + } else if (!strcmp(v[i], "--terse")) { + opt_verbosity = 0; + verbosity_flag = "--terse"; + } else if (!strcmp(v[i], "--help")) { + usage(groups, 0); + } else if (!strcmp(v[i], "--list-tests")) { + usage(groups, 1); + } else if (!strcmp(v[i], "--timeout")) { + ++i; + if (i >= c) { + fprintf(stderr, "--timeout requires argument\n"); + return -1; + } + opt_timeout = (unsigned)atoi(v[i]); + } else { + fprintf(stderr, "Unknown option %s. Try --help\n", v[i]); + return -1; + } + } else { + int r = process_test_option(groups, v[i]); + if (r<0) + return -1; + n += r; + } + } + if (!n) + tinytest_set_flag_(groups, "..", 1, TT_ENABLED_); + +#ifdef _IONBF + setvbuf(stdout, NULL, _IONBF, 0); +#endif + + ++in_tinytest_main; + for (i = 0; groups[i].prefix; ++i) { + struct testgroup_t *group = &groups[i]; + for (j = 0; group->cases[j].name; ++j) { + struct testcase_t *testcase = &group->cases[j]; + int test_attempts = 3; + int test_ret_err; + + if (!(testcase->flags & TT_ENABLED_)) + continue; + + for (;;) { + test_ret_err = testcase_run_one(group, testcase); + + if (test_ret_err == OK) + break; + if (!(testcase->flags & TT_RETRIABLE)) + break; + printf("\n [RETRYING %s (%i)]\n", testcase->name, test_attempts); + if (!test_attempts--) + break; + } + + switch (test_ret_err) { + case OK: ++n_ok; break; + case SKIP: ++n_skipped; break; + default: ++n_bad; break; + } + } + } + + --in_tinytest_main; + + if (opt_verbosity==0) + puts(""); + + if (n_bad) + printf("%d/%d TESTS FAILED. (%d skipped)\n", n_bad, + n_bad+n_ok,n_skipped); + else if (opt_verbosity >= 1) + printf("%d tests ok. (%d skipped)\n", n_ok, n_skipped); + + return (n_bad == 0) ? 0 : 1; +} + +int +tinytest_get_verbosity_(void) +{ + return opt_verbosity; +} + +void +tinytest_set_test_failed_(void) +{ + if (opt_verbosity <= 0 && cur_test_name) { + if (opt_verbosity==0) puts(""); + printf("%s%s: ", cur_test_prefix, cur_test_name); + cur_test_name = NULL; + } + cur_test_outcome = FAIL; +} + +void +tinytest_set_test_skipped_(void) +{ + if (cur_test_outcome==OK) + cur_test_outcome = SKIP; +} + +char * +tinytest_format_hex_(const void *val_, unsigned long len) +{ + const unsigned char *val = val_; + char *result, *cp; + size_t i; + + if (!val) + return strdup("null"); + if (!(result = malloc(len*2+1))) + return strdup("<allocation failure>"); + cp = result; + for (i=0;i<len;++i) { + *cp++ = "0123456789ABCDEF"[val[i] >> 4]; + *cp++ = "0123456789ABCDEF"[val[i] & 0x0f]; + } + *cp = 0; + return result; +} diff --git a/libevent/test/tinytest.h b/libevent/test/tinytest.h new file mode 100644 index 0000000..d321dd4 --- /dev/null +++ b/libevent/test/tinytest.h @@ -0,0 +1,103 @@ +/* tinytest.h -- Copyright 2009-2012 Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TINYTEST_H_INCLUDED_ +#define TINYTEST_H_INCLUDED_ + +/** Flag for a test that needs to run in a subprocess. */ +#define TT_FORK (1<<0) +/** Runtime flag for a test we've decided to skip. */ +#define TT_SKIP (1<<1) +/** Internal runtime flag for a test we've decided to run. */ +#define TT_ENABLED_ (1<<2) +/** Flag for a test that's off by default. */ +#define TT_OFF_BY_DEFAULT (1<<3) +/** Flag for a test that should be runned again in case of failure (but not + * more then 3 times). */ +#define TT_RETRIABLE (1<<4) +/** If you add your own flags, make them start at this point. */ +#define TT_FIRST_USER_FLAG (1<<5) + +typedef void (*testcase_fn)(void *); + +struct testcase_t; + +/** Functions to initialize/teardown a structure for a testcase. */ +struct testcase_setup_t { + /** Return a new structure for use by a given testcase. */ + void *(*setup_fn)(const struct testcase_t *); + /** Clean/free a structure from setup_fn. Return 1 if ok, 0 on err. */ + int (*cleanup_fn)(const struct testcase_t *, void *); +}; + +/** A single test-case that you can run. */ +struct testcase_t { + const char *name; /**< An identifier for this case. */ + testcase_fn fn; /**< The function to run to implement this case. */ + unsigned long flags; /**< Bitfield of TT_* flags. */ + const struct testcase_setup_t *setup; /**< Optional setup/cleanup fns*/ + void *setup_data; /**< Extra data usable by setup function */ +}; +#define END_OF_TESTCASES { NULL, NULL, 0, NULL, NULL } + +/** A group of tests that are selectable together. */ +struct testgroup_t { + const char *prefix; /**< Prefix to prepend to testnames. */ + struct testcase_t *cases; /** Array, ending with END_OF_TESTCASES */ +}; +#define END_OF_GROUPS { NULL, NULL} + +struct testlist_alias_t { + const char *name; + const char **tests; +}; +#define END_OF_ALIASES { NULL, NULL } + +/** Implementation: called from a test to indicate failure, before logging. */ +void tinytest_set_test_failed_(void); +/** Implementation: called from a test to indicate that we're skipping. */ +void tinytest_set_test_skipped_(void); +/** Implementation: return 0 for quiet, 1 for normal, 2 for loud. */ +int tinytest_get_verbosity_(void); +/** Implementation: Set a flag on tests matching a name; returns number + * of tests that matched. */ +int tinytest_set_flag_(struct testgroup_t *, const char *, int set, unsigned long); +/** Implementation: Put a chunk of memory into hex. */ +char *tinytest_format_hex_(const void *, unsigned long); + +/** Set all tests in 'groups' matching the name 'named' to be skipped. */ +#define tinytest_skip(groups, named) \ + tinytest_set_flag_(groups, named, 1, TT_SKIP) + +/** Run a single testcase in a single group. */ +int testcase_run_one(const struct testgroup_t *,const struct testcase_t *); + +void tinytest_set_aliases(const struct testlist_alias_t *aliases); + +/** Run a set of testcases from an END_OF_GROUPS-terminated array of groups, + as selected from the command line. */ +int tinytest_main(int argc, const char **argv, struct testgroup_t *groups); + +#endif diff --git a/libevent/test/tinytest_local.h b/libevent/test/tinytest_local.h new file mode 100644 index 0000000..87ec2fa --- /dev/null +++ b/libevent/test/tinytest_local.h @@ -0,0 +1,12 @@ + +#include "util-internal.h" +#ifdef _WIN32 +#include <winsock2.h> +#endif + +#include "event2/util.h" + +#ifdef snprintf +#undef snprintf +#endif +#define snprintf evutil_snprintf diff --git a/libevent/test/tinytest_macros.h b/libevent/test/tinytest_macros.h new file mode 100644 index 0000000..e01f5d5 --- /dev/null +++ b/libevent/test/tinytest_macros.h @@ -0,0 +1,213 @@ +/* tinytest_macros.h -- Copyright 2009-2012 Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TINYTEST_MACROS_H_INCLUDED_ +#define TINYTEST_MACROS_H_INCLUDED_ + +/* Helpers for defining statement-like macros */ +#define TT_STMT_BEGIN do { +#define TT_STMT_END } while (0) + +/* Redefine this if your test functions want to abort with something besides + * "goto end;" */ +#ifndef TT_EXIT_TEST_FUNCTION +#define TT_EXIT_TEST_FUNCTION TT_STMT_BEGIN goto end; TT_STMT_END +#endif + +/* Redefine this if you want to note success/failure in some different way. */ +#ifndef TT_DECLARE +#define TT_DECLARE(prefix, args) \ + TT_STMT_BEGIN \ + printf("\n %s %s:%d: ",prefix,__FILE__,__LINE__); \ + printf args ; \ + TT_STMT_END +#endif + +/* Announce a failure. Args are parenthesized printf args. */ +#define TT_GRIPE(args) TT_DECLARE("FAIL", args) + +/* Announce a non-failure if we're verbose. */ +#define TT_BLATHER(args) \ + TT_STMT_BEGIN \ + if (tinytest_get_verbosity_()>1) TT_DECLARE(" OK", args); \ + TT_STMT_END + +#define TT_DIE(args) \ + TT_STMT_BEGIN \ + tinytest_set_test_failed_(); \ + TT_GRIPE(args); \ + TT_EXIT_TEST_FUNCTION; \ + TT_STMT_END + +#define TT_FAIL(args) \ + TT_STMT_BEGIN \ + tinytest_set_test_failed_(); \ + TT_GRIPE(args); \ + TT_STMT_END + +/* Fail and abort the current test for the reason in msg */ +#define tt_abort_printf(msg) TT_DIE(msg) +#define tt_abort_perror(op) TT_DIE(("%s: %s [%d]",(op),strerror(errno), errno)) +#define tt_abort_msg(msg) TT_DIE(("%s", msg)) +#define tt_abort() TT_DIE(("%s", "(Failed.)")) + +/* Fail but do not abort the current test for the reason in msg. */ +#define tt_failprint_f(msg) TT_FAIL(msg) +#define tt_fail_perror(op) TT_FAIL(("%s: %s [%d]",(op),strerror(errno), errno)) +#define tt_fail_msg(msg) TT_FAIL(("%s", msg)) +#define tt_fail() TT_FAIL(("%s", "(Failed.)")) + +/* End the current test, and indicate we are skipping it. */ +#define tt_skip() \ + TT_STMT_BEGIN \ + tinytest_set_test_skipped_(); \ + TT_EXIT_TEST_FUNCTION; \ + TT_STMT_END + +#define tt_want_(b, msg, fail) \ + TT_STMT_BEGIN \ + if (!(b)) { \ + tinytest_set_test_failed_(); \ + TT_GRIPE(("%s",msg)); \ + fail; \ + } else { \ + TT_BLATHER(("%s",msg)); \ + } \ + TT_STMT_END + +/* Assert b, but do not stop the test if b fails. Log msg on failure. */ +#define tt_want_msg(b, msg) \ + tt_want_(b, msg, ); + +/* Assert b and stop the test if b fails. Log msg on failure. */ +#define tt_assert_msg(b, msg) \ + tt_want_(b, msg, TT_EXIT_TEST_FUNCTION); + +/* Assert b, but do not stop the test if b fails. */ +#define tt_want(b) tt_want_msg( (b), "want("#b")") +/* Assert b, and stop the test if b fails. */ +#define tt_assert(b) tt_assert_msg((b), "assert("#b")") + +#define tt_assert_test_fmt_type(a,b,str_test,type,test,printf_type,printf_fmt, \ + setup_block,cleanup_block,die_on_fail) \ + TT_STMT_BEGIN \ + type val1_ = (type)(a); \ + type val2_ = (type)(b); \ + int tt_status_ = (test); \ + if (!tt_status_ || tinytest_get_verbosity_()>1) { \ + printf_type print_; \ + printf_type print1_; \ + printf_type print2_; \ + type value_ = val1_; \ + setup_block; \ + print1_ = print_; \ + value_ = val2_; \ + setup_block; \ + print2_ = print_; \ + TT_DECLARE(tt_status_?" OK":"FAIL", \ + ("assert(%s): "printf_fmt" vs "printf_fmt, \ + str_test, print1_, print2_)); \ + print_ = print1_; \ + cleanup_block; \ + print_ = print2_; \ + cleanup_block; \ + if (!tt_status_) { \ + tinytest_set_test_failed_(); \ + die_on_fail ; \ + } \ + } \ + TT_STMT_END + +#define tt_assert_test_type(a,b,str_test,type,test,fmt,die_on_fail) \ + tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt, \ + {print_=value_;},{},die_on_fail) + +#define tt_assert_test_type_opt(a,b,str_test,type,test,fmt,die_on_fail) \ + tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt, \ + {print_=value_?value_:"<NULL>";},{},die_on_fail) + +/* Helper: assert that a op b, when cast to type. Format the values with + * printf format fmt on failure. */ +#define tt_assert_op_type(a,op,b,type,fmt) \ + tt_assert_test_type(a,b,#a" "#op" "#b,type,(val1_ op val2_),fmt, \ + TT_EXIT_TEST_FUNCTION) + +#define tt_int_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,long,(val1_ op val2_), \ + "%ld",TT_EXIT_TEST_FUNCTION) + +/** To compare SOCKET(windows)/fd */ +#define tt_fd_op(a,op,b) do { \ + int _a = (int)(a); \ + int _b = (int)(b); \ + tt_assert_test_type(_a,_b,#a" "#op" "#b,long,(val1_ op val2_), \ + "%ld",TT_EXIT_TEST_FUNCTION); \ +} while (0) + +#define tt_uint_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ + (val1_ op val2_),"%lu",TT_EXIT_TEST_FUNCTION) + +#define tt_ptr_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,const void*, \ + (val1_ op val2_),"%p",TT_EXIT_TEST_FUNCTION) + +/** XXX: have some issues with printing this non-NUL terminated strings */ +#define tt_nstr_op(n,a,op,b) \ + tt_assert_test_type_opt(a,b,#a" "#op" "#b,const char *, \ + (val1_ && val2_ && strncmp(val1_,val2_,(n)) op 0),"<%s>", \ + TT_EXIT_TEST_FUNCTION) + +#define tt_str_op(a,op,b) \ + tt_assert_test_type_opt(a,b,#a" "#op" "#b,const char *, \ + (val1_ && val2_ && strcmp(val1_,val2_) op 0),"<%s>", \ + TT_EXIT_TEST_FUNCTION) + +#define tt_mem_op(expr1, op, expr2, len) \ + tt_assert_test_fmt_type(expr1,expr2,#expr1" "#op" "#expr2, \ + const void *, \ + (val1_ && val2_ && memcmp(val1_, val2_, len) op 0), \ + char *, "%s", \ + { print_ = tinytest_format_hex_(value_, (len)); }, \ + { if (print_) free(print_); }, \ + TT_EXIT_TEST_FUNCTION \ + ); + +#define tt_want_int_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,long,(val1_ op val2_),"%ld",(void)0) + +#define tt_want_uint_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ + (val1_ op val2_),"%lu",(void)0) + +#define tt_want_ptr_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,const void*, \ + (val1_ op val2_),"%p",(void)0) + +#define tt_want_str_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,const char *, \ + (strcmp(val1_,val2_) op 0),"<%s>",(void)0) + +#endif |