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