Skip to content

Commit

Permalink
[7.0.x] unittest: restore UnitTestFunction.obj to return unbound ra…
Browse files Browse the repository at this point in the history
…ther than bound method (#9656)

Co-authored-by: Ran Benita <ran@unusedvar.com>
  • Loading branch information
github-actions[bot] and bluetech committed Feb 9, 2022
1 parent ed8255c commit 6684110
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 0 deletions.
3 changes: 3 additions & 0 deletions changelog/9610.bugfix.rst
@@ -0,0 +1,3 @@
Restore `UnitTestFunction.obj` to return unbound rather than bound method.
Fixes a crash during a failed teardown in unittest TestCases with non-default `__init__`.
Regressed in pytest 7.0.0.
9 changes: 9 additions & 0 deletions src/_pytest/unittest.py
Expand Up @@ -185,6 +185,15 @@ class TestCaseFunction(Function):
_excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None
_testcase: Optional["unittest.TestCase"] = None

def _getobj(self):
assert self.parent is not None
# Unlike a regular Function in a Class, where `item.obj` returns
# a *bound* method (attached to an instance), TestCaseFunction's
# `obj` returns an *unbound* method (not attached to an instance).
# This inconsistency is probably not desirable, but needs some
# consideration before changing.
return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined]

def setup(self) -> None:
# A bound method to be called during teardown() if set (see 'runtest()').
self._explicit_tearDown: Optional[Callable[[], None]] = None
Expand Down
26 changes: 26 additions & 0 deletions testing/test_unittest.py
Expand Up @@ -1472,3 +1472,29 @@ def test_cleanup_called_the_right_number_of_times():
passed, skipped, failed = reprec.countoutcomes()
assert failed == 2
assert passed == 1


def test_traceback_pruning(pytester: Pytester) -> None:
"""Regression test for #9610 - doesn't crash during traceback pruning."""
pytester.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
def __init__(self, test_method):
unittest.TestCase.__init__(self, test_method)
class TestIt(MyTestCase):
@classmethod
def tearDownClass(cls) -> None:
assert False
def test_it(self):
pass
"""
)
reprec = pytester.inline_run()
passed, skipped, failed = reprec.countoutcomes()
assert passed == 1
assert failed == 1
assert reprec.ret == 1

0 comments on commit 6684110

Please sign in to comment.