diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 97a625369..a30ca4753 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -16,7 +16,7 @@ def pytest_addoption(parser): group = parser.getgroup("general") group._addoption( '--pdb', dest="usepdb", action="store_true", - help="start the interactive Python debugger on errors.") + help="start the interactive Python debugger on errors or KeyboardInterrupt.") group._addoption( '--pdbcls', dest="usepdb_cls", metavar="modulename:classname", help="start a custom interactive Python debugger on errors. " diff --git a/_pytest/runner.py b/_pytest/runner.py index f62d34df2..6df558a7f 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -176,7 +176,8 @@ def check_interactive_exception(call, report): def call_runtest_hook(item, when, **kwds): hookname = "pytest_runtest_" + when ihook = getattr(item.ihook, hookname) - return CallInfo(lambda: ihook(item=item, **kwds), when=when) + return CallInfo(lambda: ihook(item=item, **kwds), when=when, + treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb")) class CallInfo(object): @@ -184,7 +185,7 @@ class CallInfo(object): #: None or ExceptionInfo object. excinfo = None - def __init__(self, func, when): + def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False): #: context of invocation: one of "setup", "call", #: "teardown", "memocollect" self.when = when @@ -192,8 +193,11 @@ class CallInfo(object): try: self.result = func() except KeyboardInterrupt: - self.stop = time() - raise + if treat_keyboard_interrupt_as_exception: + self.excinfo = ExceptionInfo() + else: + self.stop = time() + raise except: # noqa self.excinfo = ExceptionInfo() self.stop = time() diff --git a/changelog/3299.feature.rst b/changelog/3299.feature.rst new file mode 100644 index 000000000..5fe591e44 --- /dev/null +++ b/changelog/3299.feature.rst @@ -0,0 +1,2 @@ +The ``--pdb`` option now causes KeyboardInterrupt to enter the debugger, instead of stopping the test session. +On python 2.7, hitting CTRL+C again exits the debugger. On python 3.2 and higher, use CTRL+D. \ No newline at end of file diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 3d5b0536e..72b2eedc9 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -152,9 +152,9 @@ allows one to drop into the PDB_ prompt via a command line option:: pytest --pdb -This will invoke the Python debugger on every failure. Often you might -only want to do this for the first failing test to understand a certain -failure situation:: +This will invoke the Python debugger on every failure (or KeyboardInterrupt). +Often you might only want to do this for the first failing test to understand +a certain failure situation:: pytest -x --pdb # drop to PDB on first failure, then end test session pytest --pdb --maxfail=3 # drop to PDB for first three failures diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 85817f79b..29277caf1 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function import sys import platform import os +import signal import _pytest._code from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN @@ -114,6 +115,14 @@ class TestPDB(object): assert rep.failed assert len(pdblist) == 0 + def test_pdb_on_KeyboardInterrupt(self, testdir, pdblist): + rep = runpdb_and_get_report(testdir, """ + def test_func(): + raise KeyboardInterrupt + """) + assert rep.failed + assert len(pdblist) == 1 + def test_pdb_interaction(self, testdir): p1 = testdir.makepyfile(""" def test_1():