Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/3610.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added the `--trace` option to enter the debugger at the start of a test.
12 changes: 12 additions & 0 deletions doc/en/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ for example::
>>> sys.last_value
AssertionError('assert result == "ok"',)

.. _trace-option:

Dropping to PDB_ (Python Debugger) at the start of a test
----------------------------------------------------------


``pytest`` allows one to drop into the PDB_ prompt immediately at the start of each test via a command line option::

pytest --trace

This will invoke the Python debugger at the start of every test.

.. _breakpoints:

Setting breakpoints
Expand Down
39 changes: 37 additions & 2 deletions src/_pytest/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import os
from doctest import UnexpectedException

from _pytest.config import hookimpl

try:
from builtins import breakpoint # noqa

Expand All @@ -28,6 +30,12 @@ def pytest_addoption(parser):
help="start a custom interactive Python debugger on errors. "
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
)
group._addoption(
"--trace",
dest="trace",
action="store_true",
help="Immediately break when running each test.",
)


def pytest_configure(config):
Expand All @@ -38,6 +46,8 @@ def pytest_configure(config):
else:
pdb_cls = pdb.Pdb

if config.getvalue("trace"):
config.pluginmanager.register(PdbTrace(), "pdbtrace")
if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), "pdbinvoke")

Expand Down Expand Up @@ -71,7 +81,7 @@ class pytestPDB(object):
_pdb_cls = pdb.Pdb

@classmethod
def set_trace(cls):
def set_trace(cls, set_break=True):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
import _pytest.config

Expand All @@ -84,7 +94,8 @@ def set_trace(cls):
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
cls._pdb_cls().set_trace(frame)
if set_break:
cls._pdb_cls().set_trace(frame)


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


class PdbTrace(object):
@hookimpl(hookwrapper=True)
def pytest_pyfunc_call(self, pyfuncitem):
_test_pytest_function(pyfuncitem)
yield


def _test_pytest_function(pyfuncitem):
pytestPDB.set_trace(set_break=False)
testfunction = pyfuncitem.obj
pyfuncitem.obj = pdb.runcall
if pyfuncitem._isyieldedfunction():
arg_list = list(pyfuncitem._args)
arg_list.insert(0, testfunction)
pyfuncitem._args = tuple(arg_list)
else:
if "func" in pyfuncitem._fixtureinfo.argnames:
raise ValueError("--trace can't be used with a fixture named func!")
pyfuncitem.funcargs["func"] = testfunction
new_list = list(pyfuncitem._fixtureinfo.argnames)
new_list.append("func")
pyfuncitem._fixtureinfo.argnames = tuple(new_list)


def _enter_pdb(node, excinfo, rep):
# XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles
Expand Down
37 changes: 37 additions & 0 deletions testing/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,40 @@ def test_1():
assert "1 failed" in rest
assert "reading from stdin while output" not in rest
TestPDB.flush(child)


class TestTraceOption:
def test_trace_sets_breakpoint(self, testdir):
p1 = testdir.makepyfile(
"""
def test_1():
assert True
"""
)
child = testdir.spawn_pytest("--trace " + str(p1))
child.expect("test_1")
child.expect("(Pdb)")
child.sendeof()
rest = child.read().decode("utf8")
assert "1 passed" in rest
assert "reading from stdin while output" not in rest
TestPDB.flush(child)

def test_trace_against_yield_test(self, testdir):
p1 = testdir.makepyfile(
"""
def is_equal(a, b):
assert a == b

def test_1():
yield is_equal, 1, 1
"""
)
child = testdir.spawn_pytest("--trace " + str(p1))
child.expect("is_equal")
child.expect("(Pdb)")
child.sendeof()
rest = child.read().decode("utf8")
assert "1 passed" in rest
assert "reading from stdin while output" not in rest
TestPDB.flush(child)