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

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

""" support for skip/xfail functions and markers. """ 

from __future__ import absolute_import 

from __future__ import division 

from __future__ import print_function 

 

from _pytest.config import hookimpl 

from _pytest.mark.evaluate import MarkEvaluator 

from _pytest.outcomes import fail 

from _pytest.outcomes import skip 

from _pytest.outcomes import xfail 

 

 

def pytest_addoption(parser): 

group = parser.getgroup("general") 

group.addoption( 

"--runxfail", 

action="store_true", 

dest="runxfail", 

default=False, 

help="run tests even if they are marked xfail", 

) 

 

parser.addini( 

"xfail_strict", 

"default for the strict parameter of xfail " 

"markers when not given explicitly (default: False)", 

default=False, 

type="bool", 

) 

 

 

def pytest_configure(config): 

if config.option.runxfail: 

# yay a hack 

import pytest 

 

old = pytest.xfail 

config._cleanup.append(lambda: setattr(pytest, "xfail", old)) 

 

def nop(*args, **kwargs): 

pass 

 

nop.Exception = xfail.Exception 

setattr(pytest, "xfail", nop) 

 

config.addinivalue_line( 

"markers", 

"skip(reason=None): skip the given test function with an optional reason. " 

'Example: skip(reason="no way of currently testing this") skips the ' 

"test.", 

) 

config.addinivalue_line( 

"markers", 

"skipif(condition): skip the given test function if eval(condition) " 

"results in a True value. Evaluation happens within the " 

"module global context. Example: skipif('sys.platform == \"win32\"') " 

"skips the test if we are on the win32 platform. see " 

"https://docs.pytest.org/en/latest/skipping.html", 

) 

config.addinivalue_line( 

"markers", 

"xfail(condition, reason=None, run=True, raises=None, strict=False): " 

"mark the test function as an expected failure if eval(condition) " 

"has a True value. Optionally specify a reason for better reporting " 

"and run=False if you don't even want to execute the test function. " 

"If only specific exception(s) are expected, you can list them in " 

"raises, and if the test fails in other ways, it will be reported as " 

"a true failure. See https://docs.pytest.org/en/latest/skipping.html", 

) 

 

 

@hookimpl(tryfirst=True) 

def pytest_runtest_setup(item): 

# Check if skip or skipif are specified as pytest marks 

item._skipped_by_mark = False 

eval_skipif = MarkEvaluator(item, "skipif") 

if eval_skipif.istrue(): 

item._skipped_by_mark = True 

skip(eval_skipif.getexplanation()) 

 

for skip_info in item.iter_markers(name="skip"): 

item._skipped_by_mark = True 

if "reason" in skip_info.kwargs: 

skip(skip_info.kwargs["reason"]) 

elif skip_info.args: 

skip(skip_info.args[0]) 

else: 

skip("unconditional skip") 

 

item._evalxfail = MarkEvaluator(item, "xfail") 

check_xfail_no_run(item) 

 

 

@hookimpl(hookwrapper=True) 

def pytest_pyfunc_call(pyfuncitem): 

check_xfail_no_run(pyfuncitem) 

outcome = yield 

passed = outcome.excinfo is None 

if passed: 

check_strict_xfail(pyfuncitem) 

 

 

def check_xfail_no_run(item): 

"""check xfail(run=False)""" 

if not item.config.option.runxfail: 

evalxfail = item._evalxfail 

if evalxfail.istrue(): 

if not evalxfail.get("run", True): 

xfail("[NOTRUN] " + evalxfail.getexplanation()) 

 

 

def check_strict_xfail(pyfuncitem): 

"""check xfail(strict=True) for the given PASSING test""" 

evalxfail = pyfuncitem._evalxfail 

if evalxfail.istrue(): 

strict_default = pyfuncitem.config.getini("xfail_strict") 

is_strict_xfail = evalxfail.get("strict", strict_default) 

if is_strict_xfail: 

del pyfuncitem._evalxfail 

explanation = evalxfail.getexplanation() 

fail("[XPASS(strict)] " + explanation, pytrace=False) 

 

 

@hookimpl(hookwrapper=True) 

def pytest_runtest_makereport(item, call): 

outcome = yield 

rep = outcome.get_result() 

evalxfail = getattr(item, "_evalxfail", None) 

# unitttest special case, see setting of _unexpectedsuccess 

if hasattr(item, "_unexpectedsuccess") and rep.when == "call": 

from _pytest.compat import _is_unittest_unexpected_success_a_failure 

 

if item._unexpectedsuccess: 

rep.longrepr = "Unexpected success: {}".format(item._unexpectedsuccess) 

else: 

rep.longrepr = "Unexpected success" 

if _is_unittest_unexpected_success_a_failure(): 

rep.outcome = "failed" 

else: 

rep.outcome = "passed" 

rep.wasxfail = rep.longrepr 

elif item.config.option.runxfail: 

pass # don't interefere 

elif call.excinfo and call.excinfo.errisinstance(xfail.Exception): 

rep.wasxfail = "reason: " + call.excinfo.value.msg 

rep.outcome = "skipped" 

elif evalxfail and not rep.skipped and evalxfail.wasvalid() and evalxfail.istrue(): 

if call.excinfo: 

if evalxfail.invalidraise(call.excinfo.value): 

rep.outcome = "failed" 

else: 

rep.outcome = "skipped" 

rep.wasxfail = evalxfail.getexplanation() 

elif call.when == "call": 

strict_default = item.config.getini("xfail_strict") 

is_strict_xfail = evalxfail.get("strict", strict_default) 

explanation = evalxfail.getexplanation() 

if is_strict_xfail: 

rep.outcome = "failed" 

rep.longrepr = "[XPASS(strict)] {}".format(explanation) 

else: 

rep.outcome = "passed" 

rep.wasxfail = explanation 

elif ( 

getattr(item, "_skipped_by_mark", False) 

and rep.skipped 

and type(rep.longrepr) is tuple 

): 

# skipped by mark.skipif; change the location of the failure 

# to point to the item definition, otherwise it will display 

# the location of where the skip exception was raised within pytest 

filename, line, reason = rep.longrepr 

filename, line = item.location[:2] 

rep.longrepr = filename, line, reason 

 

 

# called by terminalreporter progress reporting 

 

 

def pytest_report_teststatus(report): 

if hasattr(report, "wasxfail"): 

if report.skipped: 

return "xfailed", "x", "XFAIL" 

elif report.passed: 

return "xpassed", "X", "XPASS" 

 

 

# called by the terminalreporter instance/plugin 

 

 

def pytest_terminal_summary(terminalreporter): 

tr = terminalreporter 

if not tr.reportchars: 

return 

 

lines = [] 

for char in tr.reportchars: 

action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None) 

action(terminalreporter, lines) 

 

if lines: 

tr._tw.sep("=", "short test summary info") 

for line in lines: 

tr._tw.line(line) 

 

 

def show_simple(terminalreporter, lines, stat): 

failed = terminalreporter.stats.get(stat) 

if failed: 

for rep in failed: 

verbose_word = _get_report_str(terminalreporter, rep) 

pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) 

lines.append("%s %s" % (verbose_word, pos)) 

 

 

def show_xfailed(terminalreporter, lines): 

xfailed = terminalreporter.stats.get("xfailed") 

if xfailed: 

for rep in xfailed: 

verbose_word = _get_report_str(terminalreporter, rep) 

pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) 

reason = rep.wasxfail 

lines.append("%s %s" % (verbose_word, pos)) 

if reason: 

lines.append(" " + str(reason)) 

 

 

def show_xpassed(terminalreporter, lines): 

xpassed = terminalreporter.stats.get("xpassed") 

if xpassed: 

for rep in xpassed: 

verbose_word = _get_report_str(terminalreporter, rep) 

pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) 

reason = rep.wasxfail 

lines.append("%s %s %s" % (verbose_word, pos, reason)) 

 

 

def folded_skips(skipped): 

d = {} 

for event in skipped: 

key = event.longrepr 

assert len(key) == 3, (event, key) 

keywords = getattr(event, "keywords", {}) 

# folding reports with global pytestmark variable 

# this is workaround, because for now we cannot identify the scope of a skip marker 

# TODO: revisit after marks scope would be fixed 

if ( 

event.when == "setup" 

and "skip" in keywords 

and "pytestmark" not in keywords 

): 

key = (key[0], None, key[2]) 

d.setdefault(key, []).append(event) 

values = [] 

for key, events in d.items(): 

values.append((len(events),) + key) 

return values 

 

 

def show_skipped(terminalreporter, lines): 

tr = terminalreporter 

skipped = tr.stats.get("skipped", []) 

if skipped: 

verbose_word = _get_report_str(terminalreporter, report=skipped[0]) 

fskips = folded_skips(skipped) 

if fskips: 

for num, fspath, lineno, reason in fskips: 

if reason.startswith("Skipped: "): 

reason = reason[9:] 

if lineno is not None: 

lines.append( 

"%s [%d] %s:%d: %s" 

% (verbose_word, num, fspath, lineno + 1, reason) 

) 

else: 

lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) 

 

 

def shower(stat): 

def show_(terminalreporter, lines): 

return show_simple(terminalreporter, lines, stat) 

 

return show_ 

 

 

def _get_report_str(terminalreporter, report): 

_category, _short, verbose = terminalreporter.config.hook.pytest_report_teststatus( 

report=report, config=terminalreporter.config 

) 

return verbose 

 

 

REPORTCHAR_ACTIONS = { 

"x": show_xfailed, 

"X": show_xpassed, 

"f": shower("failed"), 

"F": shower("failed"), 

"s": show_skipped, 

"S": show_skipped, 

"p": shower("passed"), 

"E": shower("error"), 

}