diff options
author | Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> | 2018-04-10 07:15:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-10 07:15:29 +0200 |
commit | 2241c98b18cbc2c9ac26b94a07872610cfb36da3 (patch) | |
tree | 9512a04ae38b44717c2db6c90a71b6fddfa94c8e | |
parent | 715337011be81c721a35d0056a352d7cdc2731ec (diff) | |
parent | 0762666bd116d92b55f746cde8e25e965ea2912b (diff) | |
download | pytest-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-- | AUTHORS | 1 | ||||
-rw-r--r-- | _pytest/debugging.py | 15 | ||||
-rw-r--r-- | changelog/3180.feature.rst | 1 | ||||
-rw-r--r-- | doc/en/usage.rst | 14 | ||||
-rw-r--r-- | testing/test_pdb.py | 148 |
5 files changed, 179 insertions, 0 deletions
@@ -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) |