summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonny Pfannschmidt <opensource@ronnypfannschmidt.de>2018-04-10 07:15:29 +0200
committerGitHub <noreply@github.com>2018-04-10 07:15:29 +0200
commit2241c98b18cbc2c9ac26b94a07872610cfb36da3 (patch)
tree9512a04ae38b44717c2db6c90a71b6fddfa94c8e
parent715337011be81c721a35d0056a352d7cdc2731ec (diff)
parent0762666bd116d92b55f746cde8e25e965ea2912b (diff)
downloadpytest-2241c98b18cbc2c9ac26b94a07872610cfb36da3.tar.gz
Merge pull request #3331 from tonybaloney/breakpoint_support
Support for the new builtin breakpoint function in Python 3.7
-rw-r--r--AUTHORS1
-rw-r--r--_pytest/debugging.py15
-rw-r--r--changelog/3180.feature.rst1
-rw-r--r--doc/en/usage.rst14
-rw-r--r--testing/test_pdb.py148
5 files changed, 179 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
index e415cc50d..8376c3870 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -17,6 +17,7 @@ Andreas Zeidler
Andrzej Ostrowski
Andy Freeland
Anthon van der Neut
+Anthony Shaw
Anthony Sottile
Antony Lee
Armin Rigo
diff --git a/_pytest/debugging.py b/_pytest/debugging.py
index fada117e5..97a625369 100644
--- a/_pytest/debugging.py
+++ b/_pytest/debugging.py
@@ -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
diff --git a/changelog/3180.feature.rst b/changelog/3180.feature.rst
new file mode 100644
index 000000000..31db646f4
--- /dev/null
+++ b/changelog/3180.feature.rst
@@ -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.
diff --git a/doc/en/usage.rst b/doc/en/usage.rst
index 9b6db82c5..3d5b0536e 100644
--- a/doc/en/usage.rst
+++ b/doc/en/usage.rst
@@ -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
diff --git a/testing/test_pdb.py b/testing/test_pdb.py
index 445cafcc5..85817f79b 100644
--- a/testing/test_pdb.py
+++ b/testing/test_pdb.py
@@ -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)