~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: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2009 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""Blackbox tests for debugger breakin"""
18
 
 
19
 
try:
20
 
    import ctypes
21
 
    have_ctypes = True
22
 
except ImportError:
23
 
    have_ctypes = False
24
 
import errno
25
 
import os
26
 
import signal
27
 
import subprocess
28
 
import sys
29
 
import time
30
 
 
31
 
from bzrlib import (
32
 
    breakin,
33
 
    errors,
34
 
    tests,
35
 
    )
36
 
 
37
 
 
38
 
class TestBreakin(tests.TestCase):
39
 
    # FIXME: If something is broken, these tests may just hang indefinitely in
40
 
    # wait() waiting for the child to exit when it's not going to.
41
 
 
42
 
    def setUp(self):
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)
99
 
 
100
 
    def _dont_SIGQUIT_on_darwin(self):
101
 
        if sys.platform == 'darwin':
102
 
            # At least on Leopard and with python 2.6, this test will raise a
103
 
            # popup window asking if the python failure should be reported to
104
 
            # Apple... That's not the point of the test :) Marking the test as
105
 
            # not applicable Until we find a way to disable that intrusive
106
 
            # behavior... --vila20080611
107
 
            raise tests.TestNotApplicable(
108
 
                '%s raises a popup on OSX' % self.id())
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
 
 
143
 
    # port 0 means to allocate any port
144
 
    _test_process_args = ['serve', '--port', 'localhost:0']
145
 
 
146
 
    def test_breakin(self):
147
 
        # Break in to a debugger while bzr is running
148
 
        # we need to test against a command that will wait for
149
 
        # a while -- bzr serve should do
150
 
        proc = self.start_bzr_subprocess(self._test_process_args,
151
 
                env_changes=dict(BZR_SIGQUIT_PDB=None))
152
 
        # wait for it to get started, and print the 'listening' line
153
 
        proc.stderr.readline()
154
 
        # first sigquit pops into debugger
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
164
 
        proc.stdin.write("q\n")
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)
175
 
 
176
 
    def test_breakin_harder(self):
177
 
        """SIGQUITting twice ends the process."""
178
 
        self._dont_SIGQUIT_on_darwin()
179
 
        proc = self.start_bzr_subprocess(self._test_process_args,
180
 
                env_changes=dict(BZR_SIGQUIT_PDB=None))
181
 
        # wait for it to get started, and print the 'listening' line
182
 
        proc.stderr.readline()
183
 
        # break into the debugger
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()))
195
 
 
196
 
    def test_breakin_disabled(self):
197
 
        self._dont_SIGQUIT_on_darwin()
198
 
        proc = self.start_bzr_subprocess(self._test_process_args,
199
 
                env_changes=dict(BZR_SIGQUIT_PDB='0'))
200
 
        # wait for it to get started, and print the 'listening' line
201
 
        proc.stderr.readline()
202
 
        # first hit should just kill it
203
 
        self._send_signal(proc.pid, 'break')
204
 
        proc.wait()