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