Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

""" interactive debugging with PDB, the Python Debugger. """ 

from __future__ import absolute_import 

from __future__ import division 

from __future__ import print_function 

 

import pdb 

import sys 

from doctest import UnexpectedException 

 

from _pytest import outcomes 

from _pytest.config import hookimpl 

 

 

def pytest_addoption(parser): 

group = parser.getgroup("general") 

group._addoption( 

"--pdb", 

dest="usepdb", 

action="store_true", 

help="start the interactive Python debugger on errors or KeyboardInterrupt.", 

) 

group._addoption( 

"--pdbcls", 

dest="usepdb_cls", 

metavar="modulename:classname", 

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

if config.getvalue("usepdb_cls"): 

modname, classname = config.getvalue("usepdb_cls").split(":") 

__import__(modname) 

pdb_cls = getattr(sys.modules[modname], classname) 

else: 

pdb_cls = pdb.Pdb 

 

if config.getvalue("trace"): 

config.pluginmanager.register(PdbTrace(), "pdbtrace") 

if config.getvalue("usepdb"): 

config.pluginmanager.register(PdbInvoke(), "pdbinvoke") 

 

pytestPDB._saved.append( 

(pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config, pytestPDB._pdb_cls) 

) 

pdb.set_trace = pytestPDB.set_trace 

pytestPDB._pluginmanager = config.pluginmanager 

pytestPDB._config = config 

pytestPDB._pdb_cls = pdb_cls 

 

# NOTE: not using pytest_unconfigure, since it might get called although 

# pytest_configure was not (if another plugin raises UsageError). 

def fin(): 

( 

pdb.set_trace, 

pytestPDB._pluginmanager, 

pytestPDB._config, 

pytestPDB._pdb_cls, 

) = pytestPDB._saved.pop() 

 

config._cleanup.append(fin) 

 

 

class pytestPDB(object): 

""" Pseudo PDB that defers to the real pdb. """ 

 

_pluginmanager = None 

_config = None 

_pdb_cls = pdb.Pdb 

_saved = [] 

_recursive_debug = 0 

 

@classmethod 

def _init_pdb(cls, *args, **kwargs): 

""" Initialize PDB debugging, dropping any IO capturing. """ 

import _pytest.config 

 

if cls._pluginmanager is not None: 

capman = cls._pluginmanager.getplugin("capturemanager") 

if capman: 

capman.suspend_global_capture(in_=True) 

tw = _pytest.config.create_terminal_writer(cls._config) 

tw.line() 

if cls._recursive_debug == 0: 

# Handle header similar to pdb.set_trace in py37+. 

header = kwargs.pop("header", None) 

if header is not None: 

tw.sep(">", header) 

elif capman and capman.is_globally_capturing(): 

tw.sep(">", "PDB set_trace (IO-capturing turned off)") 

else: 

tw.sep(">", "PDB set_trace") 

 

class _PdbWrapper(cls._pdb_cls, object): 

_pytest_capman = capman 

_continued = False 

 

def do_debug(self, arg): 

cls._recursive_debug += 1 

ret = super(_PdbWrapper, self).do_debug(arg) 

cls._recursive_debug -= 1 

return ret 

 

def do_continue(self, arg): 

ret = super(_PdbWrapper, self).do_continue(arg) 

if self._pytest_capman: 

tw = _pytest.config.create_terminal_writer(cls._config) 

tw.line() 

if cls._recursive_debug == 0: 

if self._pytest_capman.is_globally_capturing(): 

tw.sep(">", "PDB continue (IO-capturing resumed)") 

else: 

tw.sep(">", "PDB continue") 

self._pytest_capman.resume_global_capture() 

cls._pluginmanager.hook.pytest_leave_pdb( 

config=cls._config, pdb=self 

) 

self._continued = True 

return ret 

 

do_c = do_cont = do_continue 

 

def set_quit(self): 

super(_PdbWrapper, self).set_quit() 

outcomes.exit("Quitting debugger") 

 

def setup(self, f, tb): 

"""Suspend on setup(). 

 

Needed after do_continue resumed, and entering another 

breakpoint again. 

""" 

ret = super(_PdbWrapper, self).setup(f, tb) 

if not ret and self._continued: 

# pdb.setup() returns True if the command wants to exit 

# from the interaction: do not suspend capturing then. 

if self._pytest_capman: 

self._pytest_capman.suspend_global_capture(in_=True) 

return ret 

 

_pdb = _PdbWrapper(**kwargs) 

cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) 

else: 

_pdb = cls._pdb_cls(**kwargs) 

return _pdb 

 

@classmethod 

def set_trace(cls, *args, **kwargs): 

"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing.""" 

frame = sys._getframe().f_back 

_pdb = cls._init_pdb(*args, **kwargs) 

_pdb.set_trace(frame) 

 

 

class PdbInvoke(object): 

def pytest_exception_interact(self, node, call, report): 

capman = node.config.pluginmanager.getplugin("capturemanager") 

if capman: 

capman.suspend_global_capture(in_=True) 

out, err = capman.read_global_capture() 

sys.stdout.write(out) 

sys.stdout.write(err) 

_enter_pdb(node, call.excinfo, report) 

 

def pytest_internalerror(self, excrepr, excinfo): 

tb = _postmortem_traceback(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): 

_pdb = pytestPDB._init_pdb() 

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 

# for not completely clear reasons. 

tw = node.config.pluginmanager.getplugin("terminalreporter")._tw 

tw.line() 

 

showcapture = node.config.option.showcapture 

 

for sectionname, content in ( 

("stdout", rep.capstdout), 

("stderr", rep.capstderr), 

("log", rep.caplog), 

): 

if showcapture in (sectionname, "all") and content: 

tw.sep(">", "captured " + sectionname) 

if content[-1:] == "\n": 

content = content[:-1] 

tw.line(content) 

 

tw.sep(">", "traceback") 

rep.toterminal(tw) 

tw.sep(">", "entering PDB") 

tb = _postmortem_traceback(excinfo) 

rep._pdbshown = True 

post_mortem(tb) 

return rep 

 

 

def _postmortem_traceback(excinfo): 

if isinstance(excinfo.value, UnexpectedException): 

# A doctest.UnexpectedException is not useful for post_mortem. 

# Use the underlying exception instead: 

return excinfo.value.exc_info[2] 

else: 

return excinfo._excinfo[2] 

 

 

def _find_last_non_hidden_frame(stack): 

i = max(0, len(stack) - 1) 

while i and stack[i][0].f_locals.get("__tracebackhide__", False): 

i -= 1 

return i 

 

 

def post_mortem(t): 

class Pdb(pytestPDB._pdb_cls): 

def get_stack(self, f, t): 

stack, i = pdb.Pdb.get_stack(self, f, t) 

if f is None: 

i = _find_last_non_hidden_frame(stack) 

return stack, i 

 

p = Pdb() 

p.reset() 

p.interaction(None, t) 

if p.quitting: 

outcomes.exit("Quitting debugger")