Format: text/x-python
⬇ Download File (15.0 KB)
   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