mirror of
https://github.com/libevent/libevent.git
synced 2025-01-09 00:56:20 +08:00
96c6956e02
This is a glass-box test to get more coverage on the event loop backends. We've run into bugs here before with fencepost errors, and it turns out that none of our unit tests had enough events to exercise the resize code. Most of the backends have some kind of logic that resizes an array when: - The highest fd is too high - The number of events added since the last iteration of the loop is too high - The number of active events is too high. This test hits all 3 cases, and increases coverage in select.c by 7%, in poll by 1%, and in kqueue by 9%. svn:r1482
1936 lines
40 KiB
C
1936 lines
40 KiB
C
/*
|
|
* Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu>
|
|
* Copyright (c) 2007-2009 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.
|
|
*/
|
|
|
|
#ifdef WIN32
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "event-config.h"
|
|
#endif
|
|
|
|
#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 "log-internal.h"
|
|
|
|
#include "regress.h"
|
|
|
|
#ifndef WIN32
|
|
#include "regress.gen.h"
|
|
#endif
|
|
|
|
int 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"
|
|
#define SECONDS 1
|
|
|
|
#ifndef SHUT_WR
|
|
#define SHUT_WR 1
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#define write(fd,buf,len) send((fd),(buf),(len),0)
|
|
#define read(fd,buf,len) recv((fd),(buf),(len),0)
|
|
#endif
|
|
|
|
struct basic_cb_args
|
|
{
|
|
struct event_base *eb;
|
|
struct event *ev;
|
|
unsigned int callcount;
|
|
};
|
|
|
|
static void
|
|
simple_read_cb(int 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(int 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(int fd, short event, void *arg)
|
|
{
|
|
}
|
|
|
|
static void
|
|
simple_write_cb(int 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(int fd, short event, void *arg)
|
|
{
|
|
struct event *ev = arg;
|
|
int len;
|
|
|
|
len = 128;
|
|
if (woff + len >= 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 >= sizeof(wbuf)) {
|
|
shutdown(fd, SHUT_WR);
|
|
if (usepersist)
|
|
event_del(ev);
|
|
return;
|
|
}
|
|
|
|
if (!usepersist) {
|
|
if (event_add(ev, NULL) == -1)
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
multiple_read_cb(int 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(int fd, short event, void *arg)
|
|
{
|
|
struct timeval tv;
|
|
int diff;
|
|
|
|
evutil_gettimeofday(&tcalled, NULL);
|
|
if (evutil_timercmp(&tcalled, &tset, >))
|
|
evutil_timersub(&tcalled, &tset, &tv);
|
|
else
|
|
evutil_timersub(&tset, &tcalled, &tv);
|
|
|
|
diff = tv.tv_sec*1000 + tv.tv_usec/1000 - SECONDS * 1000;
|
|
if (diff < 0)
|
|
diff = -diff;
|
|
|
|
if (diff < 100)
|
|
test_ok = 1;
|
|
}
|
|
|
|
struct both {
|
|
struct event ev;
|
|
int nread;
|
|
};
|
|
|
|
static void
|
|
combined_read_cb(int 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(int fd, short event, void *arg)
|
|
{
|
|
struct both *both = arg;
|
|
char buf[128];
|
|
int len;
|
|
|
|
len = sizeof(buf);
|
|
if (len > both->nread)
|
|
len = both->nread;
|
|
|
|
len = write(fd, buf, len);
|
|
if (len == -1)
|
|
fprintf(stderr, "%s: write\n", __func__);
|
|
if (len <= 0) {
|
|
shutdown(fd, SHUT_WR);
|
|
return;
|
|
}
|
|
|
|
both->nread -= len;
|
|
if (event_add(&both->ev, NULL) == -1)
|
|
exit(1);
|
|
}
|
|
|
|
/* Test infrastructure */
|
|
|
|
static int
|
|
setup_test(const char *name)
|
|
{
|
|
if (in_legacy_test_wrapper)
|
|
return 0;
|
|
|
|
fprintf(stdout, "%s", name);
|
|
|
|
if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
|
|
fprintf(stderr, "%s: socketpair\n", __func__);
|
|
exit(1);
|
|
}
|
|
|
|
if (evutil_make_socket_nonblocking(pair[0]) == -1)
|
|
fprintf(stderr, "fcntl(O_NONBLOCK)");
|
|
|
|
if (evutil_make_socket_nonblocking(pair[1]) == -1)
|
|
fprintf(stderr, "fcntl(O_NONBLOCK)");
|
|
|
|
test_ok = 0;
|
|
called = 0;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cleanup_test(void)
|
|
{
|
|
if (in_legacy_test_wrapper)
|
|
return 0;
|
|
|
|
#ifndef WIN32
|
|
close(pair[0]);
|
|
close(pair[1]);
|
|
#else
|
|
CloseHandle((HANDLE)pair[0]);
|
|
CloseHandle((HANDLE)pair[1]);
|
|
#endif
|
|
if (test_ok)
|
|
fprintf(stdout, "OK\n");
|
|
else {
|
|
fprintf(stdout, "FAILED\n");
|
|
exit(1);
|
|
}
|
|
test_ok = 0;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
test_simpleread(void)
|
|
{
|
|
struct event ev;
|
|
|
|
/* Very simple read test */
|
|
setup_test("Simple read: ");
|
|
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
shutdown(pair[0], 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(int 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: ");
|
|
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
shutdown(pair[0], 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 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 < 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 < 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 = 0;
|
|
tv.tv_sec = SECONDS;
|
|
evtimer_set(&ev, timeout_cb, NULL);
|
|
evtimer_add(&ev, &tv);
|
|
|
|
evutil_gettimeofday(&tset, NULL);
|
|
event_dispatch();
|
|
|
|
cleanup_test();
|
|
}
|
|
|
|
static void
|
|
periodic_timeout_cb(int 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;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
#ifndef WIN32
|
|
static void signal_cb(int fd, short event, void *arg);
|
|
|
|
extern struct event_base *current_base;
|
|
|
|
static void
|
|
child_signal_cb(int fd, short event, void *arg)
|
|
{
|
|
struct timeval tv;
|
|
int *pint = arg;
|
|
|
|
*pint = 1;
|
|
|
|
tv.tv_usec = 500000;
|
|
tv.tv_sec = 0;
|
|
event_loopexit(&tv);
|
|
}
|
|
|
|
static void
|
|
test_fork(void)
|
|
{
|
|
int status, got_sigchld = 0;
|
|
struct event ev, sig_ev;
|
|
pid_t pid;
|
|
|
|
setup_test("After fork: ");
|
|
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
|
|
event_set(&ev, pair[1], EV_READ, simple_read_cb, &ev);
|
|
if (event_add(&ev, NULL) == -1)
|
|
exit(1);
|
|
|
|
evsignal_set(&sig_ev, SIGCHLD, child_signal_cb, &got_sigchld);
|
|
evsignal_add(&sig_ev, NULL);
|
|
|
|
if ((pid = fork()) == 0) {
|
|
/* in the child */
|
|
if (event_reinit(current_base) == -1) {
|
|
fprintf(stdout, "FAILED (reinit)\n");
|
|
exit(1);
|
|
}
|
|
|
|
evsignal_del(&sig_ev);
|
|
|
|
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 for the child to read the data */
|
|
sleep(1);
|
|
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
|
|
if (waitpid(pid, &status, 0) == -1) {
|
|
fprintf(stdout, "FAILED (fork)\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (WEXITSTATUS(status) != 76) {
|
|
fprintf(stdout, "FAILED (exit): %d\n", WEXITSTATUS(status));
|
|
exit(1);
|
|
}
|
|
|
|
/* test that the current event loop still works */
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
shutdown(pair[0], SHUT_WR);
|
|
|
|
event_dispatch();
|
|
|
|
if (!got_sigchld) {
|
|
fprintf(stdout, "FAILED (sigchld)\n");
|
|
exit(1);
|
|
}
|
|
|
|
evsignal_del(&sig_ev);
|
|
|
|
cleanup_test();
|
|
}
|
|
|
|
static void
|
|
signal_cb_sa(int sig)
|
|
{
|
|
test_ok = 2;
|
|
}
|
|
|
|
static void
|
|
signal_cb(int fd, short event, void *arg)
|
|
{
|
|
struct event *ev = arg;
|
|
|
|
evsignal_del(ev);
|
|
test_ok = 1;
|
|
}
|
|
|
|
static void
|
|
test_simplesignal(void)
|
|
{
|
|
struct event ev;
|
|
struct itimerval itv;
|
|
|
|
setup_test("Simple signal: ");
|
|
evsignal_set(&ev, SIGALRM, signal_cb, &ev);
|
|
evsignal_add(&ev, NULL);
|
|
/* find bugs in which operations are re-ordered */
|
|
evsignal_del(&ev);
|
|
evsignal_add(&ev, NULL);
|
|
|
|
memset(&itv, 0, sizeof(itv));
|
|
itv.it_value.tv_sec = 1;
|
|
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_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 = 1;
|
|
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;
|
|
printf("Immediate signal: ");
|
|
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();
|
|
printf("Signal dealloc: ");
|
|
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;
|
|
printf("Signal pipeloss: ");
|
|
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;
|
|
printf("Signal switchbase: ");
|
|
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;
|
|
printf("Signal handler assert: ");
|
|
/* 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);
|
|
/* only way to verify we were in evsig_handler() */
|
|
if (base->sig.evsig_caught)
|
|
test_ok = 0;
|
|
else
|
|
test_ok = 1;
|
|
|
|
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;
|
|
printf("Signal handler restore: ");
|
|
#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(int 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
|
|
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 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 = 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], 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 = 0;
|
|
tv.tv_sec = 1;
|
|
event_loopexit(&tv);
|
|
|
|
evutil_gettimeofday(&tv_start, NULL);
|
|
event_dispatch();
|
|
evutil_gettimeofday(&tv_end, NULL);
|
|
evutil_timersub(&tv_end, &tv_start, &tv_end);
|
|
|
|
evtimer_del(&ev);
|
|
|
|
if (tv.tv_sec < 2)
|
|
test_ok = 1;
|
|
|
|
cleanup_test();
|
|
}
|
|
|
|
static void
|
|
test_loopexit_multiple(void)
|
|
{
|
|
struct timeval tv;
|
|
struct event_base *base;
|
|
|
|
setup_test("Loop Multiple exit: ");
|
|
|
|
base = event_base_new();
|
|
|
|
tv.tv_usec = 0;
|
|
tv.tv_sec = 1;
|
|
event_base_loopexit(base, &tv);
|
|
|
|
tv.tv_usec = 0;
|
|
tv.tv_sec = 2;
|
|
event_base_loopexit(base, &tv);
|
|
|
|
event_base_dispatch(base);
|
|
|
|
event_base_free(base);
|
|
|
|
test_ok = 1;
|
|
|
|
cleanup_test();
|
|
}
|
|
|
|
static void
|
|
break_cb(int fd, short events, void *arg)
|
|
{
|
|
test_ok = 1;
|
|
event_loopbreak();
|
|
}
|
|
|
|
static void
|
|
fail_cb(int 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();
|
|
|
|
evtimer_del(&ev1);
|
|
evtimer_del(&ev2);
|
|
|
|
cleanup_test();
|
|
}
|
|
|
|
static struct event *readd_test_event_last_added = NULL;
|
|
static void
|
|
re_add_read_cb(int fd, short event, void *arg)
|
|
{
|
|
char buf[256];
|
|
int len;
|
|
struct event *ev_other = arg;
|
|
readd_test_event_last_added = ev_other;
|
|
len = read(fd, buf, sizeof(buf));
|
|
event_add(ev_other, NULL);
|
|
++test_ok;
|
|
}
|
|
|
|
static void
|
|
test_nonpersist_readd(void)
|
|
{
|
|
struct event ev1, ev2;
|
|
int n, m;
|
|
|
|
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);
|
|
n = write(pair[0], "Hello", 5);
|
|
m = write(pair[1], "Hello", 5);
|
|
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(int 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);
|
|
}
|
|
|
|
|
|
static void
|
|
test_multiple_cb(int 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);
|
|
write(pair[1], TEST1, strlen(TEST1)+1);
|
|
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(int 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 */
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
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: ");
|
|
|
|
write(pair[0], TEST1, strlen(TEST1)+1);
|
|
|
|
/* Setup the loop termination */
|
|
evutil_timerclear(&tv);
|
|
tv.tv_sec = 1;
|
|
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 = EVBUFFER_LENGTH(tmp);
|
|
evtag_encode_int(tmp, integers[i]);
|
|
newlen = 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)
|
|
{
|
|
u_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 < sizeof(buffer); i++)
|
|
buffer[i] = rand();
|
|
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] = 0xff;
|
|
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 = EVBUFFER_LENGTH(tmp);
|
|
evtag_encode_tag(tmp, integers[i]);
|
|
newlen = 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);
|
|
}
|
|
|
|
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 = toupper(*cp);
|
|
}
|
|
}
|
|
|
|
#ifdef WIN32
|
|
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);
|
|
}
|
|
static void unsetenv(const char *k)
|
|
{
|
|
char b[256];
|
|
evutil_snprintf(b, sizeof(b), "%s=",k);
|
|
putenv(b);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
test_base_environ(void *arg)
|
|
{
|
|
const char **basenames;
|
|
char varbuf[128];
|
|
int i, n_methods=0;
|
|
struct event_base *base = NULL;
|
|
struct event_config *cfg = NULL;
|
|
const char *defaultname;
|
|
|
|
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 ? */
|
|
methodname_to_envvar(defaultname, varbuf, sizeof(varbuf));
|
|
setenv(varbuf, "1", 1);
|
|
|
|
/* 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(defaultname, ==, event_base_get_method(base));
|
|
|
|
end:
|
|
if (base)
|
|
event_base_free(base);
|
|
if (cfg)
|
|
event_config_free(cfg);
|
|
}
|
|
|
|
static void
|
|
read_called_once_cb(int fd, short event, void *arg)
|
|
{
|
|
tt_int_op(event, ==, EV_READ);
|
|
called += 1;
|
|
end:
|
|
;
|
|
}
|
|
|
|
static void
|
|
timeout_called_once_cb(int fd, short event, void *arg)
|
|
{
|
|
tt_int_op(event, ==, EV_TIMEOUT);
|
|
called += 100;
|
|
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);
|
|
|
|
write(data->pair[1], TEST1, strlen(TEST1)+1);
|
|
shutdown(data->pair[1], SHUT_WR);
|
|
|
|
event_base_dispatch(data->base);
|
|
|
|
tt_int_op(called, ==, 101);
|
|
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, diff;
|
|
|
|
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);
|
|
|
|
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, >));
|
|
evutil_timeradd(&now, &tv, &tv);
|
|
evutil_timersub(&tv2, &tv, &diff);
|
|
tt_int_op(diff.tv_sec, ==, 0);
|
|
tt_int_op(labs(diff.tv_usec), <, 1000);
|
|
|
|
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 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);
|
|
}
|
|
|
|
static void
|
|
many_event_cb(int fd, short event, void *arg)
|
|
{
|
|
int *calledp = arg;
|
|
*calledp += 1;
|
|
}
|
|
|
|
static void
|
|
test_many_events(void *arg)
|
|
{
|
|
/* Try 64 events that should all be aready at once. This will
|
|
* exercise the "resize" code on most of the backends. */
|
|
#define MANY 64
|
|
|
|
struct basic_test_data *data = arg;
|
|
struct event_base *base = data->base;
|
|
evutil_socket_t sock[MANY];
|
|
struct event *ev[MANY];
|
|
int called[MANY];
|
|
int i;
|
|
|
|
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, many_event_cb,
|
|
&called[i]);
|
|
event_add(ev[i], NULL);
|
|
}
|
|
|
|
event_base_loop(base, EVLOOP_NONBLOCK);
|
|
|
|
for (i = 0; i < MANY; ++i) {
|
|
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
|
|
}
|
|
|
|
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(bad_assign, TT_FORK|TT_NEED_BASE|TT_NO_LOGS),
|
|
|
|
/* These are still using the old API */
|
|
LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE),
|
|
LEGACY(priorities, TT_FORK|TT_NEED_BASE),
|
|
|
|
/* These legacy tests may not all need all of these flags. */
|
|
LEGACY(simpleread, TT_ISOLATED),
|
|
LEGACY(simpleread_multiple, TT_ISOLATED),
|
|
LEGACY(simplewrite, TT_ISOLATED),
|
|
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_pending", test_event_pending, TT_ISOLATED, &basic_setup,
|
|
NULL },
|
|
{ "mm_functions", test_mm_functions, TT_FORK, NULL, NULL },
|
|
BASIC(many_events, TT_ISOLATED),
|
|
|
|
#ifndef WIN32
|
|
LEGACY(fork, TT_ISOLATED),
|
|
#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(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),
|
|
LEGACY(signal_restore, TT_ISOLATED),
|
|
LEGACY(signal_assert, TT_ISOLATED),
|
|
LEGACY(signal_while_processing, TT_ISOLATED),
|
|
#endif
|
|
END_OF_TESTCASES
|
|
};
|
|
|