From 49f5a28012462f4a914e86751538cf2b9cb27f0c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 11 May 2019 12:27:42 +0200 Subject: [PATCH 1/2] pdb: fix usage in child thread after main thread exited Fixes https://github.com/pytest-dev/pytest/issues/5228. --- changelog/5228.bugfix.rst | 1 + src/_pytest/debugging.py | 11 ++------- testing/test_pdb.py | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 changelog/5228.bugfix.rst diff --git a/changelog/5228.bugfix.rst b/changelog/5228.bugfix.rst new file mode 100644 index 00000000000..6c8612576e9 --- /dev/null +++ b/changelog/5228.bugfix.rst @@ -0,0 +1 @@ +Fix ``pdb.set_trace`` wrapper when used in child threads after main thread exited. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 52c6536f4dc..4bb68416f45 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -77,9 +77,7 @@ def pytest_configure(config): if config.getvalue("usepdb"): config.pluginmanager.register(PdbInvoke(), "pdbinvoke") - pytestPDB._saved.append( - (pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, pytestPDB._pdb_cls) - ) + pytestPDB._saved.append(pdb.set_trace) pdb.set_trace = pytestPDB.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config @@ -88,12 +86,7 @@ def pytest_configure(config): # NOTE: not using pytest_unconfigure, since it might get called although # pytest_configure was not (if another plugin raises UsageError). def fin(): - ( - pdb.set_trace, - pytestPDB._pluginmanager, - pytestPDB._config, - pytestPDB._pdb_cls, - ) = pytestPDB._saved.pop() + pdb.set_trace = pytestPDB._saved.pop() config._cleanup.append(fin) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 3b21bacd902..178a66d6e70 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1191,3 +1191,50 @@ def test(monkeypatch): result = testdir.runpytest(str(p1)) result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"]) assert result.ret == 1 + + +def test_pdb_in_thread_after_exit(testdir): + """Ensure that pdb.set_trace works after main thread exited already. + + This tests both continuation after the main thread exited, and a new + set_trace afterwards. + """ + p1 = testdir.makepyfile( + """ + import threading + + main_thread = threading.current_thread() + evt = threading.Event() + evt2 = threading.Event() + + def test(): + + def target(): + print("target_" + "start") + evt.set() + assert main_thread.is_alive() + __import__('pdb').set_trace() + assert not main_thread.is_alive() + __import__('pdb').set_trace() + print("target_" + "end") + + thread = threading.Thread(target=target) + thread.start() + + evt.wait() + evt2.wait() + """ + ) + child = testdir.spawn_pytest(str(p1) + " -s") + child.expect("target_start") + child.expect(r"\(Pdb") + child.sendline("evt2.set()") + child.expect("= 1 passed in") # main thread exited + child.sendline("c") + child.expect(r"\(Pdb") + child.sendline("c") + child.expect("target_end") + child.wait() + rest = child.read().decode("utf8") + assert "Exception in thread" not in rest + assert child.exitstatus == 0 From 3a2a5b09ebb39e7f3d1fb6c8a8a23f414231a696 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 11 May 2019 14:14:01 +0200 Subject: [PATCH 2/2] tests: fix test_trace_after_runpytest --- testing/test_pdb.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 178a66d6e70..5cab95ae708 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1027,19 +1027,25 @@ def test_trace_after_runpytest(testdir): from _pytest.debugging import pytestPDB def test_outer(testdir): - from _pytest.debugging import pytestPDB - assert len(pytestPDB._saved) == 1 - testdir.runpytest("-k test_inner") + testdir.makepyfile( + \""" + from _pytest.debugging import pytestPDB - __import__('pdb').set_trace() + def test_inner(): + assert len(pytestPDB._saved) == 2 + print("test_inner_" + "end") + \""" + ) - def test_inner(testdir): - assert len(pytestPDB._saved) == 2 + result = testdir.runpytest("-s", "-k", "test_inner") + assert result.ret == 0 + __import__('pdb').set_trace() """ ) - child = testdir.spawn_pytest("-p pytester %s -k test_outer" % p1) + child = testdir.spawn_pytest("-s -p pytester %s -k test_outer" % p1) + child.expect("test_inner_end") child.expect(r"\(Pdb") child.sendline("c") rest = child.read().decode("utf8")