[Python-checkins] r80090 - in python/branches/signalfd-issue8407: Lib/test/test_inspect.py Lib/test/test_signal.py Modules/signalmodule.c

jean-paul.calderone python-checkins at python.org
Thu Apr 15 07:06:32 CEST 2010


Author: jean-paul.calderone
Date: Thu Apr 15 07:06:31 2010
New Revision: 80090

Log:
Partial implementation of signal.signalfd and signal.sigprocmask

Modified:
   python/branches/signalfd-issue8407/Lib/test/test_inspect.py
   python/branches/signalfd-issue8407/Lib/test/test_signal.py
   python/branches/signalfd-issue8407/Modules/signalmodule.c

Modified: python/branches/signalfd-issue8407/Lib/test/test_inspect.py
==============================================================================
--- python/branches/signalfd-issue8407/Lib/test/test_inspect.py	(original)
+++ python/branches/signalfd-issue8407/Lib/test/test_inspect.py	Thu Apr 15 07:06:31 2010
@@ -372,10 +372,24 @@
         self.assertRaises(IOError, inspect.getsource, unicodedata)
         self.assertRaises(IOError, inspect.findsource, unicodedata)
 
+
+
+class _BrokenDescriptor(object):
+    """
+    A descriptor which raises an exception when it is accessed as an attribute
+    of the class it is on.  This is used to test inspect's handling of such
+    descriptors.
+    """
+    def __get__(self, *args):
+        raise RuntimeError("This descriptor cannot be gotten.")
+
+
+
 # Helper for testing classify_class_attrs.
 def attrs_wo_objs(cls):
     return [t[:3] for t in inspect.classify_class_attrs(cls)]
 
+
 class TestClassesAndFunctions(unittest.TestCase):
     def test_classic_mro(self):
         # Test classic-class method resolution order.
@@ -462,6 +476,8 @@
 
             datablob = '1'
 
+            broken_data = _BrokenDescriptor()
+
         attrs = attrs_wo_objs(A)
         self.assertIn(('s', 'static method', A), attrs, 'missing static method')
         self.assertIn(('c', 'class method', A), attrs, 'missing class method')

Modified: python/branches/signalfd-issue8407/Lib/test/test_signal.py
==============================================================================
--- python/branches/signalfd-issue8407/Lib/test/test_signal.py	(original)
+++ python/branches/signalfd-issue8407/Lib/test/test_signal.py	Thu Apr 15 07:06:31 2010
@@ -395,9 +395,161 @@
         # and the handler should have been called
         self.assertEqual(self.hndl_called, True)
 
+
+
+class SomeException(Exception):
+    """
+    A unique exception class to be raised by a signal handler to verify that the
+    signal handler was invoked.
+    """
+
+
+
+def raiser(*args):
+    """A signal handler which raises SomeException.
+    """
+    raise SomeException()
+
+
+
+class SigprocmaskTests(unittest.TestCase):
+    """
+    Tests for sigprocmask.
+    """
+    def _handle_sigusr1(self):
+        old_handler = signal.signal(signal.SIGUSR1, raiser)
+        self.addCleanup(signal.signal, signal.SIGUSR1, old_handler)
+        return SomeException
+
+
+    def test_signature(self):
+        """When invoked with other than two arguments, sigprocmask raises
+        TypeError.
+        """
+        self.assertRaises(TypeError, signal.sigprocmask)
+        self.assertRaises(TypeError, signal.sigprocmask, 1)
+        self.assertRaises(TypeError, signal.sigprocmask, 1, 2, 3)
+
+
+    def test_invalid_how(self):
+        """If a valid other than SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK is
+        passed for the how argument to sigprocmask, ValueError is raised.
+        """
+        with self.assertRaises(ValueError) as cm:
+            signal.sigprocmask(1700, [])
+        exc = cm.exception
+        self.assertTrue(isinstance(exc, ValueError))
+        self.assertEquals(str(exc), "value specified for how (1700) invalid")
+
+
+    def test_return_previous_mask(self):
+        """sigprocmask returns a list of the signals previously masked.
+        """
+        previous = signal.sigprocmask(signal.SIG_BLOCK, [1, 3, 5])
+        result = signal.sigprocmask(signal.SIG_BLOCK, previous)
+        self.assertEquals(result, [1, 3, 5])
+
+
+    def test_block(self):
+        """When invoked with SIG_BLOCK, sigprocmask blocks the signals in the
+        sigmask list.
+        """
+        self._handle_sigusr1()
+        previous = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGUSR1])
+        os.kill(os.getpid(), signal.SIGUSR1)
+        try:
+            signal.sigprocmask(signal.SIG_SETMASK, previous)
+        except SomeException:
+            pass
+        else:
+            self.fail("Expected to receive SIGUSR1 after unblocking it.")
+
+
+    def test_unblock(self):
+        """When invoked with SIG_UNBLOCK, sigprocmask unblocks the signals in
+        the sigmask list.
+        """
+        self._handle_sigusr1()
+        previous = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGUSR1])
+        self.addCleanup(signal.sigprocmask, signal.SIG_SETMASK, previous)
+        signal.sigprocmask(signal.SIG_UNBLOCK, [signal.SIGUSR1])
+        try:
+            os.kill(os.getpid(), signal.SIGUSR1)
+            time.sleep(1)
+        except SomeException:
+            pass
+        else:
+            self.fail("SomeException was expected but not raised")
+
+
+class SignalfdTests(unittest.TestCase):
+    """
+    Tests for signal.signalfd.
+    """
+    def test_signature(self):
+        """When invoked with fewer than two arguments or more than three,
+        signalfd raises TypeError.
+        """
+        self.assertRaises(TypeError, signal.signalfd)
+        self.assertRaises(TypeError, signal.signalfd, 1)
+        self.assertRaises(TypeError, signal.signalfd, 1, 2, 3, 4)
+
+
+    def test_create_signalfd(self):
+        """When invoked with a file descriptor of -1, signalfd allocates a new
+        file descriptor for signal information delivery and returns it.
+        """
+        fd = signal.signalfd(-1, [])
+        self.assertTrue(isinstance(fd, int))
+        os.close(fd)
+
+
+    def test_non_iterable_signals(self):
+        """If an object which is not iterable is passed for the sigmask list
+        argument to signalfd, the exception raised by trying to iterate over
+        that object is raised.
+        """
+        self.assertRaises(TypeError, signal.signalfd, -1, object())
+
+
+    def test_non_integer_signals(self):
+        """If any non-integer values are included in the sigmask list argument
+        to signalfd, the exception raised by the attempt to convert them to an
+        integer is raised.
+        """
+        self.assertRaises(TypeError, signal.signalfd, -1, [object()])
+
+
+    def test_out_of_range_signal(self):
+        """If a signal number that is out of the valid range is included in the
+        sigmask list argument to signalfd, ValueError is raised.
+        """
+        with self.assertRaises(ValueError) as cm:
+            signal.signalfd(-1, [-2])
+        exc = cm.exception
+        self.assertTrue(isinstance(exc, ValueError))
+        self.assertEquals(str(exc), "signal number -2 out of range")
+
+
+    def test_handle_signals(self):
+        """After signalfd is called, if a signal is received which was in the
+        sigmask list passed to that call, information about the signal can be
+        read from the fd returned by that call.
+        """
+        fd = signal.signalfd(-1, [signal.SIGUSR2])
+        self.addCleanup(os.close, fd)
+        previous = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGUSR2])
+        self.addCleanup(signal.sigprocmask, signal.SIG_SETMASK, previous)
+        os.kill(os.getpid(), signal.SIGUSR2)
+        bytes = os.read(fd, 128)
+        self.assertTrue(bytes)
+
+
 def test_main():
-    test_support.run_unittest(BasicSignalTests, InterProcessSignalTests,
-        WakeupSignalTests, SiginterruptTest, ItimerTest)
+    test_support.run_unittest(
+        BasicSignalTests, InterProcessSignalTests,
+        WakeupSignalTests, SiginterruptTest, ItimerTest, SignalfdTests,
+        SigprocmaskTests)
 
 
 if __name__ == "__main__":

Modified: python/branches/signalfd-issue8407/Modules/signalmodule.c
==============================================================================
--- python/branches/signalfd-issue8407/Modules/signalmodule.c	(original)
+++ python/branches/signalfd-issue8407/Modules/signalmodule.c	Thu Apr 15 07:06:31 2010
@@ -22,6 +22,10 @@
 #ifdef HAVE_SYS_TIME_H
 #include <sys/time.h>
 #endif
+#ifdef HAVE_SIGNALFD
+#include <sys/signalfd.h>
+#endif
+
 
 #ifndef SIG_ERR
 #define SIG_ERR ((PyOS_sighandler_t)(-1))
@@ -459,6 +463,130 @@
 #endif
 
 
+static int
+_iterable_to_mask(PyObject *iterable, sigset_t *mask)
+{
+    static const char* range_format = "signal number %d out of range";
+    char range_buffer[1024];
+
+    PyObject *item, *iterator;
+
+    sigemptyset(mask);
+
+    iterator = PyObject_GetIter(iterable);
+    if (iterator == NULL) {
+        return -1;
+    }
+
+    for (item = PyIter_Next(iterator); item; item = PyIter_Next(iterator)) {
+        int signum = PyInt_AsLong(item);
+        if (signum == -1 && PyErr_Occurred()) {
+            return -1;
+        }
+        if (sigaddset(mask, signum) == -1) {
+            PyOS_snprintf(range_buffer, sizeof(range_buffer), range_format, signum);
+            PyErr_SetString(PyExc_ValueError, range_buffer);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+
+#ifdef HAVE_SIGPROCMASK
+static PyObject *
+signal_sigprocmask(PyObject *self, PyObject *args)
+{
+    static const char* how_format = "value specified for how (%d) invalid";
+    char how_buffer[1024];
+
+    int how, sig;
+    PyObject *signals, *result, *signum;
+    sigset_t mask, previous;
+
+    if (!PyArg_ParseTuple(args, "iO:sigprocmask", &how, &signals)) {
+        return NULL;
+    }
+
+    if (_iterable_to_mask(signals, &mask) == -1) {
+        return NULL;
+    }
+
+    if (sigprocmask(how, &mask, &previous) == -1) {
+        PyOS_snprintf(how_buffer, sizeof(how_buffer), how_format, how);
+        PyErr_SetString(PyExc_ValueError, how_buffer);
+        return NULL;
+    }
+
+    result = PyList_New(0);
+    if (result == NULL) {
+        return NULL;
+    }
+
+    for (sig = 1; sig < NSIG; ++sig) {
+        if (sigismember(&previous, sig) == 1) {
+            /* Handle the case where it is a member by adding the signal to
+               the result list.  Ignore the other cases because they mean the
+               signal isn't a member of the mask or the signal was invalid,
+               and an invalid signal must have been our fault in constructing
+               the loop boundaries. */
+            signum = PyInt_FromLong(sig);
+            if (signum == NULL) {
+                Py_DECREF(result);
+                return NULL;
+            }
+            if (PyList_Append(result, signum) == -1) {
+                Py_DECREF(signum);
+                Py_DECREF(result);
+                return NULL;
+            }
+        }
+    }
+    return result;
+}
+
+PyDoc_STRVAR(sigprocmask_doc,
+"sigprocmask(how, mask) -> old mask\n\
+\n\
+Examine and change blocked signals.");
+#endif
+
+#ifdef HAVE_SIGNALFD
+static PyObject *
+signal_signalfd(PyObject *self, PyObject *args)
+{
+    int result;
+    sigset_t mask;
+
+    int fd;
+    PyObject *signals;
+
+    if (!PyArg_ParseTuple(args, "iO:signalfd", &fd, &signals)) {
+        return NULL;
+    }
+
+    if (_iterable_to_mask(signals, &mask) == -1) {
+        return NULL;
+    }
+
+    result = signalfd(-1, &mask, 0);
+
+    if (result == -1) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+
+    return PyInt_FromLong(result);
+}
+
+PyDoc_STRVAR(signalfd_doc,
+"signalfd(fd, mask, flags)\n\
+\n\
+Create a file descriptor for accepting signals.");
+
+#endif
+
+
 /* List of functions defined in the module */
 static PyMethodDef signal_methods[] = {
 #ifdef HAVE_ALARM
@@ -473,6 +601,12 @@
 	{"signal",	        signal_signal, METH_VARARGS, signal_doc},
 	{"getsignal",	        signal_getsignal, METH_VARARGS, getsignal_doc},
 	{"set_wakeup_fd",	signal_set_wakeup_fd, METH_VARARGS, set_wakeup_fd_doc},
+#ifdef HAVE_SIGPROCMASK
+        {"sigprocmask",         signal_sigprocmask, METH_VARARGS, sigprocmask_doc},
+#endif
+#ifdef HAVE_SIGNALFD
+        {"signalfd",            signal_signalfd, METH_VARARGS, signalfd_doc},
+#endif
 #ifdef HAVE_SIGINTERRUPT
  	{"siginterrupt",	signal_siginterrupt, METH_VARARGS, siginterrupt_doc},
 #endif
@@ -794,6 +928,24 @@
     	PyDict_SetItemString(d, "ItimerError", ItimerError);
 #endif
 
+#ifdef SIG_BLOCK
+    x = PyLong_FromLong(SIG_BLOCK);
+    PyDict_SetItemString(d, "SIG_BLOCK", x);
+    Py_DECREF(x);
+#endif
+
+#ifdef SIG_UNBLOCK
+    x = PyLong_FromLong(SIG_UNBLOCK);
+    PyDict_SetItemString(d, "SIG_UNBLOCK", x);
+    Py_DECREF(x);
+#endif
+
+#ifdef SIG_SETMASK
+    x = PyLong_FromLong(SIG_SETMASK);
+    PyDict_SetItemString(d, "SIG_SETMASK", x);
+    Py_DECREF(x);
+#endif
+
 #ifdef CTRL_C_EVENT
     x = PyInt_FromLong(CTRL_C_EVENT);
     PyDict_SetItemString(d, "CTRL_C_EVENT", x);


More information about the Python-checkins mailing list