Report class cleanup exceptions

This commit is contained in:
Daniel Miller 2024-04-26 10:24:58 -04:00
parent 4eb8b6d525
commit 4e13a5fe3f
4 changed files with 75 additions and 0 deletions

View File

@ -101,6 +101,7 @@ Cyrus Maden
Damian Skrzypczak
Daniel Grana
Daniel Hahler
Daniel Miller
Daniel Nuri
Daniel Sánchez Castelló
Daniel Valenzuela Zenteno

View File

@ -0,0 +1 @@
Class cleanup exceptions are now reported.

View File

@ -111,6 +111,19 @@ class UnitTestCase(Class):
return None
cleanup = getattr(cls, "doClassCleanups", lambda: None)
def process_teardown_exceptions(raise_last: bool):
errors = getattr(cls, "tearDown_exceptions", None)
if not errors:
return
others = errors[:-1] if raise_last else errors
if others:
num = len(errors)
for n, (exc_type, exc, tb) in enumerate(others, start=1):
print(f"\nclass cleanup error ({n} of {num}):", file=sys.stderr)
traceback.print_exception(exc_type, exc, tb)
if raise_last:
raise errors[-1][1]
def unittest_setup_class_fixture(
request: FixtureRequest,
) -> Generator[None, None, None]:
@ -125,6 +138,7 @@ class UnitTestCase(Class):
# follow this here.
except Exception:
cleanup()
process_teardown_exceptions(raise_last=False)
raise
yield
try:
@ -132,6 +146,7 @@ class UnitTestCase(Class):
teardown()
finally:
cleanup()
process_teardown_exceptions(raise_last=True)
self.session._fixturemanager._register_fixture(
# Use a unique name to speed up lookup.

View File

@ -1500,6 +1500,64 @@ def test_do_cleanups_on_teardown_failure(pytester: Pytester) -> None:
assert passed == 1
def test_class_cleanups_failure_in_setup(pytester: Pytester) -> None:
testpath = pytester.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
def cleanup(n):
raise Exception(f"fail {n}")
cls.addClassCleanup(cleanup, 2)
cls.addClassCleanup(cleanup, 1)
raise Exception("fail 0")
def test(self):
pass
"""
)
result = pytester.runpytest('-s', testpath)
result.assert_outcomes(passed=0, errors=1)
result.stderr.fnmatch_lines([
'class cleanup error (1 of 2):',
'Exception: fail 1',
'class cleanup error (2 of 2):',
'Exception: fail 2',
])
result.stdout.fnmatch_lines([
'* ERROR at setup of MyTestCase.test *',
'E * Exception: fail 0',
])
def test_class_cleanups_failure_in_teardown(pytester: Pytester) -> None:
testpath = pytester.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
def cleanup(n):
raise Exception(f"fail {n}")
cls.addClassCleanup(cleanup, 2)
cls.addClassCleanup(cleanup, 1)
def test(self):
pass
"""
)
result = pytester.runpytest('-s', testpath)
result.assert_outcomes(passed=1, errors=1)
result.stderr.fnmatch_lines([
'class cleanup error (1 of 2):',
'Traceback *',
'Exception: fail 1',
])
result.stdout.fnmatch_lines([
'* ERROR at teardown of MyTestCase.test *',
'E * Exception: fail 2',
])
def test_traceback_pruning(pytester: Pytester) -> None:
"""Regression test for #9610 - doesn't crash during traceback pruning."""
pytester.makepyfile(