/*--------------------------------------------------------------------------
Copyright 1999,2000, Dan Kegel http://www.kegel.com/
See the file COPYING
(Also freely licensed to Disappearing, Inc. under a separate license
which allows them to do absolutely anything they want with it, without
regard to the GPL.)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--------------------------------------------------------------------------*/
#include "Poller_poll.h"
#include "Poller_select.h"
#include "Poller_kqueue.h"
#include "Poller_devpoll.h"
#include "Poller_sigio.h"
#include "Poller_sigfd.h"
#include "dprint.h"
#include "CHECK.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
/*----------------------------------------------------------------------
Portable function to set a socket into nonblocking mode.
----------------------------------------------------------------------*/
static int setNonblocking(int fd)
{
int flags;
/* Caution: O_NONBLOCK is defined but broken on SunOS 4.1.x & AIX 3.2.5. */
#if defined(O_NONBLOCK)
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
flags = 0;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
#else
flags = 1;
return ioctl(fd, FIOBIO, &flags);
#endif
}
// handy little function used to count number of zeroes to right of given bit
// converts a bit mask like POLLIN into an index into m_eventCounts[].
static int log2(int val)
{
int i;
for (i=0; i<16; i++)
if (val == (1 << i))
return i;
return -1;
}
static const char *eventbitname(int bitnum)
{
switch (1<<bitnum) {
case POLLIN: return "POLLIN";
case POLLPRI: return "POLLPRI";
case POLLOUT: return "POLLOUT";
case POLLERR: return "POLLERR";
case POLLHUP: return "POLLHUP";
case POLLNVAL: return "POLLNVAL";
default: return "unknown";
}
return "bug";
}
template<class T>
class Poller_test : Poller::Client
{
private:
/// The object being tested.
T m_p;
/// The two file descriptors we use to test Poller.
int m_filedes[2];
/// Number of bits in struct pollfd's events field we care about.
static const int EVENTBITS = 8;
/// Number of times each bit in struct pollfd's events field has been set.
int m_eventCounts[EVENTBITS];
public:
/// Just increments counts in m_eventCounts
int notifyPollEvent(Poller::PollEvent *event)
{
int i;
int fd = event->fd;
short eventflags = event->revents;
DPRINT(("notifyPollEvent: fd %d, eventflags %x\n", fd, eventflags));
for (i=0; i<EVENTBITS; i++) {
if (eventflags & (1 << i)) {
m_eventCounts[i]++;
DPRINT(("notifyPollEvent: m_eventCounts[%d %s] = %d\n", i, eventbitname(i), m_eventCounts[i]));
}
}
(void) fd;
return 0;
}
void clear() {
DPRINT(("clear: clearing m_eventCounts\n"));
for (int i=0; i<EVENTBITS; i++)
m_eventCounts[i] = 0;
}
/// The main function for this test.
void unitTest()
{
testWakeUp();
testCaching();
testRejection("127.0.0.1", 2222);
// To fully test nonblocking connections, must also test with a *remote*
// computer that will reject the connection.
//testRejection("192.168.123.254", 2222);
testMondo();
}
/// My original big hairy test.
void testMondo()
{
CHECK(0, m_p.init());
CHECK(0, m_p.setSignum(SIGRTMIN));
clear();
// Create a pipe so we have file descriptors to watch with Poller.
// We use a pipe rather than a file or socket because it's easy to
// generate readiness events by writing to one end of the pipe.
CHECK(0, pipe(m_filedes));
CHECK(0, setNonblocking(m_filedes[0]));
CHECK(0, setNonblocking(m_filedes[1]));
// Add a client to watch the standard input. We'll delete it shortly.
CHECK(0, m_p.add(0, this, POLLIN));
// Watch our 2 test file descriptors, and count their readiness events.
// The same client watches both file descriptors.
CHECK(0, m_p.add(m_filedes[1], this, POLLOUT));
CHECK(0, m_p.add(m_filedes[0], this, POLLIN|POLLPRI));
// Delete the client registered for standard input; this used to trigger a bug in Poller.
CHECK(0, m_p.del(0));
/* For sigio and friends, assume that fd isn't really ready, and
* clear sigio's spurious readiness notification, as if we
* had tried to do I/O and gotten an EWOULDBLOCK.
* Err, except for POLLOUT on the writing end of the pipe, of course.
*/
m_p.clearReadiness(m_filedes[0], POLLIN|POLLPRI);
m_p.clearReadiness(m_filedes[1], POLLIN);
// Initially, the only thing that's ready is the writing end of the pipe
// Verify we get notified that the expected number of fd's are readable/writable
clear();
CHECK(0, m_p.waitAndDispatchEvents(0));
CHECK(0, m_eventCounts[log2(POLLIN)]);
CHECK(1, m_eventCounts[log2(POLLOUT)]);
CHECK(0, m_eventCounts[log2(POLLPRI)]);
CHECK(0, m_eventCounts[log2(POLLERR)]);
CHECK(0, m_eventCounts[log2(POLLHUP)]);
CHECK(0, m_eventCounts[log2(POLLNVAL)]);
DPRINT(("unitTest: same thing, but mask off all events first\n"));
clear();
CHECK(0, m_p.andMask(m_filedes[0],0));
CHECK(0, m_p.andMask(m_filedes[1],0));
CHECK(EWOULDBLOCK, m_p.waitAndDispatchEvents(0));
CHECK(0, m_eventCounts[log2(POLLOUT)]);
DPRINT(("unitTest: make sure andMask can't set bits\n"));
CHECK(0, m_p.andMask(m_filedes[0],~0));
CHECK(0, m_p.andMask(m_filedes[1],~0));
CHECK(EWOULDBLOCK, m_p.waitAndDispatchEvents(0));
CHECK(0, m_eventCounts[log2(POLLOUT)]);
DPRINT(("unitTest: Flush cached events\n"));
CHECK(EWOULDBLOCK, m_p.waitAndDispatchEvents(100));
clear();
CHECK(EWOULDBLOCK, m_p.waitAndDispatchEvents(100));
CHECK(0, m_eventCounts[log2(POLLIN)]);
CHECK(0, m_eventCounts[log2(POLLPRI)]);
CHECK(0, m_eventCounts[log2(POLLOUT)]);
CHECK(0, m_eventCounts[log2(POLLERR)]);
CHECK(0, m_eventCounts[log2(POLLHUP)]);
CHECK(0, m_eventCounts[log2(POLLNVAL)]);
DPRINT(("unitTest: write to fd %d until full, make sure readiness goes off\n", m_filedes[1]));
CHECK(0, m_p.setMask(m_filedes[0],POLLIN|POLLPRI));
CHECK(0, m_p.setMask(m_filedes[1],POLLOUT));
while (write(m_filedes[1], "hi", 2) == 2)
;
CHECK(EWOULDBLOCK, errno);
/* We got an EWOULDBLOCK, which is exactly when we are
* required to clear our readiness notification
*/
m_p.clearReadiness(m_filedes[1], POLLOUT);
clear();
CHECK(0, m_p.waitAndDispatchEvents(1000)); // FIXME: fails with Poller_sigio
// After we write to the write end of the pipe, the write end is no longer ready,
// and the read end is ready for reading.
CHECK(1, m_eventCounts[log2(POLLIN)]);
CHECK(0, m_eventCounts[log2(POLLPRI)]);
CHECK(0, m_eventCounts[log2(POLLOUT)]);
CHECK(0, m_eventCounts[log2(POLLERR)]);
CHECK(0, m_eventCounts[log2(POLLHUP)]);
CHECK(0, m_eventCounts[log2(POLLNVAL)]);
// Check for bug reported by Jeon:
// Deleting a client who has events waiting and then calling getNextEvent
// returns the deleted file descriptor and a NULL client.
// At this point, we know that m_filedes[0] is readable.
CHECK(0, m_p.waitForEvents(0));
CHECK(0, m_p.del(m_filedes[0]));
Poller::PollEvent e;
CHECK(EWOULDBLOCK, m_p.getNextEvent( &e ));
m_p.shutdown();
}
/// Just test rejected connections.
void testRejection(const char *theHost, int thePort)
{
CHECK(0, m_p.init());
CHECK(0, m_p.setSignum(SIGRTMIN));
DPRINT(("Connect to a port that will refuse connections, verify error reported.\n"));
// Requires theHost = a nonlocal IP adr, thePort = port where that host will refuse a connection
// i.e. this test assumes that if you do `telnet theHost thePort`, you get a quick rejection
int sock = socket (AF_INET, SOCK_STREAM, 0);
CHECKNE(sock, -1);
CHECK(0, setNonblocking(sock));
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(thePort);
sin.sin_addr.s_addr=inet_addr(theHost);
DPRINT(("connecting to host %s port %d, expect rejection\n", theHost, thePort));
CHECK(-1, connect (sock, reinterpret_cast<struct sockaddr *>(&sin), sizeof (sin)));
CHECK(errno, EINPROGRESS);
/* Don't add the socket to the Poller until after you've
* called connect(). Otherwise, it's ambiguous what writability
* means; unconnected sockets are always 'writable', but once you
* call connect(), it won't become writable again until the connect
* completes or fails.
* Be careful to only ask for POLLOUT + POLLERR until connection
* completes; other events would just be confusing.
*/
m_p.add(sock, this, POLLOUT|POLLERR);
clear();
CHECK(0, m_p.waitAndDispatchEvents(1000));
// we expect rejection, so the error count should be 1
// however, select doesn't tell us about errors,
//CHECK(1, m_eventCounts[log2(POLLERR)]);
// so we have to do I/O to tell whether it failed
CHECK(true, m_eventCounts[log2(POLLOUT)]||m_eventCounts[log2(POLLERR)]);
// nonblocking connect completion signalled by POLLOUT; see Stevens UNP vol 1 p. 410
/* check to see if connect succeeded */
int connecterr = -1;
socklen_t len = sizeof(connecterr);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&connecterr, &len) < 0) {
int xerr = errno;
EDPRINT(("can't getsockopt SO_ERROR fd %d fails, errno %d\n", sock, xerr));
CHECK(0, 1);
}
DPRINT(("got connection errno %d\n", connecterr));
if (connecterr == EINPROGRESS) {
DPRINT(("Hmm, kernel says we're still connecting...\n"));
/* after every EWOULDBLOCK/EINPROGRESS, must clear readiness */
m_p.clearReadiness(sock, POLLOUT);
/* This would happen if we had a spurious writability readiness
* event. Normally we'd just continue in the event loop until
* next event. I haven't coded that here out of laziness.
*/
CHECK(0, 1);
} else if (connecterr) {
DPRINT(("connection failed as expected\n"));
} else {
DPRINT(("connection succeeded. Are you sure you edited the source and specified a remote computer that will reject the connection??\n"));
CHECK(0, 1);
}
CHECK(0, m_p.del(sock));
close(sock);
m_p.shutdown();
}
/// Just test wakeUp.
void testWakeUp()
{
CHECK(0, m_p.init());
CHECK(0, m_p.setSignum(SIGRTMIN));
DPRINT(("testWakeUp: Set up a wakeup pipe\n"));
CHECK(0, m_p.initWakeUpPipe());
/* Clear the cached readiness bits */
m_p.waitAndDispatchEvents(0);
DPRINT(("testWakeUp: Verify that waitForEvents() will block at moment\n"));
CHECK(EWOULDBLOCK, m_p.waitAndDispatchEvents(0));
DPRINT(("testWakeUp: Verify that wakeUp will keep waitForEvents() from blocking\n"));
CHECK(0, m_p.wakeUp());
int err = m_p.waitAndDispatchEvents(1000);
if (err != 0) {
fprintf(stderr, "Your kernel might not support SIGIO with pipes...\n"
"see http://www.cs.helsinki.fi/linux/linux-kernel/2002-13/0191.html\n");
}
CHECK(0, err);
m_p.shutdown();
}
/// Just test state caching.
void testCaching(void)
{
DPRINT(("testCaching\n"));
CHECK(0, m_p.init());
CHECK(0, m_p.setSignum(SIGRTMIN));
// Create a pipe so we have file descriptors to watch with Poller.
// We use a pipe rather than a file or socket because it's easy to
// generate readiness events by writing to one end of the pipe.
CHECK(0, pipe(m_filedes));
CHECK(0, setNonblocking(m_filedes[0]));
CHECK(0, setNonblocking(m_filedes[1]));
// Watch our 2 test file descriptors, and count their readiness events.
// The same client watches both file descriptors.
CHECK(0, m_p.add(m_filedes[1], this, POLLOUT));
CHECK(0, m_p.add(m_filedes[0], this, POLLIN|POLLPRI));
for (int i=0; i<3; i++) {
DPRINT(("testCaching: loop iter %d\n", i));
// Initially, the only thing that's ready is the writing end of the pipe
// Verify we get notified that the expected number of fd's are readable/writable
clear();
CHECK(0, m_p.waitAndDispatchEvents(0));
/* well, these should be 0 and 1, but Poller is allowed to give us spurious
* wakeups for anything we're registered for, and it does initially for Poller_sigio.
* Makes this hard to test, eh?
*/
#if 0
CHECK(0, m_eventCounts[log2(POLLIN)]);
CHECK(1, m_eventCounts[log2(POLLOUT)]);
#endif
CHECK(0, m_eventCounts[log2(POLLPRI)]);
CHECK(0, m_eventCounts[log2(POLLERR)]);
CHECK(0, m_eventCounts[log2(POLLHUP)]);
CHECK(0, m_eventCounts[log2(POLLNVAL)]);
}
CHECK(0, m_p.del(m_filedes[0]));
CHECK(0, m_p.del(m_filedes[1]));
close(m_filedes[0]);
close(m_filedes[1]);
m_p.shutdown();
}
};
int main(int argc, char **argv)
{
(void) argc;
(void) argv;
// send all DPRINTs to logfile
FILE *log = fopen("Poller_test.log", "w");
CHECKNE(0,log);
DPRINT_SETFP(log);
// setlinebuf(log)
setvbuf(log, (char *)NULL, _IOLBF, 0);
DPRINT_ENABLE(true);
DPRINT(("poll test\n"));
Poller_test<Poller_poll> ptp;
ptp.unitTest();
DPRINT(("select test\n"));
Poller_test<Poller_select> pts;
pts.unitTest();
#if HAVE_KQUEUE
DPRINT(("kqueue test\n"));
Poller_test<Poller_kqueue> ptk;
ptk.unitTest();
#endif
#if HAVE_DEVPOLL
DPRINT(("devpoll test\n"));
Poller_test<Poller_devpoll> ptd;
ptd.unitTest();
#endif
#if HAVE_F_SETSIG
DPRINT(("sigio test\n"));
Poller_test<Poller_sigio> ptr;
ptr.unitTest();
#endif
#if HAVE_F_SETSIG && HAVE_F_SETAUXFL
DPRINT(("sigfd test\n"));
Poller_test<Poller_sigfd> ptf;
ptf.unitTest();
#endif
unlink("Poller_test.log");
printf("Poller_test: test passed\n");
exit(0);
}
syntax highlighted by Code2HTML, v. 0.9.1