unittest: report class cleanup exceptions (#12250)
Fixes #11728 --------- Co-authored-by: Bruno Oliveira <bruno@soliv.dev>
This commit is contained in:
parent
d208c1d4a5
commit
7e7503c0b0
1
AUTHORS
1
AUTHORS
|
@ -101,6 +101,7 @@ Cyrus Maden
|
|||
Damian Skrzypczak
|
||||
Daniel Grana
|
||||
Daniel Hahler
|
||||
Daniel Miller
|
||||
Daniel Nuri
|
||||
Daniel Sánchez Castelló
|
||||
Daniel Valenzuela Zenteno
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
For ``unittest``-based tests, exceptions during class cleanup (as raised by functions registered with :meth:`TestCase.addClassCleanup <unittest.TestCase.addClassCleanup>`) are now reported instead of silently failing.
|
|
@ -32,6 +32,9 @@ from _pytest.runner import CallInfo
|
|||
import pytest
|
||||
|
||||
|
||||
if sys.version_info[:2] < (3, 11):
|
||||
from exceptiongroup import ExceptionGroup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import unittest
|
||||
|
||||
|
@ -111,6 +114,20 @@ class UnitTestCase(Class):
|
|||
return None
|
||||
cleanup = getattr(cls, "doClassCleanups", lambda: None)
|
||||
|
||||
def process_teardown_exceptions() -> None:
|
||||
# tearDown_exceptions is a list set in the class containing exc_infos for errors during
|
||||
# teardown for the class.
|
||||
exc_infos = getattr(cls, "tearDown_exceptions", None)
|
||||
if not exc_infos:
|
||||
return
|
||||
exceptions = [exc for (_, exc, _) in exc_infos]
|
||||
# If a single exception, raise it directly as this provides a more readable
|
||||
# error (hopefully this will improve in #12255).
|
||||
if len(exceptions) == 1:
|
||||
raise exceptions[0]
|
||||
else:
|
||||
raise ExceptionGroup("Unittest class cleanup errors", exceptions)
|
||||
|
||||
def unittest_setup_class_fixture(
|
||||
request: FixtureRequest,
|
||||
) -> Generator[None, None, None]:
|
||||
|
@ -125,6 +142,7 @@ class UnitTestCase(Class):
|
|||
# follow this here.
|
||||
except Exception:
|
||||
cleanup()
|
||||
process_teardown_exceptions()
|
||||
raise
|
||||
yield
|
||||
try:
|
||||
|
@ -132,6 +150,7 @@ class UnitTestCase(Class):
|
|||
teardown()
|
||||
finally:
|
||||
cleanup()
|
||||
process_teardown_exceptions()
|
||||
|
||||
self.session._fixturemanager._register_fixture(
|
||||
# Use a unique name to speed up lookup.
|
||||
|
|
|
@ -1500,6 +1500,95 @@ def test_do_cleanups_on_teardown_failure(pytester: Pytester) -> None:
|
|||
assert passed == 1
|
||||
|
||||
|
||||
class TestClassCleanupErrors:
|
||||
"""
|
||||
Make sure to show exceptions raised during class cleanup function (those registered
|
||||
via addClassCleanup()).
|
||||
|
||||
See #11728.
|
||||
"""
|
||||
|
||||
def test_class_cleanups_failure_in_setup(self, 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.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Unittest class cleanup errors *2 sub-exceptions*",
|
||||
"*Exception: fail 1",
|
||||
"*Exception: fail 2",
|
||||
]
|
||||
)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"* ERROR at setup of MyTestCase.test *",
|
||||
"E * Exception: fail 0",
|
||||
]
|
||||
)
|
||||
|
||||
def test_class_cleanups_failure_in_teardown(self, 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.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Unittest class cleanup errors *2 sub-exceptions*",
|
||||
"*Exception: fail 1",
|
||||
"*Exception: fail 2",
|
||||
]
|
||||
)
|
||||
|
||||
def test_class_cleanup_1_failure_in_teardown(self, 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, 1)
|
||||
def test(self):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("-s", testpath)
|
||||
result.assert_outcomes(passed=1, errors=1)
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*ERROR at teardown of MyTestCase.test*",
|
||||
"*Exception: fail 1",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_traceback_pruning(pytester: Pytester) -> None:
|
||||
"""Regression test for #9610 - doesn't crash during traceback pruning."""
|
||||
pytester.makepyfile(
|
||||
|
|
Loading…
Reference in New Issue