Merge pull request #3331 from tonybaloney/breakpoint_support

Support for the new builtin breakpoint function in Python 3.7
This commit is contained in:
Ronny Pfannschmidt 2018-04-10 07:15:29 +02:00 committed by GitHub
commit 2241c98b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 0 deletions

View File

@ -17,6 +17,7 @@ Andreas Zeidler
Andrzej Ostrowski
Andy Freeland
Anthon van der Neut
Anthony Shaw
Anthony Sottile
Antony Lee
Armin Rigo

View File

@ -2,8 +2,15 @@
from __future__ import absolute_import, division, print_function
import pdb
import sys
import os
from doctest import UnexpectedException
try:
from builtins import breakpoint # noqa
SUPPORTS_BREAKPOINT_BUILTIN = True
except ImportError:
SUPPORTS_BREAKPOINT_BUILTIN = False
def pytest_addoption(parser):
group = parser.getgroup("general")
@ -27,12 +34,20 @@ def pytest_configure(config):
if config.getvalue("usepdb"):
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)
def fin():
pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None
pytestPDB._pdb_cls = pdb.Pdb
if SUPPORTS_BREAKPOINT_BUILTIN:
sys.breakpointhook = sys.__breakpointhook__
pdb.set_trace = pytestPDB.set_trace
pytestPDB._pluginmanager = config.pluginmanager

View File

@ -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.

View File

@ -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
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:
Profiling test execution duration

View File

@ -1,11 +1,16 @@
from __future__ import absolute_import, division, print_function
import sys
import platform
import os
import _pytest._code
from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN
import pytest
_ENVIRON_PYTHONBREAKPOINT = os.environ.get('PYTHONBREAKPOINT', '')
def runpdb_and_get_report(testdir, source):
p = testdir.makepyfile(source)
result = testdir.runpytest_inprocess("--pdb", p)
@ -33,6 +38,30 @@ def custom_pdb_calls():
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):
@pytest.fixture
@ -470,3 +499,122 @@ class TestPDB(object):
child.expect('custom set_trace>')
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)