summaryrefslogtreecommitdiffstats
path: root/libevent/test
diff options
context:
space:
mode:
Diffstat (limited to 'libevent/test')
-rw-r--r--libevent/test/Makefile.nmake79
-rw-r--r--libevent/test/bench.c235
-rw-r--r--libevent/test/bench_cascade.c188
-rw-r--r--libevent/test/bench_http.c200
-rw-r--r--libevent/test/bench_httpclient.c240
-rwxr-xr-xlibevent/test/check-dumpevents.py54
-rw-r--r--libevent/test/include.am176
-rw-r--r--libevent/test/print-winsock-errors.c86
-rw-r--r--libevent/test/regress.c3615
-rw-r--r--libevent/test/regress.gen.c1
-rw-r--r--libevent/test/regress.gen.h1
-rw-r--r--libevent/test/regress.h149
-rw-r--r--libevent/test/regress.rpc25
-rw-r--r--libevent/test/regress_buffer.c2870
-rw-r--r--libevent/test/regress_bufferevent.c1467
-rw-r--r--libevent/test/regress_dns.c2518
-rw-r--r--libevent/test/regress_et.c270
-rw-r--r--libevent/test/regress_finalize.c395
-rw-r--r--libevent/test/regress_http.c4807
-rw-r--r--libevent/test/regress_iocp.c352
-rw-r--r--libevent/test/regress_listener.c348
-rw-r--r--libevent/test/regress_main.c528
-rw-r--r--libevent/test/regress_minheap.c99
-rw-r--r--libevent/test/regress_rpc.c952
-rw-r--r--libevent/test/regress_ssl.c1078
-rw-r--r--libevent/test/regress_testutils.c233
-rw-r--r--libevent/test/regress_testutils.h67
-rw-r--r--libevent/test/regress_thread.c590
-rw-r--r--libevent/test/regress_thread.h56
-rw-r--r--libevent/test/regress_util.c1681
-rw-r--r--libevent/test/regress_zlib.c348
-rwxr-xr-xlibevent/test/rpcgen_wrapper.sh43
-rw-r--r--libevent/test/test-changelist.c224
-rw-r--r--libevent/test/test-closed.c111
-rw-r--r--libevent/test/test-dumpevents.c179
-rw-r--r--libevent/test/test-eof.c117
-rw-r--r--libevent/test/test-fdleak.c252
-rw-r--r--libevent/test/test-init.c65
-rw-r--r--libevent/test/test-ratelim.c688
-rw-r--r--libevent/test/test-time.c123
-rw-r--r--libevent/test/test-weof.c114
-rwxr-xr-xlibevent/test/test.sh188
-rw-r--r--libevent/test/tinytest.c610
-rw-r--r--libevent/test/tinytest.h103
-rw-r--r--libevent/test/tinytest_local.h12
-rw-r--r--libevent/test/tinytest_macros.h213
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, ==, "&lt;script&gt;");
+ free(escaped);
+
+ escaped = evhttp_htmlescape("\"\'&");
+ tt_assert(escaped);
+ tt_str_op(escaped, ==, "&quot;&#039;&amp;");
+
+ 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, &note_one) == -1 ||
+ EVTAG_ARRAY_GET(run, notes, 1, &note_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 == &times[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, &times[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(&times[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, &times[0], 100);
+ test_timeval_diff_eq(&starttime, &times[1], 200);
+ test_timeval_diff_eq(&starttime, &times[2], 400);
+ test_timeval_diff_eq(&starttime, &times[3], 450);
+ test_timeval_diff_eq(&starttime, &times[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