##// END OF EJS Templates
Fix test_ipdb_magics doctest for Python 3.13
Thomas Kluyver -
Show More
@@ -1,579 +1,581
1 1 """Tests for debugging machinery.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import builtins
8 8 import os
9 9 import sys
10 10 import platform
11 11
12 12 from tempfile import NamedTemporaryFile
13 13 from textwrap import dedent
14 14 from unittest.mock import patch
15 15
16 16 from IPython.core import debugger
17 17 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
18 18 from IPython.testing.decorators import skip_win32
19 19 import pytest
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Helper classes, from CPython's Pdb test suite
23 23 #-----------------------------------------------------------------------------
24 24
25 25 class _FakeInput(object):
26 26 """
27 27 A fake input stream for pdb's interactive debugger. Whenever a
28 28 line is read, print it (to simulate the user typing it), and then
29 29 return it. The set of lines to return is specified in the
30 30 constructor; they should not have trailing newlines.
31 31 """
32 32 def __init__(self, lines):
33 33 self.lines = iter(lines)
34 34
35 35 def readline(self):
36 36 line = next(self.lines)
37 37 print(line)
38 38 return line+'\n'
39 39
40 40 class PdbTestInput(object):
41 41 """Context manager that makes testing Pdb in doctests easier."""
42 42
43 43 def __init__(self, input):
44 44 self.input = input
45 45
46 46 def __enter__(self):
47 47 self.real_stdin = sys.stdin
48 48 sys.stdin = _FakeInput(self.input)
49 49
50 50 def __exit__(self, *exc):
51 51 sys.stdin = self.real_stdin
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Tests
55 55 #-----------------------------------------------------------------------------
56 56
57 57 def test_ipdb_magics():
58 58 '''Test calling some IPython magics from ipdb.
59 59
60 60 First, set up some test functions and classes which we can inspect.
61 61
62 >>> class ExampleClass(object):
63 ... """Docstring for ExampleClass."""
64 ... def __init__(self):
65 ... """Docstring for ExampleClass.__init__"""
66 ... pass
67 ... def __str__(self):
68 ... return "ExampleClass()"
62 In [1]: class ExampleClass(object):
63 ...: """Docstring for ExampleClass."""
64 ...: def __init__(self):
65 ...: """Docstring for ExampleClass.__init__"""
66 ...: pass
67 ...: def __str__(self):
68 ...: return "ExampleClass()"
69 69
70 >>> def example_function(x, y, z="hello"):
71 ... """Docstring for example_function."""
72 ... pass
70 In [2]: def example_function(x, y, z="hello"):
71 ...: """Docstring for example_function."""
72 ...: pass
73 73
74 >>> old_trace = sys.gettrace()
74 In [3]: old_trace = sys.gettrace()
75 75
76 76 Create a function which triggers ipdb.
77 77
78 >>> def trigger_ipdb():
79 ... a = ExampleClass()
80 ... debugger.Pdb().set_trace()
81
82 >>> with PdbTestInput([
83 ... 'pdef example_function',
84 ... 'pdoc ExampleClass',
85 ... 'up',
86 ... 'down',
87 ... 'list',
88 ... 'pinfo a',
89 ... 'll',
90 ... 'continue',
91 ... ]):
92 ... trigger_ipdb()
93 --Return--
94 None
95 > <doctest ...>(3)trigger_ipdb()
78 In [4]: def trigger_ipdb():
79 ...: a = ExampleClass()
80 ...: debugger.Pdb().set_trace()
81
82 Run ipdb with faked input & check output. Because of a difference between
83 Python 3.13 & older versions, the first bit of the output is inconsistent.
84 We need to use ... to accommodate that, so the examples have to use IPython
85 prompts so that ... is distinct from the Python PS2 prompt.
86
87 In [5]: with PdbTestInput([
88 ...: 'pdef example_function',
89 ...: 'pdoc ExampleClass',
90 ...: 'up',
91 ...: 'down',
92 ...: 'list',
93 ...: 'pinfo a',
94 ...: 'll',
95 ...: 'continue',
96 ...: ]):
97 ...: trigger_ipdb()
98 ...> <doctest ...>(3)trigger_ipdb()
96 99 1 def trigger_ipdb():
97 100 2 a = ExampleClass()
98 101 ----> 3 debugger.Pdb().set_trace()
99 102 <BLANKLINE>
100 103 ipdb> pdef example_function
101 104 example_function(x, y, z='hello')
102 105 ipdb> pdoc ExampleClass
103 106 Class docstring:
104 107 Docstring for ExampleClass.
105 108 Init docstring:
106 109 Docstring for ExampleClass.__init__
107 110 ipdb> up
108 111 > <doctest ...>(11)<module>()
109 112 7 'pinfo a',
110 113 8 'll',
111 114 9 'continue',
112 115 10 ]):
113 116 ---> 11 trigger_ipdb()
114 117 <BLANKLINE>
115 ipdb> down
116 None
118 ipdb> down...
117 119 > <doctest ...>(3)trigger_ipdb()
118 120 1 def trigger_ipdb():
119 121 2 a = ExampleClass()
120 122 ----> 3 debugger.Pdb().set_trace()
121 123 <BLANKLINE>
122 124 ipdb> list
123 125 1 def trigger_ipdb():
124 126 2 a = ExampleClass()
125 127 ----> 3 debugger.Pdb().set_trace()
126 128 <BLANKLINE>
127 129 ipdb> pinfo a
128 130 Type: ExampleClass
129 131 String form: ExampleClass()
130 132 Namespace: Local...
131 133 Docstring: Docstring for ExampleClass.
132 134 Init docstring: Docstring for ExampleClass.__init__
133 135 ipdb> ll
134 136 1 def trigger_ipdb():
135 137 2 a = ExampleClass()
136 138 ----> 3 debugger.Pdb().set_trace()
137 139 <BLANKLINE>
138 140 ipdb> continue
139 141
140 142 Restore previous trace function, e.g. for coverage.py
141 143
142 >>> sys.settrace(old_trace)
144 In [6]: sys.settrace(old_trace)
143 145 '''
144 146
145 147 def test_ipdb_magics2():
146 148 '''Test ipdb with a very short function.
147 149
148 150 >>> old_trace = sys.gettrace()
149 151
150 152 >>> def bar():
151 153 ... pass
152 154
153 155 Run ipdb.
154 156
155 157 >>> with PdbTestInput([
156 158 ... 'continue',
157 159 ... ]):
158 160 ... debugger.Pdb().runcall(bar)
159 161 > <doctest ...>(2)bar()
160 162 1 def bar():
161 163 ----> 2 pass
162 164 <BLANKLINE>
163 165 ipdb> continue
164 166
165 167 Restore previous trace function, e.g. for coverage.py
166 168
167 169 >>> sys.settrace(old_trace)
168 170 '''
169 171
170 172 def can_quit():
171 173 '''Test that quit work in ipydb
172 174
173 175 >>> old_trace = sys.gettrace()
174 176
175 177 >>> def bar():
176 178 ... pass
177 179
178 180 >>> with PdbTestInput([
179 181 ... 'quit',
180 182 ... ]):
181 183 ... debugger.Pdb().runcall(bar)
182 184 > <doctest ...>(2)bar()
183 185 1 def bar():
184 186 ----> 2 pass
185 187 <BLANKLINE>
186 188 ipdb> quit
187 189
188 190 Restore previous trace function, e.g. for coverage.py
189 191
190 192 >>> sys.settrace(old_trace)
191 193 '''
192 194
193 195
194 196 def can_exit():
195 197 '''Test that quit work in ipydb
196 198
197 199 >>> old_trace = sys.gettrace()
198 200
199 201 >>> def bar():
200 202 ... pass
201 203
202 204 >>> with PdbTestInput([
203 205 ... 'exit',
204 206 ... ]):
205 207 ... debugger.Pdb().runcall(bar)
206 208 > <doctest ...>(2)bar()
207 209 1 def bar():
208 210 ----> 2 pass
209 211 <BLANKLINE>
210 212 ipdb> exit
211 213
212 214 Restore previous trace function, e.g. for coverage.py
213 215
214 216 >>> sys.settrace(old_trace)
215 217 '''
216 218
217 219
218 220 def test_interruptible_core_debugger():
219 221 """The debugger can be interrupted.
220 222
221 223 The presumption is there is some mechanism that causes a KeyboardInterrupt
222 224 (this is implemented in ipykernel). We want to ensure the
223 225 KeyboardInterrupt cause debugging to cease.
224 226 """
225 227 def raising_input(msg="", called=[0]):
226 228 called[0] += 1
227 229 assert called[0] == 1, "input() should only be called once!"
228 230 raise KeyboardInterrupt()
229 231
230 232 tracer_orig = sys.gettrace()
231 233 try:
232 234 with patch.object(builtins, "input", raising_input):
233 235 debugger.InterruptiblePdb().set_trace()
234 236 # The way this test will fail is by set_trace() never exiting,
235 237 # resulting in a timeout by the test runner. The alternative
236 238 # implementation would involve a subprocess, but that adds issues
237 239 # with interrupting subprocesses that are rather complex, so it's
238 240 # simpler just to do it this way.
239 241 finally:
240 242 # restore the original trace function
241 243 sys.settrace(tracer_orig)
242 244
243 245
244 246 @skip_win32
245 247 def test_xmode_skip():
246 248 """that xmode skip frames
247 249
248 250 Not as a doctest as pytest does not run doctests.
249 251 """
250 252 import pexpect
251 253 env = os.environ.copy()
252 254 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
253 255
254 256 child = pexpect.spawn(
255 257 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
256 258 )
257 259 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
258 260
259 261 child.expect("IPython")
260 262 child.expect("\n")
261 263 child.expect_exact("In [1]")
262 264
263 265 block = dedent(
264 266 """
265 267 def f():
266 268 __tracebackhide__ = True
267 269 g()
268 270
269 271 def g():
270 272 raise ValueError
271 273
272 274 f()
273 275 """
274 276 )
275 277
276 278 for line in block.splitlines():
277 279 child.sendline(line)
278 280 child.expect_exact(line)
279 281 child.expect_exact("skipping")
280 282
281 283 block = dedent(
282 284 """
283 285 def f():
284 286 __tracebackhide__ = True
285 287 g()
286 288
287 289 def g():
288 290 from IPython.core.debugger import set_trace
289 291 set_trace()
290 292
291 293 f()
292 294 """
293 295 )
294 296
295 297 for line in block.splitlines():
296 298 child.sendline(line)
297 299 child.expect_exact(line)
298 300
299 301 child.expect("ipdb>")
300 302 child.sendline("w")
301 303 child.expect("hidden")
302 304 child.expect("ipdb>")
303 305 child.sendline("skip_hidden false")
304 306 child.sendline("w")
305 307 child.expect("__traceba")
306 308 child.expect("ipdb>")
307 309
308 310 child.close()
309 311
310 312
311 313 skip_decorators_blocks = (
312 314 """
313 315 def helpers_helper():
314 316 pass # should not stop here except breakpoint
315 317 """,
316 318 """
317 319 def helper_1():
318 320 helpers_helper() # should not stop here
319 321 """,
320 322 """
321 323 def helper_2():
322 324 pass # should not stop here
323 325 """,
324 326 """
325 327 def pdb_skipped_decorator2(function):
326 328 def wrapped_fn(*args, **kwargs):
327 329 __debuggerskip__ = True
328 330 helper_2()
329 331 __debuggerskip__ = False
330 332 result = function(*args, **kwargs)
331 333 __debuggerskip__ = True
332 334 helper_2()
333 335 return result
334 336 return wrapped_fn
335 337 """,
336 338 """
337 339 def pdb_skipped_decorator(function):
338 340 def wrapped_fn(*args, **kwargs):
339 341 __debuggerskip__ = True
340 342 helper_1()
341 343 __debuggerskip__ = False
342 344 result = function(*args, **kwargs)
343 345 __debuggerskip__ = True
344 346 helper_2()
345 347 return result
346 348 return wrapped_fn
347 349 """,
348 350 """
349 351 @pdb_skipped_decorator
350 352 @pdb_skipped_decorator2
351 353 def bar(x, y):
352 354 return x * y
353 355 """,
354 356 """import IPython.terminal.debugger as ipdb""",
355 357 """
356 358 def f():
357 359 ipdb.set_trace()
358 360 bar(3, 4)
359 361 """,
360 362 """
361 363 f()
362 364 """,
363 365 )
364 366
365 367
366 368 def _decorator_skip_setup():
367 369 import pexpect
368 370
369 371 env = os.environ.copy()
370 372 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
371 373 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
372 374
373 375 child = pexpect.spawn(
374 376 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
375 377 )
376 378 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
377 379
378 380 child.expect("IPython")
379 381 child.expect("\n")
380 382
381 383 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
382 384 child.str_last_chars = 500
383 385
384 386 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
385 387 in_prompt_number = 1
386 388 for cblock in dedented_blocks:
387 389 child.expect_exact(f"In [{in_prompt_number}]:")
388 390 in_prompt_number += 1
389 391 for line in cblock.splitlines():
390 392 child.sendline(line)
391 393 child.expect_exact(line)
392 394 child.sendline("")
393 395 return child
394 396
395 397
396 398 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
397 399 @skip_win32
398 400 def test_decorator_skip():
399 401 """test that decorator frames can be skipped."""
400 402
401 403 child = _decorator_skip_setup()
402 404
403 405 child.expect_exact("ipython-input-8")
404 406 child.expect_exact("3 bar(3, 4)")
405 407 child.expect("ipdb>")
406 408
407 409 child.expect("ipdb>")
408 410 child.sendline("step")
409 411 child.expect_exact("step")
410 412 child.expect_exact("--Call--")
411 413 child.expect_exact("ipython-input-6")
412 414
413 415 child.expect_exact("1 @pdb_skipped_decorator")
414 416
415 417 child.sendline("s")
416 418 child.expect_exact("return x * y")
417 419
418 420 child.close()
419 421
420 422
421 423 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
422 424 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
423 425 @skip_win32
424 426 def test_decorator_skip_disabled():
425 427 """test that decorator frame skipping can be disabled"""
426 428
427 429 child = _decorator_skip_setup()
428 430
429 431 child.expect_exact("3 bar(3, 4)")
430 432
431 433 for input_, expected in [
432 434 ("skip_predicates debuggerskip False", ""),
433 435 ("skip_predicates", "debuggerskip : False"),
434 436 ("step", "---> 2 def wrapped_fn"),
435 437 ("step", "----> 3 __debuggerskip__"),
436 438 ("step", "----> 4 helper_1()"),
437 439 ("step", "---> 1 def helper_1():"),
438 440 ("next", "----> 2 helpers_helper()"),
439 441 ("next", "--Return--"),
440 442 ("next", "----> 5 __debuggerskip__ = False"),
441 443 ]:
442 444 child.expect("ipdb>")
443 445 child.sendline(input_)
444 446 child.expect_exact(input_)
445 447 child.expect_exact(expected)
446 448
447 449 child.close()
448 450
449 451
450 452 @pytest.mark.xfail(
451 453 sys.version_info.releaselevel not in ("final", "candidate"),
452 454 reason="fails on 3.13.dev",
453 455 strict=True,
454 456 )
455 457 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
456 458 @skip_win32
457 459 def test_decorator_skip_with_breakpoint():
458 460 """test that decorator frame skipping can be disabled"""
459 461
460 462 import pexpect
461 463
462 464 env = os.environ.copy()
463 465 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
464 466 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
465 467
466 468 child = pexpect.spawn(
467 469 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
468 470 )
469 471 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
470 472 child.str_last_chars = 500
471 473
472 474 child.expect("IPython")
473 475 child.expect("\n")
474 476
475 477 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
476 478
477 479 ### we need a filename, so we need to exec the full block with a filename
478 480 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
479 481 name = tf.name[:-3].split("/")[-1]
480 482 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
481 483 tf.flush()
482 484 codeblock = f"from {name} import f"
483 485
484 486 dedented_blocks = [
485 487 codeblock,
486 488 "f()",
487 489 ]
488 490
489 491 in_prompt_number = 1
490 492 for cblock in dedented_blocks:
491 493 child.expect_exact(f"In [{in_prompt_number}]:")
492 494 in_prompt_number += 1
493 495 for line in cblock.splitlines():
494 496 child.sendline(line)
495 497 child.expect_exact(line)
496 498 child.sendline("")
497 499
498 500 # as the filename does not exists, we'll rely on the filename prompt
499 501 child.expect_exact("47 bar(3, 4)")
500 502
501 503 for input_, expected in [
502 504 (f"b {name}.py:3", ""),
503 505 ("step", "1---> 3 pass # should not stop here except"),
504 506 ("step", "---> 38 @pdb_skipped_decorator"),
505 507 ("continue", ""),
506 508 ]:
507 509 child.expect("ipdb>")
508 510 child.sendline(input_)
509 511 child.expect_exact(input_)
510 512 child.expect_exact(expected)
511 513
512 514 child.close()
513 515
514 516
515 517 @skip_win32
516 518 def test_where_erase_value():
517 519 """Test that `where` does not access f_locals and erase values."""
518 520 import pexpect
519 521
520 522 env = os.environ.copy()
521 523 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
522 524
523 525 child = pexpect.spawn(
524 526 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
525 527 )
526 528 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
527 529
528 530 child.expect("IPython")
529 531 child.expect("\n")
530 532 child.expect_exact("In [1]")
531 533
532 534 block = dedent(
533 535 """
534 536 def simple_f():
535 537 myvar = 1
536 538 print(myvar)
537 539 1/0
538 540 print(myvar)
539 541 simple_f() """
540 542 )
541 543
542 544 for line in block.splitlines():
543 545 child.sendline(line)
544 546 child.expect_exact(line)
545 547 child.expect_exact("ZeroDivisionError")
546 548 child.expect_exact("In [2]:")
547 549
548 550 child.sendline("%debug")
549 551
550 552 ##
551 553 child.expect("ipdb>")
552 554
553 555 child.sendline("myvar")
554 556 child.expect("1")
555 557
556 558 ##
557 559 child.expect("ipdb>")
558 560
559 561 child.sendline("myvar = 2")
560 562
561 563 ##
562 564 child.expect_exact("ipdb>")
563 565
564 566 child.sendline("myvar")
565 567
566 568 child.expect_exact("2")
567 569
568 570 ##
569 571 child.expect("ipdb>")
570 572 child.sendline("where")
571 573
572 574 ##
573 575 child.expect("ipdb>")
574 576 child.sendline("myvar")
575 577
576 578 child.expect_exact("2")
577 579 child.expect("ipdb>")
578 580
579 581 child.close()
General Comments 0
You need to be logged in to leave comments. Login now