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 Damian Skrzypczak
Daniel Grana Daniel Grana
Daniel Hahler Daniel Hahler
Daniel Miller
Daniel Nuri Daniel Nuri
Daniel Sánchez Castelló Daniel Sánchez Castelló
Daniel Valenzuela Zenteno 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 return None
cleanup = getattr(cls, "doClassCleanups", lambda: 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( def unittest_setup_class_fixture(
request: FixtureRequest, request: FixtureRequest,
) -> Generator[None, None, None]: ) -> Generator[None, None, None]:
@ -125,6 +138,7 @@ class UnitTestCase(Class):
# follow this here. # follow this here.
except Exception: except Exception:
cleanup() cleanup()
process_teardown_exceptions(raise_last=False)
raise raise
yield yield
try: try:
@ -132,6 +146,7 @@ class UnitTestCase(Class):
teardown() teardown()
finally: finally:
cleanup() cleanup()
process_teardown_exceptions(raise_last=True)
self.session._fixturemanager._register_fixture( self.session._fixturemanager._register_fixture(
# Use a unique name to speed up lookup. # 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 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: def test_traceback_pruning(pytester: Pytester) -> None:
"""Regression test for #9610 - doesn't crash during traceback pruning.""" """Regression test for #9610 - doesn't crash during traceback pruning."""
pytester.makepyfile( pytester.makepyfile(