15
15
from typing import Union
16
16
17
17
import _pytest ._code
18
- from _pytest .compat import getimfunc
19
18
from _pytest .compat import is_async_function
20
19
from _pytest .config import hookimpl
21
20
from _pytest .fixtures import FixtureRequest
@@ -63,6 +62,14 @@ class UnitTestCase(Class):
63
62
# to declare that our children do not support funcargs.
64
63
nofuncargs = True
65
64
65
+ def newinstance (self ):
66
+ # TestCase __init__ takes the method (test) name. The TestCase
67
+ # constructor treats the name "runTest" as a special no-op, so it can be
68
+ # used when a dummy instance is needed. While unittest.TestCase has a
69
+ # default, some subclasses omit the default (#9610), so always supply
70
+ # it.
71
+ return self .obj ("runTest" )
72
+
66
73
def collect (self ) -> Iterable [Union [Item , Collector ]]:
67
74
from unittest import TestLoader
68
75
@@ -76,15 +83,15 @@ def collect(self) -> Iterable[Union[Item, Collector]]:
76
83
self ._register_unittest_setup_class_fixture (cls )
77
84
self ._register_setup_class_fixture ()
78
85
79
- self .session ._fixturemanager .parsefactories (self , unittest = True )
86
+ self .session ._fixturemanager .parsefactories (self .newinstance (), self .nodeid )
87
+
80
88
loader = TestLoader ()
81
89
foundsomething = False
82
90
for name in loader .getTestCaseNames (self .obj ):
83
91
x = getattr (self .obj , name )
84
92
if not getattr (x , "__test__" , True ):
85
93
continue
86
- funcobj = getimfunc (x )
87
- yield TestCaseFunction .from_parent (self , name = name , callobj = funcobj )
94
+ yield TestCaseFunction .from_parent (self , name = name )
88
95
foundsomething = True
89
96
90
97
if not foundsomething :
@@ -169,31 +176,28 @@ def unittest_setup_method_fixture(
169
176
class TestCaseFunction (Function ):
170
177
nofuncargs = True
171
178
_excinfo : Optional [List [_pytest ._code .ExceptionInfo [BaseException ]]] = None
172
- _testcase : Optional ["unittest.TestCase" ] = None
173
179
174
180
def _getobj (self ):
175
- assert self .parent is not None
176
- # Unlike a regular Function in a Class, where `item.obj` returns
177
- # a *bound* method (attached to an instance), TestCaseFunction's
178
- # `obj` returns an *unbound* method (not attached to an instance).
179
- # This inconsistency is probably not desirable, but needs some
180
- # consideration before changing.
181
- return getattr (self .parent .obj , self .originalname ) # type: ignore[attr-defined]
181
+ assert isinstance (self .parent , UnitTestCase )
182
+ testcase = self .parent .obj (self .name )
183
+ return getattr (testcase , self .name )
184
+
185
+ # Backward compat for pytest-django; can be removed after pytest-django
186
+ # updates + some slack.
187
+ @property
188
+ def _testcase (self ):
189
+ return self ._obj .__self__
182
190
183
191
def setup (self ) -> None :
184
192
# A bound method to be called during teardown() if set (see 'runtest()').
185
193
self ._explicit_tearDown : Optional [Callable [[], None ]] = None
186
- assert self .parent is not None
187
- self ._testcase = self .parent .obj (self .name ) # type: ignore[attr-defined]
188
- self ._obj = getattr (self ._testcase , self .name )
189
194
super ().setup ()
190
195
191
196
def teardown (self ) -> None :
192
197
super ().teardown ()
193
198
if self ._explicit_tearDown is not None :
194
199
self ._explicit_tearDown ()
195
200
self ._explicit_tearDown = None
196
- self ._testcase = None
197
201
self ._obj = None
198
202
199
203
def startTest (self , testcase : "unittest.TestCase" ) -> None :
@@ -292,14 +296,14 @@ def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None:
292
296
def runtest (self ) -> None :
293
297
from _pytest .debugging import maybe_wrap_pytest_function_for_tracing
294
298
295
- assert self ._testcase is not None
299
+ testcase = self .obj . __self__
296
300
297
301
maybe_wrap_pytest_function_for_tracing (self )
298
302
299
303
# Let the unittest framework handle async functions.
300
304
if is_async_function (self .obj ):
301
305
# Type ignored because self acts as the TestResult, but is not actually one.
302
- self . _testcase (result = self ) # type: ignore[arg-type]
306
+ testcase (result = self ) # type: ignore[arg-type]
303
307
else :
304
308
# When --pdb is given, we want to postpone calling tearDown() otherwise
305
309
# when entering the pdb prompt, tearDown() would have probably cleaned up
@@ -311,16 +315,16 @@ def runtest(self) -> None:
311
315
assert isinstance (self .parent , UnitTestCase )
312
316
skipped = _is_skipped (self .obj ) or _is_skipped (self .parent .obj )
313
317
if self .config .getoption ("usepdb" ) and not skipped :
314
- self ._explicit_tearDown = self . _testcase .tearDown
315
- setattr (self . _testcase , "tearDown" , lambda * args : None )
318
+ self ._explicit_tearDown = testcase .tearDown
319
+ setattr (testcase , "tearDown" , lambda * args : None )
316
320
317
321
# We need to update the actual bound method with self.obj, because
318
322
# wrap_pytest_function_for_tracing replaces self.obj by a wrapper.
319
- setattr (self . _testcase , self .name , self .obj )
323
+ setattr (testcase , self .name , self .obj )
320
324
try :
321
- self . _testcase (result = self ) # type: ignore[arg-type]
325
+ testcase (result = self ) # type: ignore[arg-type]
322
326
finally :
323
- delattr (self . _testcase , self .name )
327
+ delattr (testcase , self .name )
324
328
325
329
def _traceback_filter (
326
330
self , excinfo : _pytest ._code .ExceptionInfo [BaseException ]
0 commit comments