# HG changeset patch # User Tero Marttila # Date 1251752694 -10800 # Node ID b45a6648931c6ed9ae09151903a6cd7d86094292 # Parent 64b4ffb44754dbf2925d71e65e6c5f8f24eb307c# Parent 0ff56f7216ee80c1dd12de3a51f02d83e66a6372 merge diff -r 64b4ffb44754 -r b45a6648931c qmsk/net/lib/event2/base.pyx --- a/qmsk/net/lib/event2/base.pyx Tue Sep 01 00:04:26 2009 +0300 +++ b/qmsk/net/lib/event2/base.pyx Tue Sep 01 00:04:54 2009 +0300 @@ -1,6 +1,24 @@ from qmsk.net.lib.event2.base cimport * from qmsk.net.lib.event2.event cimport build_timeout +class EventError (Exception) : + """ + Base class of errors raised by lib.event2 code + """ + + pass + +class EventCallError (EventError) : + """ + Some libevent function returned an error code. + """ + + def __init__ (self, func, msg=None) : + super(EventCallError, self).__init__(func) + + self.func = func + self.msg = msg + cdef class event_base : def __init__ (self) : @@ -9,7 +27,7 @@ self.ev_base = lib.event_base_new() if self.ev_base == NULL : - raise Exception("event_base_new") + raise EventCallError("event_base_new") def reinit (self) : """ @@ -19,7 +37,7 @@ """ if lib.event_reinit(self.ev_base) < 0 : - raise Exception("event_reinit: could not re-add all events") + raise EventCallError("event_reinit", "could not re-add all events") property method : def __get__ (self) : @@ -29,13 +47,14 @@ return lib.event_base_get_method(self.ev_base) - def loop (self, once = False, nonblock = False) : """ Run the event loop. once - only run the event loop once, at most nonblock - do not block waiting for events + + Returns True if succesfull, False if no events were registered """ cdef int flags = 0 @@ -48,8 +67,16 @@ flags |= lib.EVLOOP_NONBLOCK # event_base_loop() - if lib.event_base_loop(self.ev_base, flags) < 0 : - raise Exception("event_base_loop") + ret = lib.event_base_loop(self.ev_base, flags) + + if ret < 0 : + raise EventCallError("event_base_loop") + + elif ret > 0 : + return False + + else : + return True def loopexit (self, timeout = None) : """ @@ -62,7 +89,7 @@ cdef lib.timeval tv if lib.event_base_loopexit(self.ev_base, build_timeout(&tv, timeout)) < 0 : - raise Exception("event_base_loopexit") + raise EventCallError("event_base_loopexit") def loopbreak (self) : """ @@ -73,7 +100,7 @@ """ if lib.event_base_loopbreak(self.ev_base) < 0 : - raise Exception("event_base_loopbreak") + raise EventCallError("event_base_loopbreak") def __dealloc__ (self) : """ diff -r 64b4ffb44754 -r b45a6648931c qmsk/net/lib/event2/event.pxd --- a/qmsk/net/lib/event2/event.pxd Tue Sep 01 00:04:26 2009 +0300 +++ b/qmsk/net/lib/event2/event.pxd Tue Sep 01 00:04:54 2009 +0300 @@ -20,12 +20,7 @@ fd - OS file descriptor to watch, or -1 events - bitmask of EV_* flags that represents the events to wait for - When the event fires, it will "call" this event object, so __call__ gets the following parameters: - fd - OS file descriptor the event occured on, or -1 - mask - bitmask of EV_* flags that represents the triggered event - - The default implementation of __call__ does nothing. The method's return value will be ignored, and should be - None. Any errors raised by the callback will be printed out as warning messages, and ignored. + When the event fires, it will "call" this event object. See __call__(). """ # the underlying event object diff -r 64b4ffb44754 -r b45a6648931c qmsk/net/lib/event2/event.pyx --- a/qmsk/net/lib/event2/event.pyx Tue Sep 01 00:04:26 2009 +0300 +++ b/qmsk/net/lib/event2/event.pyx Tue Sep 01 00:04:54 2009 +0300 @@ -9,7 +9,7 @@ cdef double t - if timeout : + if timeout is not None : t = timeout tv.tv_sec = (t) @@ -39,7 +39,11 @@ cdef class event : - def __init__ (self, event_base base, lib.evutil_socket_t fd, short events) : + def __init__ (self, event_base base not None, lib.evutil_socket_t fd, short events) : + + # libevent seems to segfault otherwise... + if events & (lib.EV_READ | lib.EV_WRITE) and fd < 0 : + raise ValueError(fd) # construct event using our internal callback, with self as the arg self.ev = lib.event_new(base.ev_base, fd, events, ev_callback, self) @@ -48,7 +52,7 @@ raise Exception("event_new") - def assign (self, event_base base, lib.evutil_socket_t fd, short events) : + def assign (self, event_base base not None, lib.evutil_socket_t fd, short events) : """ Re-assign our event parameters to the given ones. @@ -98,10 +102,13 @@ lib.event_active(self.ev, fd, mask) - def pending (self, short mask) : + def pending (self, short mask = 0xffff) : """ Returns a bool indicating if this event is pending (that is, has been .add()'d). + For convenience, this defaults to testing all possible flags, and so will return true if any events are + pending. + XXX: support returning timeout value? """ @@ -119,7 +126,14 @@ def __call__ (self, lib.evutil_socket_t fd, short mask) : """ - The method invoked by the internal libevent callback. + The method invoked by the internal libevent callback when the event becomes active. + + fd - OS file descriptor the event occured on, or -1 + mask - bitmask of EV_* flags that represents the triggered event + + + The default implementation of __call__ does nothing. The method's return value will be ignored, and should + be None. Any errors raised by the callback will be printed out as warning messages, and ignored. """ pass @@ -131,8 +145,9 @@ XXX: what happens if event_base's __dealloc__ is triggered, but there are still event objects alive? """ - - lib.event_free(self.ev) + + if self.ev != NULL : + lib.event_free(self.ev) class CallbackEvent (event) : """ diff -r 64b4ffb44754 -r b45a6648931c test/lib_event.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/lib_event.py Tue Sep 01 00:04:54 2009 +0300 @@ -0,0 +1,192 @@ +import unittest + +from qmsk.net.lib.event2.base import event_base +from qmsk.net.lib.event2.event import event +from qmsk.net.lib.event2.constants import * + +import os + +class MyEvent (event) : + def __init__ (self, base, fd, ev) : + event.__init__(self, base, fd, ev) + + self.ev_in = ev + self.ev_out = None + + self.res = None + + def __call__ (self, fd, mask) : + self.ev_out = mask + + self.res = (mask == self.ev_in) + +class TestEventBase (unittest.TestCase) : + """ + Simple event_base bits + """ + + def setUp (self) : + self.ev_base = event_base() + + def test_loop_empty (self) : + """ + loop() on an empty event_base returns False and doesn't block + """ + + self.assertFalse(self.ev_base.loop(nonblock=False, once=False)) + + def test_loop_empty_nb (self) : + """ + loop(nonblock=False) on an empty event_base returns False + """ + + self.assertFalse(self.ev_base.loop(nonblock=True)) + +class TestEvent (unittest.TestCase) : + """ + Simple event bits. + """ + + def setUp (self) : + self.ev_base = event_base() + self.ev = event(self.ev_base, 0, EV_READ) + + def test_add (self) : + """ + .add() works. + """ + + self.assertFalse(self.ev.pending(EV_READ)) + + self.ev.add() + + self.assertTrue(self.ev.pending(EV_READ)) + + def test_add_timeout (self) : + """ + .add() with timeout works + """ + + self.ev.add(1.1) + self.assertTrue(self.ev.pending()) + + def test_add_del (self) : + """ + .add() and then .delete() works + """ + + self.assertFalse(self.ev.pending()) + + # add it in + self.ev.add() + + self.assertTrue(self.ev.pending()) + + # ensure that the ev_base is indeed not empty + self.assertTrue(self.ev_base.loop(nonblock=True)) + + + # remove it again + self.ev.delete() + + self.assertFalse(self.ev.pending()) + + # ensure that the ev_base is indeed empty + self.assertFalse(self.ev_base.loop(nonblock=True)) + + def test_invalid_base (self) : + """ + Building an event with invalid base (None) fails + """ + + # XXX: this doesn't actually seem to check against None, just the type..? + self.assertRaises(TypeError, event, None, 0, EV_READ) + + def test_illegal_fd (self) : + """ + Building an event with an illegal fd (-1) fails + """ + + self.assertRaises(ValueError, event, self.ev_base, -1, EV_READ) + + def test_invalid_fd (self) : + """ + Building an event with an invalid fd (>= 0) fails when calling .add() + """ + + ev = event(self.ev_base, 666, EV_READ) + + # XXX: be more specific about Exception + self.assertRaises(Exception, ev.add) + + def test_invalid_mask_zero (self) : + """ + Building an event with an invalid mask (0)... XXX: works? + """ + + ev = event(self.ev_base, 0, 0) + ev.add() + +class TestEventPipe (unittest.TestCase) : + """ + Test basic event stuff using a pipe. + """ + + def setUp (self) : + self.ev_base = event_base() + self.fd_read, self.fd_write = os.pipe() + + def test_event (self) : + """ + Constructing an event works + """ + + self.assertTrue(event(self.ev_base, self.fd_read, EV_READ)) + + def test_read (self) : + """ + Read event on pipe with data fires + """ + + ev = MyEvent(self.ev_base, self.fd_read, EV_READ) + ev.add(0.1) # 100ms + + # trigger + os.write(self.fd_write, "foo") + + # loop + self.ev_base.loop(nonblock=True) + + # test + self.assertEquals(ev.ev_out, EV_READ) + + def test_read_timeout (self) : + """ + Read event on empty pipe timeouts + """ + + ev = MyEvent(self.ev_base, self.fd_read, EV_READ) + ev.add(0.0) + + # loop + self.ev_base.loop(once=True) + + # test + self.assertEquals(ev.ev_out, EV_TIMEOUT) + + def test_read_nonblock (self) : + """ + Read event on empty pipe doesn't block loop with nonblock=True + """ + + ev = MyEvent(self.ev_base, self.fd_read, EV_READ) + ev.add() + + # loop once + self.ev_base.loop(nonblock=True) + + # test + self.assertEquals(ev.ev_out, None) + +if __name__ == '__main__' : + unittest.main()