Skip to content

Commit 757ada2

Browse files
committed
pdb: try to import --pdbcls in pytest_configure only
Fixes #5039.
1 parent 46df1d5 commit 757ada2

File tree

3 files changed

+61
-24
lines changed

3 files changed

+61
-24
lines changed

changelog/5039.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix regression with ``--pdbcls``, which stopped working with local modules in 4.0.0.

src/_pytest/debugging.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,18 @@
1010

1111
from _pytest import outcomes
1212
from _pytest.config import hookimpl
13+
from _pytest.config.exceptions import UsageError
1314

1415

1516
def _validate_usepdb_cls(value):
17+
"""Validate syntax of --pdbcls option."""
1618
try:
1719
modname, classname = value.split(":")
1820
except ValueError:
1921
raise argparse.ArgumentTypeError(
2022
"{!r} is not in the format 'modname:classname'".format(value)
2123
)
22-
23-
try:
24-
__import__(modname)
25-
mod = sys.modules[modname]
26-
27-
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
28-
parts = classname.split(".")
29-
pdb_cls = getattr(mod, parts[0])
30-
for part in parts[1:]:
31-
pdb_cls = getattr(pdb_cls, part)
32-
33-
return pdb_cls
34-
except Exception as exc:
35-
raise argparse.ArgumentTypeError(
36-
"could not get pdb class for {!r}: {}".format(value, exc)
37-
)
24+
return (modname, classname)
3825

3926

4027
def pytest_addoption(parser):
@@ -61,9 +48,28 @@ def pytest_addoption(parser):
6148
)
6249

6350

51+
def _import_pdbcls(modname, classname):
52+
try:
53+
__import__(modname)
54+
mod = sys.modules[modname]
55+
56+
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
57+
parts = classname.split(".")
58+
pdb_cls = getattr(mod, parts[0])
59+
for part in parts[1:]:
60+
pdb_cls = getattr(pdb_cls, part)
61+
62+
return pdb_cls
63+
except Exception as exc:
64+
value = ":".join((modname, classname))
65+
raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc))
66+
67+
6468
def pytest_configure(config):
6569
pdb_cls = config.getvalue("usepdb_cls")
66-
if not pdb_cls:
70+
if pdb_cls:
71+
pdb_cls = _import_pdbcls(*pdb_cls)
72+
else:
6773
pdb_cls = pdb.Pdb
6874

6975
if config.getvalue("trace"):

testing/test_pdb.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from __future__ import division
33
from __future__ import print_function
44

5-
import argparse
65
import os
76
import platform
87
import sys
@@ -803,13 +802,12 @@ def test_pdb_custom_cls_invalid(self, testdir):
803802
)
804803

805804
def test_pdb_validate_usepdb_cls(self, testdir):
806-
assert _validate_usepdb_cls("os.path:dirname.__name__") == "dirname"
805+
assert _validate_usepdb_cls("os.path:dirname.__name__") == (
806+
"os.path",
807+
"dirname.__name__",
808+
)
807809

808-
with pytest.raises(
809-
argparse.ArgumentTypeError,
810-
match=r"^could not get pdb class for 'pdb:DoesNotExist': .*'DoesNotExist'",
811-
):
812-
_validate_usepdb_cls("pdb:DoesNotExist")
810+
assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist")
813811

814812
def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls):
815813
p1 = testdir.makepyfile("""xxx """)
@@ -1121,3 +1119,35 @@ def test_inner({fixture}):
11211119
assert child.exitstatus == 0
11221120
assert "= 1 passed in " in rest
11231121
assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest
1122+
1123+
1124+
def test_pdbcls_via_local_module(testdir):
1125+
"""It should be imported in pytest_configure or later only."""
1126+
p1 = testdir.makepyfile(
1127+
"""
1128+
def test():
1129+
print("before_settrace")
1130+
__import__("pdb").set_trace()
1131+
""",
1132+
mypdb="""
1133+
class Wrapped:
1134+
class MyPdb:
1135+
def set_trace(self, *args):
1136+
print("mypdb_called", args)
1137+
""",
1138+
)
1139+
result = testdir.runpytest(
1140+
str(p1), "--pdbcls=really.invalid:Value", syspathinsert=True
1141+
)
1142+
result.stderr.fnmatch_lines(
1143+
[
1144+
"ERROR: --pdbcls: could not import 'really.invalid:Value': No module named *really*"
1145+
]
1146+
)
1147+
assert result.ret == 4
1148+
1149+
result = testdir.runpytest(
1150+
str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True
1151+
)
1152+
assert result.ret == 0
1153+
result.stdout.fnmatch_lines(["*mypdb_called*", "* 1 passed in *"])

0 commit comments

Comments
 (0)