~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/blackbox/test_breakin.py

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Blackbox tests for debugger breakin"""
18
18
 
 
19
try:
 
20
    import ctypes
 
21
    have_ctypes = True
 
22
except ImportError:
 
23
    have_ctypes = False
 
24
import errno
19
25
import os
20
26
import signal
21
27
import subprocess
22
28
import sys
23
29
import time
24
30
 
25
 
from bzrlib.tests import TestCase, TestSkipped
26
 
 
27
 
 
28
 
class TestBreakin(TestCase):
 
31
from bzrlib import (
 
32
    breakin,
 
33
    errors,
 
34
    tests,
 
35
    )
 
36
 
 
37
 
 
38
class TestBreakin(tests.TestCase):
29
39
    # FIXME: If something is broken, these tests may just hang indefinitely in
30
40
    # wait() waiting for the child to exit when it's not going to.
31
41
 
32
42
    def setUp(self):
33
 
        if sys.platform == 'win32':
34
 
            raise TestSkipped('breakin signal not tested on win32')
35
43
        super(TestBreakin, self).setUp()
 
44
        if breakin.determine_signal() is None:
 
45
            raise tests.TestSkipped('this platform is missing SIGQUIT'
 
46
                                    ' or SIGBREAK')
 
47
        if sys.platform == 'win32':
 
48
            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
 
49
            # We trigger SIGBREAK via a Console api so we need ctypes to access
 
50
            # the function
 
51
            if not have_ctypes:
 
52
                raise tests.UnavailableFeature('ctypes')
 
53
            self._send_signal = self._send_signal_win32
 
54
        else:
 
55
            self._send_signal = self._send_signal_via_kill
 
56
 
 
57
    def _send_signal_via_kill(self, pid, sig_type):
 
58
        if sig_type == 'break':
 
59
            sig_num = signal.SIGQUIT
 
60
        elif sig_type == 'kill':
 
61
            sig_num = signal.SIGKILL
 
62
        else:
 
63
            raise ValueError("unknown signal type: %s" % (sig_type,))
 
64
        os.kill(pid, sig_num)
 
65
 
 
66
    def _send_signal_win32(self, pid, sig_type):
 
67
        """Send a 'signal' on Windows.
 
68
 
 
69
        Windows doesn't really have signals in the same way. All it really
 
70
        supports is:
 
71
            1) Sending SIGINT to the *current* process group (so self, and all
 
72
                children of self)
 
73
            2) Sending SIGBREAK to a process that shares the current console,
 
74
                which can be in its own process group.
 
75
        So we have start_bzr_subprocess create a new process group for the
 
76
        spawned process (via a flag to Popen), and then we map
 
77
            SIGQUIT to GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT)
 
78
            SIGKILL to TerminateProcess
 
79
        """
 
80
        if sig_type == 'break':
 
81
            CTRL_BREAK_EVENT = 1
 
82
            # CTRL_C_EVENT = 0
 
83
            ret = ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
 
84
                    CTRL_BREAK_EVENT, pid)
 
85
            if ret == 0: #error
 
86
                err = ctypes.FormatError()
 
87
                raise RuntimeError('failed to send CTRL_BREAK: %s'
 
88
                                   % (err,))
 
89
        elif sig_type == 'kill':
 
90
            # Does the exit code matter? For now we are just setting it to
 
91
            # something other than 0
 
92
            exit_code = breakin.determine_signal()
 
93
            ctypes.windll.kernel32.TerminateProcess(pid, exit_code)
 
94
 
 
95
    def _popen(self, *args, **kwargs):
 
96
        if sys.platform == 'win32':
 
97
            CREATE_NEW_PROCESS_GROUP = 512
 
98
            # This allows us to send a signal to the child, *without* also
 
99
            # sending it to ourselves
 
100
            kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
 
101
        return super(TestBreakin, self)._popen(*args, **kwargs)
 
102
 
 
103
    def _dont_SIGQUIT_on_darwin(self):
 
104
        if sys.platform == 'darwin':
 
105
            # At least on Leopard and with python 2.6, this test will raise a
 
106
            # popup window asking if the python failure should be reported to
 
107
            # Apple... That's not the point of the test :) Marking the test as
 
108
            # not applicable Until we find a way to disable that intrusive
 
109
            # behavior... --vila20080611
 
110
            raise tests.TestNotApplicable(
 
111
                '%s raises a popup on OSX' % self.id())
 
112
 
 
113
    def _wait_for_process(self, pid, sig=None):
 
114
        # We don't know quite how long waiting for the process 'pid' will take,
 
115
        # but if it's more than 10s then it's probably not going to work.
 
116
        for i in range(100):
 
117
            time.sleep(0.1)
 
118
            if sig is not None:
 
119
                self._send_signal(pid, sig)
 
120
            # Use WNOHANG to ensure we don't get blocked, doing so, we may
 
121
            # leave the process continue after *we* die...
 
122
            # Win32 doesn't support WNOHANG, so we just pass 0
 
123
            opts = getattr(os, 'WNOHANG', 0)
 
124
            try:
 
125
                # TODO: waitpid doesn't work well on windows, we might consider
 
126
                #       using WaitForSingleObject(proc._handle, TIMEOUT)
 
127
                #       instead. Most notably, the WNOHANG isn't allowed, so
 
128
                #       this can hang indefinitely.
 
129
                pid_killed, returncode = os.waitpid(pid, opts)
 
130
                if (pid_killed, returncode) != (0, 0):
 
131
                    if sig is not None:
 
132
                        # high bit in low byte says if core was dumped; we
 
133
                        # don't care
 
134
                        status, sig = (returncode >> 8, returncode & 0x7f)
 
135
                        return True, sig
 
136
            except OSError, e:
 
137
                if e.errno in (errno.ECHILD, errno.ESRCH):
 
138
                    # The process doesn't exist anymore
 
139
                    return True, None
 
140
                else:
 
141
                    raise
 
142
 
 
143
        return False, None
36
144
 
37
145
    # port 0 means to allocate any port
38
146
    _test_process_args = ['serve', '--port', 'localhost:0']
39
147
 
40
148
    def test_breakin(self):
41
149
        # Break in to a debugger while bzr is running
42
 
        # we need to test against a command that will wait for 
 
150
        # we need to test against a command that will wait for
43
151
        # a while -- bzr serve should do
44
152
        proc = self.start_bzr_subprocess(self._test_process_args,
45
153
                env_changes=dict(BZR_SIGQUIT_PDB=None))
46
154
        # wait for it to get started, and print the 'listening' line
47
 
        proc.stdout.readline()
 
155
        proc.stderr.readline()
48
156
        # first sigquit pops into debugger
49
 
        os.kill(proc.pid, signal.SIGQUIT)
 
157
        self._send_signal(proc.pid, 'break')
 
158
        # Wait for the debugger to acknowledge the signal reception
 
159
        # Note that it is possible for this to deadlock if the child doesn't
 
160
        # acknowlege the signal and write to stderr. Perhaps we should try
 
161
        # os.read(proc.stderr.fileno())?
 
162
        err = proc.stderr.readline()
 
163
        self.assertContainsRe(err, r'entering debugger')
 
164
        # Now that the debugger is entered, we can ask him to quit
50
165
        proc.stdin.write("q\n")
51
 
        time.sleep(.5)
52
 
        err = proc.stderr.readline()
53
 
        self.assertContainsRe(err, r'entering debugger')
 
166
        # We wait a bit to let the child process handles our query and avoid
 
167
        # triggering deadlocks leading to hangs on multi-core hosts...
 
168
        dead, sig = self._wait_for_process(proc.pid)
 
169
        if not dead:
 
170
            # The process didn't finish, let's kill it before reporting failure
 
171
            dead, sig = self._wait_for_process(proc.pid, 'kill')
 
172
            if dead:
 
173
                raise tests.KnownFailure(
 
174
                    "subprocess wasn't terminated, it had to be killed")
 
175
            else:
 
176
                self.fail("subprocess %d wasn't terminated by repeated SIGKILL",
 
177
                          proc.pid)
54
178
 
55
179
    def test_breakin_harder(self):
 
180
        """SIGQUITting twice ends the process."""
 
181
        self._dont_SIGQUIT_on_darwin()
56
182
        proc = self.start_bzr_subprocess(self._test_process_args,
57
183
                env_changes=dict(BZR_SIGQUIT_PDB=None))
58
184
        # wait for it to get started, and print the 'listening' line
59
 
        proc.stdout.readline()
 
185
        proc.stderr.readline()
60
186
        # break into the debugger
61
 
        os.kill(proc.pid, signal.SIGQUIT)
62
 
        # now send a second sigquit, which should cause it to exit.  That
63
 
        # won't happen until the original signal has been noticed by the
64
 
        # child and it's run its signal handler.  We don't know quite how long
65
 
        # this will take, but if it's more than 10s then it's probably not
66
 
        # going to work.
67
 
        for i in range(100):
68
 
            time.sleep(0.1)
69
 
            os.kill(proc.pid, signal.SIGQUIT)
70
 
            # note: waitpid is different on win32, but this test only runs on
71
 
            # unix
72
 
            r = os.waitpid(proc.pid, os.WNOHANG)
73
 
            if r != (0, 0):
74
 
                # high bit says if core was dumped; we don't care
75
 
                self.assertEquals(r[1] & 0x7f, signal.SIGQUIT)
76
 
                break
77
 
        else:
78
 
            self.fail("subprocess wasn't terminated by repeated SIGQUIT")
 
187
        self._send_signal(proc.pid, 'break')
 
188
        # Wait for the debugger to acknowledge the signal reception (since we
 
189
        # want to send a second signal, we ensure it doesn't get lost by
 
190
        # validating the first get received and produce its effect).
 
191
        err = proc.stderr.readline()
 
192
        self.assertContainsRe(err, r'entering debugger')
 
193
        dead, sig = self._wait_for_process(proc.pid, 'break')
 
194
        self.assertTrue(dead)
 
195
        # Either the child was dead before we could read its status, or the
 
196
        # child was dead from the signal we sent it.
 
197
        self.assertTrue(sig in (None, breakin.determine_signal()))
79
198
 
80
199
    def test_breakin_disabled(self):
 
200
        self._dont_SIGQUIT_on_darwin()
81
201
        proc = self.start_bzr_subprocess(self._test_process_args,
82
202
                env_changes=dict(BZR_SIGQUIT_PDB='0'))
83
203
        # wait for it to get started, and print the 'listening' line
84
 
        proc.stdout.readline()
 
204
        proc.stderr.readline()
85
205
        # first hit should just kill it
86
 
        os.kill(proc.pid, signal.SIGQUIT)
 
206
        self._send_signal(proc.pid, 'break')
87
207
        proc.wait()