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""" discovery and running of std-library "unittest" style tests. """ 

2import sys 

3import traceback 

4 

5import _pytest._code 

6import pytest 

7from _pytest.compat import getimfunc 

8from _pytest.config import hookimpl 

9from _pytest.outcomes import exit 

10from _pytest.outcomes import fail 

11from _pytest.outcomes import skip 

12from _pytest.outcomes import xfail 

13from _pytest.python import Class 

14from _pytest.python import Function 

15from _pytest.runner import CallInfo 

16 

17 

18def pytest_pycollect_makeitem(collector, name, obj): 

19 # has unittest been imported and is obj a subclass of its TestCase? 

20 try: 

21 if not issubclass(obj, sys.modules["unittest"].TestCase): 

22 return 

23 except Exception: 

24 return 

25 # yes, so let's collect it 

26 return UnitTestCase(name, parent=collector) 

27 

28 

29class UnitTestCase(Class): 

30 # marker for fixturemanger.getfixtureinfo() 

31 # to declare that our children do not support funcargs 

32 nofuncargs = True 

33 

34 def collect(self): 

35 from unittest import TestLoader 

36 

37 cls = self.obj 

38 if not getattr(cls, "__test__", True): 

39 return 

40 

41 skipped = getattr(cls, "__unittest_skip__", False) 

42 if not skipped: 

43 self._inject_setup_teardown_fixtures(cls) 

44 self._inject_setup_class_fixture() 

45 

46 self.session._fixturemanager.parsefactories(self, unittest=True) 

47 loader = TestLoader() 

48 foundsomething = False 

49 for name in loader.getTestCaseNames(self.obj): 

50 x = getattr(self.obj, name) 

51 if not getattr(x, "__test__", True): 

52 continue 

53 funcobj = getimfunc(x) 

54 yield TestCaseFunction(name, parent=self, callobj=funcobj) 

55 foundsomething = True 

56 

57 if not foundsomething: 

58 runtest = getattr(self.obj, "runTest", None) 

59 if runtest is not None: 

60 ut = sys.modules.get("twisted.trial.unittest", None) 

61 if ut is None or runtest != ut.TestCase.runTest: 

62 yield TestCaseFunction("runTest", parent=self) 

63 

64 def _inject_setup_teardown_fixtures(self, cls): 

65 """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding 

66 teardown functions (#517)""" 

67 class_fixture = _make_xunit_fixture( 

68 cls, "setUpClass", "tearDownClass", scope="class", pass_self=False 

69 ) 

70 if class_fixture: 

71 cls.__pytest_class_setup = class_fixture 

72 

73 method_fixture = _make_xunit_fixture( 

74 cls, "setup_method", "teardown_method", scope="function", pass_self=True 

75 ) 

76 if method_fixture: 

77 cls.__pytest_method_setup = method_fixture 

78 

79 

80def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self): 

81 setup = getattr(obj, setup_name, None) 

82 teardown = getattr(obj, teardown_name, None) 

83 if setup is None and teardown is None: 

84 return None 

85 

86 @pytest.fixture(scope=scope, autouse=True) 

87 def fixture(self, request): 

88 if getattr(self, "__unittest_skip__", None): 

89 reason = self.__unittest_skip_why__ 

90 pytest.skip(reason) 

91 if setup is not None: 

92 if pass_self: 

93 setup(self, request.function) 

94 else: 

95 setup() 

96 yield 

97 if teardown is not None: 

98 if pass_self: 

99 teardown(self, request.function) 

100 else: 

101 teardown() 

102 

103 return fixture 

104 

105 

106class TestCaseFunction(Function): 

107 nofuncargs = True 

108 _excinfo = None 

109 _testcase = None 

110 

111 def setup(self): 

112 self._testcase = self.parent.obj(self.name) 

113 self._obj = getattr(self._testcase, self.name) 

114 if hasattr(self, "_request"): 

115 self._request._fillfixtures() 

116 

117 def teardown(self): 

118 self._testcase = None 

119 self._obj = None 

120 

121 def startTest(self, testcase): 

122 pass 

123 

124 def _addexcinfo(self, rawexcinfo): 

125 # unwrap potential exception info (see twisted trial support below) 

126 rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) 

127 try: 

128 excinfo = _pytest._code.ExceptionInfo(rawexcinfo) 

129 # invoke the attributes to trigger storing the traceback 

130 # trial causes some issue there 

131 excinfo.value 

132 excinfo.traceback 

133 except TypeError: 

134 try: 

135 try: 

136 values = traceback.format_exception(*rawexcinfo) 

137 values.insert( 

138 0, 

139 "NOTE: Incompatible Exception Representation, " 

140 "displaying natively:\n\n", 

141 ) 

142 fail("".join(values), pytrace=False) 

143 except (fail.Exception, KeyboardInterrupt): 

144 raise 

145 except: # noqa 

146 fail( 

147 "ERROR: Unknown Incompatible Exception " 

148 "representation:\n%r" % (rawexcinfo,), 

149 pytrace=False, 

150 ) 

151 except KeyboardInterrupt: 

152 raise 

153 except fail.Exception: 

154 excinfo = _pytest._code.ExceptionInfo.from_current() 

155 self.__dict__.setdefault("_excinfo", []).append(excinfo) 

156 

157 def addError(self, testcase, rawexcinfo): 

158 try: 

159 if isinstance(rawexcinfo[1], exit.Exception): 

160 exit(rawexcinfo[1].msg) 

161 except TypeError: 

162 pass 

163 self._addexcinfo(rawexcinfo) 

164 

165 def addFailure(self, testcase, rawexcinfo): 

166 self._addexcinfo(rawexcinfo) 

167 

168 def addSkip(self, testcase, reason): 

169 try: 

170 skip(reason) 

171 except skip.Exception: 

172 self._skipped_by_mark = True 

173 self._addexcinfo(sys.exc_info()) 

174 

175 def addExpectedFailure(self, testcase, rawexcinfo, reason=""): 

176 try: 

177 xfail(str(reason)) 

178 except xfail.Exception: 

179 self._addexcinfo(sys.exc_info()) 

180 

181 def addUnexpectedSuccess(self, testcase, reason=""): 

182 self._unexpectedsuccess = reason 

183 

184 def addSuccess(self, testcase): 

185 pass 

186 

187 def stopTest(self, testcase): 

188 pass 

189 

190 def _handle_skip(self): 

191 # implements the skipping machinery (see #2137) 

192 # analog to pythons Lib/unittest/case.py:run 

193 testMethod = getattr(self._testcase, self._testcase._testMethodName) 

194 if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr( 

195 testMethod, "__unittest_skip__", False 

196 ): 

197 # If the class or method was skipped. 

198 skip_why = getattr( 

199 self._testcase.__class__, "__unittest_skip_why__", "" 

200 ) or getattr(testMethod, "__unittest_skip_why__", "") 

201 self._testcase._addSkip(self, self._testcase, skip_why) 

202 return True 

203 return False 

204 

205 def runtest(self): 

206 if self.config.pluginmanager.get_plugin("pdbinvoke") is None: 

207 self._testcase(result=self) 

208 else: 

209 # disables tearDown and cleanups for post mortem debugging (see #1890) 

210 if self._handle_skip(): 

211 return 

212 self._testcase.debug() 

213 

214 def _prunetraceback(self, excinfo): 

215 Function._prunetraceback(self, excinfo) 

216 traceback = excinfo.traceback.filter( 

217 lambda x: not x.frame.f_globals.get("__unittest") 

218 ) 

219 if traceback: 

220 excinfo.traceback = traceback 

221 

222 

223@hookimpl(tryfirst=True) 

224def pytest_runtest_makereport(item, call): 

225 if isinstance(item, TestCaseFunction): 

226 if item._excinfo: 

227 call.excinfo = item._excinfo.pop(0) 

228 try: 

229 del call.result 

230 except AttributeError: 

231 pass 

232 

233 unittest = sys.modules.get("unittest") 

234 if unittest and call.excinfo and call.excinfo.errisinstance(unittest.SkipTest): 

235 # let's substitute the excinfo with a pytest.skip one 

236 call2 = CallInfo.from_call( 

237 lambda: pytest.skip(str(call.excinfo.value)), call.when 

238 ) 

239 call.excinfo = call2.excinfo 

240 

241 

242# twisted trial support 

243 

244 

245@hookimpl(hookwrapper=True) 

246def pytest_runtest_protocol(item): 

247 if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: 

248 ut = sys.modules["twisted.python.failure"] 

249 Failure__init__ = ut.Failure.__init__ 

250 check_testcase_implements_trial_reporter() 

251 

252 def excstore( 

253 self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None 

254 ): 

255 if exc_value is None: 

256 self._rawexcinfo = sys.exc_info() 

257 else: 

258 if exc_type is None: 

259 exc_type = type(exc_value) 

260 self._rawexcinfo = (exc_type, exc_value, exc_tb) 

261 try: 

262 Failure__init__( 

263 self, exc_value, exc_type, exc_tb, captureVars=captureVars 

264 ) 

265 except TypeError: 

266 Failure__init__(self, exc_value, exc_type, exc_tb) 

267 

268 ut.Failure.__init__ = excstore 

269 yield 

270 ut.Failure.__init__ = Failure__init__ 

271 else: 

272 yield 

273 

274 

275def check_testcase_implements_trial_reporter(done=[]): 

276 if done: 

277 return 

278 from zope.interface import classImplements 

279 from twisted.trial.itrial import IReporter 

280 

281 classImplements(TestCaseFunction, IReporter) 

282 done.append(1)