Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
36 changes: 34 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 @@ -13,6 +15,9 @@
SUPPORTS_BREAKPOINT_BUILTIN = False


immediately_break = False


def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption(
Expand All @@ -28,6 +33,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 +49,8 @@ def pytest_configure(config):
else:
pdb_cls = pdb.Pdb

global immediately_break
immediately_break = config.getvalue("trace")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a option to the pdbinvoke plugin, which then could handle the pyfunc_call

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

Expand All @@ -63,6 +76,24 @@ def fin():
config._cleanup.append(fin)


@hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
if immediately_break:
pytestPDB.set_trace(set_break=False)
testfunction = pyfuncitem.obj
pyfuncitem.obj = pdb.runcall
if pyfuncitem._isyieldedfunction():
pyfuncitem.args = [testfunction, pyfuncitem._args]
else:
if "func" in pyfuncitem._fixtureinfo.argnames:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is already availiable in request - the fixture should be named pytest_test_function and be availiable in any case

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)
yield


class pytestPDB(object):
""" Pseudo PDB that defers to the real pdb. """

Expand All @@ -71,7 +102,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 +115,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 Down
18 changes: 18 additions & 0 deletions testing/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,21 @@ 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)