Skip to content

Commit 303133f

Browse files
authored
Merge pull request #3647 from jeffreyrack/3610-add-trace-option
3610 add trace option
2 parents 1e94ac7 + 067de25 commit 303133f

File tree

4 files changed

+87
-2
lines changed

4 files changed

+87
-2
lines changed

changelog/3610.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added the `--trace` option to enter the debugger at the start of a test.

doc/en/usage.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ for example::
171171
>>> sys.last_value
172172
AssertionError('assert result == "ok"',)
173173

174+
.. _trace-option:
175+
176+
Dropping to PDB_ (Python Debugger) at the start of a test
177+
----------------------------------------------------------
178+
179+
180+
``pytest`` allows one to drop into the PDB_ prompt immediately at the start of each test via a command line option::
181+
182+
pytest --trace
183+
184+
This will invoke the Python debugger at the start of every test.
185+
174186
.. _breakpoints:
175187

176188
Setting breakpoints

src/_pytest/debugging.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import os
66
from doctest import UnexpectedException
77

8+
from _pytest.config import hookimpl
9+
810
try:
911
from builtins import breakpoint # noqa
1012

@@ -28,6 +30,12 @@ def pytest_addoption(parser):
2830
help="start a custom interactive Python debugger on errors. "
2931
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
3032
)
33+
group._addoption(
34+
"--trace",
35+
dest="trace",
36+
action="store_true",
37+
help="Immediately break when running each test.",
38+
)
3139

3240

3341
def pytest_configure(config):
@@ -38,6 +46,8 @@ def pytest_configure(config):
3846
else:
3947
pdb_cls = pdb.Pdb
4048

49+
if config.getvalue("trace"):
50+
config.pluginmanager.register(PdbTrace(), "pdbtrace")
4151
if config.getvalue("usepdb"):
4252
config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
4353

@@ -71,7 +81,7 @@ class pytestPDB(object):
7181
_pdb_cls = pdb.Pdb
7282

7383
@classmethod
74-
def set_trace(cls):
84+
def set_trace(cls, set_break=True):
7585
""" invoke PDB set_trace debugging, dropping any IO capturing. """
7686
import _pytest.config
7787

@@ -84,7 +94,8 @@ def set_trace(cls):
8494
tw.line()
8595
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
8696
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
87-
cls._pdb_cls().set_trace(frame)
97+
if set_break:
98+
cls._pdb_cls().set_trace(frame)
8899

89100

90101
class PdbInvoke(object):
@@ -104,6 +115,30 @@ def pytest_internalerror(self, excrepr, excinfo):
104115
post_mortem(tb)
105116

106117

118+
class PdbTrace(object):
119+
@hookimpl(hookwrapper=True)
120+
def pytest_pyfunc_call(self, pyfuncitem):
121+
_test_pytest_function(pyfuncitem)
122+
yield
123+
124+
125+
def _test_pytest_function(pyfuncitem):
126+
pytestPDB.set_trace(set_break=False)
127+
testfunction = pyfuncitem.obj
128+
pyfuncitem.obj = pdb.runcall
129+
if pyfuncitem._isyieldedfunction():
130+
arg_list = list(pyfuncitem._args)
131+
arg_list.insert(0, testfunction)
132+
pyfuncitem._args = tuple(arg_list)
133+
else:
134+
if "func" in pyfuncitem._fixtureinfo.argnames:
135+
raise ValueError("--trace can't be used with a fixture named func!")
136+
pyfuncitem.funcargs["func"] = testfunction
137+
new_list = list(pyfuncitem._fixtureinfo.argnames)
138+
new_list.append("func")
139+
pyfuncitem._fixtureinfo.argnames = tuple(new_list)
140+
141+
107142
def _enter_pdb(node, excinfo, rep):
108143
# XXX we re-use the TerminalReporter's terminalwriter
109144
# because this seems to avoid some encoding related troubles

testing/test_pdb.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,3 +696,40 @@ def test_1():
696696
assert "1 failed" in rest
697697
assert "reading from stdin while output" not in rest
698698
TestPDB.flush(child)
699+
700+
701+
class TestTraceOption:
702+
def test_trace_sets_breakpoint(self, testdir):
703+
p1 = testdir.makepyfile(
704+
"""
705+
def test_1():
706+
assert True
707+
"""
708+
)
709+
child = testdir.spawn_pytest("--trace " + str(p1))
710+
child.expect("test_1")
711+
child.expect("(Pdb)")
712+
child.sendeof()
713+
rest = child.read().decode("utf8")
714+
assert "1 passed" in rest
715+
assert "reading from stdin while output" not in rest
716+
TestPDB.flush(child)
717+
718+
def test_trace_against_yield_test(self, testdir):
719+
p1 = testdir.makepyfile(
720+
"""
721+
def is_equal(a, b):
722+
assert a == b
723+
724+
def test_1():
725+
yield is_equal, 1, 1
726+
"""
727+
)
728+
child = testdir.spawn_pytest("--trace " + str(p1))
729+
child.expect("is_equal")
730+
child.expect("(Pdb)")
731+
child.sendeof()
732+
rest = child.read().decode("utf8")
733+
assert "1 passed" in rest
734+
assert "reading from stdin while output" not in rest
735+
TestPDB.flush(child)

0 commit comments

Comments
 (0)