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