~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-01-25 15:55:48 UTC
  • mto: (4985.1.4 add-attr-cleanup)
  • mto: This revision was merged to the branch mainline in revision 4988.
  • Revision ID: v.ladeuil+lp@free.fr-20100125155548-0l352pujvt5bzl5e
Deploy addAttrCleanup on the whole test suite.

Several use case worth mentioning:

- setting a module or any other object attribute is the majority
by far. In some cases the setting itself is deferred but most of
the time we want to set at the same time we add the cleanup.

- there multiple occurrences of protecting hooks or ui factory
which are now useless (the test framework takes care of that now),

- there was some lambda uses that can now be avoided.

That first cleanup already simplifies things a lot.

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
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
19
24
import errno
20
25
import os
21
26
import signal
24
29
import time
25
30
 
26
31
from bzrlib import (
 
32
    breakin,
27
33
    errors,
28
34
    tests,
29
35
    )
34
40
    # wait() waiting for the child to exit when it's not going to.
35
41
 
36
42
    def setUp(self):
37
 
        if sys.platform == 'win32':
38
 
            raise tests.TestSkipped('breakin signal not tested on win32')
39
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)
40
99
 
41
100
    def _dont_SIGQUIT_on_darwin(self):
42
101
        if sys.platform == 'darwin':
48
107
            raise tests.TestNotApplicable(
49
108
                '%s raises a popup on OSX' % self.id())
50
109
 
51
 
    def _wait_for_process(self, pid, sig=None):
 
110
    def _wait_for_process(self, pid, sig=None, count=100):
52
111
        # We don't know quite how long waiting for the process 'pid' will take,
53
112
        # but if it's more than 10s then it's probably not going to work.
54
 
        for i in range(100):
55
 
            time.sleep(0.1)
 
113
        for i in range(count):
56
114
            if sig is not None:
57
 
                os.kill(pid, sig)
 
115
                self._send_signal(pid, sig)
58
116
            # Use WNOHANG to ensure we don't get blocked, doing so, we may
59
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)
60
120
            try:
61
 
                # note: waitpid is different on win32, but this test only runs
62
 
                # on unix
63
 
                pid_killed, returncode = os.waitpid(pid, os.WNOHANG)
64
 
                if (pid_killed, returncode) != (0, 0):
 
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:
65
127
                    if sig is not None:
66
128
                        # high bit in low byte says if core was dumped; we
67
129
                        # don't care
73
135
                    return True, None
74
136
                else:
75
137
                    raise
 
138
            if i + 1 != count:
 
139
                time.sleep(0.1)
76
140
 
77
141
        return False, None
78
142
 
88
152
        # wait for it to get started, and print the 'listening' line
89
153
        proc.stderr.readline()
90
154
        # first sigquit pops into debugger
91
 
        os.kill(proc.pid, signal.SIGQUIT)
 
155
        self._send_signal(proc.pid, 'break')
92
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())?
93
160
        err = proc.stderr.readline()
94
161
        self.assertContainsRe(err, r'entering debugger')
 
162
        # Try to shutdown cleanly;
95
163
        # Now that the debugger is entered, we can ask him to quit
96
164
        proc.stdin.write("q\n")
97
 
        # We wait a bit to let the child process handles our query and avoid
98
 
        # triggering deadlocks leading to hangs on multi-core hosts...
99
 
        dead, sig = self._wait_for_process(proc.pid)
 
165
        # But we don't really care if it doesn't.
 
166
        dead, sig = self._wait_for_process(proc.pid, count=3)
100
167
        if not dead:
101
 
            # The process didn't finish, let's kill it before reporting failure
102
 
            dead, sig = self._wait_for_process(proc.pid, signal.SIGKILL)
103
 
            if dead:
104
 
                raise tests.KnownFailure(
105
 
                    "subprocess wasn't terminated, it had to be killed")
106
 
            else:
107
 
                self.fail("subprocess %d wasn't terminated by repeated SIGKILL",
 
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" %
108
174
                          proc.pid)
109
175
 
110
176
    def test_breakin_harder(self):
115
181
        # wait for it to get started, and print the 'listening' line
116
182
        proc.stderr.readline()
117
183
        # break into the debugger
118
 
        os.kill(proc.pid, signal.SIGQUIT)
 
184
        self._send_signal(proc.pid, 'break')
119
185
        # Wait for the debugger to acknowledge the signal reception (since we
120
186
        # want to send a second signal, we ensure it doesn't get lost by
121
187
        # validating the first get received and produce its effect).
122
188
        err = proc.stderr.readline()
123
189
        self.assertContainsRe(err, r'entering debugger')
124
 
        dead, sig = self._wait_for_process(proc.pid, signal.SIGQUIT)
125
 
        self.assertTrue((dead and sig == signal.SIGQUIT),
126
 
                        msg="subprocess wasn't terminated by repeated SIGQUIT")
 
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()))
127
195
 
128
196
    def test_breakin_disabled(self):
129
197
        self._dont_SIGQUIT_on_darwin()
132
200
        # wait for it to get started, and print the 'listening' line
133
201
        proc.stderr.readline()
134
202
        # first hit should just kill it
135
 
        os.kill(proc.pid, signal.SIGQUIT)
 
203
        self._send_signal(proc.pid, 'break')
136
204
        proc.wait()