diff --git a/changelog/12367.bugfix.rst b/changelog/12367.bugfix.rst new file mode 100644 index 00000000000..e8bf2e4f155 --- /dev/null +++ b/changelog/12367.bugfix.rst @@ -0,0 +1 @@ +Fix a regression in pytest 8.2.0 where unittest class instances (a fresh one is created for each test) were not released promptly on test teardown but only on session teardown. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 8f1791bf744..baca58bd9b2 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -212,11 +212,12 @@ def setup(self) -> None: super().setup() def teardown(self) -> None: - super().teardown() if self._explicit_tearDown is not None: self._explicit_tearDown() self._explicit_tearDown = None self._obj = None + self._instance = None + super().teardown() def startTest(self, testcase: "unittest.TestCase") -> None: pass diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 96223b22a2e..d238ffbfa74 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,5 +1,4 @@ # mypy: allow-untyped-defs -import gc import sys from typing import List @@ -192,30 +191,35 @@ def test_check(self): def test_teardown_issue1649(pytester: Pytester) -> None: """ Are TestCase objects cleaned up? Often unittest TestCase objects set - attributes that are large and expensive during setUp. + attributes that are large and expensive during test run or setUp. The TestCase will not be cleaned up if the test fails, because it would then exist in the stackframe. + + Regression test for #1649 (see also #12367). """ - testpath = pytester.makepyfile( + pytester.makepyfile( """ import unittest - class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase): - def setUp(self): - self.an_expensive_object = 1 - def test_demo(self): - pass + import gc - """ + class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase): + def test_expensive(self): + self.an_expensive_obj = object() + + def test_is_it_still_alive(self): + gc.collect() + for obj in gc.get_objects(): + if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp": + assert not hasattr(obj, "an_expensive_obj") + break + else: + assert False, "Could not find TestCaseObjectsShouldBeCleanedUp instance" + """ ) - pytester.inline_run("-s", testpath) - gc.collect() - - # Either already destroyed, or didn't run setUp. - for obj in gc.get_objects(): - if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp": - assert not hasattr(obj, "an_expensive_obj") + result = pytester.runpytest() + assert result.ret == ExitCode.OK def test_unittest_skip_issue148(pytester: Pytester) -> None: