Revert "Revert "Upgrade to 2.1.8-stable (2017-01-22)." and "Probably Mac build fix?""

This reverts commit 83a0c9c65a60a92d3ea5542596b3ba56db492c37.

Bug: 64543673
Test: make checkbuild
Test: Manual tombstoned test
Change-Id: I84bb128d1dec433195f2cbdbf70236ba17fa9955
diff --git a/test/Makefile.nmake b/test/Makefile.nmake
new file mode 100644
index 0000000..30c3eb7
--- /dev/null
+++ b/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/test/bench.c b/test/bench.c
new file mode 100644
index 0000000..214479c
--- /dev/null
+++ b/test/bench.c
@@ -0,0 +1,207 @@
+/*
+ * 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 <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 int count, writes, fired, failures;
+static evutil_socket_t *pipes;
+static int num_pipes, num_active, num_writes;
+static struct event *events;
+
+
+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_set(&events[i], cp[0], EV_READ | EV_PERSIST, read_cb, (void *)(ev_intptr_t) i);
+		event_add(&events[i], NULL);
+	}
+
+	event_loop(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_loop(EVLOOP_ONCE | EVLOOP_NONBLOCK);
+		xcount++;
+	} while (count != fired);
+	evutil_gettimeofday(&te, NULL);
+
+	if (xcount != count) fprintf(stderr, "Xcount: %d, Rcount: %d\n", xcount, count);
+	}
+
+	evutil_timersub(&te, &ts, &te);
+
+	return (&te);
+}
+
+int
+main(int argc, char **argv)
+{
+#ifdef HAVE_SETRLIMIT 
+	struct rlimit rl;
+#endif
+	int i, c;
+	struct timeval *tv;
+	evutil_socket_t *cp;
+
+#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:")) != -1) {
+		switch (c) {
+		case 'n':
+			num_pipes = atoi(optarg);
+			break;
+		case 'a':
+			num_active = atoi(optarg);
+			break;
+		case 'w':
+			num_writes = atoi(optarg);
+			break;
+		default:
+			fprintf(stderr, "Illegal argument \"%c\"\n", c);
+			exit(1);
+		}
+	}
+
+#ifdef 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);
+	}
+
+	event_init();
+
+	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/test/bench_cascade.c b/test/bench_cascade.c
new file mode 100644
index 0000000..2d85cc1
--- /dev/null
+++ b/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>
+#else
+#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 <getopt.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 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 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/test/bench_http.c b/test/bench_http.c
new file mode 100644
index 0000000..d20d3bc
--- /dev/null
+++ b/test/bench_http.c
@@ -0,0 +1,197 @@
+/*
+ * 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
+
+	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/test/bench_httpclient.c b/test/bench_httpclient.c
new file mode 100644
index 0000000..e159295
--- /dev/null
+++ b/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 HAVE_SO_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 HAVE_SO_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/test/check-dumpevents.py b/test/check-dumpevents.py
new file mode 100755
index 0000000..16fe9bc
--- /dev/null
+++ b/test/check-dumpevents.py
@@ -0,0 +1,54 @@
+#!/usr/bin/python2
+#
+# 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:
+    print >>sys.stderr, "Missing expected dividing line in dumpevents output"
+    sys.exit(1)
+
+if not (expect_inserted_pos < expect_active_pos <
+        got_inserted_pos < got_active_pos):
+    print >>sys.stderr, "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:
+    print >>sys.stderr, "Inserted event lists were not as expected!"
+    sys.exit(1)
+
+if set(got_active) != set(want_active):
+    print >>sys.stderr, "Active event lists were not as expected!"
+    sys.exit(1)
+
diff --git a/test/include.am b/test/include.am
new file mode 100644
index 0000000..eea249f
--- /dev/null
+++ b/test/include.am
@@ -0,0 +1,175 @@
+# 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/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: test/test.sh
+	test/test.sh -b EPOLL
+test_runner_select: test/test.sh
+	test/test.sh -b SELECT
+test_runner_kqueue: test/test.sh
+	test/test.sh -b KQUEUE
+test_runner_evport: test/test.sh
+	test/test.sh -b EVPORT
+test_runner_devpoll: test/test.sh
+	test/test.sh -b DEVPOLL
+test_runner_poll: test/test.sh
+	test/test.sh -b POLL
+test_runner_win32: test/test.sh
+	test/test.sh -b WIN32
+test_runner_timerfd: test/test.sh
+	test/test.sh -b "" -t
+test_runner_changelist: test/test.sh
+	test/test.sh -b "" -c
+test_runner_timerfd_changelist: test/test.sh
+	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.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/test/regress.c b/test/regress.c
new file mode 100644
index 0000000..d8a6b9b
--- /dev/null
+++ b/test/regress.c
@@ -0,0 +1,3473 @@
+/*
+ * 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
+
+#ifdef EVENT__HAVE_PTHREADS
+#include <pthread.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 <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"
+
+#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(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_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++;
+}
+static void
+test_fork(void)
+{
+	char c;
+	int status;
+	struct event ev, sig_ev, usr_ev, existing_ev;
+	pid_t pid;
+	int wait_flags = 0;
+
+#ifdef EVENT__HAVE_WAITPID_WITH_WNOWAIT
+	wait_flags |= WNOWAIT;
+#endif
+
+	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);
+		raise(SIGUSR1);
+		raise(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 : 76);
+	}
+
+	/** 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");
+	}
+
+	TT_BLATHER(("Before waitpid"));
+	if (waitpid(pid, &status, wait_flags) == -1) {
+		perror("waitpid");
+		exit(1);
+	}
+	TT_BLATHER(("After waitpid"));
+
+	if (WEXITSTATUS(status) != 76) {
+		fprintf(stdout, "FAILED (exit): %d\n", WEXITSTATUS(status));
+		exit(1);
+	}
+
+	/* 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);
+	raise(SIGUSR1);
+	raise(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 EVENT__HAVE_PTHREADS
+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"));
+	evutil_usleep_(&delay);
+	test_ok = 1;
+}
+
+static void
+test_del_wait(void)
+{
+	struct event ev;
+	pthread_t thread;
+
+	setup_test("event_del will wait: ");
+
+	event_set(&ev, pair[1], EV_READ, del_wait_cb, &ev);
+	event_add(&ev, NULL);
+
+	pthread_create(&thread, NULL, 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);
+	}
+
+	pthread_join(thread, NULL);
+
+	end:
+	;
+}
+#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);
+	raise(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 */
+	raise(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);
+		raise(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);
+
+	raise(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);
+
+	raise(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)
+		raise(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);
+		raise(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();
+	if (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 */
+	} else {
+		tt_fail_msg("failed to create event_base for test");
+	}
+
+	base1 = event_init();
+	tt_assert(base1);
+	event_assign(&ev1, base1, 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;
+	(void) write(*sockp, "A", 1);
+}
+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));
+		return;
+	} else {
+		event_add(ev_other, NULL);
+		++test_ok;
+	}
+}
+
+static void
+test_nonpersist_readd(void)
+{
+	struct event ev1, ev2;
+
+	setup_test("Re-add nonpersistent events: ");
+	event_set(&ev1, pair[0], EV_READ, re_add_read_cb, &ev2);
+	event_set(&ev2, pair[1], EV_READ, re_add_read_cb, &ev1);
+
+	if (write(pair[0], "Hello", 5) < 0) {
+		tt_fail_perror("write(pair[0])");
+	}
+
+	if (write(pair[1], "Hello", 5) < 0) {
+		tt_fail_perror("write(pair[1])\n");
+	}
+
+	if (event_add(&ev1, NULL) == -1 ||
+	    event_add(&ev2, NULL) == -1) {
+		test_ok = 0;
+	}
+	if (test_ok != 0)
+		exit(1);
+	event_loop(EVLOOP_ONCE);
+	if (test_ok != 2)
+		exit(1);
+	/* 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.
+	 */
+	if (!readd_test_event_last_added) {
+		test_ok = 0;
+	} else if (readd_test_event_last_added == &ev1) {
+		if (!event_pending(&ev1, EV_READ, NULL) ||
+		    event_pending(&ev2, EV_READ, NULL))
+			test_ok = 0;
+	} else {
+		if (event_pending(&ev1, EV_READ, NULL) ||
+		    !event_pending(&ev2, EV_READ, NULL))
+			test_ok = 0;
+	}
+
+	event_del(&ev1);
+	event_del(&ev2);
+
+	cleanup_test();
+}
+
+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);
+		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_int_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_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);
+#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 | 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(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),
+	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, &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", test_simpleclose, TT_FORK, &basic_setup,
+	  NULL },
+	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),
+	LEGACY(nonpersist_readd, TT_ISOLATED),
+	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 EVENT__HAVE_PTHREADS
+	/** TODO: support win32 */
+	LEGACY(del_wait, 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/test/regress.gen.c b/test/regress.gen.c
new file mode 100644
index 0000000..da994a5
--- /dev/null
+++ b/test/regress.gen.c
@@ -0,0 +1,1225 @@
+/*
+ * Automatically generated from ./test/regress.rpc
+ * by event_rpcgen.py/0.1.  DO NOT EDIT THIS FILE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <event2/event-config.h>
+#include <event2/event.h>
+#include <event2/buffer.h>
+#include <event2/tag.h>
+
+#if defined(EVENT____func__) && !defined(__func__)
+#define __func__ EVENT____func__
+#endif
+
+
+#include "regress.gen.h"
+
+void event_warn(const char *fmt, ...);
+void event_warnx(const char *fmt, ...);
+
+
+/*
+ * Implementation of msg
+ */
+
+static struct msg_access_ msg_base__ = {
+  msg_from_name_assign,
+  msg_from_name_get,
+  msg_to_name_assign,
+  msg_to_name_get,
+  msg_attack_assign,
+  msg_attack_get,
+  msg_run_assign,
+  msg_run_get,
+  msg_run_add,
+};
+
+struct msg *
+msg_new(void)
+{
+  return msg_new_with_arg(NULL);
+}
+
+struct msg *
+msg_new_with_arg(void *unused)
+{
+  struct msg *tmp;
+  if ((tmp = malloc(sizeof(struct msg))) == NULL) {
+    event_warn("%s: malloc", __func__);
+    return (NULL);
+  }
+  tmp->base = &msg_base__;
+
+  tmp->from_name_data = NULL;
+  tmp->from_name_set = 0;
+
+  tmp->to_name_data = NULL;
+  tmp->to_name_set = 0;
+
+  tmp->attack_data = NULL;
+  tmp->attack_set = 0;
+
+  tmp->run_data = NULL;
+  tmp->run_length = 0;
+  tmp->run_num_allocated = 0;
+  tmp->run_set = 0;
+
+  return (tmp);
+}
+
+
+
+
+static int
+msg_run_expand_to_hold_more(struct msg *msg)
+{
+  int tobe_allocated = msg->run_num_allocated;
+  struct run** new_data = NULL;
+  tobe_allocated = !tobe_allocated ? 1 : tobe_allocated << 1;
+  new_data = (struct run**) realloc(msg->run_data,
+      tobe_allocated * sizeof(struct run*));
+  if (new_data == NULL)
+    return -1;
+  msg->run_data = new_data;
+  msg->run_num_allocated = tobe_allocated;
+  return 0;}
+
+struct run* 
+msg_run_add(struct msg *msg)
+{
+  if (++msg->run_length >= msg->run_num_allocated) {
+    if (msg_run_expand_to_hold_more(msg)<0)
+      goto error;
+  }
+  msg->run_data[msg->run_length - 1] = run_new();
+  if (msg->run_data[msg->run_length - 1] == NULL)
+    goto error;
+  msg->run_set = 1;
+  return (msg->run_data[msg->run_length - 1]);
+error:
+  --msg->run_length;
+  return (NULL);
+}
+
+int
+msg_from_name_assign(struct msg *msg,
+    const char * value)
+{
+  if (msg->from_name_data != NULL)
+    free(msg->from_name_data);
+  if ((msg->from_name_data = strdup(value)) == NULL)
+    return (-1);
+  msg->from_name_set = 1;
+  return (0);
+}
+
+int
+msg_to_name_assign(struct msg *msg,
+    const char * value)
+{
+  if (msg->to_name_data != NULL)
+    free(msg->to_name_data);
+  if ((msg->to_name_data = strdup(value)) == NULL)
+    return (-1);
+  msg->to_name_set = 1;
+  return (0);
+}
+
+int
+msg_attack_assign(struct msg *msg,
+    const struct kill* value)
+{
+   struct evbuffer *tmp = NULL;
+   if (msg->attack_set) {
+     kill_clear(msg->attack_data);
+     msg->attack_set = 0;
+   } else {
+     msg->attack_data = kill_new();
+     if (msg->attack_data == NULL) {
+       event_warn("%s: kill_new()", __func__);
+       goto error;
+     }
+   }
+   if ((tmp = evbuffer_new()) == NULL) {
+     event_warn("%s: evbuffer_new()", __func__);
+     goto error;
+   }
+   kill_marshal(tmp, value);
+   if (kill_unmarshal(msg->attack_data, tmp) == -1) {
+     event_warnx("%s: kill_unmarshal", __func__);
+     goto error;
+   }
+   msg->attack_set = 1;
+   evbuffer_free(tmp);
+   return (0);
+ error:
+   if (tmp != NULL)
+     evbuffer_free(tmp);
+   if (msg->attack_data != NULL) {
+     kill_free(msg->attack_data);
+     msg->attack_data = NULL;
+   }
+   return (-1);
+}
+
+int
+msg_run_assign(struct msg *msg, int off,
+    const struct run* value)
+{
+  if (!msg->run_set || off < 0 || off >= msg->run_length)
+    return (-1);
+
+  {
+    int had_error = 0;
+    struct evbuffer *tmp = NULL;
+    run_clear(msg->run_data[off]);
+    if ((tmp = evbuffer_new()) == NULL) {
+      event_warn("%s: evbuffer_new()", __func__);
+      had_error = 1;
+      goto done;
+    }
+    run_marshal(tmp, value);
+    if (run_unmarshal(msg->run_data[off], tmp) == -1) {
+      event_warnx("%s: run_unmarshal", __func__);
+      had_error = 1;
+      goto done;
+    }
+    done:if (tmp != NULL)
+      evbuffer_free(tmp);
+    if (had_error) {
+      run_clear(msg->run_data[off]);
+      return (-1);
+    }
+  }
+  return (0);
+}
+
+int
+msg_from_name_get(struct msg *msg, char * *value)
+{
+  if (msg->from_name_set != 1)
+    return (-1);
+  *value = msg->from_name_data;
+  return (0);
+}
+
+int
+msg_to_name_get(struct msg *msg, char * *value)
+{
+  if (msg->to_name_set != 1)
+    return (-1);
+  *value = msg->to_name_data;
+  return (0);
+}
+
+int
+msg_attack_get(struct msg *msg, struct kill* *value)
+{
+  if (msg->attack_set != 1) {
+    msg->attack_data = kill_new();
+    if (msg->attack_data == NULL)
+      return (-1);
+    msg->attack_set = 1;
+  }
+  *value = msg->attack_data;
+  return (0);
+}
+
+int
+msg_run_get(struct msg *msg, int offset,
+    struct run* *value)
+{
+  if (!msg->run_set || offset < 0 || offset >= msg->run_length)
+    return (-1);
+  *value = msg->run_data[offset];
+  return (0);
+}
+
+void
+msg_clear(struct msg *tmp)
+{
+  if (tmp->from_name_set == 1) {
+    free(tmp->from_name_data);
+    tmp->from_name_data = NULL;
+    tmp->from_name_set = 0;
+  }
+  if (tmp->to_name_set == 1) {
+    free(tmp->to_name_data);
+    tmp->to_name_data = NULL;
+    tmp->to_name_set = 0;
+  }
+  if (tmp->attack_set == 1) {
+    kill_free(tmp->attack_data);
+    tmp->attack_data = NULL;
+    tmp->attack_set = 0;
+  }
+  if (tmp->run_set == 1) {
+    int i;
+    for (i = 0; i < tmp->run_length; ++i) {
+      run_free(tmp->run_data[i]);
+    }
+    free(tmp->run_data);
+    tmp->run_data = NULL;
+    tmp->run_set = 0;
+    tmp->run_length = 0;
+    tmp->run_num_allocated = 0;
+  }
+}
+
+void
+msg_free(struct msg *tmp)
+{
+  if (tmp->from_name_data != NULL)
+      free (tmp->from_name_data);
+  if (tmp->to_name_data != NULL)
+      free (tmp->to_name_data);
+  if (tmp->attack_data != NULL)
+      kill_free(tmp->attack_data);
+  if (tmp->run_set == 1) {
+    int i;
+    for (i = 0; i < tmp->run_length; ++i) {
+      run_free(tmp->run_data[i]);
+    }
+    free(tmp->run_data);
+    tmp->run_data = NULL;
+    tmp->run_set = 0;
+    tmp->run_length = 0;
+    tmp->run_num_allocated = 0;
+  }
+  free(tmp->run_data);
+  free(tmp);
+}
+
+void
+msg_marshal(struct evbuffer *evbuf, const struct msg *tmp){
+  evtag_marshal_string(evbuf, MSG_FROM_NAME, tmp->from_name_data);
+  evtag_marshal_string(evbuf, MSG_TO_NAME, tmp->to_name_data);
+  if (tmp->attack_set) {
+    evtag_marshal_kill(evbuf, MSG_ATTACK, tmp->attack_data);
+  }
+  if (tmp->run_set) {
+    {
+      int i;
+      for (i = 0; i < tmp->run_length; ++i) {
+    evtag_marshal_run(evbuf, MSG_RUN, tmp->run_data[i]);
+      }
+    }
+  }
+}
+
+int
+msg_unmarshal(struct msg *tmp,  struct evbuffer *evbuf)
+{
+  ev_uint32_t tag;
+  while (evbuffer_get_length(evbuf) > 0) {
+    if (evtag_peek(evbuf, &tag) == -1)
+      return (-1);
+    switch (tag) {
+
+      case MSG_FROM_NAME:
+
+        if (tmp->from_name_set)
+          return (-1);
+        if (evtag_unmarshal_string(evbuf, MSG_FROM_NAME, &tmp->from_name_data) == -1) {
+          event_warnx("%s: failed to unmarshal from_name", __func__);
+          return (-1);
+        }
+        tmp->from_name_set = 1;
+        break;
+
+      case MSG_TO_NAME:
+
+        if (tmp->to_name_set)
+          return (-1);
+        if (evtag_unmarshal_string(evbuf, MSG_TO_NAME, &tmp->to_name_data) == -1) {
+          event_warnx("%s: failed to unmarshal to_name", __func__);
+          return (-1);
+        }
+        tmp->to_name_set = 1;
+        break;
+
+      case MSG_ATTACK:
+
+        if (tmp->attack_set)
+          return (-1);
+        tmp->attack_data = kill_new();
+        if (tmp->attack_data == NULL)
+          return (-1);
+        if (evtag_unmarshal_kill(evbuf, MSG_ATTACK, tmp->attack_data) == -1) {
+          event_warnx("%s: failed to unmarshal attack", __func__);
+          return (-1);
+        }
+        tmp->attack_set = 1;
+        break;
+
+      case MSG_RUN:
+
+        if (tmp->run_length >= tmp->run_num_allocated &&
+            msg_run_expand_to_hold_more(tmp) < 0) {
+          puts("HEY NOW");
+          return (-1);
+        }
+        tmp->run_data[tmp->run_length] = run_new();
+        if (tmp->run_data[tmp->run_length] == NULL)
+          return (-1);
+        if (evtag_unmarshal_run(evbuf, MSG_RUN, tmp->run_data[tmp->run_length]) == -1) {
+          event_warnx("%s: failed to unmarshal run", __func__);
+          return (-1);
+        }
+        ++tmp->run_length;
+        tmp->run_set = 1;
+        break;
+
+      default:
+        return -1;
+    }
+  }
+
+  if (msg_complete(tmp) == -1)
+    return (-1);
+  return (0);
+}
+
+int
+msg_complete(struct msg *msg)
+{
+  if (!msg->from_name_set)
+    return (-1);
+  if (!msg->to_name_set)
+    return (-1);
+  if (msg->attack_set && kill_complete(msg->attack_data) == -1)
+    return (-1);
+  {
+    int i;
+    for (i = 0; i < msg->run_length; ++i) {
+      if (msg->run_set && run_complete(msg->run_data[i]) == -1)
+        return (-1);
+    }
+  }
+  return (0);
+}
+
+int
+evtag_unmarshal_msg(struct evbuffer *evbuf, ev_uint32_t need_tag, struct msg *msg)
+{
+  ev_uint32_t tag;
+  int res = -1;
+
+  struct evbuffer *tmp = evbuffer_new();
+
+  if (evtag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag)
+    goto error;
+
+  if (msg_unmarshal(msg, tmp) == -1)
+    goto error;
+
+  res = 0;
+
+ error:
+  evbuffer_free(tmp);
+  return (res);
+}
+
+void
+evtag_marshal_msg(struct evbuffer *evbuf, ev_uint32_t tag, const struct msg *msg)
+{
+  struct evbuffer *buf_ = evbuffer_new();
+  assert(buf_ != NULL);
+  msg_marshal(buf_, msg);
+  evtag_marshal_buffer(evbuf, tag, buf_);
+   evbuffer_free(buf_);
+}
+
+/*
+ * Implementation of kill
+ */
+
+static struct kill_access_ kill_base__ = {
+  kill_weapon_assign,
+  kill_weapon_get,
+  kill_action_assign,
+  kill_action_get,
+  kill_how_often_assign,
+  kill_how_often_get,
+  kill_how_often_add,
+};
+
+struct kill *
+kill_new(void)
+{
+  return kill_new_with_arg(NULL);
+}
+
+struct kill *
+kill_new_with_arg(void *unused)
+{
+  struct kill *tmp;
+  if ((tmp = malloc(sizeof(struct kill))) == NULL) {
+    event_warn("%s: malloc", __func__);
+    return (NULL);
+  }
+  tmp->base = &kill_base__;
+
+  tmp->weapon_data = NULL;
+  tmp->weapon_set = 0;
+
+  tmp->action_data = NULL;
+  tmp->action_set = 0;
+
+  tmp->how_often_data = NULL;
+  tmp->how_often_length = 0;
+  tmp->how_often_num_allocated = 0;
+  tmp->how_often_set = 0;
+
+  return (tmp);
+}
+
+
+
+static int
+kill_how_often_expand_to_hold_more(struct kill *msg)
+{
+  int tobe_allocated = msg->how_often_num_allocated;
+  ev_uint32_t* new_data = NULL;
+  tobe_allocated = !tobe_allocated ? 1 : tobe_allocated << 1;
+  new_data = (ev_uint32_t*) realloc(msg->how_often_data,
+      tobe_allocated * sizeof(ev_uint32_t));
+  if (new_data == NULL)
+    return -1;
+  msg->how_often_data = new_data;
+  msg->how_often_num_allocated = tobe_allocated;
+  return 0;}
+
+ev_uint32_t *
+kill_how_often_add(struct kill *msg, const ev_uint32_t value)
+{
+  if (++msg->how_often_length >= msg->how_often_num_allocated) {
+    if (kill_how_often_expand_to_hold_more(msg)<0)
+      goto error;
+  }
+  msg->how_often_data[msg->how_often_length - 1] = value;
+  msg->how_often_set = 1;
+  return &(msg->how_often_data[msg->how_often_length - 1]);
+error:
+  --msg->how_often_length;
+  return (NULL);
+}
+
+int
+kill_weapon_assign(struct kill *msg,
+    const char * value)
+{
+  if (msg->weapon_data != NULL)
+    free(msg->weapon_data);
+  if ((msg->weapon_data = strdup(value)) == NULL)
+    return (-1);
+  msg->weapon_set = 1;
+  return (0);
+}
+
+int
+kill_action_assign(struct kill *msg,
+    const char * value)
+{
+  if (msg->action_data != NULL)
+    free(msg->action_data);
+  if ((msg->action_data = strdup(value)) == NULL)
+    return (-1);
+  msg->action_set = 1;
+  return (0);
+}
+
+int
+kill_how_often_assign(struct kill *msg, int off,
+    const ev_uint32_t value)
+{
+  if (!msg->how_often_set || off < 0 || off >= msg->how_often_length)
+    return (-1);
+
+  {
+    msg->how_often_data[off] = value;
+  }
+  return (0);
+}
+
+int
+kill_weapon_get(struct kill *msg, char * *value)
+{
+  if (msg->weapon_set != 1)
+    return (-1);
+  *value = msg->weapon_data;
+  return (0);
+}
+
+int
+kill_action_get(struct kill *msg, char * *value)
+{
+  if (msg->action_set != 1)
+    return (-1);
+  *value = msg->action_data;
+  return (0);
+}
+
+int
+kill_how_often_get(struct kill *msg, int offset,
+    ev_uint32_t *value)
+{
+  if (!msg->how_often_set || offset < 0 || offset >= msg->how_often_length)
+    return (-1);
+  *value = msg->how_often_data[offset];
+  return (0);
+}
+
+void
+kill_clear(struct kill *tmp)
+{
+  if (tmp->weapon_set == 1) {
+    free(tmp->weapon_data);
+    tmp->weapon_data = NULL;
+    tmp->weapon_set = 0;
+  }
+  if (tmp->action_set == 1) {
+    free(tmp->action_data);
+    tmp->action_data = NULL;
+    tmp->action_set = 0;
+  }
+  if (tmp->how_often_set == 1) {
+    free(tmp->how_often_data);
+    tmp->how_often_data = NULL;
+    tmp->how_often_set = 0;
+    tmp->how_often_length = 0;
+    tmp->how_often_num_allocated = 0;
+  }
+}
+
+void
+kill_free(struct kill *tmp)
+{
+  if (tmp->weapon_data != NULL)
+      free (tmp->weapon_data);
+  if (tmp->action_data != NULL)
+      free (tmp->action_data);
+  if (tmp->how_often_set == 1) {
+    free(tmp->how_often_data);
+    tmp->how_often_data = NULL;
+    tmp->how_often_set = 0;
+    tmp->how_often_length = 0;
+    tmp->how_often_num_allocated = 0;
+  }
+  free(tmp->how_often_data);
+  free(tmp);
+}
+
+void
+kill_marshal(struct evbuffer *evbuf, const struct kill *tmp){
+  evtag_marshal_string(evbuf, KILL_WEAPON, tmp->weapon_data);
+  evtag_marshal_string(evbuf, KILL_ACTION, tmp->action_data);
+  if (tmp->how_often_set) {
+    {
+      int i;
+      for (i = 0; i < tmp->how_often_length; ++i) {
+    evtag_marshal_int(evbuf, KILL_HOW_OFTEN, tmp->how_often_data[i]);
+      }
+    }
+  }
+}
+
+int
+kill_unmarshal(struct kill *tmp,  struct evbuffer *evbuf)
+{
+  ev_uint32_t tag;
+  while (evbuffer_get_length(evbuf) > 0) {
+    if (evtag_peek(evbuf, &tag) == -1)
+      return (-1);
+    switch (tag) {
+
+      case KILL_WEAPON:
+
+        if (tmp->weapon_set)
+          return (-1);
+        if (evtag_unmarshal_string(evbuf, KILL_WEAPON, &tmp->weapon_data) == -1) {
+          event_warnx("%s: failed to unmarshal weapon", __func__);
+          return (-1);
+        }
+        tmp->weapon_set = 1;
+        break;
+
+      case KILL_ACTION:
+
+        if (tmp->action_set)
+          return (-1);
+        if (evtag_unmarshal_string(evbuf, KILL_ACTION, &tmp->action_data) == -1) {
+          event_warnx("%s: failed to unmarshal action", __func__);
+          return (-1);
+        }
+        tmp->action_set = 1;
+        break;
+
+      case KILL_HOW_OFTEN:
+
+        if (tmp->how_often_length >= tmp->how_often_num_allocated &&
+            kill_how_often_expand_to_hold_more(tmp) < 0) {
+          puts("HEY NOW");
+          return (-1);
+        }
+        if (evtag_unmarshal_int(evbuf, KILL_HOW_OFTEN, &tmp->how_often_data[tmp->how_often_length]) == -1) {
+          event_warnx("%s: failed to unmarshal how_often", __func__);
+          return (-1);
+        }
+        ++tmp->how_often_length;
+        tmp->how_often_set = 1;
+        break;
+
+      default:
+        return -1;
+    }
+  }
+
+  if (kill_complete(tmp) == -1)
+    return (-1);
+  return (0);
+}
+
+int
+kill_complete(struct kill *msg)
+{
+  if (!msg->weapon_set)
+    return (-1);
+  if (!msg->action_set)
+    return (-1);
+  return (0);
+}
+
+int
+evtag_unmarshal_kill(struct evbuffer *evbuf, ev_uint32_t need_tag, struct kill *msg)
+{
+  ev_uint32_t tag;
+  int res = -1;
+
+  struct evbuffer *tmp = evbuffer_new();
+
+  if (evtag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag)
+    goto error;
+
+  if (kill_unmarshal(msg, tmp) == -1)
+    goto error;
+
+  res = 0;
+
+ error:
+  evbuffer_free(tmp);
+  return (res);
+}
+
+void
+evtag_marshal_kill(struct evbuffer *evbuf, ev_uint32_t tag, const struct kill *msg)
+{
+  struct evbuffer *buf_ = evbuffer_new();
+  assert(buf_ != NULL);
+  kill_marshal(buf_, msg);
+  evtag_marshal_buffer(evbuf, tag, buf_);
+   evbuffer_free(buf_);
+}
+
+/*
+ * Implementation of run
+ */
+
+static struct run_access_ run_base__ = {
+  run_how_assign,
+  run_how_get,
+  run_some_bytes_assign,
+  run_some_bytes_get,
+  run_fixed_bytes_assign,
+  run_fixed_bytes_get,
+  run_notes_assign,
+  run_notes_get,
+  run_notes_add,
+  run_large_number_assign,
+  run_large_number_get,
+  run_other_numbers_assign,
+  run_other_numbers_get,
+  run_other_numbers_add,
+};
+
+struct run *
+run_new(void)
+{
+  return run_new_with_arg(NULL);
+}
+
+struct run *
+run_new_with_arg(void *unused)
+{
+  struct run *tmp;
+  if ((tmp = malloc(sizeof(struct run))) == NULL) {
+    event_warn("%s: malloc", __func__);
+    return (NULL);
+  }
+  tmp->base = &run_base__;
+
+  tmp->how_data = NULL;
+  tmp->how_set = 0;
+
+  tmp->some_bytes_data = NULL;
+  tmp->some_bytes_length = 0;
+  tmp->some_bytes_set = 0;
+
+  memset(tmp->fixed_bytes_data, 0, sizeof(tmp->fixed_bytes_data));
+  tmp->fixed_bytes_set = 0;
+
+  tmp->notes_data = NULL;
+  tmp->notes_length = 0;
+  tmp->notes_num_allocated = 0;
+  tmp->notes_set = 0;
+
+  tmp->large_number_data = 0;
+  tmp->large_number_set = 0;
+
+  tmp->other_numbers_data = NULL;
+  tmp->other_numbers_length = 0;
+  tmp->other_numbers_num_allocated = 0;
+  tmp->other_numbers_set = 0;
+
+  return (tmp);
+}
+
+
+
+
+static int
+run_notes_expand_to_hold_more(struct run *msg)
+{
+  int tobe_allocated = msg->notes_num_allocated;
+  char ** new_data = NULL;
+  tobe_allocated = !tobe_allocated ? 1 : tobe_allocated << 1;
+  new_data = (char **) realloc(msg->notes_data,
+      tobe_allocated * sizeof(char *));
+  if (new_data == NULL)
+    return -1;
+  msg->notes_data = new_data;
+  msg->notes_num_allocated = tobe_allocated;
+  return 0;}
+
+char * *
+run_notes_add(struct run *msg, const char * value)
+{
+  if (++msg->notes_length >= msg->notes_num_allocated) {
+    if (run_notes_expand_to_hold_more(msg)<0)
+      goto error;
+  }
+  if (value != NULL) {
+    msg->notes_data[msg->notes_length - 1] = strdup(value);
+    if (msg->notes_data[msg->notes_length - 1] == NULL) {
+      goto error;
+    }
+  } else {
+    msg->notes_data[msg->notes_length - 1] = NULL;
+  }
+  msg->notes_set = 1;
+  return &(msg->notes_data[msg->notes_length - 1]);
+error:
+  --msg->notes_length;
+  return (NULL);
+}
+
+
+static int
+run_other_numbers_expand_to_hold_more(struct run *msg)
+{
+  int tobe_allocated = msg->other_numbers_num_allocated;
+  ev_uint32_t* new_data = NULL;
+  tobe_allocated = !tobe_allocated ? 1 : tobe_allocated << 1;
+  new_data = (ev_uint32_t*) realloc(msg->other_numbers_data,
+      tobe_allocated * sizeof(ev_uint32_t));
+  if (new_data == NULL)
+    return -1;
+  msg->other_numbers_data = new_data;
+  msg->other_numbers_num_allocated = tobe_allocated;
+  return 0;}
+
+ev_uint32_t *
+run_other_numbers_add(struct run *msg, const ev_uint32_t value)
+{
+  if (++msg->other_numbers_length >= msg->other_numbers_num_allocated) {
+    if (run_other_numbers_expand_to_hold_more(msg)<0)
+      goto error;
+  }
+  msg->other_numbers_data[msg->other_numbers_length - 1] = value;
+  msg->other_numbers_set = 1;
+  return &(msg->other_numbers_data[msg->other_numbers_length - 1]);
+error:
+  --msg->other_numbers_length;
+  return (NULL);
+}
+
+int
+run_how_assign(struct run *msg,
+    const char * value)
+{
+  if (msg->how_data != NULL)
+    free(msg->how_data);
+  if ((msg->how_data = strdup(value)) == NULL)
+    return (-1);
+  msg->how_set = 1;
+  return (0);
+}
+
+int
+run_some_bytes_assign(struct run *msg, const ev_uint8_t * value, ev_uint32_t len)
+{
+  if (msg->some_bytes_data != NULL)
+    free (msg->some_bytes_data);
+  msg->some_bytes_data = malloc(len);
+  if (msg->some_bytes_data == NULL)
+    return (-1);
+  msg->some_bytes_set = 1;
+  msg->some_bytes_length = len;
+  memcpy(msg->some_bytes_data, value, len);
+  return (0);
+}
+
+int
+run_fixed_bytes_assign(struct run *msg, const ev_uint8_t *value)
+{
+  msg->fixed_bytes_set = 1;
+  memcpy(msg->fixed_bytes_data, value, 24);
+  return (0);
+}
+
+int
+run_notes_assign(struct run *msg, int off,
+    const char * value)
+{
+  if (!msg->notes_set || off < 0 || off >= msg->notes_length)
+    return (-1);
+
+  {
+    if (msg->notes_data[off] != NULL)
+      free(msg->notes_data[off]);
+    msg->notes_data[off] = strdup(value);
+    if (msg->notes_data[off] == NULL) {
+      event_warnx("%s: strdup", __func__);
+      return (-1);
+    }
+  }
+  return (0);
+}
+
+int
+run_large_number_assign(struct run *msg, const ev_uint64_t value)
+{
+  msg->large_number_set = 1;
+  msg->large_number_data = value;
+  return (0);
+}
+
+int
+run_other_numbers_assign(struct run *msg, int off,
+    const ev_uint32_t value)
+{
+  if (!msg->other_numbers_set || off < 0 || off >= msg->other_numbers_length)
+    return (-1);
+
+  {
+    msg->other_numbers_data[off] = value;
+  }
+  return (0);
+}
+
+int
+run_how_get(struct run *msg, char * *value)
+{
+  if (msg->how_set != 1)
+    return (-1);
+  *value = msg->how_data;
+  return (0);
+}
+
+int
+run_some_bytes_get(struct run *msg, ev_uint8_t * *value, ev_uint32_t *plen)
+{
+  if (msg->some_bytes_set != 1)
+    return (-1);
+  *value = msg->some_bytes_data;
+  *plen = msg->some_bytes_length;
+  return (0);
+}
+
+int
+run_fixed_bytes_get(struct run *msg, ev_uint8_t **value)
+{
+  if (msg->fixed_bytes_set != 1)
+    return (-1);
+  *value = msg->fixed_bytes_data;
+  return (0);
+}
+
+int
+run_notes_get(struct run *msg, int offset,
+    char * *value)
+{
+  if (!msg->notes_set || offset < 0 || offset >= msg->notes_length)
+    return (-1);
+  *value = msg->notes_data[offset];
+  return (0);
+}
+
+int
+run_large_number_get(struct run *msg, ev_uint64_t *value)
+{
+  if (msg->large_number_set != 1)
+    return (-1);
+  *value = msg->large_number_data;
+  return (0);
+}
+
+int
+run_other_numbers_get(struct run *msg, int offset,
+    ev_uint32_t *value)
+{
+  if (!msg->other_numbers_set || offset < 0 || offset >= msg->other_numbers_length)
+    return (-1);
+  *value = msg->other_numbers_data[offset];
+  return (0);
+}
+
+void
+run_clear(struct run *tmp)
+{
+  if (tmp->how_set == 1) {
+    free(tmp->how_data);
+    tmp->how_data = NULL;
+    tmp->how_set = 0;
+  }
+  if (tmp->some_bytes_set == 1) {
+    free (tmp->some_bytes_data);
+    tmp->some_bytes_data = NULL;
+    tmp->some_bytes_length = 0;
+    tmp->some_bytes_set = 0;
+  }
+  tmp->fixed_bytes_set = 0;
+  memset(tmp->fixed_bytes_data, 0, sizeof(tmp->fixed_bytes_data));
+  if (tmp->notes_set == 1) {
+    int i;
+    for (i = 0; i < tmp->notes_length; ++i) {
+      if (tmp->notes_data[i] != NULL) free(tmp->notes_data[i]);
+    }
+    free(tmp->notes_data);
+    tmp->notes_data = NULL;
+    tmp->notes_set = 0;
+    tmp->notes_length = 0;
+    tmp->notes_num_allocated = 0;
+  }
+  tmp->large_number_set = 0;
+  if (tmp->other_numbers_set == 1) {
+    free(tmp->other_numbers_data);
+    tmp->other_numbers_data = NULL;
+    tmp->other_numbers_set = 0;
+    tmp->other_numbers_length = 0;
+    tmp->other_numbers_num_allocated = 0;
+  }
+}
+
+void
+run_free(struct run *tmp)
+{
+  if (tmp->how_data != NULL)
+      free (tmp->how_data);
+  if (tmp->some_bytes_data != NULL)
+      free(tmp->some_bytes_data);
+  if (tmp->notes_set == 1) {
+    int i;
+    for (i = 0; i < tmp->notes_length; ++i) {
+      if (tmp->notes_data[i] != NULL) free(tmp->notes_data[i]);
+    }
+    free(tmp->notes_data);
+    tmp->notes_data = NULL;
+    tmp->notes_set = 0;
+    tmp->notes_length = 0;
+    tmp->notes_num_allocated = 0;
+  }
+  free(tmp->notes_data);
+  if (tmp->other_numbers_set == 1) {
+    free(tmp->other_numbers_data);
+    tmp->other_numbers_data = NULL;
+    tmp->other_numbers_set = 0;
+    tmp->other_numbers_length = 0;
+    tmp->other_numbers_num_allocated = 0;
+  }
+  free(tmp->other_numbers_data);
+  free(tmp);
+}
+
+void
+run_marshal(struct evbuffer *evbuf, const struct run *tmp){
+  evtag_marshal_string(evbuf, RUN_HOW, tmp->how_data);
+  if (tmp->some_bytes_set) {
+    evtag_marshal(evbuf, RUN_SOME_BYTES, tmp->some_bytes_data, tmp->some_bytes_length);
+  }
+  evtag_marshal(evbuf, RUN_FIXED_BYTES, tmp->fixed_bytes_data, (24));
+  if (tmp->notes_set) {
+    {
+      int i;
+      for (i = 0; i < tmp->notes_length; ++i) {
+    evtag_marshal_string(evbuf, RUN_NOTES, tmp->notes_data[i]);
+      }
+    }
+  }
+  if (tmp->large_number_set) {
+    evtag_marshal_int64(evbuf, RUN_LARGE_NUMBER, tmp->large_number_data);
+  }
+  if (tmp->other_numbers_set) {
+    {
+      int i;
+      for (i = 0; i < tmp->other_numbers_length; ++i) {
+    evtag_marshal_int(evbuf, RUN_OTHER_NUMBERS, tmp->other_numbers_data[i]);
+      }
+    }
+  }
+}
+
+int
+run_unmarshal(struct run *tmp,  struct evbuffer *evbuf)
+{
+  ev_uint32_t tag;
+  while (evbuffer_get_length(evbuf) > 0) {
+    if (evtag_peek(evbuf, &tag) == -1)
+      return (-1);
+    switch (tag) {
+
+      case RUN_HOW:
+
+        if (tmp->how_set)
+          return (-1);
+        if (evtag_unmarshal_string(evbuf, RUN_HOW, &tmp->how_data) == -1) {
+          event_warnx("%s: failed to unmarshal how", __func__);
+          return (-1);
+        }
+        tmp->how_set = 1;
+        break;
+
+      case RUN_SOME_BYTES:
+
+        if (tmp->some_bytes_set)
+          return (-1);
+        if (evtag_payload_length(evbuf, &tmp->some_bytes_length) == -1)
+          return (-1);
+        if (tmp->some_bytes_length > evbuffer_get_length(evbuf))
+          return (-1);
+        if ((tmp->some_bytes_data = malloc(tmp->some_bytes_length)) == NULL)
+          return (-1);
+        if (evtag_unmarshal_fixed(evbuf, RUN_SOME_BYTES, tmp->some_bytes_data, tmp->some_bytes_length) == -1) {
+          event_warnx("%s: failed to unmarshal some_bytes", __func__);
+          return (-1);
+        }
+        tmp->some_bytes_set = 1;
+        break;
+
+      case RUN_FIXED_BYTES:
+
+        if (tmp->fixed_bytes_set)
+          return (-1);
+        if (evtag_unmarshal_fixed(evbuf, RUN_FIXED_BYTES, tmp->fixed_bytes_data, (24)) == -1) {
+          event_warnx("%s: failed to unmarshal fixed_bytes", __func__);
+          return (-1);
+        }
+        tmp->fixed_bytes_set = 1;
+        break;
+
+      case RUN_NOTES:
+
+        if (tmp->notes_length >= tmp->notes_num_allocated &&
+            run_notes_expand_to_hold_more(tmp) < 0) {
+          puts("HEY NOW");
+          return (-1);
+        }
+        if (evtag_unmarshal_string(evbuf, RUN_NOTES, &tmp->notes_data[tmp->notes_length]) == -1) {
+          event_warnx("%s: failed to unmarshal notes", __func__);
+          return (-1);
+        }
+        ++tmp->notes_length;
+        tmp->notes_set = 1;
+        break;
+
+      case RUN_LARGE_NUMBER:
+
+        if (tmp->large_number_set)
+          return (-1);
+        if (evtag_unmarshal_int64(evbuf, RUN_LARGE_NUMBER, &tmp->large_number_data) == -1) {
+          event_warnx("%s: failed to unmarshal large_number", __func__);
+          return (-1);
+        }
+        tmp->large_number_set = 1;
+        break;
+
+      case RUN_OTHER_NUMBERS:
+
+        if (tmp->other_numbers_length >= tmp->other_numbers_num_allocated &&
+            run_other_numbers_expand_to_hold_more(tmp) < 0) {
+          puts("HEY NOW");
+          return (-1);
+        }
+        if (evtag_unmarshal_int(evbuf, RUN_OTHER_NUMBERS, &tmp->other_numbers_data[tmp->other_numbers_length]) == -1) {
+          event_warnx("%s: failed to unmarshal other_numbers", __func__);
+          return (-1);
+        }
+        ++tmp->other_numbers_length;
+        tmp->other_numbers_set = 1;
+        break;
+
+      default:
+        return -1;
+    }
+  }
+
+  if (run_complete(tmp) == -1)
+    return (-1);
+  return (0);
+}
+
+int
+run_complete(struct run *msg)
+{
+  if (!msg->how_set)
+    return (-1);
+  if (!msg->fixed_bytes_set)
+    return (-1);
+  return (0);
+}
+
+int
+evtag_unmarshal_run(struct evbuffer *evbuf, ev_uint32_t need_tag, struct run *msg)
+{
+  ev_uint32_t tag;
+  int res = -1;
+
+  struct evbuffer *tmp = evbuffer_new();
+
+  if (evtag_unmarshal(evbuf, &tag, tmp) == -1 || tag != need_tag)
+    goto error;
+
+  if (run_unmarshal(msg, tmp) == -1)
+    goto error;
+
+  res = 0;
+
+ error:
+  evbuffer_free(tmp);
+  return (res);
+}
+
+void
+evtag_marshal_run(struct evbuffer *evbuf, ev_uint32_t tag, const struct run *msg)
+{
+  struct evbuffer *buf_ = evbuffer_new();
+  assert(buf_ != NULL);
+  run_marshal(buf_, msg);
+  evtag_marshal_buffer(evbuf, tag, buf_);
+   evbuffer_free(buf_);
+}
+
diff --git a/test/regress.gen.h b/test/regress.gen.h
new file mode 100644
index 0000000..86b8d7f
--- /dev/null
+++ b/test/regress.gen.h
@@ -0,0 +1,207 @@
+/*
+ * Automatically generated from ./test/regress.rpc
+ */
+
+#ifndef EVENT_RPCOUT___TEST_REGRESS_RPC_
+#define EVENT_RPCOUT___TEST_REGRESS_RPC_
+
+#include <event2/util.h> /* for ev_uint*_t */
+#include <event2/rpc.h>
+
+struct msg;
+struct kill;
+struct run;
+
+/* Tag definition for msg */
+enum msg_ {
+  MSG_FROM_NAME=1,
+  MSG_TO_NAME=2,
+  MSG_ATTACK=3,
+  MSG_RUN=4,
+  MSG_MAX_TAGS
+};
+
+/* Structure declaration for msg */
+struct msg_access_ {
+  int (*from_name_assign)(struct msg *, const char *);
+  int (*from_name_get)(struct msg *, char * *);
+  int (*to_name_assign)(struct msg *, const char *);
+  int (*to_name_get)(struct msg *, char * *);
+  int (*attack_assign)(struct msg *, const struct kill*);
+  int (*attack_get)(struct msg *, struct kill* *);
+  int (*run_assign)(struct msg *, int, const struct run*);
+  int (*run_get)(struct msg *, int, struct run* *);
+  struct run*  (*run_add)(struct msg *msg);
+};
+
+struct msg {
+  struct msg_access_ *base;
+
+  char *from_name_data;
+  char *to_name_data;
+  struct kill* attack_data;
+  struct run* *run_data;
+  int run_length;
+  int run_num_allocated;
+
+  ev_uint8_t from_name_set;
+  ev_uint8_t to_name_set;
+  ev_uint8_t attack_set;
+  ev_uint8_t run_set;
+};
+
+struct msg *msg_new(void);
+struct msg *msg_new_with_arg(void *);
+void msg_free(struct msg *);
+void msg_clear(struct msg *);
+void msg_marshal(struct evbuffer *, const struct msg *);
+int msg_unmarshal(struct msg *, struct evbuffer *);
+int msg_complete(struct msg *);
+void evtag_marshal_msg(struct evbuffer *, ev_uint32_t,
+    const struct msg *);
+int evtag_unmarshal_msg(struct evbuffer *, ev_uint32_t,
+    struct msg *);
+int msg_from_name_assign(struct msg *, const char *);
+int msg_from_name_get(struct msg *, char * *);
+int msg_to_name_assign(struct msg *, const char *);
+int msg_to_name_get(struct msg *, char * *);
+int msg_attack_assign(struct msg *, const struct kill*);
+int msg_attack_get(struct msg *, struct kill* *);
+int msg_run_assign(struct msg *, int, const struct run*);
+int msg_run_get(struct msg *, int, struct run* *);
+struct run*  msg_run_add(struct msg *msg);
+/* --- msg done --- */
+
+/* Tag definition for kill */
+enum kill_ {
+  KILL_WEAPON=65825,
+  KILL_ACTION=2,
+  KILL_HOW_OFTEN=3,
+  KILL_MAX_TAGS
+};
+
+/* Structure declaration for kill */
+struct kill_access_ {
+  int (*weapon_assign)(struct kill *, const char *);
+  int (*weapon_get)(struct kill *, char * *);
+  int (*action_assign)(struct kill *, const char *);
+  int (*action_get)(struct kill *, char * *);
+  int (*how_often_assign)(struct kill *, int, const ev_uint32_t);
+  int (*how_often_get)(struct kill *, int, ev_uint32_t *);
+  ev_uint32_t * (*how_often_add)(struct kill *msg, const ev_uint32_t value);
+};
+
+struct kill {
+  struct kill_access_ *base;
+
+  char *weapon_data;
+  char *action_data;
+  ev_uint32_t *how_often_data;
+  int how_often_length;
+  int how_often_num_allocated;
+
+  ev_uint8_t weapon_set;
+  ev_uint8_t action_set;
+  ev_uint8_t how_often_set;
+};
+
+struct kill *kill_new(void);
+struct kill *kill_new_with_arg(void *);
+void kill_free(struct kill *);
+void kill_clear(struct kill *);
+void kill_marshal(struct evbuffer *, const struct kill *);
+int kill_unmarshal(struct kill *, struct evbuffer *);
+int kill_complete(struct kill *);
+void evtag_marshal_kill(struct evbuffer *, ev_uint32_t,
+    const struct kill *);
+int evtag_unmarshal_kill(struct evbuffer *, ev_uint32_t,
+    struct kill *);
+int kill_weapon_assign(struct kill *, const char *);
+int kill_weapon_get(struct kill *, char * *);
+int kill_action_assign(struct kill *, const char *);
+int kill_action_get(struct kill *, char * *);
+int kill_how_often_assign(struct kill *, int, const ev_uint32_t);
+int kill_how_often_get(struct kill *, int, ev_uint32_t *);
+ev_uint32_t * kill_how_often_add(struct kill *msg, const ev_uint32_t value);
+/* --- kill done --- */
+
+/* Tag definition for run */
+enum run_ {
+  RUN_HOW=1,
+  RUN_SOME_BYTES=2,
+  RUN_FIXED_BYTES=3,
+  RUN_NOTES=4,
+  RUN_LARGE_NUMBER=5,
+  RUN_OTHER_NUMBERS=6,
+  RUN_MAX_TAGS
+};
+
+/* Structure declaration for run */
+struct run_access_ {
+  int (*how_assign)(struct run *, const char *);
+  int (*how_get)(struct run *, char * *);
+  int (*some_bytes_assign)(struct run *, const ev_uint8_t *, ev_uint32_t);
+  int (*some_bytes_get)(struct run *, ev_uint8_t * *, ev_uint32_t *);
+  int (*fixed_bytes_assign)(struct run *, const ev_uint8_t *);
+  int (*fixed_bytes_get)(struct run *, ev_uint8_t **);
+  int (*notes_assign)(struct run *, int, const char *);
+  int (*notes_get)(struct run *, int, char * *);
+  char * * (*notes_add)(struct run *msg, const char * value);
+  int (*large_number_assign)(struct run *, const ev_uint64_t);
+  int (*large_number_get)(struct run *, ev_uint64_t *);
+  int (*other_numbers_assign)(struct run *, int, const ev_uint32_t);
+  int (*other_numbers_get)(struct run *, int, ev_uint32_t *);
+  ev_uint32_t * (*other_numbers_add)(struct run *msg, const ev_uint32_t value);
+};
+
+struct run {
+  struct run_access_ *base;
+
+  char *how_data;
+  ev_uint8_t *some_bytes_data;
+  ev_uint32_t some_bytes_length;
+  ev_uint8_t fixed_bytes_data[24];
+  char * *notes_data;
+  int notes_length;
+  int notes_num_allocated;
+  ev_uint64_t large_number_data;
+  ev_uint32_t *other_numbers_data;
+  int other_numbers_length;
+  int other_numbers_num_allocated;
+
+  ev_uint8_t how_set;
+  ev_uint8_t some_bytes_set;
+  ev_uint8_t fixed_bytes_set;
+  ev_uint8_t notes_set;
+  ev_uint8_t large_number_set;
+  ev_uint8_t other_numbers_set;
+};
+
+struct run *run_new(void);
+struct run *run_new_with_arg(void *);
+void run_free(struct run *);
+void run_clear(struct run *);
+void run_marshal(struct evbuffer *, const struct run *);
+int run_unmarshal(struct run *, struct evbuffer *);
+int run_complete(struct run *);
+void evtag_marshal_run(struct evbuffer *, ev_uint32_t,
+    const struct run *);
+int evtag_unmarshal_run(struct evbuffer *, ev_uint32_t,
+    struct run *);
+int run_how_assign(struct run *, const char *);
+int run_how_get(struct run *, char * *);
+int run_some_bytes_assign(struct run *, const ev_uint8_t *, ev_uint32_t);
+int run_some_bytes_get(struct run *, ev_uint8_t * *, ev_uint32_t *);
+int run_fixed_bytes_assign(struct run *, const ev_uint8_t *);
+int run_fixed_bytes_get(struct run *, ev_uint8_t **);
+int run_notes_assign(struct run *, int, const char *);
+int run_notes_get(struct run *, int, char * *);
+char * * run_notes_add(struct run *msg, const char * value);
+int run_large_number_assign(struct run *, const ev_uint64_t);
+int run_large_number_get(struct run *, ev_uint64_t *);
+int run_other_numbers_assign(struct run *, int, const ev_uint32_t);
+int run_other_numbers_get(struct run *, int, ev_uint32_t *);
+ev_uint32_t * run_other_numbers_add(struct run *msg, const ev_uint32_t value);
+/* --- run done --- */
+
+#endif  /* EVENT_RPCOUT___TEST_REGRESS_RPC_ */
diff --git a/test/regress.h b/test/regress.h
new file mode 100644
index 0000000..de1aed3
--- /dev/null
+++ b/test/regress.h
@@ -0,0 +1,144 @@
+/*
+ * 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 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)
+
+/* 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(void);
+SSL_CTX *get_ssl_ctx(void);
+void init_ssl(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* REGRESS_H_INCLUDED_ */
diff --git a/test/regress.rpc b/test/regress.rpc
new file mode 100644
index 0000000..0ee904e
--- /dev/null
+++ b/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/test/regress_buffer.c b/test/regress_buffer.c
new file mode 100644
index 0000000..1af75f5
--- /dev/null
+++ b/test/regress_buffer.c
@@ -0,0 +1,2563 @@
+/*
+ * 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"
+
+/* 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];
+
+    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);
+
+    /* move three bytes over */
+    evbuffer_remove_buffer(src, dst, 3);
+
+    evbuffer_validate(src);
+    evbuffer_validate(dst);
+
+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);
+
+	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);
+
+ 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);
+
+	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);
+
+ end:
+	evbuffer_free(src);
+	evbuffer_free(dst);
+	evbuffer_free(buf);
+}
+
+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_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);
+	evbuffer_setcb(buf, self_draining_callback, NULL);
+	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);
+	evbuffer_setcb(buf, NULL, NULL);
+	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
+
+ 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_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 evbuffer *buf = 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(ptr, "start");
+	char *cp;
+	char charbuf[128];
+	int r;
+	size_t orig_length;
+	struct evbuffer_iovec v[1];
+
+	if (!start)
+		tt_str_op(ptr, ==, "end");
+
+	buf = evbuffer_new();
+	tmp_buf = evbuffer_new();
+	tt_assert(tmp_buf);
+
+	evbuffer_add(buf, string, strlen(string));
+	evbuffer_freeze(buf, start); /* Freeze the start or the end.*/
+
+#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);
+	/* TODO: test add_buffer, add_file, read */
+
+	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);
+	/* TODO: Test remove_buffer, add_buffer, write, prepend_buffer */
+
+	if (start)
+		tt_int_op(orig_length, ==, evbuffer_get_length(buf));
+
+end:
+	if (buf)
+		evbuffer_free(buf);
+
+	if (tmp_buf)
+		evbuffer_free(tmp_buf);
+}
+
+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 },
+	{ "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" },
+	{ "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 },
+	{ "peek", test_evbuffer_peek, 0, NULL, NULL },
+	{ "peek_first_gt", test_evbuffer_peek_first_gt, 0, NULL, NULL },
+	{ "freeze_start", test_evbuffer_freeze, 0, &nil_setup, (void*)"start" },
+	{ "freeze_end", test_evbuffer_freeze, 0, &nil_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 },
+
+#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/test/regress_bufferevent.c b/test/regress_bufferevent.c
new file mode 100644
index 0000000..249985e
--- /dev/null
+++ b/test/regress_bufferevent.c
@@ -0,0 +1,1443 @@
+/*
+ * 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
+
+#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_int_op(bufferevent_getfd(bev1), ==, -1);
+		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_int_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)
+/**
+ * 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_int_op(bufferevent_getfd(bev1), ==, -1);
+	tt_int_op(bufferevent_getfd(bev2), ==, -1);
+
+	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 int
+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
+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 bufferevent *bev = NULL;
+	struct evconnlistener *lev = NULL;
+	struct sockaddr_in localhost;
+	ev_socklen_t slen = sizeof(localhost);
+	evutil_socket_t fake_listener = -1;
+
+	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() */
+	bufferevent_socket_connect(bev, (struct sockaddr *)&localhost, slen);
+	tt_int_op(n_events_invoked, ==, 0);
+	tt_int_op(n_reads_invoked, ==, 0);
+	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
+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(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 one_second = { 1, 0 };
+	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 second. 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, &one_second);
+	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;
+	int n_read_timeouts;
+	int n_write_timeouts;
+	int total_calls;
+};
+
+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;
+	char buf[1024];
+
+	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, NULL, bev_timeout_write_cb,
+	    bev_timeout_event_cb, &res1);
+	bufferevent_setwatermark(bev1, EV_WRITE, 1024*1024+10, 0);
+	bufferevent_set_timeouts(bev1, &tv_r, &tv_w);
+	if (use_pair) {
+		/* For a pair, the fact that the other side isn't reading
+		 * makes the writer stall */
+		bufferevent_write(bev1, "ABCDEFG", 7);
+	} else {
+		/* For a real socket, the kernel's TCP buffers can eat a
+		 * fair number of bytes; make sure that at some point we
+		 * have some bytes that will stall. */
+		struct evbuffer *output = bufferevent_get_output(bev1);
+		int i;
+		memset(buf, 0xbb, sizeof(buf));
+		for (i=0;i<1024;++i) {
+			evbuffer_add_reference(output, buf, sizeof(buf),
+			    NULL, NULL);
+		}
+	}
+	bufferevent_enable(bev1, EV_READ|EV_WRITE);
+
+	/* bev2 has nothing to say, and isn't listening. */
+	bufferevent_setcb(bev2, NULL,  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. */
+
+	/* Each buf1 timeout happens, and happens only once. */
+	tt_want(res1.n_read_timeouts);
+	tt_want(res1.n_write_timeouts);
+	tt_want(res1.n_read_timeouts == 1);
+	tt_want(res1.n_write_timeouts == 1);
+
+	test_timeval_diff_eq(&started_at, &res1.read_timeout_at, 150);
+	test_timeval_diff_eq(&started_at, &res1.write_timeout_at, 100);
+
+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)
+	{ "bufferevent_pair_release_lock", test_bufferevent_pair_release_lock,
+	  TT_FORK|TT_ISOLATED|TT_NEED_THREADS|TT_NEED_BASE|TT_LEGACY,
+	  &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|TT_NEED_SOCKETPAIR, &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,
+};
+
+struct testcase_t bufferevent_iocp_testcases[] = {
+
+	LEGACY(bufferevent, TT_ISOLATED|TT_ENABLE_IOCP),
+	LEGACY(bufferevent_flush_normal, TT_ISOLATED),
+	LEGACY(bufferevent_flush_flush, TT_ISOLATED),
+	LEGACY(bufferevent_flush_finished, TT_ISOLATED),
+	LEGACY(bufferevent_watermarks, TT_ISOLATED|TT_ENABLE_IOCP),
+	LEGACY(bufferevent_filters, TT_ISOLATED|TT_ENABLE_IOCP),
+	LEGACY(bufferevent_filters_disable, TT_ISOLATED|TT_ENABLE_IOCP),
+	{ "bufferevent_connect", test_bufferevent_connect,
+	  TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, (void*)"" },
+	{ "bufferevent_connect_defer", test_bufferevent_connect,
+	  TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, (void*)"defer" },
+	{ "bufferevent_connect_lock", test_bufferevent_connect,
+	  TT_FORK|TT_NEED_BASE|TT_NEED_THREADS|TT_ENABLE_IOCP, &basic_setup,
+	  (void*)"lock" },
+	{ "bufferevent_connect_lock_defer", test_bufferevent_connect,
+	  TT_FORK|TT_NEED_BASE|TT_NEED_THREADS|TT_ENABLE_IOCP, &basic_setup,
+	  (void*)"defer lock" },
+	{ "bufferevent_connect_fail", test_bufferevent_connect_fail,
+	  TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL },
+	{ "bufferevent_connect_nonblocking", test_bufferevent_connect,
+	  TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup,
+	  (void*)"unset_connectex" },
+
+	{ "bufferevent_connect_fail_eventcb_defer",
+	  test_bufferevent_connect_fail_eventcb,
+	  TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup,
+	  (void*)BEV_OPT_DEFER_CALLBACKS },
+	{ "bufferevent_connect_fail_eventcb",
+	  test_bufferevent_connect_fail_eventcb,
+	  TT_FORK|TT_NEED_BASE|TT_ENABLE_IOCP, &basic_setup, NULL },
+
+	END_OF_TESTCASES,
+};
diff --git a/test/regress_dns.c b/test/regress_dns.c
new file mode 100644
index 0000000..8950440
--- /dev/null
+++ b/test/regress_dns.c
@@ -0,0 +1,2172 @@
+/*
+ * 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>
+
+#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 "log-internal.h"
+#include "regress.h"
+#include "regress_testutils.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, 1/* init name servers */);
+	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)
+{
+	return dns_search_test_impl(arg, 0);
+}
+static void
+dns_search_lower_test(void *arg)
+{
+	return 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);
+}
+
+/* === 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_BLATHER(("Got a bufferevent event %d", what));
+		got->what = what;
+
+		if ((what & BEV_EVENT_CONNECTED) || (what & BEV_EVENT_ERROR)) {
+			int r;
+			if ((r = bufferevent_socket_get_dns_error(bev))) {
+				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));
+
+			if (total_n_accepted >= 3 && total_connected_or_failed >= 5)
+				event_base_loopexit(be_connect_hostname_base,
+				    NULL);
+		}
+	} else {
+		TT_FAIL(("Two events on one bufferevent. %d,%d",
+			got->what, (int)what));
+	}
+}
+
+static void
+test_bufferevent_connect_hostname(void *arg)
+{
+	struct basic_test_data *data = arg;
+	struct evconnlistener *listener = NULL;
+	struct bufferevent *be1=NULL, *be2=NULL, *be3=NULL, *be4=NULL, *be5=NULL;
+	struct be_conn_hostname_result be1_outcome={0,0}, be2_outcome={0,0},
+	       be3_outcome={0,0}, be4_outcome={0,0}, be5_outcome={0,0};
+	int expect_err5;
+	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];
+
+	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);
+
+	/* 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. */
+	be1 = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE);
+	be2 = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE);
+	be3 = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE);
+	be4 = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE);
+	be5 = bufferevent_socket_new(data->base, -1, BEV_OPT_CLOSE_ON_FREE);
+
+	bufferevent_setcb(be1, NULL, NULL, be_connect_hostname_event_cb,
+	    &be1_outcome);
+	bufferevent_setcb(be2, NULL, NULL, be_connect_hostname_event_cb,
+	    &be2_outcome);
+	bufferevent_setcb(be3, NULL, NULL, be_connect_hostname_event_cb,
+	    &be3_outcome);
+	bufferevent_setcb(be4, NULL, NULL, be_connect_hostname_event_cb,
+	    &be4_outcome);
+	bufferevent_setcb(be5, NULL, NULL, be_connect_hostname_event_cb,
+	    &be5_outcome);
+
+	/* 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(be4, NULL, AF_INET,
+		"localhost", listener_port));
+	/* Use the blocking resolver with a nonexistent hostname. */
+	tt_assert(!bufferevent_socket_connect_hostname(be5, 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_err5 = evutil_getaddrinfo(
+			"nonesuch.nowhere.example.com", "80", &hints, &ai);
+	}
+	/* Launch an async resolve that will fail. */
+	tt_assert(!bufferevent_socket_connect_hostname(be1, dns, AF_INET,
+		"nosuchplace.example.com", listener_port));
+	/* Connect to the IP without resolving. */
+	tt_assert(!bufferevent_socket_connect_hostname(be2, dns, AF_INET,
+		"127.0.0.1", listener_port));
+	/* Launch an async resolve that will succeed. */
+	tt_assert(!bufferevent_socket_connect_hostname(be3, dns, AF_INET,
+		"nobodaddy.example.com", listener_port));
+
+	event_base_dispatch(data->base);
+
+	tt_int_op(be1_outcome.what, ==, BEV_EVENT_ERROR);
+	tt_int_op(be1_outcome.dnserr, ==, EVUTIL_EAI_NONAME);
+	tt_int_op(be2_outcome.what, ==, BEV_EVENT_CONNECTED);
+	tt_int_op(be2_outcome.dnserr, ==, 0);
+	tt_int_op(be3_outcome.what, ==, BEV_EVENT_CONNECTED);
+	tt_int_op(be3_outcome.dnserr, ==, 0);
+	tt_int_op(be4_outcome.what, ==, BEV_EVENT_CONNECTED);
+	tt_int_op(be4_outcome.dnserr, ==, 0);
+	if (expect_err5) {
+		tt_int_op(be5_outcome.what, ==, BEV_EVENT_ERROR);
+		tt_int_op(be5_outcome.dnserr, ==, expect_err5);
+	}
+
+	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);
+	if (be1)
+		bufferevent_free(be1);
+	if (be2)
+		bufferevent_free(be2);
+	if (be3)
+		bufferevent_free(be3);
+	if (be4)
+		bufferevent_free(be4);
+	if (be5)
+		bufferevent_free(be5);
+}
+
+
+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];
+	int 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<(int)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 pending = 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 (--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);
+	++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;
+	int 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);
+
+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;
+	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)
+		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", "ssh", 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);
+}
+
+
+#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 },
+	{ "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, &basic_setup, NULL },
+
+	{ "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, &basic_setup, NULL },
+	{ "client_fail_requests_getaddrinfo",
+	  dns_client_fail_requests_getaddrinfo_test,
+	  TT_FORK|TT_NEED_BASE, &basic_setup, NULL },
+
+	END_OF_TESTCASES
+};
+
diff --git a/test/regress_et.c b/test/regress_et.c
new file mode 100644
index 0000000..f75c59b
--- /dev/null
+++ b/test/regress_et.c
@@ -0,0 +1,204 @@
+/*
+ * 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 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);
+}
+
+#ifdef _WIN32
+#define LOCAL_SOCKETPAIR_AF AF_INET
+#else
+#define LOCAL_SOCKETPAIR_AF AF_UNIX
+#endif
+
+static void
+test_edgetriggered(void *et)
+{
+	struct event *ev = NULL;
+	struct event_base *base = NULL;
+	const char *test = "test string";
+	evutil_socket_t pair[2] = {-1,-1};
+	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.
+	 */
+#ifdef __linux__
+	if (evutil_ersatz_socketpair_(AF_INET, SOCK_STREAM, 0, pair) == -1) {
+		tt_abort_perror("socketpair");
+	}
+#else
+	if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0, pair) == -1) {
+		tt_abort_perror("socketpair");
+	}
+#endif
+
+	called = was_et = 0;
+
+	tt_int_op(send(pair[0], test, (int)strlen(test)+1, 0), >, 0);
+	shutdown(pair[0], EVUTIL_SHUT_WR);
+
+	/* Initalize the event library */
+	base = event_base_new();
+
+	if (!strcmp(event_base_get_method(base), "epoll") ||
+	    !strcmp(event_base_get_method(base), "epoll (with changelist)") ||
+	    !strcmp(event_base_get_method(base), "kqueue"))
+		supports_et = 1;
+	else
+		supports_et = 0;
+
+	TT_BLATHER(("Checking for edge-triggered events with %s, which should %s"
+				"support edge-triggering", event_base_get_method(base),
+				supports_et?"":"not "));
+
+	/* Initalize one event */
+	ev = event_new(base, pair[1], EV_READ|EV_ET|EV_PERSIST, read_cb, &ev);
+
+	event_add(ev, NULL);
+
+	/* 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). */
+	event_base_loop(base,EVLOOP_NONBLOCK|EVLOOP_ONCE);
+	event_base_loop(base,EVLOOP_NONBLOCK|EVLOOP_ONCE);
+
+	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);
+	}
+	if (base)
+		event_base_free(base);
+	evutil_closesocket(pair[0]);
+	evutil_closesocket(pair[1]);
+}
+
+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);
+}
+
+struct testcase_t edgetriggered_testcases[] = {
+	{ "et", test_edgetriggered, TT_FORK, NULL, NULL },
+	{ "et_mix_error", test_edgetriggered_mix_error,
+	  TT_FORK|TT_NEED_SOCKETPAIR|TT_NO_LOGS, &basic_setup, NULL },
+	END_OF_TESTCASES
+};
diff --git a/test/regress_finalize.c b/test/regress_finalize.c
new file mode 100644
index 0000000..552210f
--- /dev/null
+++ b/test/regress_finalize.c
@@ -0,0 +1,347 @@
+/*
+ * 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:
+	;
+}
+
+#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(many, TT_FORK|TT_NEED_BASE),
+
+
+	END_OF_TESTCASES
+};
+
diff --git a/test/regress_http.c b/test/regress_http.c
new file mode 100644
index 0000000..8010050
--- /dev/null
+++ b/test/regress_http.c
@@ -0,0 +1,4654 @@
+/*
+ * 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"
+
+/* 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_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_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(ev_uint16_t *pport, struct event_base *base, int mask)
+{
+	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
+
+	/* Register a callback for certain types of requests */
+	evhttp_set_cb(myhttp, "/test", http_basic_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);
+}
+
+#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;
+	int 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 ((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;
+	event_debug(("%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);
+
+	/* 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_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));
+	event_debug(("%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 void
+http_complete_write(evutil_socket_t fd, short what, void *arg)
+{
+	struct bufferevent *bev = arg;
+	const char *http_request = "host\r\n"
+	    "Connection: close\r\n"
+	    "\r\n";
+	bufferevent_write(bev, http_request, strlen(http_request));
+}
+
+static struct bufferevent *
+create_bev(struct event_base *base, int fd, int ssl_mask)
+{
+	int flags = BEV_OPT_DEFER_CALLBACKS;
+	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_basic_test_impl(void *arg, int ssl)
+{
+	struct basic_test_data *data = arg;
+	struct timeval tv;
+	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);
+
+	exit_base = data->base;
+	test_ok = 0;
+
+	/* 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);
+	bufferevent_setcb(bev, http_readcb, http_writecb,
+	    http_errorcb, data->base);
+
+	/* first half of the http request */
+	http_request =
+	    "GET /test HTTP/1.1\r\n"
+	    "Host: some";
+
+	bufferevent_write(bev, http_request, strlen(http_request));
+	evutil_timerclear(&tv);
+	tv.tv_usec = 100000;
+	event_base_once(data->base,
+	    -1, EV_TIMEOUT, http_complete_write, bev, &tv);
+
+	event_base_dispatch(data->base);
+
+	tt_assert(test_ok == 3);
+
+	/* connect to the second port */
+	bufferevent_free(bev);
+	evutil_closesocket(fd);
+
+	fd = http_connect("127.0.0.1", port2);
+
+	/* Stupid thing to send a request */
+	bev = create_bev(data->base, fd, ssl);
+	bufferevent_setcb(bev, http_readcb, http_writecb,
+	    http_errorcb, data->base);
+
+	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(data->base);
+
+	tt_assert(test_ok == 5);
+
+	/* Connect to the second port again. This time, send an absolute uri. */
+	bufferevent_free(bev);
+	evutil_closesocket(fd);
+
+	fd = http_connect("127.0.0.1", port2);
+
+	/* Stupid thing to send a request */
+	bev = create_bev(data->base, fd, ssl);
+	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));
+
+	event_base_dispatch(data->base);
+
+	tt_assert(test_ok == 7);
+
+	evhttp_free(http);
+ end:
+	if (bev)
+		bufferevent_free(bev);
+}
+static void http_basic_test(void *arg)
+{ return http_basic_test_impl(arg, 0); }
+
+
+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)
+{
+	event_debug(("%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)
+{
+	event_debug(("%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 = -1;
+	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_int_op(fd, >=, 0);
+
+	/* 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);
+
+	/* 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);
+	}
+
+	event_debug(("%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 = -1;
+	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_int_op(fd, >=, 0);
+
+	/* 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 = -1;
+
+	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);
+	}
+
+	event_debug(("%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);
+
+	event_debug(("%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 = -1;
+	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_int_op(fd, >=, 0);
+
+	/* 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_int_op(fd1, >=, 0);
+
+	/* 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_int_op(fd2, >=, 0);
+
+	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_int_op(fd3, >=, 0);
+
+	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);
+
+	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 */
+	evhttp_add_header(evhttp_request_get_output_headers(req[0]), "Host", "somehost");
+	evhttp_add_header(evhttp_request_get_output_headers(req[0]), "Connection", "close");
+	evhttp_add_header(evhttp_request_get_output_headers(req[0]), "Empty", "itis");
+	evhttp_add_header(evhttp_request_get_output_headers(req[1]), "Host", "somehost");
+	evhttp_add_header(evhttp_request_get_output_headers(req[1]), "Connection", "close");
+	evhttp_add_header(evhttp_request_get_output_headers(req[1]), "Empty", "itis");
+
+	/* We give ownership of the request to the connection */
+	if (evhttp_make_request(evcon, req[0], EVHTTP_REQ_GET, "/test") == -1) {
+		tt_abort_msg("couldn't make request");
+	}
+	if (evhttp_make_request(evcon, req[1], 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;
+
+	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);
+
+	/* 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();
+	event_debug(("%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;
+	event_debug(("%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;
+	event_debug(("%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 = -1;
+	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_int_op(fd, >=, 0);
+
+	/* 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_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_int_op(fd, >=, 0);
+
+	/* 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_int_op(fd, >=, 0);
+
+	/* Stupid thing to send a request */
+	bev = create_bev(data->base, fd, ssl);
+	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 = -1;
+	}
+
+	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;
+	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);
+
+	/* Stupid thing to send a request */
+	bev = create_bev(data->base, fd, ssl);
+	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);
+
+	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);
+	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)
+{ return 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);
+	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)
+{ return 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);
+	/* 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)
+{ return 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)
+{
+	struct basic_test_data *data = arg;
+	struct evhttp_connection *evcon = NULL;
+	struct evhttp_request *req = NULL;
+	struct bufferevent *bev;
+	struct http_server hs = { .port = 0, .ssl = ssl, };
+	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);
+#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, "/test") == -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)
+{ return http_simple_test_impl(arg, 0, 0); }
+
+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 = { .port = 0, .ssl = ssl, };
+	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);
+	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)
+{ return http_connection_retry_conn_address_test_impl(arg, 0); }
+
+static void
+http_connection_retry_test_impl(void *arg, int ssl)
+{
+	return http_connection_retry_test_basic(arg, "127.0.0.1", NULL, ssl);
+}
+static void
+http_connection_retry_test(void *arg)
+{ return 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 = -1;
+	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_int_op(fd, !=, -1);
+
+	/* 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);
+}
+#ifndef WIN32
+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);
+}
+#endif
+
+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;
+#ifndef WIN32
+	if (read_on_write_error)
+		cb = http_data_length_constraints_test_done;
+#endif
+
+	tt_assert(continue_size < size);
+
+	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));
+
+	/* 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_data_length_constraints_test_done, data->base);
+	tt_assert(req);
+
+	long_str = malloc(size);
+	memset(long_str, 'a', size);
+	long_str[size - 1] = '\0';
+	/* Add the information that we care about */
+	evhttp_set_max_headers_size(http, size - 1);
+	evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
+	evhttp_add_header(evhttp_request_get_output_headers(req), "Longheader", long_str);
+
+	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 */
+	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, long_str) == -1) {
+		tt_abort_msg("Couldn't make request");
+	}
+	event_base_dispatch(data->base);
+
+#ifndef WIN32
+	if (read_on_write_error)
+		cb = http_large_entity_test_done;
+#endif
+	evhttp_set_max_body_size(http, size - 2);
+	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);
+	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);
+	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_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");
+	long_str[continue_size] = '\0';
+	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);
+
+#ifndef WIN32
+	if (read_on_write_error)
+		cb = http_expectation_failed_done;
+#endif
+	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);
+	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 = -1;
+	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);
+
+	/* 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;
+	int 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);
+	bev = create_bev(data->base, fd, 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);
+
+	if (bev)
+		bufferevent_free(bev);
+	if (http)
+		evhttp_free(http);
+}
+static void http_write_during_read_test(void *arg)
+{ return 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;
+}
+
+#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_N(title, name, arg) \
+	{ #title, http_##name##_test, TT_ISOLATED, &basic_setup, HTTP_CAST_ARG(arg) }
+#define HTTP(name) HTTP_N(name, name, 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)
+{ return http_basic_test_impl(arg, 1); }
+static void https_filter_basic_test(void *arg)
+{ return http_basic_test_impl(arg, 1 | HTTP_SSL_FILTER); }
+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)
+{ return http_simple_test_impl(arg, 1, 0); }
+static void https_simple_dirty_test(void *arg)
+{ return http_simple_test_impl(arg, 1, 1); }
+static void https_connection_retry_conn_address_test(void *arg)
+{ return http_connection_retry_conn_address_test_impl(arg, 1); }
+static void https_connection_retry_test(void *arg)
+{ return http_connection_retry_test_impl(arg, 1); }
+static void https_chunk_out_test(void *arg)
+{ return http_chunk_out_test_impl(arg, 1); }
+static void https_filter_chunk_out_test(void *arg)
+{ return http_chunk_out_test_impl(arg, 1 | HTTP_SSL_FILTER); }
+static void https_stream_out_test(void *arg)
+{ return http_stream_out_test_impl(arg, 1); }
+static void https_connection_fail_test(void *arg)
+{ return http_connection_fail_test_impl(arg, 1); }
+static void https_write_during_read_test(void *arg)
+{ return http_write_during_read_test_impl(arg, 1); }
+static void https_connection_test(void *arg)
+{ return http_connection_test_(arg, 0, "127.0.0.1", NULL, 0, AF_UNSPEC, 1); }
+static void https_persist_connection_test(void *arg)
+{ return 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_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(simple),
+
+	HTTP_N(cancel, cancel, BASIC),
+	HTTP_N(cancel_by_host, cancel, BY_HOST),
+	HTTP_N(cancel_by_host_no_ns, cancel, BY_HOST | NO_NS),
+	HTTP_N(cancel_by_host_inactive_server, cancel, BY_HOST | INACTIVE_SERVER),
+	HTTP_N(cancel_inactive_server, cancel, INACTIVE_SERVER),
+	HTTP_N(cancel_by_host_no_ns_inactive_server, cancel, 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_N(cancel_by_host_ns_timeout, cancel, BY_HOST | NO_NS | NS_TIMEOUT),
+	HTTP_N(cancel_by_host_ns_timeout_inactive_server, cancel, 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),
+
+#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
+};
+
diff --git a/test/regress_iocp.c b/test/regress_iocp.c
new file mode 100644
index 0000000..17b3852
--- /dev/null
+++ b/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/test/regress_listener.c b/test/regress_listener.c
new file mode 100644
index 0000000..72f7237
--- /dev/null
+++ b/test/regress_listener.c
@@ -0,0 +1,258 @@
+/*
+ * 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 = -1, fd2 = -1, fd3 = -1;
+
+	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_int_op(evconnlistener_get_fd(listener1), >=, 0);
+	tt_int_op(evconnlistener_get_fd(listener2), >=, 0);
+	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 = -1;
+	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);
+}
+
+#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,
+	  &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"},
+
+	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/test/regress_main.c b/test/regress_main.c
new file mode 100644
index 0000000..44e2911
--- /dev/null
+++ b/test/regress_main.c
@@ -0,0 +1,464 @@
+/*
+ * 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
+
+#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 "tinytest.h"
+#include "tinytest_macros.h"
+#include "../iocp-internal.h"
+#include "../event-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)
+{
+}
+
+static 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;
+
+#ifndef _WIN32
+	if (testcase->flags & TT_ENABLE_IOCP_FLAG)
+		return (void*)TT_SKIP;
+#endif
+
+	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;
+}
+
+static 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 },
+#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");
+#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 (tinytest_main(argc,argv,testgroups))
+		return 1;
+
+	libevent_global_shutdown();
+
+	return 0;
+}
+
diff --git a/test/regress_minheap.c b/test/regress_minheap.c
new file mode 100644
index 0000000..05db32e
--- /dev/null
+++ b/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/test/regress_rpc.c b/test/regress_rpc.c
new file mode 100644
index 0000000..01a058c
--- /dev/null
+++ b/test/regress_rpc.c
@@ -0,0 +1,905 @@
+/*
+ * 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.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);
+}
+
+#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(client_timeout),
+	RPC_LEGACY(test),
+
+	END_OF_TESTCASES,
+};
diff --git a/test/regress_ssl.c b/test/regress_ssl.c
new file mode 100644
index 0000000..681705f
--- /dev/null
+++ b/test/regress_ssl.c
@@ -0,0 +1,814 @@
+/*
+ * 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/bio.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(void)
+{
+	/* 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;
+	EVP_PKEY *key = ssl_getkey();
+	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_time_adj(X509_get_notBefore(x509), 0, &now);
+	now += 3600;
+	X509_time_adj(X509_get_notAfter(x509), 0, &now);
+	X509_set_pubkey(x509, key);
+	tt_assert(0 != X509_sign(x509, key, EVP_sha1()));
+
+	return x509;
+end:
+	X509_free(x509);
+	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;
+}
+
+void
+init_ssl(void)
+{
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+	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
+}
+
+/* ====================
+   Here's a simple test: we read a number from the input, increment it, and
+   reply, until we get to 1001.
+*/
+
+static int test_is_done = 0;
+static int n_connected = 0;
+static int got_close = 0;
+static int got_error = 0;
+static int got_timeout = 0;
+static int renegotiate_at = -1;
+static int stop_when_connected = 0;
+static int pending_connect_events = 0;
+static struct event_base *exit_base = NULL;
+
+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,
+};
+
+static void
+bufferevent_openssl_check_fd(struct bufferevent *bev, int filter)
+{
+	tt_int_op(bufferevent_getfd(bev), !=, -1);
+	tt_int_op(bufferevent_setfd(bev, -1), ==, 0);
+	if (filter) {
+		tt_int_op(bufferevent_getfd(bev), !=, -1);
+	} else {
+		tt_int_op(bufferevent_getfd(bev), ==, -1);
+	}
+
+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
+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)
+{
+	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;
+		X509 *peer_cert;
+		++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:
+	;
+}
+
+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;
+	X509 *cert = ssl_getcert();
+	EVP_PKEY *key = ssl_getkey();
+	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;
+
+	tt_assert(cert);
+	tt_assert(key);
+
+	init_ssl();
+
+	if (type & REGRESS_OPENSSL_RENEGOTIATE) {
+		if (SSLeay() >= 0x10001000 &&
+		    SSLeay() <  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, cert);
+	SSL_use_PrivateKey(ssl2, 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_int_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);
+	}
+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, ssl_getcert());
+	SSL_use_PrivateKey(ssl, ssl_getkey());
+
+	bev = bufferevent_openssl_socket_new(
+		data->base,
+		fd,
+		ssl,
+		BUFFEREVENT_SSL_ACCEPTING,
+		BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
+
+	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);
+}
+
+struct rwcount
+{
+	int 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)
+{
+	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)
+{
+	long ret = 0;
+	switch (cmd) {
+	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;
+	BIO *bio;
+	struct rwcount rw = { -1, 0, 0 };
+	enum regress_openssl_type type;
+
+	type = (enum regress_openssl_type)data->setup_data;
+
+	init_ssl();
+
+	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, NULL, 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) {
+		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:
+	;
+}
+
+struct testcase_t ssl_testcases[] = {
+#define T(a) ((void *)(a))
+	{ "bufferevent_socketpair", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup, T(REGRESS_OPENSSL_SOCKETPAIR) },
+	{ "bufferevent_socketpair_write_after_connect", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR|REGRESS_OPENSSL_CLIENT_WRITE) },
+	{ "bufferevent_filter", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup, T(REGRESS_OPENSSL_FILTER) },
+	{ "bufferevent_filter_write_after_connect", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER|REGRESS_OPENSSL_CLIENT_WRITE) },
+	{ "bufferevent_renegotiate_socketpair", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_RENEGOTIATE) },
+	{ "bufferevent_renegotiate_filter", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_RENEGOTIATE) },
+	{ "bufferevent_socketpair_startopen", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_OPEN) },
+	{ "bufferevent_filter_startopen", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_OPEN) },
+
+	{ "bufferevent_socketpair_dirty_shutdown", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_DIRTY_SHUTDOWN) },
+	{ "bufferevent_filter_dirty_shutdown", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_DIRTY_SHUTDOWN) },
+	{ "bufferevent_renegotiate_socketpair_dirty_shutdown",
+	  regress_bufferevent_openssl,
+	  TT_ISOLATED,
+	  &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_RENEGOTIATE | REGRESS_OPENSSL_DIRTY_SHUTDOWN) },
+	{ "bufferevent_renegotiate_filter_dirty_shutdown",
+	  regress_bufferevent_openssl,
+	  TT_ISOLATED,
+	  &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_RENEGOTIATE | REGRESS_OPENSSL_DIRTY_SHUTDOWN) },
+	{ "bufferevent_socketpair_startopen_dirty_shutdown",
+	  regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_OPEN | REGRESS_OPENSSL_DIRTY_SHUTDOWN) },
+	{ "bufferevent_filter_startopen_dirty_shutdown",
+	  regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_OPEN | REGRESS_OPENSSL_DIRTY_SHUTDOWN) },
+
+	{ "bufferevent_socketpair_fd", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_FD) },
+	{ "bufferevent_socketpair_freed", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_FREED) },
+	{ "bufferevent_socketpair_freed_fd", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_FREED | REGRESS_OPENSSL_FD) },
+	{ "bufferevent_filter_freed_fd", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_FILTER | REGRESS_OPENSSL_FREED | REGRESS_OPENSSL_FD) },
+
+	{ "bufferevent_socketpair_timeout", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_setup,
+	  T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_TIMEOUT) },
+	{ "bufferevent_socketpair_timeout_freed_fd", regress_bufferevent_openssl,
+	  TT_ISOLATED, &basic_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, &basic_setup, NULL },
+	{ "bufferevent_connect_sleep", regress_bufferevent_openssl_connect,
+	  TT_FORK|TT_NEED_BASE, &basic_setup, T(REGRESS_OPENSSL_SLEEP) },
+
+#undef T
+
+	END_OF_TESTCASES,
+};
diff --git a/test/regress_testutils.c b/test/regress_testutils.c
new file mode 100644
index 0000000..959347e
--- /dev/null
+++ b/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 = htonl(0x7f000001UL);
+	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/test/regress_testutils.h b/test/regress_testutils.h
new file mode 100644
index 0000000..040516a
--- /dev/null
+++ b/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/test/regress_thread.c b/test/regress_thread.c
new file mode 100644
index 0000000..689c23d
--- /dev/null
+++ b/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)							\
+	{ #name, thread_##name, TT_FORK|TT_NEED_THREADS|TT_NEED_BASE,	\
+	  &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),
+	{ "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),
+#endif
+	END_OF_TESTCASES
+};
+
diff --git a/test/regress_thread.h b/test/regress_thread.h
new file mode 100644
index 0000000..831b51e
--- /dev/null
+++ b/test/regress_thread.h
@@ -0,0 +1,48 @@
+/*
+ * 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_
+
+#ifdef EVENT__HAVE_PTHREADS
+#define THREAD_T pthread_t
+#define THREAD_FN void *
+#define THREAD_RETURN() return (NULL)
+#define THREAD_START(threadvar, fn, arg) \
+	pthread_create(&(threadvar), NULL, fn, arg)
+#define THREAD_JOIN(th) pthread_join(th, NULL)
+#else
+#define THREAD_T HANDLE
+#define THREAD_FN unsigned __stdcall
+#define THREAD_RETURN() return (0)
+#define THREAD_START(threadvar, fn, arg) do {		\
+	uintptr_t threadhandle = _beginthreadex(NULL,0,fn,(arg),0,NULL); \
+	(threadvar) = (HANDLE) threadhandle; \
+	} while (0)
+#define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE)
+#endif
+
+#endif
diff --git a/test/regress_util.c b/test/regress_util.c
new file mode 100644
index 0000000..ef6a148
--- /dev/null
+++ b/test/regress_util.c
@@ -0,0 +1,1489 @@
+/*
+ * 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.
+ */
+#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 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_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);
+}
+
+#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
+	/** 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 */,
+	{  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:
+	;
+}
+
+struct testcase_t util_testcases[] = {
+	{ "ipv4_parse", regress_ipv4_parse, 0, NULL, NULL },
+	{ "ipv6_parse", regress_ipv6_parse, 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 },
+	{ "getaddrinfo", test_evutil_getaddrinfo, TT_FORK, NULL, NULL },
+	{ "getaddrinfo_live", test_evutil_getaddrinfo_live, 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, 0, 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, 0, &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 },
+	END_OF_TESTCASES,
+};
+
diff --git a/test/regress_zlib.c b/test/regress_zlib.c
new file mode 100644
index 0000000..5fe7749
--- /dev/null
+++ b/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/test/rpcgen_wrapper.sh b/test/rpcgen_wrapper.sh
new file mode 100755
index 0000000..aaa0303
--- /dev/null
+++ b/test/rpcgen_wrapper.sh
@@ -0,0 +1,52 @@
+#!/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
+}
+
+if [ -x /usr/bin/python2 ] ; then
+  PYTHON2=/usr/bin/python2
+elif [ "x`which python2`" != x ] ; then
+  PYTHON2=python2
+else
+  PYTHON2=python
+fi
+
+srcdir=$1
+srcdir=${srcdir:-.}
+
+${PYTHON2} ${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/test/test-changelist.c b/test/test-changelist.c
new file mode 100644
index 0000000..6e2466d
--- /dev/null
+++ b/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);
+
+	/* Initalize the event library */
+	if (!(base = event_base_new()))
+		return (1);
+
+	/* Initalize 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/test/test-closed.c b/test/test-closed.c
new file mode 100644
index 0000000..1dd9885
--- /dev/null
+++ b/test/test-closed.c
@@ -0,0 +1,110 @@
+/*
+ * 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_base_free(base);
+	return 0;
+}
+
diff --git a/test/test-dumpevents.c b/test/test-dumpevents.c
new file mode 100644
index 0000000..1c272d4
--- /dev/null
+++ b/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/test/test-eof.c b/test/test-eof.c
new file mode 100644
index 0000000..284ead7
--- /dev/null
+++ b/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);
+
+	/* Initalize the event library */
+	event_init();
+
+	/* Initalize 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/test/test-fdleak.c b/test/test-fdleak.c
new file mode 100644
index 0000000..4c4eba2
--- /dev/null
+++ b/test/test-fdleak.c
@@ -0,0 +1,249 @@
+/*
+ * 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 | BEV_EVENT_ERROR)) {
+		bufferevent_free(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);
+}
+
+/*
+===============================================
+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) {
+		event_base_loopbreak(base);
+	} else {
+		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/test/test-init.c b/test/test-init.c
new file mode 100644
index 0000000..92fbc6b
--- /dev/null
+++ b/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
+
+	/* Initalize the event library */
+	event_init();
+
+	return (0);
+}
+
diff --git a/test/test-ratelim.c b/test/test-ratelim.c
new file mode 100644
index 0000000..9ee989b
--- /dev/null
+++ b/test/test-ratelim.c
@@ -0,0 +1,603 @@
+/*
+ * 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"
+
+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;
+
+/* 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 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;
+
+	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();
+
+	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_del(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/test/test-time.c b/test/test-time.c
new file mode 100644
index 0000000..c4d031e
--- /dev/null
+++ b/test/test-time.c
@@ -0,0 +1,116 @@
+/*
+ * 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 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);
+
+	/* Initalize the event library */
+	event_init();
+
+	for (i = 0; i < NEVENT; i++) {
+		ev[i] = malloc(sizeof(struct event));
+
+		/* Initalize one event */
+		evtimer_set(ev[i], time_cb, ev[i]);
+		tv.tv_sec = 0;
+		tv.tv_usec = rand_int(50000);
+		evtimer_add(ev[i], &tv);
+	}
+
+	event_dispatch();
+
+
+	printf("%d, %d\n", called, NEVENT);
+	return (called < NEVENT);
+}
+
diff --git a/test/test-weof.c b/test/test-weof.c
new file mode 100644
index 0000000..52c7afb
--- /dev/null
+++ b/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);
+
+	/* Initalize the event library */
+	event_init();
+
+	/* Initalize one event */
+	event_set(&ev, pair[1], EV_WRITE, write_cb, &ev);
+
+	event_add(&ev, NULL);
+
+	event_dispatch();
+
+	return (test_okay);
+}
+
diff --git a/test/test.sh b/test/test.sh
new file mode 100755
index 0000000..dd3d986
--- /dev/null
+++ b/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 python2 -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 | python2 $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/test/tinytest.c b/test/tinytest.c
new file mode 100644
index 0000000..3a8e331
--- /dev/null
+++ b/test/tinytest.c
@@ -0,0 +1,493 @@
+/* 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
+
+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 */
+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;
+
+#ifdef _WIN32
+/* Copy of argv[0] for win32. */
+static char commandname[MAX_PATH+1];
+#endif
+
+static void usage(struct testgroup_t *groups, int list_groups)
+  __attribute__((noreturn));
+static int process_test_option(struct testgroup_t *groups, const char *test);
+
+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;
+	testcase->fn(env);
+	outcome = cur_test_outcome;
+
+	if (testcase->setup) {
+		if (testcase->setup->cleanup_fn(testcase, env) == 0)
+			outcome = FAIL;
+	}
+
+	return outcome;
+}
+
+#define MAGIC_EXITCODE 42
+
+#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 exitcode;
+
+	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 %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 0;
+	}
+	WaitForSingleObject(info.hProcess, INFINITE);
+	GetExitCodeProcess(info.hProcess, &exitcode);
+	CloseHandle(info.hProcess);
+	CloseHandle(info.hThread);
+	if (exitcode == 0)
+		return OK;
+	else if (exitcode == 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;
+		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 0;
+		} else if (r != 1) {
+			perror("read outcome from pipe");
+		}
+		waitpid(pid, &status, 0);
+		close(outcome_pipe[0]);
+		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) {
+		++n_ok;
+		if (opt_verbosity>0 && !opt_forked)
+			puts(opt_verbosity==1?"OK":"");
+	} else if (outcome == SKIP) {
+		++n_skipped;
+		if (opt_verbosity>0 && !opt_forked)
+			puts("SKIPPED");
+	} else {
+		++n_bad;
+		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]");
+	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 {
+				printf("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)
+		for (j=0; groups[i].cases[j].name; ++j)
+			if (groups[i].cases[j].flags & TT_ENABLED_)
+				testcase_run_one(&groups[i],
+						 &groups[i].cases[j]);
+
+	--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 = 0;
+}
+
+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/test/tinytest.h b/test/tinytest.h
new file mode 100644
index 0000000..ed07b26
--- /dev/null
+++ b/test/tinytest.h
@@ -0,0 +1,100 @@
+/* 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)
+/** If you add your own flags, make them start at this point. */
+#define TT_FIRST_USER_FLAG (1<<4)
+
+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/test/tinytest_local.h b/test/tinytest_local.h
new file mode 100644
index 0000000..87ec2fa
--- /dev/null
+++ b/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/test/tinytest_macros.h b/test/tinytest_macros.h
new file mode 100644
index 0000000..2c02a74
--- /dev/null
+++ b/test/tinytest_macros.h
@@ -0,0 +1,205 @@
+/* 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_ = (a);						\
+	type val2_ = (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)
+
+#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