From 9edcb7edc643feafd91ebe0922d02bcafaceb1fb Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 22 Mar 2018 17:25:27 +1100 Subject: [PATCH 01/22] start acceptance testing --- testing/test_debugging.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 testing/test_debugging.py diff --git a/testing/test_debugging.py b/testing/test_debugging.py new file mode 100644 index 000000000..a8b124b95 --- /dev/null +++ b/testing/test_debugging.py @@ -0,0 +1,18 @@ +# encoding: utf-8 +from __future__ import absolute_import +from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN +import sys + + +class TestDebugging(object): + + def test_supports_breakpoint_module_global(self): + """ + Test that supports breakpoint global marks on Python 3.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 From 3bca983a95d935b3c91130a1397e910fb460f338 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 22 Mar 2018 17:27:28 +1100 Subject: [PATCH 02/22] add a module global for whether the current runtime supports the builtin breakpoint function --- _pytest/debugging.py | 6 ++++++ testing/test_debugging.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 43472f23b..bb21a9e0e 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -4,6 +4,12 @@ import pdb import sys 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") diff --git a/testing/test_debugging.py b/testing/test_debugging.py index a8b124b95..9d21d0561 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -8,7 +8,8 @@ class TestDebugging(object): def test_supports_breakpoint_module_global(self): """ - Test that supports breakpoint global marks on Python 3.7+ + 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 From 91d99affb7a81d4665fe50be4f51c4eaeb340dcd Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 22 Mar 2018 17:40:14 +1100 Subject: [PATCH 03/22] assert that custom PDB class is used as breakpoint hook where supported --- testing/test_debugging.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 9d21d0561..0b5c8a8a9 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -1,8 +1,9 @@ # encoding: utf-8 from __future__ import absolute_import -from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN +from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB +import pytest import sys - +import os class TestDebugging(object): @@ -17,3 +18,11 @@ class TestDebugging(object): 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(sys.version_info < (3,7), reason="Requires python3.7") + def test_sys_breakpointhook(self): + """ + Test that sys.breakpointhook is set to the custom Pdb class + """ + if 'PYTHONBREAKPOINT' not in os.environ or os.environ['PYTHONBREAKPOINT'] == '': + assert isinstance(sys.breakpointhook, pytestPDB) From 5a53b9aabb296f18b77edabb6b873195c27ebd34 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 22 Mar 2018 20:40:35 +1100 Subject: [PATCH 04/22] move tests to test_pdb --- testing/test_debugging.py | 28 ---------------------------- testing/test_pdb.py | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 28 deletions(-) delete mode 100644 testing/test_debugging.py diff --git a/testing/test_debugging.py b/testing/test_debugging.py deleted file mode 100644 index 0b5c8a8a9..000000000 --- a/testing/test_debugging.py +++ /dev/null @@ -1,28 +0,0 @@ -# encoding: utf-8 -from __future__ import absolute_import -from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB -import pytest -import sys -import os - -class TestDebugging(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(sys.version_info < (3,7), reason="Requires python3.7") - def test_sys_breakpointhook(self): - """ - Test that sys.breakpointhook is set to the custom Pdb class - """ - if 'PYTHONBREAKPOINT' not in os.environ or os.environ['PYTHONBREAKPOINT'] == '': - assert isinstance(sys.breakpointhook, pytestPDB) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 01604b633..c798be6fc 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1,8 +1,10 @@ from __future__ import absolute_import, division, print_function import sys import platform +import os import _pytest._code +from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB import pytest @@ -434,3 +436,26 @@ 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(sys.version_info < (3,7), reason="Requires python3.7") + def test_sys_breakpointhook(self): + """ + Test that sys.breakpointhook is set to the custom Pdb class + """ + if 'PYTHONBREAKPOINT' not in os.environ or os.environ['PYTHONBREAKPOINT'] == '': + assert isinstance(sys.breakpointhook, pytestPDB) From a1ff758d0d0fbb1655e85472fe896b3356dac404 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 14:18:56 +1100 Subject: [PATCH 05/22] "Added acceptance tests for configuration of sys.breakpointhook and resetting back when system default (pdb) is used"" --- testing/test_pdb.py | 81 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index c798be6fc..c31939d7f 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -4,10 +4,14 @@ import platform import os import _pytest._code -from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB +from _pytest.debugging import (SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB, + pytest_configure) import pytest +_ENVIRON_PYTHONBREAKPOINT = getattr(os.environ, 'PYTHONBREAKPOINT', '') + + def runpdb_and_get_report(testdir, source): p = testdir.makepyfile(source) result = testdir.runpytest_inprocess("--pdb", p) @@ -452,10 +456,75 @@ class TestDebuggingBreakpoints(object): if sys.version_info.major == 2 and sys.version_info.minor == 7: assert SUPPORTS_BREAKPOINT_BUILTIN is False - @pytest.mark.skipif(sys.version_info < (3,7), reason="Requires python3.7") - def test_sys_breakpointhook(self): + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") + def test_sys_breakpointhook_not_custom_pdb(self): """ - Test that sys.breakpointhook is set to the custom Pdb class + Test that sys.breakpointhook is not set to the custom Pdb class """ - if 'PYTHONBREAKPOINT' not in os.environ or os.environ['PYTHONBREAKPOINT'] == '': - assert isinstance(sys.breakpointhook, pytestPDB) + assert sys.breakpointhook != pytestPDB.set_trace + + + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") + def test_sys_breakpointhook_not_custom_pdb(self): + """ + Test that sys.breakpointhook is not set to the custom Pdb class without configuration + """ + assert sys.breakpointhook != pytestPDB.set_trace + + + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") + def test_sys_breakpointhook_configure_and_unconfigure(self, testdir): + """ + 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 + """ + config = testdir.parseconfig() + + pytest_configure(config) + assert sys.breakpointhook == pytestPDB.set_trace + + p1 = testdir.makepyfile(""" + def test_nothing(): + a = 0 + assert a == 0 + """) + result = testdir.runpytest_inprocess("", p1) + assert sys.breakpointhook != pytestPDB.set_trace + + + @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): + """ + Test that calling PDB set_trace() is not affected + when PYTHONBREAKPOINT=0 + """ + 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) From 21ada0fa23d20dcc2945ecb3f8ef5c62a96deac3 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 14:20:10 +1100 Subject: [PATCH 06/22] add check for support of breakpoint() and use Custom Pdb class when system default is set --- _pytest/debugging.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index bb21a9e0e..f73a9a6b9 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function import pdb import sys +import os from doctest import UnexpectedException try: @@ -33,12 +34,19 @@ 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 = getattr(os.environ, '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 + sys.breakpointhook = sys.__breakpointhook__ pdb.set_trace = pytestPDB.set_trace pytestPDB._pluginmanager = config.pluginmanager From 0e83e4f292a44ee86e6974dc30c652fa0bba5172 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 14:26:16 +1100 Subject: [PATCH 07/22] conditional for resetting of sys.breakpointhook for cleanup where breakpoint() not supported --- _pytest/debugging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index f73a9a6b9..68663490d 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -46,7 +46,8 @@ def pytest_configure(config): pdb.set_trace, pytestPDB._pluginmanager = old pytestPDB._config = None pytestPDB._pdb_cls = pdb.Pdb - sys.breakpointhook = sys.__breakpointhook__ + if SUPPORTS_BREAKPOINT_BUILTIN: + sys.breakpointhook = sys.__breakpointhook__ pdb.set_trace = pytestPDB.set_trace pytestPDB._pluginmanager = config.pluginmanager From db581eabcbc8a279020cceb5217a2ca593024aba Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 15:30:05 +1100 Subject: [PATCH 08/22] add tests to validate that --pdbcls custom debugger classes will be called when breakpoint() is used --- testing/test_pdb.py | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index c31939d7f..8051d04c3 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -39,6 +39,29 @@ 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 + return called + + class TestPDB(object): @pytest.fixture @@ -492,6 +515,36 @@ class TestDebuggingBreakpoints(object): assert sys.breakpointhook != pytestPDB.set_trace + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") + def test_sys_breakpointhook_configure_and_unconfigure_with_pdb_flag(self, testdir): + config = testdir.parseconfig() + + pytest_configure(config) + assert sys.breakpointhook == pytestPDB.set_trace + + p1 = testdir.makepyfile(""" + def test_nothing(): + a = 0 + assert a == 0 + """) + result = testdir.runpytest_inprocess("--pdb", p1) + assert sys.breakpointhook != pytestPDB.set_trace + + @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.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): From dcbba381d44f081dd3974326337c30c6850a65bf Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 15:31:16 +1100 Subject: [PATCH 09/22] add changelog entry --- changelog/3180.feature.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 changelog/3180.feature.rst diff --git a/changelog/3180.feature.rst b/changelog/3180.feature.rst new file mode 100644 index 000000000..ee696fad2 --- /dev/null +++ b/changelog/3180.feature.rst @@ -0,0 +1,12 @@ +Support for Python 3.7s builtin breakpoint() method. + +When breakpoint() is called and PYTHONBREAKPOINT is set to the default value, +PyTest will use the Custom 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 Pdb interface is used on both +breakpoint() and failed tests/unhandled exceptions. + +If --pdbcls is used, the custom class 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. From 1ec99132e69a9fa2d103471fc6145b375d1e5fe1 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 15:31:57 +1100 Subject: [PATCH 10/22] add myself to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 25c4384cd..a604ee013 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 From 242fb7852b66e2dc419e8a1d8b0150876ae38eac Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 15:39:34 +1100 Subject: [PATCH 11/22] linting and removed double test --- testing/test_pdb.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 8051d04c3..fe54321ca 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -469,7 +469,7 @@ class TestDebuggingBreakpoints(object): def test_supports_breakpoint_module_global(self): """ - Test that supports breakpoint global marks on Python 3.7+ and not on + 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: @@ -486,15 +486,6 @@ class TestDebuggingBreakpoints(object): """ assert sys.breakpointhook != pytestPDB.set_trace - - @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") - def test_sys_breakpointhook_not_custom_pdb(self): - """ - Test that sys.breakpointhook is not set to the custom Pdb class without configuration - """ - assert sys.breakpointhook != pytestPDB.set_trace - - @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") def test_sys_breakpointhook_configure_and_unconfigure(self, testdir): """ @@ -512,9 +503,11 @@ class TestDebuggingBreakpoints(object): assert a == 0 """) result = testdir.runpytest_inprocess("", p1) + result.stdout.fnmatch_lines([ + "*1 passed*", + ]) assert sys.breakpointhook != pytestPDB.set_trace - @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") def test_sys_breakpointhook_configure_and_unconfigure_with_pdb_flag(self, testdir): config = testdir.parseconfig() @@ -528,6 +521,9 @@ class TestDebuggingBreakpoints(object): assert a == 0 """) result = testdir.runpytest_inprocess("--pdb", p1) + result.stdout.fnmatch_lines([ + "*1 passed*", + ]) assert sys.breakpointhook != pytestPDB.set_trace @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") @@ -544,9 +540,8 @@ class TestDebuggingBreakpoints(object): ]) assert custom_debugger_hook == ["init", "set_trace"] - @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") - @pytest.mark.skipif(not _ENVIRON_PYTHONBREAKPOINT=='', reason="Requires breakpoint() default value") + @pytest.mark.skipif(not _ENVIRON_PYTHONBREAKPOINT == '', reason="Requires breakpoint() default value") def test_sys_breakpoint_interception(self, testdir): p1 = testdir.makepyfile(""" def test_1(): @@ -561,7 +556,6 @@ class TestDebuggingBreakpoints(object): 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): """ From e97bd87ee20635bf585900747c3b571fd43e0fa8 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 23 Mar 2018 16:24:15 +1100 Subject: [PATCH 12/22] fix assertion when hypothesis is installed (which is should be for developing in pytest) --- testing/test_pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index fe54321ca..882e4fda2 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -504,7 +504,7 @@ class TestDebuggingBreakpoints(object): """) result = testdir.runpytest_inprocess("", p1) result.stdout.fnmatch_lines([ - "*1 passed*", + "* passed*", ]) assert sys.breakpointhook != pytestPDB.set_trace From f1f4c8c10481ab643a497876dfaed713f54c9479 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 27 Mar 2018 17:38:17 +1100 Subject: [PATCH 13/22] updates for code review recommendations --- _pytest/debugging.py | 2 +- testing/test_pdb.py | 43 ++++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 68663490d..7b91fdf61 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -36,7 +36,7 @@ def pytest_configure(config): # Use custom Pdb class set_trace instead of default Pdb on breakpoint() call if SUPPORTS_BREAKPOINT_BUILTIN: - _environ_pythonbreakpoint = getattr(os.environ, 'PYTHONBREAKPOINT', '') + _environ_pythonbreakpoint = os.environ.get('PYTHONBREAKPOINT', '') if _environ_pythonbreakpoint == '': sys.breakpointhook = pytestPDB.set_trace diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 882e4fda2..4d768585c 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -9,7 +9,7 @@ from _pytest.debugging import (SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB, import pytest -_ENVIRON_PYTHONBREAKPOINT = getattr(os.environ, 'PYTHONBREAKPOINT', '') +_ENVIRON_PYTHONBREAKPOINT = os.environ.get('PYTHONBREAKPOINT', '') def runpdb_and_get_report(testdir, source): @@ -59,7 +59,8 @@ def custom_debugger_hook(): called.append("set_trace") _pytest._CustomDebugger = _CustomDebugger - return called + yield called + del _pytest._CustomDebugger class TestPDB(object): @@ -492,35 +493,39 @@ class TestDebuggingBreakpoints(object): 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 """ - config = testdir.parseconfig() + testdir.makeconftest(""" + from pytest import hookimpl + from _pytest.debugging import pytestPDB - pytest_configure(config) - assert sys.breakpointhook == pytestPDB.set_trace + @hookimpl(hookwrapper=True) + def pytest_configure(config): + yield + assert sys.breakpointhook is pytestPDB.set_trace + + @hookimpl(hookwrapper=True) + def pytest_unconfigure(config): + yield + assert sys.breakpointhook is sys.__breakpoint__ - p1 = testdir.makepyfile(""" - def test_nothing(): - a = 0 - assert a == 0 """) - result = testdir.runpytest_inprocess("", p1) - result.stdout.fnmatch_lines([ - "* passed*", - ]) - assert sys.breakpointhook != pytestPDB.set_trace + testdir.makepyfile(""" + def test_nothing(): pass + """) + @pytest.mark.parametrize('args', [('--pdb',), ()]) @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") - def test_sys_breakpointhook_configure_and_unconfigure_with_pdb_flag(self, testdir): + def test_sys_breakpointhook_configure_and_unconfigure_with_pdb_flag(self, testdir, args): config = testdir.parseconfig() pytest_configure(config) assert sys.breakpointhook == pytestPDB.set_trace - p1 = testdir.makepyfile(""" + testdir.makepyfile(""" def test_nothing(): a = 0 assert a == 0 """) - result = testdir.runpytest_inprocess("--pdb", p1) + result = testdir.runpytest_inprocess(*args) result.stdout.fnmatch_lines([ "*1 passed*", ]) @@ -558,10 +563,6 @@ class TestDebuggingBreakpoints(object): @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") def test_pdb_not_altered(self, testdir): - """ - Test that calling PDB set_trace() is not affected - when PYTHONBREAKPOINT=0 - """ p1 = testdir.makepyfile(""" import pdb def test_1(): From 671ab5a36cee2e4110ca12826de8d5ab5ca45aa8 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 27 Mar 2018 21:02:43 +1100 Subject: [PATCH 14/22] update documentation for new feature --- changelog/3180.feature.rst | 13 +------------ doc/en/usage.rst | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/changelog/3180.feature.rst b/changelog/3180.feature.rst index ee696fad2..9ee4a8a01 100644 --- a/changelog/3180.feature.rst +++ b/changelog/3180.feature.rst @@ -1,12 +1 @@ -Support for Python 3.7s builtin breakpoint() method. - -When breakpoint() is called and PYTHONBREAKPOINT is set to the default value, -PyTest will use the Custom 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 Pdb interface is used on both -breakpoint() and failed tests/unhandled exceptions. - -If --pdbcls is used, the custom class 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. +Support for Python 3.7's builtin ``breakpoint()`` method, see :ref:`breakpoint-builtin` for details. \ No newline at end of file diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 7f967aa4f..5d6a788a6 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -189,6 +189,21 @@ 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 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 Pdb interface is used on both +breakpoint() and failed tests/unhandled exceptions. + - If --pdbcls is used, the custom class 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 From b45006e9a3af8c60c7835ddf4c1aee6920c0caa7 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 27 Mar 2018 21:26:55 +1100 Subject: [PATCH 15/22] fix syntax --- doc/en/usage.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 5d6a788a6..9da5d3fce 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -190,7 +190,7 @@ in your code and pytest automatically disables its output capture for that test: and continue with the regular test run. -.. _breakpoint-builtin: +.. _`breakpoint-builtin`: Using the builtin breakpoint function ------------------------------------- @@ -200,8 +200,7 @@ 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 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 Pdb interface is used on both -breakpoint() and failed tests/unhandled exceptions. + - If --pdb is called on execution of Pytest, the custom Pdb interface is used on bothbreakpoint() and failed tests/unhandled exceptions. - If --pdbcls is used, the custom class 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: From 0a4200bbb3a422ff7f77fb83ca16c43737fd9231 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 27 Mar 2018 07:40:52 -0300 Subject: [PATCH 16/22] Improve docs formatting --- doc/en/usage.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 9da5d3fce..e13ff24c7 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -198,10 +198,10 @@ 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 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 Pdb interface is used on bothbreakpoint() and failed tests/unhandled exceptions. - - If --pdbcls is used, the custom class 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. + - 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: From 060f047a7ea24c9ce7671defe12ab2a1085d4db5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 27 Mar 2018 18:35:47 -0300 Subject: [PATCH 17/22] Use full link in CHANGELOG Our rst-linter unfortunately doesn't accept `ref` directives in the CHANGELOG files --- changelog/3180.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3180.feature.rst b/changelog/3180.feature.rst index 9ee4a8a01..31db646f4 100644 --- a/changelog/3180.feature.rst +++ b/changelog/3180.feature.rst @@ -1 +1 @@ -Support for Python 3.7's builtin ``breakpoint()`` method, see :ref:`breakpoint-builtin` for details. \ No newline at end of file +Support for Python 3.7's builtin ``breakpoint()`` method, see `Using the builtin breakpoint function `_ for details. From 3998b70ff6e5268e828d87d4afde39aa7f867dee Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 28 Mar 2018 09:02:37 +1100 Subject: [PATCH 18/22] add test for custom environment variable --- testing/test_pdb.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 4d768585c..165d53c1c 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -545,6 +545,30 @@ class TestDebuggingBreakpoints(object): ]) assert custom_debugger_hook == ["init", "set_trace"] + @pytest.mark.parametrize('args', [('',), ()]) + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") + def test_environ_custom_class(self, testdir, custom_debugger_hook, args): + testdir.makeconftest(""" + from pytest import hookimpl + from _pytest.debugging import pytestPDB + import os + + @hookimpl(hookwrapper=True) + def pytest_configure(config): + os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' + yield + assert sys.breakpointhook is not pytestPDB.set_trace + + @hookimpl(hookwrapper=True) + def pytest_unconfigure(config): + yield + assert sys.breakpointhook is sys.__breakpoint__ + os.environ['PYTHONBREAKPOINT'] = '' + """) + testdir.makepyfile(""" + def test_nothing(): pass + """) + @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): From 09e5a226dceb925450bd38f0e06739901d9b9706 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 29 Mar 2018 09:03:20 +1100 Subject: [PATCH 19/22] add broken test --- testing/test_pdb.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 165d53c1c..557eeae52 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -494,10 +494,10 @@ class TestDebuggingBreakpoints(object): hook is reset to system value once pytest has been unconfigured """ testdir.makeconftest(""" + import sys from pytest import hookimpl from _pytest.debugging import pytestPDB - @hookimpl(hookwrapper=True) def pytest_configure(config): yield assert sys.breakpointhook is pytestPDB.set_trace @@ -505,12 +505,16 @@ class TestDebuggingBreakpoints(object): @hookimpl(hookwrapper=True) def pytest_unconfigure(config): yield - assert sys.breakpointhook is sys.__breakpoint__ + assert sys.breakpointhook is sys.__breakpointhook__ """) testdir.makepyfile(""" def test_nothing(): pass """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*1 passed in *', + ]) @pytest.mark.parametrize('args', [('--pdb',), ()]) @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") @@ -552,8 +556,9 @@ class TestDebuggingBreakpoints(object): from pytest import hookimpl from _pytest.debugging import pytestPDB import os + import sys - @hookimpl(hookwrapper=True) + @hookimpl() def pytest_configure(config): os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' yield @@ -562,12 +567,16 @@ class TestDebuggingBreakpoints(object): @hookimpl(hookwrapper=True) def pytest_unconfigure(config): yield - assert sys.breakpointhook is sys.__breakpoint__ + assert sys.breakpointhook is sys.__breakpointhook__ os.environ['PYTHONBREAKPOINT'] = '' """) testdir.makepyfile(""" def test_nothing(): pass """) + result = testdir.runpytest() + 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") From 804392e5f2222fc2c83d7a8a098b58ca4c126195 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 28 Mar 2018 20:19:28 -0300 Subject: [PATCH 20/22] Fix tests that check that breakpoint function is configured/restored * Execute pytest in a subprocess in cases of tests which change global state * Parametrize with --pdb and without the flag --- testing/test_pdb.py | 78 +++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 557eeae52..e84c99941 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -4,8 +4,7 @@ import platform import os import _pytest._code -from _pytest.debugging import (SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB, - pytest_configure) +from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB import pytest @@ -481,14 +480,15 @@ class TestDebuggingBreakpoints(object): assert SUPPORTS_BREAKPOINT_BUILTIN is False @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") - def test_sys_breakpointhook_not_custom_pdb(self): + def test_sys_breakpointhook_is_pytest_PDB(self): """ - Test that sys.breakpointhook is not set to the custom Pdb class + Test that sys.breakpointhook by default is set to pytest's internal class. """ - assert sys.breakpointhook != pytestPDB.set_trace + assert sys.breakpointhook == pytestPDB.set_trace @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") - def test_sys_breakpointhook_configure_and_unconfigure(self, testdir): + @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 @@ -499,47 +499,28 @@ class TestDebuggingBreakpoints(object): from _pytest.debugging import pytestPDB def pytest_configure(config): - yield - assert sys.breakpointhook is pytestPDB.set_trace + config._cleanup.append(check_restored) - @hookimpl(hookwrapper=True) - def pytest_unconfigure(config): - yield - assert sys.breakpointhook is sys.__breakpointhook__ + def check_restored(): + assert sys.breakpointhook == sys.__breakpointhook__ + def test_check(): + assert sys.breakpointhook == pytestPDB.set_trace """) testdir.makepyfile(""" def test_nothing(): pass """) - result = testdir.runpytest() + args = (arg,) if arg else () + result = testdir.runpytest_subprocess(*args) result.stdout.fnmatch_lines([ '*1 passed in *', ]) - @pytest.mark.parametrize('args', [('--pdb',), ()]) - @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") - def test_sys_breakpointhook_configure_and_unconfigure_with_pdb_flag(self, testdir, args): - config = testdir.parseconfig() - - pytest_configure(config) - assert sys.breakpointhook == pytestPDB.set_trace - - testdir.makepyfile(""" - def test_nothing(): - a = 0 - assert a == 0 - """) - result = testdir.runpytest_inprocess(*args) - result.stdout.fnmatch_lines([ - "*1 passed*", - ]) - assert sys.breakpointhook != pytestPDB.set_trace - @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() + def test_nothing(): + breakpoint() """) result = testdir.runpytest_inprocess( "--pdb", "--pdbcls=_pytest:_CustomDebugger", p1) @@ -549,31 +530,30 @@ class TestDebuggingBreakpoints(object): ]) assert custom_debugger_hook == ["init", "set_trace"] - @pytest.mark.parametrize('args', [('',), ()]) + @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, args): + def test_environ_custom_class(self, testdir, custom_debugger_hook, arg): testdir.makeconftest(""" - from pytest import hookimpl - from _pytest.debugging import pytestPDB import os import sys - @hookimpl() - def pytest_configure(config): - os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' - yield - assert sys.breakpointhook is not pytestPDB.set_trace + os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' - @hookimpl(hookwrapper=True) - def pytest_unconfigure(config): - yield - assert sys.breakpointhook is sys.__breakpointhook__ - os.environ['PYTHONBREAKPOINT'] = '' + 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 """) - result = testdir.runpytest() + args = (arg,) if arg else () + result = testdir.runpytest_subprocess(*args) result.stdout.fnmatch_lines([ '*1 passed in *', ]) From 4d847593b323d041c11df15c8158a330caf07a79 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 3 Apr 2018 10:40:56 +1000 Subject: [PATCH 21/22] remove a test that would fail because pytest is being used to test itself --- testing/test_pdb.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index e84c99941..d96c460c3 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -479,13 +479,6 @@ class TestDebuggingBreakpoints(object): 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") - def test_sys_breakpointhook_is_pytest_PDB(self): - """ - Test that sys.breakpointhook by default is set to pytest's internal class. - """ - assert sys.breakpointhook == pytestPDB.set_trace - @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): From 0762666bd116d92b55f746cde8e25e965ea2912b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 3 Apr 2018 08:58:40 -0300 Subject: [PATCH 22/22] Remove unused pytestPDB import --- testing/test_pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index d96c460c3..0f5196751 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -4,7 +4,7 @@ import platform import os import _pytest._code -from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN, pytestPDB +from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN import pytest