##// END OF EJS Templates
Switch to a much simpler test. Interrupting processes encountered some issues...
Itamar Turner-Trauring -
Show More
@@ -1,298 +1,248 b''
1 """Tests for debugging machinery.
1 """Tests for debugging machinery.
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import signal
7 import signal
8 import sys
8 import sys
9 import time
9 import time
10 import warnings
10 import warnings
11 from tempfile import NamedTemporaryFile
11 from tempfile import NamedTemporaryFile
12 from subprocess import check_output, CalledProcessError, PIPE
12 from subprocess import check_output, CalledProcessError, PIPE
13 import subprocess
13 import subprocess
14 from unittest.mock import patch
15 import builtins
14
16
15 import nose.tools as nt
17 import nose.tools as nt
16
18
17 from IPython.core import debugger
19 from IPython.core import debugger
18
20
19 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
20 # Helper classes, from CPython's Pdb test suite
22 # Helper classes, from CPython's Pdb test suite
21 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
22
24
23 class _FakeInput(object):
25 class _FakeInput(object):
24 """
26 """
25 A fake input stream for pdb's interactive debugger. Whenever a
27 A fake input stream for pdb's interactive debugger. Whenever a
26 line is read, print it (to simulate the user typing it), and then
28 line is read, print it (to simulate the user typing it), and then
27 return it. The set of lines to return is specified in the
29 return it. The set of lines to return is specified in the
28 constructor; they should not have trailing newlines.
30 constructor; they should not have trailing newlines.
29 """
31 """
30 def __init__(self, lines):
32 def __init__(self, lines):
31 self.lines = iter(lines)
33 self.lines = iter(lines)
32
34
33 def readline(self):
35 def readline(self):
34 line = next(self.lines)
36 line = next(self.lines)
35 print(line)
37 print(line)
36 return line+'\n'
38 return line+'\n'
37
39
38 class PdbTestInput(object):
40 class PdbTestInput(object):
39 """Context manager that makes testing Pdb in doctests easier."""
41 """Context manager that makes testing Pdb in doctests easier."""
40
42
41 def __init__(self, input):
43 def __init__(self, input):
42 self.input = input
44 self.input = input
43
45
44 def __enter__(self):
46 def __enter__(self):
45 self.real_stdin = sys.stdin
47 self.real_stdin = sys.stdin
46 sys.stdin = _FakeInput(self.input)
48 sys.stdin = _FakeInput(self.input)
47
49
48 def __exit__(self, *exc):
50 def __exit__(self, *exc):
49 sys.stdin = self.real_stdin
51 sys.stdin = self.real_stdin
50
52
51 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
52 # Tests
54 # Tests
53 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
54
56
55 def test_longer_repr():
57 def test_longer_repr():
56 from reprlib import repr as trepr
58 from reprlib import repr as trepr
57
59
58 a = '1234567890'* 7
60 a = '1234567890'* 7
59 ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'"
61 ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'"
60 a_trunc = "'123456789012...8901234567890'"
62 a_trunc = "'123456789012...8901234567890'"
61 nt.assert_equal(trepr(a), a_trunc)
63 nt.assert_equal(trepr(a), a_trunc)
62 # The creation of our tracer modifies the repr module's repr function
64 # The creation of our tracer modifies the repr module's repr function
63 # in-place, since that global is used directly by the stdlib's pdb module.
65 # in-place, since that global is used directly by the stdlib's pdb module.
64 with warnings.catch_warnings():
66 with warnings.catch_warnings():
65 warnings.simplefilter('ignore', DeprecationWarning)
67 warnings.simplefilter('ignore', DeprecationWarning)
66 debugger.Tracer()
68 debugger.Tracer()
67 nt.assert_equal(trepr(a), ar)
69 nt.assert_equal(trepr(a), ar)
68
70
69 def test_ipdb_magics():
71 def test_ipdb_magics():
70 '''Test calling some IPython magics from ipdb.
72 '''Test calling some IPython magics from ipdb.
71
73
72 First, set up some test functions and classes which we can inspect.
74 First, set up some test functions and classes which we can inspect.
73
75
74 >>> class ExampleClass(object):
76 >>> class ExampleClass(object):
75 ... """Docstring for ExampleClass."""
77 ... """Docstring for ExampleClass."""
76 ... def __init__(self):
78 ... def __init__(self):
77 ... """Docstring for ExampleClass.__init__"""
79 ... """Docstring for ExampleClass.__init__"""
78 ... pass
80 ... pass
79 ... def __str__(self):
81 ... def __str__(self):
80 ... return "ExampleClass()"
82 ... return "ExampleClass()"
81
83
82 >>> def example_function(x, y, z="hello"):
84 >>> def example_function(x, y, z="hello"):
83 ... """Docstring for example_function."""
85 ... """Docstring for example_function."""
84 ... pass
86 ... pass
85
87
86 >>> old_trace = sys.gettrace()
88 >>> old_trace = sys.gettrace()
87
89
88 Create a function which triggers ipdb.
90 Create a function which triggers ipdb.
89
91
90 >>> def trigger_ipdb():
92 >>> def trigger_ipdb():
91 ... a = ExampleClass()
93 ... a = ExampleClass()
92 ... debugger.Pdb().set_trace()
94 ... debugger.Pdb().set_trace()
93
95
94 >>> with PdbTestInput([
96 >>> with PdbTestInput([
95 ... 'pdef example_function',
97 ... 'pdef example_function',
96 ... 'pdoc ExampleClass',
98 ... 'pdoc ExampleClass',
97 ... 'up',
99 ... 'up',
98 ... 'down',
100 ... 'down',
99 ... 'list',
101 ... 'list',
100 ... 'pinfo a',
102 ... 'pinfo a',
101 ... 'll',
103 ... 'll',
102 ... 'continue',
104 ... 'continue',
103 ... ]):
105 ... ]):
104 ... trigger_ipdb()
106 ... trigger_ipdb()
105 --Return--
107 --Return--
106 None
108 None
107 > <doctest ...>(3)trigger_ipdb()
109 > <doctest ...>(3)trigger_ipdb()
108 1 def trigger_ipdb():
110 1 def trigger_ipdb():
109 2 a = ExampleClass()
111 2 a = ExampleClass()
110 ----> 3 debugger.Pdb().set_trace()
112 ----> 3 debugger.Pdb().set_trace()
111 <BLANKLINE>
113 <BLANKLINE>
112 ipdb> pdef example_function
114 ipdb> pdef example_function
113 example_function(x, y, z='hello')
115 example_function(x, y, z='hello')
114 ipdb> pdoc ExampleClass
116 ipdb> pdoc ExampleClass
115 Class docstring:
117 Class docstring:
116 Docstring for ExampleClass.
118 Docstring for ExampleClass.
117 Init docstring:
119 Init docstring:
118 Docstring for ExampleClass.__init__
120 Docstring for ExampleClass.__init__
119 ipdb> up
121 ipdb> up
120 > <doctest ...>(11)<module>()
122 > <doctest ...>(11)<module>()
121 7 'pinfo a',
123 7 'pinfo a',
122 8 'll',
124 8 'll',
123 9 'continue',
125 9 'continue',
124 10 ]):
126 10 ]):
125 ---> 11 trigger_ipdb()
127 ---> 11 trigger_ipdb()
126 <BLANKLINE>
128 <BLANKLINE>
127 ipdb> down
129 ipdb> down
128 None
130 None
129 > <doctest ...>(3)trigger_ipdb()
131 > <doctest ...>(3)trigger_ipdb()
130 1 def trigger_ipdb():
132 1 def trigger_ipdb():
131 2 a = ExampleClass()
133 2 a = ExampleClass()
132 ----> 3 debugger.Pdb().set_trace()
134 ----> 3 debugger.Pdb().set_trace()
133 <BLANKLINE>
135 <BLANKLINE>
134 ipdb> list
136 ipdb> list
135 1 def trigger_ipdb():
137 1 def trigger_ipdb():
136 2 a = ExampleClass()
138 2 a = ExampleClass()
137 ----> 3 debugger.Pdb().set_trace()
139 ----> 3 debugger.Pdb().set_trace()
138 <BLANKLINE>
140 <BLANKLINE>
139 ipdb> pinfo a
141 ipdb> pinfo a
140 Type: ExampleClass
142 Type: ExampleClass
141 String form: ExampleClass()
143 String form: ExampleClass()
142 Namespace: Local...
144 Namespace: Local...
143 Docstring: Docstring for ExampleClass.
145 Docstring: Docstring for ExampleClass.
144 Init docstring: Docstring for ExampleClass.__init__
146 Init docstring: Docstring for ExampleClass.__init__
145 ipdb> ll
147 ipdb> ll
146 1 def trigger_ipdb():
148 1 def trigger_ipdb():
147 2 a = ExampleClass()
149 2 a = ExampleClass()
148 ----> 3 debugger.Pdb().set_trace()
150 ----> 3 debugger.Pdb().set_trace()
149 <BLANKLINE>
151 <BLANKLINE>
150 ipdb> continue
152 ipdb> continue
151
153
152 Restore previous trace function, e.g. for coverage.py
154 Restore previous trace function, e.g. for coverage.py
153
155
154 >>> sys.settrace(old_trace)
156 >>> sys.settrace(old_trace)
155 '''
157 '''
156
158
157 def test_ipdb_magics2():
159 def test_ipdb_magics2():
158 '''Test ipdb with a very short function.
160 '''Test ipdb with a very short function.
159
161
160 >>> old_trace = sys.gettrace()
162 >>> old_trace = sys.gettrace()
161
163
162 >>> def bar():
164 >>> def bar():
163 ... pass
165 ... pass
164
166
165 Run ipdb.
167 Run ipdb.
166
168
167 >>> with PdbTestInput([
169 >>> with PdbTestInput([
168 ... 'continue',
170 ... 'continue',
169 ... ]):
171 ... ]):
170 ... debugger.Pdb().runcall(bar)
172 ... debugger.Pdb().runcall(bar)
171 > <doctest ...>(2)bar()
173 > <doctest ...>(2)bar()
172 1 def bar():
174 1 def bar():
173 ----> 2 pass
175 ----> 2 pass
174 <BLANKLINE>
176 <BLANKLINE>
175 ipdb> continue
177 ipdb> continue
176
178
177 Restore previous trace function, e.g. for coverage.py
179 Restore previous trace function, e.g. for coverage.py
178
180
179 >>> sys.settrace(old_trace)
181 >>> sys.settrace(old_trace)
180 '''
182 '''
181
183
182 def can_quit():
184 def can_quit():
183 '''Test that quit work in ipydb
185 '''Test that quit work in ipydb
184
186
185 >>> old_trace = sys.gettrace()
187 >>> old_trace = sys.gettrace()
186
188
187 >>> def bar():
189 >>> def bar():
188 ... pass
190 ... pass
189
191
190 >>> with PdbTestInput([
192 >>> with PdbTestInput([
191 ... 'quit',
193 ... 'quit',
192 ... ]):
194 ... ]):
193 ... debugger.Pdb().runcall(bar)
195 ... debugger.Pdb().runcall(bar)
194 > <doctest ...>(2)bar()
196 > <doctest ...>(2)bar()
195 1 def bar():
197 1 def bar():
196 ----> 2 pass
198 ----> 2 pass
197 <BLANKLINE>
199 <BLANKLINE>
198 ipdb> quit
200 ipdb> quit
199
201
200 Restore previous trace function, e.g. for coverage.py
202 Restore previous trace function, e.g. for coverage.py
201
203
202 >>> sys.settrace(old_trace)
204 >>> sys.settrace(old_trace)
203 '''
205 '''
204
206
205
207
206 def can_exit():
208 def can_exit():
207 '''Test that quit work in ipydb
209 '''Test that quit work in ipydb
208
210
209 >>> old_trace = sys.gettrace()
211 >>> old_trace = sys.gettrace()
210
212
211 >>> def bar():
213 >>> def bar():
212 ... pass
214 ... pass
213
215
214 >>> with PdbTestInput([
216 >>> with PdbTestInput([
215 ... 'exit',
217 ... 'exit',
216 ... ]):
218 ... ]):
217 ... debugger.Pdb().runcall(bar)
219 ... debugger.Pdb().runcall(bar)
218 > <doctest ...>(2)bar()
220 > <doctest ...>(2)bar()
219 1 def bar():
221 1 def bar():
220 ----> 2 pass
222 ----> 2 pass
221 <BLANKLINE>
223 <BLANKLINE>
222 ipdb> exit
224 ipdb> exit
223
225
224 Restore previous trace function, e.g. for coverage.py
226 Restore previous trace function, e.g. for coverage.py
225
227
226 >>> sys.settrace(old_trace)
228 >>> sys.settrace(old_trace)
227 '''
229 '''
228
230
229
231
230 interruptible_debugger = """\
231 import sys
232 import threading
233 import time
234 from os import _exit
235 from bdb import BdbQuit
236
237 from IPython.core.debugger import set_trace
238
239 # Timeout if the interrupt doesn't happen:
240 def timeout():
241 time.sleep(5)
242 _exit(7)
243 threading.Thread(target=timeout, daemon=True).start()
244
245 def break_handler(*args):
246 print("BREAK!")
247 raise KeyboardInterrupt()
248
249 def main():
250 import signal
251 signal.signal(signal.SIGBREAK, break_handler)
252 set_trace()
253
254 if __name__ == '__main__':
255 try:
256 print("Starting debugger...")
257 main()
258 print("Debugger exited without error.")
259 except (KeyboardInterrupt, BdbQuit):
260 print("Caught KeyboardInterrupt or BdbQuit, PASSED")
261 except Exception as e:
262 print("Got wrong exception...")
263 raise e
264 """
265
266
267 def test_interruptible_core_debugger():
232 def test_interruptible_core_debugger():
268 """The debugger can be interrupted.
233 """The debugger can be interrupted.
269
234
270 See https://stackoverflow.com/a/35792192 for details on Windows.
235 The presumption is there is some mechanism that causes a KeyboardInterrupt
236 (this is implemented in ipykernel). We want to ensure the
237 KeyboardInterrupt cause debugging to cease.
271 """
238 """
272 with NamedTemporaryFile("w", delete=False) as f:
239 def raising_input(msg=""):
273 f.write(interruptible_debugger)
240 raise KeyboardInterrupt()
274 f.flush()
241
275 start = time.time()
242 with patch.object(builtins, "input", raising_input):
276
243 debugger.set_trace()
277 p = subprocess.Popen([sys.executable, "-u", f.name],
244 # The way this test will fail is by set_trace() never exiting,
278 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, # TODO disable on posix
245 # resulting in a timeout by the test runner. The alternative
279 encoding=sys.getdefaultencoding(),
246 # implementation would involve a subprocess, but that adds issues with
280 stderr=PIPE, stdout=PIPE)
247 # interrupting subprocesses that are rather complex, so it's simpler
281 time.sleep(1) # wait for it to hit pdb
248 # just to do it this way.
282 if sys.platform == "win32":
283 # Yes, this has to happen once. I have no idea why.
284 p.send_signal(signal.CTRL_BREAK_EVENT)
285 p.send_signal(signal.CTRL_BREAK_EVENT)
286 else:
287 p.send_signal(signal.SIGINT)
288 exit_code = p.wait()
289 stdout = p.stdout.read()
290 stderr = p.stderr.read()
291 print("STDOUT", stdout, file=sys.__stderr__)
292 print("STDERR", stderr, file=sys.__stderr__)
293 assert exit_code == 0
294 print("SUCCESS!", file=sys.__stderr__)
295 # Make sure it exited cleanly, and quickly:
296 end = time.time()
297 assert end - start < 2 # timeout is 5 seconds
298 assert "PASSED" in stdout No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now