summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_signal.py
blob: 03e8101c45b39b054aa6f504dffc82dd89a87086 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import unittest
from test import test_support
import signal
import os, sys, time, errno

class HandlerBCalled(Exception):
    pass

class InterProcessSignalTests(unittest.TestCase):
    MAX_DURATION = 20   # Entire test should last at most 20 sec.

    # Set up a child to send signals to us (the parent) after waiting
    # long enough to receive the alarm.  It seems we miss the alarm
    # for some reason.  This will hopefully stop the hangs on
    # Tru64/Alpha.  Alas, it doesn't.  Tru64 appears to miss all the
    # signals at times, or seemingly random subsets of them, and
    # nothing done in force_test_exit so far has actually helped.
    def spawn_force_test_exit_process(self, parent_pid):
        # Sigh, both imports seem necessary to avoid errors.
        import os
        fork_pid = os.fork()
        if fork_pid:
            # In parent.
            return fork_pid

        # In child.
        import os, time
        try:
            # Wait 5 seconds longer than the expected alarm to give enough
            # time for the normal sequence of events to occur.  This is
            # just a stop-gap to try to prevent the test from hanging.
            time.sleep(self.MAX_DURATION + 5)
            print("  child should not have to kill parent",
                  file=sys.__stdout__)
            for signame in "SIGHUP", "SIGUSR1", "SIGUSR2", "SIGALRM":
                os.kill(parent_pid, getattr(signal, signame))
                print("    child sent", signame, "to",
                      parent_pid, file=sys.__stdout__)
                time.sleep(1)
        finally:
            os._exit(0)

    def handlerA(self, *args):
        self.a_called = True
        if test_support.verbose:
            print("handlerA invoked", args)

    def handlerB(self, *args):
        self.b_called = True
        if test_support.verbose:
            print("handlerB invoked", args)
        raise HandlerBCalled(*args)

    def test_main(self):
        self.assertEquals(signal.getsignal(signal.SIGHUP), self.handlerA)
        self.assertEquals(signal.getsignal(signal.SIGUSR1), self.handlerB)
        self.assertEquals(signal.getsignal(signal.SIGUSR2), signal.SIG_IGN)
        self.assertEquals(signal.getsignal(signal.SIGALRM),
                          signal.default_int_handler)

        # Launch an external script to send us signals.
        # We expect the external script to:
        #    send HUP, which invokes handlerA to set a_called
        #    send USR1, which invokes handlerB to set b_called and raise
        #               HandlerBCalled
        #    send USR2, which is ignored
        #
        # Then we expect the alarm to go off, and its handler raises
        # KeyboardInterrupt, finally getting us out of the loop.

        if test_support.verbose:
            verboseflag = '-x'
        else:
            verboseflag = '+x'

        pid = self.pid
        if test_support.verbose:
            print("test runner's pid is", pid)

        # Shell script that will send us asynchronous signals
        script = """
         (
                set %(verboseflag)s
                sleep 2
                kill -HUP %(pid)d
                sleep 2
                kill -USR1 %(pid)d
                sleep 2
                kill -USR2 %(pid)d
         ) &
        """ % vars()

        signal.alarm(self.MAX_DURATION)

        handler_b_exception_raised = False

        os.system(script)
        try:
            if test_support.verbose:
                print("starting pause() loop...")
            while 1:
                try:
                    if test_support.verbose:
                        print("call pause()...")
                    signal.pause()
                    if test_support.verbose:
                        print("pause() returned")
                except HandlerBCalled:
                    handler_b_exception_raised = True
                    if test_support.verbose:
                        print("HandlerBCalled exception caught")

        except KeyboardInterrupt:
            if test_support.verbose:
                print("KeyboardInterrupt (the alarm() went off)")

        self.assert_(self.a_called)
        self.assert_(self.b_called)
        self.assert_(handler_b_exception_raised)

    def setUp(self):
        # Install handlers.
        self.hup = signal.signal(signal.SIGHUP, self.handlerA)
        self.usr1 = signal.signal(signal.SIGUSR1, self.handlerB)
        self.usr2 = signal.signal(signal.SIGUSR2, signal.SIG_IGN)
        self.alrm = signal.signal(signal.SIGALRM,
                                  signal.default_int_handler)
        self.a_called = False
        self.b_called = False
        self.pid = os.getpid()
        self.fork_pid = self.spawn_force_test_exit_process(self.pid)

    def tearDown(self):
        # Forcibly kill the child we created to ping us if there was a
        # test error.
        try:
            # Make sure we don't kill ourself if there was a fork
            # error.
            if self.fork_pid > 0:
                os.kill(self.fork_pid, signal.SIGKILL)
        except:
            # If the child killed us, it has probably exited.  Killing
            # a non-existent process will raise an error which we
            # don't care about.
            pass

        # Restore handlers.
        signal.alarm(0) # cancel alarm in case we died early
        signal.signal(signal.SIGHUP, self.hup)
        signal.signal(signal.SIGUSR1, self.usr1)
        signal.signal(signal.SIGUSR2, self.usr2)
        signal.signal(signal.SIGALRM, self.alrm)


class BasicSignalTests(unittest.TestCase):
    def test_out_of_range_signal_number_raises_error(self):
        self.assertRaises(ValueError, signal.getsignal, 4242)

        def trivial_signal_handler(*args):
            pass

        self.assertRaises(ValueError, signal.signal, 4242,
                          trivial_signal_handler)

    def test_setting_signal_handler_to_none_raises_error(self):
        self.assertRaises(TypeError, signal.signal,
                          signal.SIGUSR1, None)

class WakeupSignalTests(unittest.TestCase):
    TIMEOUT_FULL = 10
    TIMEOUT_HALF = 5

    def test_wakeup_fd_early(self):
        import select

        signal.alarm(1)
        before_time = time.time()
        # We attempt to get a signal during the sleep,
        # before select is called
        time.sleep(self.TIMEOUT_FULL)
        mid_time = time.time()
        self.assert_(mid_time - before_time < self.TIMEOUT_HALF)
        select.select([self.read], [], [], self.TIMEOUT_FULL)
        after_time = time.time()
        self.assert_(after_time - mid_time < self.TIMEOUT_HALF)

    def test_wakeup_fd_during(self):
        import select

        signal.alarm(1)
        before_time = time.time()
        # We attempt to get a signal during the select call
        self.assertRaises(select.error, select.select,
            [self.read], [], [], self.TIMEOUT_FULL)
        after_time = time.time()
        self.assert_(after_time - before_time < self.TIMEOUT_HALF)

    def setUp(self):
        import fcntl

        self.alrm = signal.signal(signal.SIGALRM, lambda x,y:None)
        self.read, self.write = os.pipe()
        flags = fcntl.fcntl(self.write, fcntl.F_GETFL, 0)
        flags = flags | os.O_NONBLOCK
        fcntl.fcntl(self.write, fcntl.F_SETFL, flags)
        self.old_wakeup = signal.set_wakeup_fd(self.write)

    def tearDown(self):
        signal.set_wakeup_fd(self.old_wakeup)
        os.close(self.read)
        os.close(self.write)
        signal.signal(signal.SIGALRM, self.alrm)

class SiginterruptTest(unittest.TestCase):
    signum = signal.SIGUSR1
    def readpipe_interrupted(self, cb):
        r, w = os.pipe()
        ppid = os.getpid()
        pid = os.fork()

        oldhandler = signal.signal(self.signum, lambda x,y: None)
        cb()
        if pid==0:
            # child code: sleep, kill, sleep. and then exit,
            # which closes the pipe from which the parent process reads
            try:
                time.sleep(0.2)
                os.kill(ppid, self.signum)
                time.sleep(0.2)
            finally:
                os._exit(0)

        try:
            os.close(w)

            try:
                d=os.read(r, 1)
                return False
            except OSError as err:
                if err.errno != errno.EINTR:
                    raise
                return True
        finally:
            signal.signal(self.signum, oldhandler)
            os.waitpid(pid, 0)

    def test_without_siginterrupt(self):
        i=self.readpipe_interrupted(lambda: None)
        self.assertEquals(i, True)

    def test_siginterrupt_on(self):
        i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 1))
        self.assertEquals(i, True)

    def test_siginterrupt_off(self):
        i=self.readpipe_interrupted(lambda: signal.siginterrupt(self.signum, 0))
        self.assertEquals(i, False)

def test_main():
    if sys.platform[:3] in ('win', 'os2'):
        raise test_support.TestSkipped("Can't test signal on %s" % \
                                       sys.platform)

    test_support.run_unittest(BasicSignalTests, InterProcessSignalTests,
        WakeupSignalTests, SiginterruptTest)


if __name__ == "__main__":
    test_main()