debugging.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. """ interactive debugging with PDB, the Python Debugger. """
  2. from __future__ import absolute_import, division, print_function
  3. import pdb
  4. import sys
  5. import os
  6. from doctest import UnexpectedException
  7. from _pytest.config import hookimpl
  8. try:
  9. from builtins import breakpoint # noqa
  10. SUPPORTS_BREAKPOINT_BUILTIN = True
  11. except ImportError:
  12. SUPPORTS_BREAKPOINT_BUILTIN = False
  13. def pytest_addoption(parser):
  14. group = parser.getgroup("general")
  15. group._addoption(
  16. "--pdb",
  17. dest="usepdb",
  18. action="store_true",
  19. help="start the interactive Python debugger on errors or KeyboardInterrupt.",
  20. )
  21. group._addoption(
  22. "--pdbcls",
  23. dest="usepdb_cls",
  24. metavar="modulename:classname",
  25. help="start a custom interactive Python debugger on errors. "
  26. "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
  27. )
  28. group._addoption(
  29. "--trace",
  30. dest="trace",
  31. action="store_true",
  32. help="Immediately break when running each test.",
  33. )
  34. def pytest_configure(config):
  35. if config.getvalue("usepdb_cls"):
  36. modname, classname = config.getvalue("usepdb_cls").split(":")
  37. __import__(modname)
  38. pdb_cls = getattr(sys.modules[modname], classname)
  39. else:
  40. pdb_cls = pdb.Pdb
  41. if config.getvalue("trace"):
  42. config.pluginmanager.register(PdbTrace(), "pdbtrace")
  43. if config.getvalue("usepdb"):
  44. config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
  45. # Use custom Pdb class set_trace instead of default Pdb on breakpoint() call
  46. if SUPPORTS_BREAKPOINT_BUILTIN:
  47. _environ_pythonbreakpoint = os.environ.get("PYTHONBREAKPOINT", "")
  48. if _environ_pythonbreakpoint == "":
  49. sys.breakpointhook = pytestPDB.set_trace
  50. old = (pdb.set_trace, pytestPDB._pluginmanager)
  51. def fin():
  52. pdb.set_trace, pytestPDB._pluginmanager = old
  53. pytestPDB._config = None
  54. pytestPDB._pdb_cls = pdb.Pdb
  55. if SUPPORTS_BREAKPOINT_BUILTIN:
  56. sys.breakpointhook = sys.__breakpointhook__
  57. pdb.set_trace = pytestPDB.set_trace
  58. pytestPDB._pluginmanager = config.pluginmanager
  59. pytestPDB._config = config
  60. pytestPDB._pdb_cls = pdb_cls
  61. config._cleanup.append(fin)
  62. class pytestPDB(object):
  63. """ Pseudo PDB that defers to the real pdb. """
  64. _pluginmanager = None
  65. _config = None
  66. _pdb_cls = pdb.Pdb
  67. @classmethod
  68. def set_trace(cls, set_break=True):
  69. """ invoke PDB set_trace debugging, dropping any IO capturing. """
  70. import _pytest.config
  71. frame = sys._getframe().f_back
  72. if cls._pluginmanager is not None:
  73. capman = cls._pluginmanager.getplugin("capturemanager")
  74. if capman:
  75. capman.suspend_global_capture(in_=True)
  76. tw = _pytest.config.create_terminal_writer(cls._config)
  77. tw.line()
  78. tw.sep(">", "PDB set_trace (IO-capturing turned off)")
  79. cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
  80. if set_break:
  81. cls._pdb_cls().set_trace(frame)
  82. class PdbInvoke(object):
  83. def pytest_exception_interact(self, node, call, report):
  84. capman = node.config.pluginmanager.getplugin("capturemanager")
  85. if capman:
  86. capman.suspend_global_capture(in_=True)
  87. out, err = capman.read_global_capture()
  88. sys.stdout.write(out)
  89. sys.stdout.write(err)
  90. _enter_pdb(node, call.excinfo, report)
  91. def pytest_internalerror(self, excrepr, excinfo):
  92. for line in str(excrepr).split("\n"):
  93. sys.stderr.write("INTERNALERROR> %s\n" % line)
  94. sys.stderr.flush()
  95. tb = _postmortem_traceback(excinfo)
  96. post_mortem(tb)
  97. class PdbTrace(object):
  98. @hookimpl(hookwrapper=True)
  99. def pytest_pyfunc_call(self, pyfuncitem):
  100. _test_pytest_function(pyfuncitem)
  101. yield
  102. def _test_pytest_function(pyfuncitem):
  103. pytestPDB.set_trace(set_break=False)
  104. testfunction = pyfuncitem.obj
  105. pyfuncitem.obj = pdb.runcall
  106. if pyfuncitem._isyieldedfunction():
  107. arg_list = list(pyfuncitem._args)
  108. arg_list.insert(0, testfunction)
  109. pyfuncitem._args = tuple(arg_list)
  110. else:
  111. if "func" in pyfuncitem._fixtureinfo.argnames:
  112. raise ValueError("--trace can't be used with a fixture named func!")
  113. pyfuncitem.funcargs["func"] = testfunction
  114. new_list = list(pyfuncitem._fixtureinfo.argnames)
  115. new_list.append("func")
  116. pyfuncitem._fixtureinfo.argnames = tuple(new_list)
  117. def _enter_pdb(node, excinfo, rep):
  118. # XXX we re-use the TerminalReporter's terminalwriter
  119. # because this seems to avoid some encoding related troubles
  120. # for not completely clear reasons.
  121. tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
  122. tw.line()
  123. showcapture = node.config.option.showcapture
  124. for sectionname, content in (
  125. ("stdout", rep.capstdout),
  126. ("stderr", rep.capstderr),
  127. ("log", rep.caplog),
  128. ):
  129. if showcapture in (sectionname, "all") and content:
  130. tw.sep(">", "captured " + sectionname)
  131. if content[-1:] == "\n":
  132. content = content[:-1]
  133. tw.line(content)
  134. tw.sep(">", "traceback")
  135. rep.toterminal(tw)
  136. tw.sep(">", "entering PDB")
  137. tb = _postmortem_traceback(excinfo)
  138. post_mortem(tb)
  139. rep._pdbshown = True
  140. return rep
  141. def _postmortem_traceback(excinfo):
  142. if isinstance(excinfo.value, UnexpectedException):
  143. # A doctest.UnexpectedException is not useful for post_mortem.
  144. # Use the underlying exception instead:
  145. return excinfo.value.exc_info[2]
  146. else:
  147. return excinfo._excinfo[2]
  148. def _find_last_non_hidden_frame(stack):
  149. i = max(0, len(stack) - 1)
  150. while i and stack[i][0].f_locals.get("__tracebackhide__", False):
  151. i -= 1
  152. return i
  153. def post_mortem(t):
  154. class Pdb(pytestPDB._pdb_cls):
  155. def get_stack(self, f, t):
  156. stack, i = pdb.Pdb.get_stack(self, f, t)
  157. if f is None:
  158. i = _find_last_non_hidden_frame(stack)
  159. return stack, i
  160. p = Pdb()
  161. p.reset()
  162. p.interaction(None, t)