diff --git a/changelog/4280.bugfix.rst b/changelog/4280.bugfix.rst new file mode 100644 index 000000000..4814823f1 --- /dev/null +++ b/changelog/4280.bugfix.rst @@ -0,0 +1,3 @@ +Improve quitting from pdb, especially with ``--trace``. + +Using ``q[quit]`` after ``pdb.set_trace()`` will quit pytest also. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index adf9d0e54..7a1060ae0 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -118,6 +118,10 @@ class pytestPDB(object): do_c = do_cont = do_continue + def set_quit(self): + super(_PdbWrapper, self).set_quit() + outcomes.exit("Quitting debugger") + def setup(self, f, tb): """Suspend on setup(). @@ -210,8 +214,7 @@ def _enter_pdb(node, excinfo, rep): tw.sep(">", "entering PDB") tb = _postmortem_traceback(excinfo) rep._pdbshown = True - if post_mortem(tb): - outcomes.exit("Quitting debugger") + post_mortem(tb) return rep @@ -242,4 +245,5 @@ def post_mortem(t): p = Pdb() p.reset() p.interaction(None, t) - return p.quitting + if p.quitting: + outcomes.exit("Quitting debugger") diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index d27939e30..14c6e9ab6 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -49,13 +49,13 @@ class Failed(OutcomeException): __module__ = "builtins" -class Exit(SystemExit): +class Exit(Exception): """ raised for immediate program exits (no tracebacks/summaries)""" def __init__(self, msg="unknown reason", returncode=None): self.msg = msg self.returncode = returncode - SystemExit.__init__(self, msg) + super(Exit, self).__init__(msg) # exposed helper methods @@ -63,7 +63,7 @@ class Exit(SystemExit): def exit(msg, returncode=None): """ - Exit testing process as if SystemExit was triggered. + Exit testing process. :param str msg: message to display upon exit. :param int returncode: return code to be used when exiting pytest. diff --git a/testing/test_pdb.py b/testing/test_pdb.py index cb1017ac4..49fbbad72 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -147,29 +147,6 @@ class TestPDB(object): assert rep.failed assert len(pdblist) == 1 - def test_pdb_interaction(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - i = 0 - assert i == 1 - - def test_not_called_due_to_quit(): - pass - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect(".*def test_1") - child.expect(".*i = 0") - child.expect("Pdb") - child.sendeof() - rest = child.read().decode("utf8") - assert "= 1 failed in" in rest - assert "def test_1" not in rest - assert "Exit: Quitting debugger" in rest - assert "PDB continue (IO-capturing resumed)" not in rest - self.flush(child) - @staticmethod def flush(child): if platform.system() == "Darwin": @@ -214,40 +191,32 @@ class TestPDB(object): child.sendeof() self.flush(child) - def test_pdb_print_captured_stdout(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - print("get\\x20rekt") - assert False - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("captured stdout") - child.expect("get rekt") - child.expect("Pdb") - child.sendeof() - rest = child.read().decode("utf8") - assert "1 failed" in rest - assert "get rekt" not in rest - self.flush(child) - - def test_pdb_print_captured_stderr(self, testdir): + def test_pdb_print_captured_stdout_and_stderr(self, testdir): p1 = testdir.makepyfile( """ def test_1(): import sys sys.stderr.write("get\\x20rekt") + print("get\\x20rekt") assert False + + def test_not_called_due_to_quit(): + pass """ ) child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("captured stdout") + child.expect("get rekt") child.expect("captured stderr") child.expect("get rekt") + child.expect("traceback") + child.expect("def test_1") child.expect("Pdb") child.sendeof() rest = child.read().decode("utf8") - assert "1 failed" in rest + assert "Exit: Quitting debugger" in rest + assert "= 1 failed in" in rest + assert "def test_1" not in rest assert "get rekt" not in rest self.flush(child) @@ -375,15 +344,17 @@ class TestPDB(object): i = 0 print("hello17") pytest.set_trace() - x = 3 + i == 1 + assert 0 """ ) child = testdir.spawn_pytest(str(p1)) - child.expect("test_1") - child.expect("x = 3") + child.expect(r"test_1\(\)") + child.expect("i == 1") child.expect("Pdb") - child.sendeof() + child.sendline("c") rest = child.read().decode("utf-8") + assert "AssertionError" in rest assert "1 failed" in rest assert "def test_1" in rest assert "hello17" in rest # out is captured @@ -398,13 +369,14 @@ class TestPDB(object): print("hello17") pytest.set_trace(header="== my_header ==") x = 3 + assert 0 """ ) child = testdir.spawn_pytest(str(p1)) child.expect("== my_header ==") assert "PDB set_trace" not in child.before.decode() child.expect("Pdb") - child.sendeof() + child.sendline("c") rest = child.read().decode("utf-8") assert "1 failed" in rest assert "def test_1" in rest @@ -424,9 +396,9 @@ class TestPDB(object): child.expect("Pdb") child.sendeof() rest = child.read().decode("utf8") - assert "1 failed" in rest + assert "no tests ran" in rest assert "reading from stdin while output" not in rest - assert "BdbQuit" in rest + assert "BdbQuit" not in rest self.flush(child) def test_pdb_and_capsys(self, testdir): @@ -518,6 +490,7 @@ class TestPDB(object): print("hello18") pytest.set_trace() x = 4 + assert 0 """ ) child = testdir.spawn_pytest(str(p1)) @@ -530,11 +503,11 @@ class TestPDB(object): child.expect(r"PDB set_trace \(IO-capturing turned off\)") child.expect("x = 4") child.expect("Pdb") - child.sendeof() + child.sendline("c") child.expect("_ test_1 _") child.expect("def test_1") - child.expect("Captured stdout call") rest = child.read().decode("utf8") + assert "Captured stdout call" in rest assert "hello17" in rest # out is captured assert "hello18" in rest # out is captured assert "1 failed" in rest @@ -795,7 +768,7 @@ class TestDebuggingBreakpoints(object): child.expect("Pdb") child.sendeof() rest = child.read().decode("utf8") - assert "1 failed" in rest + assert "Quitting debugger" in rest assert "reading from stdin while output" not in rest TestPDB.flush(child) @@ -808,12 +781,13 @@ class TestDebuggingBreakpoints(object): import pdb def test_1(): pdb.set_trace() + assert 0 """ ) child = testdir.spawn_pytest(str(p1)) child.expect("test_1") child.expect("Pdb") - child.sendeof() + child.sendline("c") rest = child.read().decode("utf8") assert "1 failed" in rest assert "reading from stdin while output" not in rest @@ -826,15 +800,29 @@ class TestTraceOption: """ def test_1(): assert True + + def test_2(): + pass + + def test_3(): + pass """ ) child = testdir.spawn_pytest("--trace " + str(p1)) child.expect("test_1") child.expect("Pdb") - child.sendeof() + child.sendline("c") + child.expect("test_2") + child.expect("Pdb") + child.sendline("c") + child.expect("test_3") + child.expect("Pdb") + child.sendline("q") + child.expect_exact("Exit: Quitting debugger") rest = child.read().decode("utf8") - assert "1 passed" in rest + assert "2 passed in" in rest assert "reading from stdin while output" not in rest + assert "Exit: Quitting debugger" in child.before.decode("utf8") TestPDB.flush(child) @@ -863,3 +851,31 @@ def test_trace_after_runpytest(testdir): rest = child.read().decode("utf8") TestPDB.flush(child) assert child.exitstatus == 0, rest + + +def test_quit_with_swallowed_SystemExit(testdir): + """Test that debugging's pytest_configure is re-entrant.""" + p1 = testdir.makepyfile( + """ + def call_pdb_set_trace(): + __import__('pdb').set_trace() + + + def test_1(): + try: + call_pdb_set_trace() + except SystemExit: + pass + + + def test_2(): + pass + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("Pdb") + child.sendline("q") + child.expect_exact("Exit: Quitting debugger") + rest = child.read().decode("utf8") + assert "no tests ran" in rest + TestPDB.flush(child)