~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Vincent Ladeuil
  • Date: 2010-02-10 15:46:03 UTC
  • mfrom: (4985.3.21 update)
  • mto: This revision was merged to the branch mainline in revision 5021.
  • Revision ID: v.ladeuil+lp@free.fr-20100210154603-k4no1gvfuqpzrw7p
Update performs two merges in a more logical order but stop on conflicts

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 import tests
 
31
from bzrlib import (
 
32
    breakin,
 
33
    errors,
 
34
    tests,
 
35
    )
26
36
 
27
37
 
28
38
class TestBreakin(tests.TestCase):
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 tests.TestSkipped('breakin signal not tested on win32')
35
43
        super(TestBreakin, self).setUp()
 
44
        self.requireFeature(tests.BreakinFeature)
 
45
        if sys.platform == 'win32':
 
46
            self._send_signal = self._send_signal_win32
 
47
        else:
 
48
            self._send_signal = self._send_signal_via_kill
 
49
 
 
50
    def _send_signal_via_kill(self, pid, sig_type):
 
51
        if sig_type == 'break':
 
52
            sig_num = signal.SIGQUIT
 
53
        elif sig_type == 'kill':
 
54
            sig_num = signal.SIGKILL
 
55
        else:
 
56
            raise ValueError("unknown signal type: %s" % (sig_type,))
 
57
        try:
 
58
            os.kill(pid, sig_num)
 
59
        except OSError, e:
 
60
            if e.errno != errno.ESRCH:
 
61
                raise
 
62
 
 
63
    def _send_signal_win32(self, pid, sig_type):
 
64
        """Send a 'signal' on Windows.
 
65
 
 
66
        Windows doesn't really have signals in the same way. All it really
 
67
        supports is:
 
68
            1) Sending SIGINT to the *current* process group (so self, and all
 
69
                children of self)
 
70
            2) Sending SIGBREAK to a process that shares the current console,
 
71
                which can be in its own process group.
 
72
        So we have start_bzr_subprocess create a new process group for the
 
73
        spawned process (via a flag to Popen), and then we map
 
74
            SIGQUIT to GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT)
 
75
            SIGKILL to TerminateProcess
 
76
        """
 
77
        if sig_type == 'break':
 
78
            CTRL_BREAK_EVENT = 1
 
79
            # CTRL_C_EVENT = 0
 
80
            ret = ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
 
81
                    CTRL_BREAK_EVENT, pid)
 
82
            if ret == 0: #error
 
83
                err = ctypes.FormatError()
 
84
                raise RuntimeError('failed to send CTRL_BREAK: %s'
 
85
                                   % (err,))
 
86
        elif sig_type == 'kill':
 
87
            # Does the exit code matter? For now we are just setting it to
 
88
            # something other than 0
 
89
            exit_code = breakin.determine_signal()
 
90
            ctypes.windll.kernel32.TerminateProcess(pid, exit_code)
 
91
 
 
92
    def _popen(self, *args, **kwargs):
 
93
        if sys.platform == 'win32':
 
94
            CREATE_NEW_PROCESS_GROUP = 512
 
95
            # This allows us to send a signal to the child, *without* also
 
96
            # sending it to ourselves
 
97
            kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
 
98
        return super(TestBreakin, self)._popen(*args, **kwargs)
36
99
 
37
100
    def _dont_SIGQUIT_on_darwin(self):
38
101
        if sys.platform == 'darwin':
44
107
            raise tests.TestNotApplicable(
45
108
                '%s raises a popup on OSX' % self.id())
46
109
 
 
110
    def _wait_for_process(self, pid, sig=None, count=100):
 
111
        # We don't know quite how long waiting for the process 'pid' will take,
 
112
        # but if it's more than 10s then it's probably not going to work.
 
113
        for i in range(count):
 
114
            if sig is not None:
 
115
                self._send_signal(pid, sig)
 
116
            # Use WNOHANG to ensure we don't get blocked, doing so, we may
 
117
            # leave the process continue after *we* die...
 
118
            # Win32 doesn't support WNOHANG, so we just pass 0
 
119
            opts = getattr(os, 'WNOHANG', 0)
 
120
            try:
 
121
                # TODO: waitpid doesn't work well on windows, we might consider
 
122
                #       using WaitForSingleObject(proc._handle, TIMEOUT)
 
123
                #       instead. Most notably, the WNOHANG isn't allowed, so
 
124
                #       this can hang indefinitely.
 
125
                pid_killed, returncode = os.waitpid(pid, opts)
 
126
                if pid_killed != 0 and returncode != 0:
 
127
                    if sig is not None:
 
128
                        # high bit in low byte says if core was dumped; we
 
129
                        # don't care
 
130
                        status, sig = (returncode >> 8, returncode & 0x7f)
 
131
                        return True, sig
 
132
            except OSError, e:
 
133
                if e.errno in (errno.ECHILD, errno.ESRCH):
 
134
                    # The process doesn't exist anymore
 
135
                    return True, None
 
136
                else:
 
137
                    raise
 
138
            if i + 1 != count:
 
139
                time.sleep(0.1)
 
140
 
 
141
        return False, None
 
142
 
47
143
    # port 0 means to allocate any port
48
144
    _test_process_args = ['serve', '--port', 'localhost:0']
49
145
 
50
146
    def test_breakin(self):
51
147
        # Break in to a debugger while bzr is running
52
 
        # we need to test against a command that will wait for 
 
148
        # we need to test against a command that will wait for
53
149
        # a while -- bzr serve should do
54
150
        proc = self.start_bzr_subprocess(self._test_process_args,
55
151
                env_changes=dict(BZR_SIGQUIT_PDB=None))
56
152
        # wait for it to get started, and print the 'listening' line
57
153
        proc.stderr.readline()
58
154
        # first sigquit pops into debugger
59
 
        os.kill(proc.pid, signal.SIGQUIT)
 
155
        self._send_signal(proc.pid, 'break')
 
156
        # Wait for the debugger to acknowledge the signal reception
 
157
        # Note that it is possible for this to deadlock if the child doesn't
 
158
        # acknowlege the signal and write to stderr. Perhaps we should try
 
159
        # os.read(proc.stderr.fileno())?
 
160
        err = proc.stderr.readline()
 
161
        self.assertContainsRe(err, r'entering debugger')
 
162
        # Try to shutdown cleanly;
 
163
        # Now that the debugger is entered, we can ask him to quit
60
164
        proc.stdin.write("q\n")
61
 
        time.sleep(.5)
62
 
        err = proc.stderr.readline()
63
 
        self.assertContainsRe(err, r'entering debugger')
 
165
        # But we don't really care if it doesn't.
 
166
        dead, sig = self._wait_for_process(proc.pid, count=3)
 
167
        if not dead:
 
168
            # The process didn't finish, let's kill it.
 
169
            dead, sig = self._wait_for_process(proc.pid, 'kill', count=10)
 
170
            if not dead:
 
171
                # process isn't gone, user will have to hunt it down and kill
 
172
                # it.
 
173
                self.fail("subprocess %d wasn't terminated by repeated SIGKILL" %
 
174
                          proc.pid)
64
175
 
65
176
    def test_breakin_harder(self):
 
177
        """SIGQUITting twice ends the process."""
66
178
        self._dont_SIGQUIT_on_darwin()
67
179
        proc = self.start_bzr_subprocess(self._test_process_args,
68
180
                env_changes=dict(BZR_SIGQUIT_PDB=None))
69
181
        # wait for it to get started, and print the 'listening' line
70
182
        proc.stderr.readline()
71
183
        # break into the debugger
72
 
        os.kill(proc.pid, signal.SIGQUIT)
73
 
        # now send a second sigquit, which should cause it to exit.  That
74
 
        # won't happen until the original signal has been noticed by the
75
 
        # child and it's run its signal handler.  We don't know quite how long
76
 
        # this will take, but if it's more than 10s then it's probably not
77
 
        # going to work.
78
 
        for i in range(100):
79
 
            time.sleep(0.1)
80
 
            os.kill(proc.pid, signal.SIGQUIT)
81
 
            # note: waitpid is different on win32, but this test only runs on
82
 
            # unix
83
 
            r = os.waitpid(proc.pid, os.WNOHANG)
84
 
            if r != (0, 0):
85
 
                # high bit says if core was dumped; we don't care
86
 
                self.assertEquals(r[1] & 0x7f, signal.SIGQUIT)
87
 
                break
88
 
        else:
89
 
            self.fail("subprocess wasn't terminated by repeated SIGQUIT")
 
184
        self._send_signal(proc.pid, 'break')
 
185
        # Wait for the debugger to acknowledge the signal reception (since we
 
186
        # want to send a second signal, we ensure it doesn't get lost by
 
187
        # validating the first get received and produce its effect).
 
188
        err = proc.stderr.readline()
 
189
        self.assertContainsRe(err, r'entering debugger')
 
190
        dead, sig = self._wait_for_process(proc.pid, 'break')
 
191
        self.assertTrue(dead)
 
192
        # Either the child was dead before we could read its status, or the
 
193
        # child was dead from the signal we sent it.
 
194
        self.assertTrue(sig in (None, breakin.determine_signal()))
90
195
 
91
196
    def test_breakin_disabled(self):
92
197
        self._dont_SIGQUIT_on_darwin()
95
200
        # wait for it to get started, and print the 'listening' line
96
201
        proc.stderr.readline()
97
202
        # first hit should just kill it
98
 
        os.kill(proc.pid, signal.SIGQUIT)
 
203
        self._send_signal(proc.pid, 'break')
99
204
        proc.wait()