Skip to content

Commit 1463321

Browse files
Fix --trace for parametrized tests
Without this, the second time it tries to stop in a parametrized function it raises instead: `ValueError: --trace can't be used with a fixture named func!` Implementation idea, test (and changelog tweaks) thanks to @blueyed Co-Authored-By: Ronny Pfannschmidt <[email protected]>
1 parent cefe6bf commit 1463321

File tree

3 files changed

+71
-12
lines changed

3 files changed

+71
-12
lines changed

changelog/6099.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``--trace`` when used with parametrized functions.

src/_pytest/debugging.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
""" interactive debugging with PDB, the Python Debugger. """
22
import argparse
3+
import bdb
4+
import functools
35
import pdb
46
import sys
57
from doctest import UnexpectedException
@@ -274,13 +276,24 @@ def pytest_pyfunc_call(self, pyfuncitem):
274276
def _test_pytest_function(pyfuncitem):
275277
_pdb = pytestPDB._init_pdb("runcall")
276278
testfunction = pyfuncitem.obj
277-
pyfuncitem.obj = _pdb.runcall
278-
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
279-
raise ValueError("--trace can't be used with a fixture named func!")
280-
pyfuncitem.funcargs["func"] = testfunction
281-
new_list = list(pyfuncitem._fixtureinfo.argnames)
282-
new_list.append("func")
283-
pyfuncitem._fixtureinfo.argnames = tuple(new_list)
279+
280+
# we need a copy of bdb.runcall because it takes `func` as a parameter
281+
# which means we can't have a kwarg called func
282+
@functools.wraps(testfunction)
283+
def runcall(*args, **kwds):
284+
_pdb.reset()
285+
sys.settrace(_pdb.trace_dispatch)
286+
res = None
287+
try:
288+
res = testfunction(*args, **kwds)
289+
except bdb.BdbQuit:
290+
pass
291+
finally:
292+
_pdb.quitting = True
293+
sys.settrace(None)
294+
return res
295+
296+
pyfuncitem.obj = runcall
284297

285298

286299
def _enter_pdb(node, excinfo, rep):

testing/test_pdb.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,51 @@ def test_3():
10251025
assert "Exit: Quitting debugger" not in child.before.decode("utf8")
10261026
TestPDB.flush(child)
10271027

1028+
def test_trace_with_parametrize_handles_shared_fixtureinfo(self, testdir):
1029+
p1 = testdir.makepyfile(
1030+
"""
1031+
import pytest
1032+
@pytest.mark.parametrize('myparam', [1,2])
1033+
def test_1(myparam, request):
1034+
assert myparam in (1, 2)
1035+
assert request.function.__name__ == "test_1"
1036+
@pytest.mark.parametrize('func', [1,2])
1037+
def test_func(func, request):
1038+
assert func in (1, 2)
1039+
assert request.function.__name__ == "test_func"
1040+
@pytest.mark.parametrize('myparam', [1,2])
1041+
def test_func_kw(myparam, request, func="func_kw"):
1042+
assert myparam in (1, 2)
1043+
assert func == "func_kw"
1044+
assert request.function.__name__ == "test_func_kw"
1045+
"""
1046+
)
1047+
child = testdir.spawn_pytest("--trace " + str(p1))
1048+
for func, argname in [
1049+
("test_1", "myparam"),
1050+
("test_func", "func"),
1051+
("test_func_kw", "myparam"),
1052+
]:
1053+
child.expect_exact("> PDB runcall (IO-capturing turned off) >")
1054+
child.expect_exact(func)
1055+
child.expect_exact("Pdb")
1056+
child.sendline("args")
1057+
child.expect_exact("{} = 1\r\n".format(argname))
1058+
child.expect_exact("Pdb")
1059+
child.sendline("c")
1060+
child.expect_exact("Pdb")
1061+
child.sendline("args")
1062+
child.expect_exact("{} = 2\r\n".format(argname))
1063+
child.expect_exact("Pdb")
1064+
child.sendline("c")
1065+
child.expect_exact("> PDB continue (IO-capturing resumed) >")
1066+
rest = child.read().decode("utf8")
1067+
assert "6 passed in" in rest
1068+
assert "reading from stdin while output" not in rest
1069+
# Only printed once - not on stderr.
1070+
assert "Exit: Quitting debugger" not in child.before.decode("utf8")
1071+
TestPDB.flush(child)
1072+
10281073

10291074
def test_trace_after_runpytest(testdir):
10301075
"""Test that debugging's pytest_configure is re-entrant."""
@@ -1143,14 +1188,14 @@ def test():
11431188
__import__("pdb").set_trace()
11441189
""",
11451190
mypdb="""
1191+
import pdb
11461192
class Wrapped:
1147-
class MyPdb:
1193+
class MyPdb(pdb.Pdb):
11481194
def set_trace(self, *args):
11491195
print("set_trace_called", args)
11501196
1151-
def runcall(self, *args, **kwds):
1152-
print("runcall_called", args, kwds)
1153-
assert "func" in kwds
1197+
def trace_dispatch(self, frame, event, arg):
1198+
print("trace_dispatch called", frame, event, arg)
11541199
""",
11551200
)
11561201
result = testdir.runpytest(
@@ -1175,7 +1220,7 @@ def runcall(self, *args, **kwds):
11751220
str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", "--trace", syspathinsert=True
11761221
)
11771222
assert result.ret == 0
1178-
result.stdout.fnmatch_lines(["*runcall_called*", "* 1 passed in *"])
1223+
result.stdout.fnmatch_lines(["*trace_dispatch called*", "* 1 passed in *"])
11791224

11801225

11811226
def test_raises_bdbquit_with_eoferror(testdir):

0 commit comments

Comments
 (0)