merge
authorTero Marttila <terom@fixme.fi>
Tue, 01 Sep 2009 00:04:54 +0300
changeset 47 b45a6648931c
parent 46 64b4ffb44754 (current diff)
parent 42 0ff56f7216ee (diff)
child 48 ee7ade660c0b
merge
qmsk/net/lib/event2/event.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) :
         """
--- 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
--- 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 = <int>(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, <void *>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) :
     """
--- /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()