Skip to content

Commit

Permalink
unittest: restore UnitTestFunction.obj to return unbound rather tha…
Browse files Browse the repository at this point in the history
…n bound method

This fixes pytest-dev#9610.

pytest 7.0.0 (unintentionally) changed `UnitTestFunction.obj`'s' behavior
to match `Function.obj`. That is probably a good thing to have, however
it evidently causes some regressions as described in the issue, so
restore the previous behavior for now. In the future we might want to
make this change again, but with proper consideration.
  • Loading branch information
bluetech committed Feb 6, 2022
1 parent 77a38a3 commit e775da4
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 e775da4

Please sign in to comment.