PageRenderTime 308ms CodeModel.GetById 161ms app.highlight 76ms RepoModel.GetById 64ms app.codeStats 1ms

/Lib/test/test_signal.py

http://unladen-swallow.googlecode.com/
Python | 401 lines | 291 code | 64 blank | 46 comment | 34 complexity | 0bc8131bad910979bdc1a5d879ef95e1 MD5 | raw file
  1import unittest
  2from test import test_support
  3from contextlib import closing, nested
  4import gc
  5import pickle
  6import select
  7import signal
  8import subprocess
  9import traceback
 10import sys, os, time, errno
 11
 12if sys.platform[:3] in ('win', 'os2') or sys.platform == 'riscos':
 13    raise test_support.TestSkipped("Can't test signal on %s" % \
 14                                   sys.platform)
 15
 16
 17class HandlerBCalled(Exception):
 18    pass
 19
 20
 21def exit_subprocess():
 22    """Use os._exit(0) to exit the current subprocess.
 23
 24    Otherwise, the test catches the SystemExit and continues executing
 25    in parallel with the original test, so you wind up with an
 26    exponential number of tests running concurrently.
 27    """
 28    os._exit(0)
 29
 30
 31def ignoring_eintr(__func, *args, **kwargs):
 32    try:
 33        return __func(*args, **kwargs)
 34    except EnvironmentError as e:
 35        if e.errno != errno.EINTR:
 36            raise
 37        return None
 38
 39
 40class InterProcessSignalTests(unittest.TestCase):
 41    MAX_DURATION = 20   # Entire test should last at most 20 sec.
 42
 43    def setUp(self):
 44        self.using_gc = gc.isenabled()
 45        gc.disable()
 46
 47    def tearDown(self):
 48        if self.using_gc:
 49            gc.enable()
 50
 51    def format_frame(self, frame, limit=None):
 52        return ''.join(traceback.format_stack(frame, limit=limit))
 53
 54    def handlerA(self, signum, frame):
 55        self.a_called = True
 56        if test_support.verbose:
 57            print "handlerA invoked from signal %s at:\n%s" % (
 58                signum, self.format_frame(frame, limit=1))
 59
 60    def handlerB(self, signum, frame):
 61        self.b_called = True
 62        if test_support.verbose:
 63            print "handlerB invoked from signal %s at:\n%s" % (
 64                signum, self.format_frame(frame, limit=1))
 65        raise HandlerBCalled(signum, self.format_frame(frame))
 66
 67    def wait(self, child):
 68        """Wait for child to finish, ignoring EINTR."""
 69        while True:
 70            try:
 71                child.wait()
 72                return
 73            except OSError as e:
 74                if e.errno != errno.EINTR:
 75                    raise
 76
 77    def run_test(self):
 78        # Install handlers. This function runs in a sub-process, so we
 79        # don't worry about re-setting the default handlers.
 80        signal.signal(signal.SIGHUP, self.handlerA)
 81        signal.signal(signal.SIGUSR1, self.handlerB)
 82        signal.signal(signal.SIGUSR2, signal.SIG_IGN)
 83        signal.signal(signal.SIGALRM, signal.default_int_handler)
 84
 85        # Variables the signals will modify:
 86        self.a_called = False
 87        self.b_called = False
 88
 89        # Let the sub-processes know who to send signals to.
 90        pid = os.getpid()
 91        if test_support.verbose:
 92            print "test runner's pid is", pid
 93
 94        child = ignoring_eintr(subprocess.Popen, ['kill', '-HUP', str(pid)])
 95        if child:
 96            self.wait(child)
 97            if not self.a_called:
 98                time.sleep(1)  # Give the signal time to be delivered.
 99        self.assertTrue(self.a_called)
100        self.assertFalse(self.b_called)
101        self.a_called = False
102
103        # Make sure the signal isn't delivered while the previous
104        # Popen object is being destroyed, because __del__ swallows
105        # exceptions.
106        del child
107        try:
108            child = subprocess.Popen(['kill', '-USR1', str(pid)])
109            # This wait should be interrupted by the signal's exception.
110            self.wait(child)
111            time.sleep(1)  # Give the signal time to be delivered.
112            self.fail('HandlerBCalled exception not thrown')
113        except HandlerBCalled:
114            self.assertTrue(self.b_called)
115            self.assertFalse(self.a_called)
116            if test_support.verbose:
117                print "HandlerBCalled exception caught"
118
119        child = ignoring_eintr(subprocess.Popen, ['kill', '-USR2', str(pid)])
120        if child:
121            self.wait(child)  # Nothing should happen.
122
123        try:
124            signal.alarm(1)
125            # The race condition in pause doesn't matter in this case,
126            # since alarm is going to raise a KeyboardException, which
127            # will skip the call.
128            signal.pause()
129            # But if another signal arrives before the alarm, pause
130            # may return early.
131            time.sleep(1)
132        except KeyboardInterrupt:
133            if test_support.verbose:
134                print "KeyboardInterrupt (the alarm() went off)"
135        except:
136            self.fail("Some other exception woke us from pause: %s" %
137                      traceback.format_exc())
138        else:
139            self.fail("pause returned of its own accord, and the signal"
140                      " didn't arrive after another second.")
141
142    def test_main(self):
143        # Call several functions once so that -Xjit=always will convert
144        # them to machine code eagerly.  Otherwise they get JITted
145        # during the select() call and make the test time out.
146        self.assertTrue(True)
147        self.assertFalse(False)
148        self.wait(ignoring_eintr(subprocess.Popen, ['true']))
149
150        # This function spawns a child process to insulate the main
151        # test-running process from all the signals. It then
152        # communicates with that child process over a pipe and
153        # re-raises information about any exceptions the child
154        # throws. The real work happens in self.run_test().
155        os_done_r, os_done_w = os.pipe()
156        with nested(closing(os.fdopen(os_done_r)),
157                    closing(os.fdopen(os_done_w, 'w'))) as (done_r, done_w):
158            child = os.fork()
159            if child == 0:
160                # In the child process; run the test and report results
161                # through the pipe.
162                try:
163                    done_r.close()
164                    # Have to close done_w again here because
165                    # exit_subprocess() will skip the enclosing with block.
166                    with closing(done_w):
167                        try:
168                            self.run_test()
169                        except:
170                            pickle.dump(traceback.format_exc(), done_w)
171                        else:
172                            pickle.dump(None, done_w)
173                except:
174                    print 'Uh oh, raised from pickle.'
175                    traceback.print_exc()
176                finally:
177                    exit_subprocess()
178
179            done_w.close()
180            # Block for up to MAX_DURATION seconds for the test to finish.
181            r, w, x = select.select([done_r], [], [], self.MAX_DURATION)
182            if done_r in r:
183                tb = pickle.load(done_r)
184                if tb:
185                    self.fail(tb)
186            else:
187                os.kill(child, signal.SIGKILL)
188                self.fail('Test deadlocked after %d seconds.' %
189                          self.MAX_DURATION)
190
191
192class BasicSignalTests(unittest.TestCase):
193    def trivial_signal_handler(self, *args):
194        pass
195
196    def test_out_of_range_signal_number_raises_error(self):
197        self.assertRaises(ValueError, signal.getsignal, 4242)
198
199        self.assertRaises(ValueError, signal.signal, 4242,
200                          self.trivial_signal_handler)
201
202    def test_setting_signal_handler_to_none_raises_error(self):
203        self.assertRaises(TypeError, signal.signal,
204                          signal.SIGUSR1, None)
205
206    def test_getsignal(self):
207        hup = signal.signal(signal.SIGHUP, self.trivial_signal_handler)
208        self.assertEquals(signal.getsignal(signal.SIGHUP),
209                          self.trivial_signal_handler)
210        signal.signal(signal.SIGHUP, hup)
211        self.assertEquals(signal.getsignal(signal.SIGHUP), hup)
212
213
214class WakeupSignalTests(unittest.TestCase):
215    TIMEOUT_FULL = 10
216    TIMEOUT_HALF = 5
217
218    def test_wakeup_fd_early(self):
219        import select
220
221        signal.alarm(1)
222        before_time = time.time()
223        # We attempt to get a signal during the sleep,
224        # before select is called
225        time.sleep(self.TIMEOUT_FULL)
226        mid_time = time.time()
227        self.assert_(mid_time - before_time < self.TIMEOUT_HALF)
228        select.select([self.read], [], [], self.TIMEOUT_FULL)
229        after_time = time.time()
230        self.assert_(after_time - mid_time < self.TIMEOUT_HALF)
231
232    def test_wakeup_fd_during(self):
233        import select
234
235        signal.alarm(1)
236        before_time = time.time()
237        # We attempt to get a signal during the select call
238        self.assertRaises(select.error, select.select,
239            [self.read], [], [], self.TIMEOUT_FULL)
240        after_time = time.time()
241        self.assert_(after_time - before_time < self.TIMEOUT_HALF)
242
243    def setUp(self):
244        import fcntl
245
246        self.alrm = signal.signal(signal.SIGALRM, lambda x,y:None)
247        self.read, self.write = os.pipe()
248        flags = fcntl.fcntl(self.write, fcntl.F_GETFL, 0)
249        flags = flags | os.O_NONBLOCK
250        fcntl.fcntl(self.write, fcntl.F_SETFL, flags)
251        self.old_wakeup = signal.set_wakeup_fd(self.write)
252
253    def tearDown(self):
254        signal.set_wakeup_fd(self.old_wakeup)
255        os.close(self.read)
256        os.close(self.write)
257        signal.signal(signal.SIGALRM, self.alrm)
258
259class SiginterruptTest(unittest.TestCase):
260    signum = signal.SIGUSR1
261    def readpipe_interrupted(self, cb):
262        r, w = os.pipe()
263        ppid = os.getpid()
264        pid = os.fork()
265
266        oldhandler = signal.signal(self.signum, lambda x,y: None)
267        cb()
268        if pid==0:
269            # child code: sleep, kill, sleep. and then exit,
270            # which closes the pipe from which the parent process reads
271            try:
272                time.sleep(0.2)
273                os.kill(ppid, self.signum)
274                time.sleep(0.2)
275            finally:
276                exit_subprocess()
277
278        try:
279            os.close(w)
280
281            try:
282                d=os.read(r, 1)
283                return False
284            except OSError, err:
285                if err.errno != errno.EINTR:
286                    raise
287                return True
288        finally:
289            signal.signal(self.signum, oldhandler)
290            os.waitpid(pid, 0)
291
292    def test_without_siginterrupt(self):
293        i=self.readpipe_interrupted(lambda: None)
294        self.assertEquals(i, True)
295
296    def test_siginterrupt_on(self):
297        i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 1))
298        self.assertEquals(i, True)
299
300    def test_siginterrupt_off(self):
301        i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 0))
302        self.assertEquals(i, False)
303
304class ItimerTest(unittest.TestCase):
305    def setUp(self):
306        self.hndl_called = False
307        self.hndl_count = 0
308        self.itimer = None
309        self.old_alarm = signal.signal(signal.SIGALRM, self.sig_alrm)
310
311    def tearDown(self):
312        signal.signal(signal.SIGALRM, self.old_alarm)
313        if self.itimer is not None: # test_itimer_exc doesn't change this attr
314            # just ensure that itimer is stopped
315            signal.setitimer(self.itimer, 0)
316
317    def sig_alrm(self, *args):
318        self.hndl_called = True
319        if test_support.verbose:
320            print("SIGALRM handler invoked", args)
321
322    def sig_vtalrm(self, *args):
323        self.hndl_called = True
324
325        if self.hndl_count > 3:
326            # it shouldn't be here, because it should have been disabled.
327            raise signal.ItimerError("setitimer didn't disable ITIMER_VIRTUAL "
328                "timer.")
329        elif self.hndl_count == 3:
330            # disable ITIMER_VIRTUAL, this function shouldn't be called anymore
331            signal.setitimer(signal.ITIMER_VIRTUAL, 0)
332            if test_support.verbose:
333                print("last SIGVTALRM handler call")
334
335        self.hndl_count += 1
336
337        if test_support.verbose:
338            print("SIGVTALRM handler invoked", args)
339
340    def sig_prof(self, *args):
341        self.hndl_called = True
342        signal.setitimer(signal.ITIMER_PROF, 0)
343
344        if test_support.verbose:
345            print("SIGPROF handler invoked", args)
346
347    def test_itimer_exc(self):
348        # XXX I'm assuming -1 is an invalid itimer, but maybe some platform
349        # defines it ?
350        self.assertRaises(signal.ItimerError, signal.setitimer, -1, 0)
351        # Negative times are treated as zero on some platforms.
352        if 0:
353            self.assertRaises(signal.ItimerError,
354                              signal.setitimer, signal.ITIMER_REAL, -1)
355
356    def test_itimer_real(self):
357        self.itimer = signal.ITIMER_REAL
358        signal.setitimer(self.itimer, 1.0)
359        if test_support.verbose:
360            print("\ncall pause()...")
361        signal.pause()
362
363        self.assertEqual(self.hndl_called, True)
364
365    def test_itimer_virtual(self):
366        self.itimer = signal.ITIMER_VIRTUAL
367        signal.signal(signal.SIGVTALRM, self.sig_vtalrm)
368        signal.setitimer(self.itimer, 0.3, 0.2)
369
370        for i in xrange(100000000):
371            # use up some virtual time by doing real work
372            _ = pow(12345, 67890, 10000019)
373            if signal.getitimer(self.itimer) == (0.0, 0.0):
374                break # sig_vtalrm handler stopped this itimer
375
376        # virtual itimer should be (0.0, 0.0) now
377        self.assertEquals(signal.getitimer(self.itimer), (0.0, 0.0))
378        # and the handler should have been called
379        self.assertEquals(self.hndl_called, True)
380
381    def test_itimer_prof(self):
382        self.itimer = signal.ITIMER_PROF
383        signal.signal(signal.SIGPROF, self.sig_prof)
384        signal.setitimer(self.itimer, 0.2, 0.2)
385
386        for i in xrange(100000000):
387            if signal.getitimer(self.itimer) == (0.0, 0.0):
388                break # sig_prof handler stopped this itimer
389
390        # profiling itimer should be (0.0, 0.0) now
391        self.assertEquals(signal.getitimer(self.itimer), (0.0, 0.0))
392        # and the handler should have been called
393        self.assertEqual(self.hndl_called, True)
394
395def test_main():
396    test_support.run_unittest(BasicSignalTests, InterProcessSignalTests,
397        WakeupSignalTests, SiginterruptTest, ItimerTest)
398
399
400if __name__ == "__main__":
401    test_main()