Merge pull request #3331 from tonybaloney/breakpoint_support
Support for the new builtin breakpoint function in Python 3.7
This commit is contained in:
commit
2241c98b18
1
AUTHORS
1
AUTHORS
|
@ -17,6 +17,7 @@ Andreas Zeidler
|
||||||
Andrzej Ostrowski
|
Andrzej Ostrowski
|
||||||
Andy Freeland
|
Andy Freeland
|
||||||
Anthon van der Neut
|
Anthon van der Neut
|
||||||
|
Anthony Shaw
|
||||||
Anthony Sottile
|
Anthony Sottile
|
||||||
Antony Lee
|
Antony Lee
|
||||||
Armin Rigo
|
Armin Rigo
|
||||||
|
|
|
@ -2,8 +2,15 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import pdb
|
import pdb
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
from doctest import UnexpectedException
|
from doctest import UnexpectedException
|
||||||
|
|
||||||
|
try:
|
||||||
|
from builtins import breakpoint # noqa
|
||||||
|
SUPPORTS_BREAKPOINT_BUILTIN = True
|
||||||
|
except ImportError:
|
||||||
|
SUPPORTS_BREAKPOINT_BUILTIN = False
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("general")
|
group = parser.getgroup("general")
|
||||||
|
@ -27,12 +34,20 @@ def pytest_configure(config):
|
||||||
if config.getvalue("usepdb"):
|
if config.getvalue("usepdb"):
|
||||||
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
|
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
|
||||||
|
|
||||||
|
# Use custom Pdb class set_trace instead of default Pdb on breakpoint() call
|
||||||
|
if SUPPORTS_BREAKPOINT_BUILTIN:
|
||||||
|
_environ_pythonbreakpoint = os.environ.get('PYTHONBREAKPOINT', '')
|
||||||
|
if _environ_pythonbreakpoint == '':
|
||||||
|
sys.breakpointhook = pytestPDB.set_trace
|
||||||
|
|
||||||
old = (pdb.set_trace, pytestPDB._pluginmanager)
|
old = (pdb.set_trace, pytestPDB._pluginmanager)
|
||||||
|
|
||||||
def fin():
|
def fin():
|
||||||
pdb.set_trace, pytestPDB._pluginmanager = old
|
pdb.set_trace, pytestPDB._pluginmanager = old
|
||||||
pytestPDB._config = None
|
pytestPDB._config = None
|
||||||
pytestPDB._pdb_cls = pdb.Pdb
|
pytestPDB._pdb_cls = pdb.Pdb
|
||||||
|
if SUPPORTS_BREAKPOINT_BUILTIN:
|
||||||
|
sys.breakpointhook = sys.__breakpointhook__
|
||||||
|
|
||||||
pdb.set_trace = pytestPDB.set_trace
|
pdb.set_trace = pytestPDB.set_trace
|
||||||
pytestPDB._pluginmanager = config.pluginmanager
|
pytestPDB._pluginmanager = config.pluginmanager
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Support for Python 3.7's builtin ``breakpoint()`` method, see `Using the builtin breakpoint function <https://docs.pytest.org/en/latest/usage.html#breakpoint-builtin>`_ for details.
|
|
@ -189,6 +189,20 @@ in your code and pytest automatically disables its output capture for that test:
|
||||||
for test output occurring after you exit the interactive PDB_ tracing session
|
for test output occurring after you exit the interactive PDB_ tracing session
|
||||||
and continue with the regular test run.
|
and continue with the regular test run.
|
||||||
|
|
||||||
|
|
||||||
|
.. _`breakpoint-builtin`:
|
||||||
|
|
||||||
|
Using the builtin breakpoint function
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Python 3.7 introduces a builtin ``breakpoint()`` function.
|
||||||
|
Pytest supports the use of ``breakpoint()`` with the following behaviours:
|
||||||
|
|
||||||
|
- When ``breakpoint()`` is called and ``PYTHONBREAKPOINT`` is set to the default value, pytest will use the custom internal PDB trace UI instead of the system default ``Pdb``.
|
||||||
|
- When tests are complete, the system will default back to the system ``Pdb`` trace UI.
|
||||||
|
- If ``--pdb`` is called on execution of pytest, the custom internal Pdb trace UI is used on ``bothbreakpoint()`` and failed tests/unhandled exceptions.
|
||||||
|
- If ``--pdbcls`` is used, the custom class debugger will be executed when a test fails (as expected within existing behaviour), but also when ``breakpoint()`` is called from within a test, the custom class debugger will be instantiated.
|
||||||
|
|
||||||
.. _durations:
|
.. _durations:
|
||||||
|
|
||||||
Profiling test execution duration
|
Profiling test execution duration
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
import os
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
_ENVIRON_PYTHONBREAKPOINT = os.environ.get('PYTHONBREAKPOINT', '')
|
||||||
|
|
||||||
|
|
||||||
def runpdb_and_get_report(testdir, source):
|
def runpdb_and_get_report(testdir, source):
|
||||||
p = testdir.makepyfile(source)
|
p = testdir.makepyfile(source)
|
||||||
result = testdir.runpytest_inprocess("--pdb", p)
|
result = testdir.runpytest_inprocess("--pdb", p)
|
||||||
|
@ -33,6 +38,30 @@ def custom_pdb_calls():
|
||||||
return called
|
return called
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def custom_debugger_hook():
|
||||||
|
called = []
|
||||||
|
|
||||||
|
# install dummy debugger class and track which methods were called on it
|
||||||
|
class _CustomDebugger(object):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
called.append("init")
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
called.append("reset")
|
||||||
|
|
||||||
|
def interaction(self, *args):
|
||||||
|
called.append("interaction")
|
||||||
|
|
||||||
|
def set_trace(self, frame):
|
||||||
|
print("**CustomDebugger**")
|
||||||
|
called.append("set_trace")
|
||||||
|
|
||||||
|
_pytest._CustomDebugger = _CustomDebugger
|
||||||
|
yield called
|
||||||
|
del _pytest._CustomDebugger
|
||||||
|
|
||||||
|
|
||||||
class TestPDB(object):
|
class TestPDB(object):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -470,3 +499,122 @@ class TestPDB(object):
|
||||||
|
|
||||||
child.expect('custom set_trace>')
|
child.expect('custom set_trace>')
|
||||||
self.flush(child)
|
self.flush(child)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDebuggingBreakpoints(object):
|
||||||
|
|
||||||
|
def test_supports_breakpoint_module_global(self):
|
||||||
|
"""
|
||||||
|
Test that supports breakpoint global marks on Python 3.7+ and not on
|
||||||
|
CPython 3.5, 2.7
|
||||||
|
"""
|
||||||
|
if sys.version_info.major == 3 and sys.version_info.minor >= 7:
|
||||||
|
assert SUPPORTS_BREAKPOINT_BUILTIN is True
|
||||||
|
if sys.version_info.major == 3 and sys.version_info.minor == 5:
|
||||||
|
assert SUPPORTS_BREAKPOINT_BUILTIN is False
|
||||||
|
if sys.version_info.major == 2 and sys.version_info.minor == 7:
|
||||||
|
assert SUPPORTS_BREAKPOINT_BUILTIN is False
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin")
|
||||||
|
@pytest.mark.parametrize('arg', ['--pdb', ''])
|
||||||
|
def test_sys_breakpointhook_configure_and_unconfigure(self, testdir, arg):
|
||||||
|
"""
|
||||||
|
Test that sys.breakpointhook is set to the custom Pdb class once configured, test that
|
||||||
|
hook is reset to system value once pytest has been unconfigured
|
||||||
|
"""
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import sys
|
||||||
|
from pytest import hookimpl
|
||||||
|
from _pytest.debugging import pytestPDB
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config._cleanup.append(check_restored)
|
||||||
|
|
||||||
|
def check_restored():
|
||||||
|
assert sys.breakpointhook == sys.__breakpointhook__
|
||||||
|
|
||||||
|
def test_check():
|
||||||
|
assert sys.breakpointhook == pytestPDB.set_trace
|
||||||
|
""")
|
||||||
|
testdir.makepyfile("""
|
||||||
|
def test_nothing(): pass
|
||||||
|
""")
|
||||||
|
args = (arg,) if arg else ()
|
||||||
|
result = testdir.runpytest_subprocess(*args)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*1 passed in *',
|
||||||
|
])
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin")
|
||||||
|
def test_pdb_custom_cls(self, testdir, custom_debugger_hook):
|
||||||
|
p1 = testdir.makepyfile("""
|
||||||
|
def test_nothing():
|
||||||
|
breakpoint()
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest_inprocess(
|
||||||
|
"--pdb", "--pdbcls=_pytest:_CustomDebugger", p1)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*CustomDebugger*",
|
||||||
|
"*1 passed*",
|
||||||
|
])
|
||||||
|
assert custom_debugger_hook == ["init", "set_trace"]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('arg', ['--pdb', ''])
|
||||||
|
@pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin")
|
||||||
|
def test_environ_custom_class(self, testdir, custom_debugger_hook, arg):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace'
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config._cleanup.append(check_restored)
|
||||||
|
|
||||||
|
def check_restored():
|
||||||
|
assert sys.breakpointhook == sys.__breakpointhook__
|
||||||
|
|
||||||
|
def test_check():
|
||||||
|
import _pytest
|
||||||
|
assert sys.breakpointhook is _pytest._CustomDebugger.set_trace
|
||||||
|
""")
|
||||||
|
testdir.makepyfile("""
|
||||||
|
def test_nothing(): pass
|
||||||
|
""")
|
||||||
|
args = (arg,) if arg else ()
|
||||||
|
result = testdir.runpytest_subprocess(*args)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'*1 passed in *',
|
||||||
|
])
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin")
|
||||||
|
@pytest.mark.skipif(not _ENVIRON_PYTHONBREAKPOINT == '', reason="Requires breakpoint() default value")
|
||||||
|
def test_sys_breakpoint_interception(self, testdir):
|
||||||
|
p1 = testdir.makepyfile("""
|
||||||
|
def test_1():
|
||||||
|
breakpoint()
|
||||||
|
""")
|
||||||
|
child = testdir.spawn_pytest(str(p1))
|
||||||
|
child.expect("test_1")
|
||||||
|
child.expect("(Pdb)")
|
||||||
|
child.sendeof()
|
||||||
|
rest = child.read().decode("utf8")
|
||||||
|
assert "1 failed" in rest
|
||||||
|
assert "reading from stdin while output" not in rest
|
||||||
|
TestPDB.flush(child)
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin")
|
||||||
|
def test_pdb_not_altered(self, testdir):
|
||||||
|
p1 = testdir.makepyfile("""
|
||||||
|
import pdb
|
||||||
|
def test_1():
|
||||||
|
pdb.set_trace()
|
||||||
|
""")
|
||||||
|
child = testdir.spawn_pytest(str(p1))
|
||||||
|
child.expect("test_1")
|
||||||
|
child.expect("(Pdb)")
|
||||||
|
child.sendeof()
|
||||||
|
rest = child.read().decode("utf8")
|
||||||
|
assert "1 failed" in rest
|
||||||
|
assert "reading from stdin while output" not in rest
|
||||||
|
TestPDB.flush(child)
|
||||||
|
|
Loading…
Reference in New Issue