mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
gh-134565: Use ExceptionGroup to handle multiple errors in unittest.doModuleCleanups() (GH-134566)
This commit is contained in:
parent
77eade39f9
commit
393773ae87
5 changed files with 101 additions and 15 deletions
|
@ -1282,14 +1282,22 @@ class TestOutputBuffering(unittest.TestCase):
|
||||||
suite(result)
|
suite(result)
|
||||||
expected_out = '\nStdout:\ndo cleanup2\ndo cleanup1\n'
|
expected_out = '\nStdout:\ndo cleanup2\ndo cleanup1\n'
|
||||||
self.assertEqual(stdout.getvalue(), expected_out)
|
self.assertEqual(stdout.getvalue(), expected_out)
|
||||||
self.assertEqual(len(result.errors), 1)
|
self.assertEqual(len(result.errors), 2)
|
||||||
description = 'tearDownModule (Module)'
|
description = 'tearDownModule (Module)'
|
||||||
test_case, formatted_exc = result.errors[0]
|
test_case, formatted_exc = result.errors[0]
|
||||||
self.assertEqual(test_case.description, description)
|
self.assertEqual(test_case.description, description)
|
||||||
self.assertIn('ValueError: bad cleanup2', formatted_exc)
|
self.assertIn('ValueError: bad cleanup2', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
self.assertNotIn('TypeError', formatted_exc)
|
self.assertNotIn('TypeError', formatted_exc)
|
||||||
self.assertIn(expected_out, formatted_exc)
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
|
||||||
|
test_case, formatted_exc = result.errors[1]
|
||||||
|
self.assertEqual(test_case.description, description)
|
||||||
|
self.assertIn('TypeError: bad cleanup1', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
|
self.assertNotIn('ValueError', formatted_exc)
|
||||||
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
|
||||||
def testBufferSetUpModule_DoModuleCleanups(self):
|
def testBufferSetUpModule_DoModuleCleanups(self):
|
||||||
with captured_stdout() as stdout:
|
with captured_stdout() as stdout:
|
||||||
result = unittest.TestResult()
|
result = unittest.TestResult()
|
||||||
|
@ -1313,22 +1321,34 @@ class TestOutputBuffering(unittest.TestCase):
|
||||||
suite(result)
|
suite(result)
|
||||||
expected_out = '\nStdout:\nset up module\ndo cleanup2\ndo cleanup1\n'
|
expected_out = '\nStdout:\nset up module\ndo cleanup2\ndo cleanup1\n'
|
||||||
self.assertEqual(stdout.getvalue(), expected_out)
|
self.assertEqual(stdout.getvalue(), expected_out)
|
||||||
self.assertEqual(len(result.errors), 2)
|
self.assertEqual(len(result.errors), 3)
|
||||||
description = 'setUpModule (Module)'
|
description = 'setUpModule (Module)'
|
||||||
test_case, formatted_exc = result.errors[0]
|
test_case, formatted_exc = result.errors[0]
|
||||||
self.assertEqual(test_case.description, description)
|
self.assertEqual(test_case.description, description)
|
||||||
self.assertIn('ZeroDivisionError: division by zero', formatted_exc)
|
self.assertIn('ZeroDivisionError: division by zero', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
self.assertNotIn('ValueError', formatted_exc)
|
self.assertNotIn('ValueError', formatted_exc)
|
||||||
self.assertNotIn('TypeError', formatted_exc)
|
self.assertNotIn('TypeError', formatted_exc)
|
||||||
self.assertIn('\nStdout:\nset up module\n', formatted_exc)
|
self.assertIn('\nStdout:\nset up module\n', formatted_exc)
|
||||||
|
|
||||||
test_case, formatted_exc = result.errors[1]
|
test_case, formatted_exc = result.errors[1]
|
||||||
self.assertIn(expected_out, formatted_exc)
|
self.assertIn(expected_out, formatted_exc)
|
||||||
self.assertEqual(test_case.description, description)
|
self.assertEqual(test_case.description, description)
|
||||||
self.assertIn('ValueError: bad cleanup2', formatted_exc)
|
self.assertIn('ValueError: bad cleanup2', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
self.assertNotIn('ZeroDivisionError', formatted_exc)
|
self.assertNotIn('ZeroDivisionError', formatted_exc)
|
||||||
self.assertNotIn('TypeError', formatted_exc)
|
self.assertNotIn('TypeError', formatted_exc)
|
||||||
self.assertIn(expected_out, formatted_exc)
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
|
||||||
|
test_case, formatted_exc = result.errors[2]
|
||||||
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
self.assertEqual(test_case.description, description)
|
||||||
|
self.assertIn('TypeError: bad cleanup1', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
|
self.assertNotIn('ZeroDivisionError', formatted_exc)
|
||||||
|
self.assertNotIn('ValueError', formatted_exc)
|
||||||
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
|
||||||
def testBufferTearDownModule_DoModuleCleanups(self):
|
def testBufferTearDownModule_DoModuleCleanups(self):
|
||||||
with captured_stdout() as stdout:
|
with captured_stdout() as stdout:
|
||||||
result = unittest.TestResult()
|
result = unittest.TestResult()
|
||||||
|
@ -1355,21 +1375,32 @@ class TestOutputBuffering(unittest.TestCase):
|
||||||
suite(result)
|
suite(result)
|
||||||
expected_out = '\nStdout:\ntear down module\ndo cleanup2\ndo cleanup1\n'
|
expected_out = '\nStdout:\ntear down module\ndo cleanup2\ndo cleanup1\n'
|
||||||
self.assertEqual(stdout.getvalue(), expected_out)
|
self.assertEqual(stdout.getvalue(), expected_out)
|
||||||
self.assertEqual(len(result.errors), 2)
|
self.assertEqual(len(result.errors), 3)
|
||||||
description = 'tearDownModule (Module)'
|
description = 'tearDownModule (Module)'
|
||||||
test_case, formatted_exc = result.errors[0]
|
test_case, formatted_exc = result.errors[0]
|
||||||
self.assertEqual(test_case.description, description)
|
self.assertEqual(test_case.description, description)
|
||||||
self.assertIn('ZeroDivisionError: division by zero', formatted_exc)
|
self.assertIn('ZeroDivisionError: division by zero', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
self.assertNotIn('ValueError', formatted_exc)
|
self.assertNotIn('ValueError', formatted_exc)
|
||||||
self.assertNotIn('TypeError', formatted_exc)
|
self.assertNotIn('TypeError', formatted_exc)
|
||||||
self.assertIn('\nStdout:\ntear down module\n', formatted_exc)
|
self.assertIn('\nStdout:\ntear down module\n', formatted_exc)
|
||||||
|
|
||||||
test_case, formatted_exc = result.errors[1]
|
test_case, formatted_exc = result.errors[1]
|
||||||
self.assertEqual(test_case.description, description)
|
self.assertEqual(test_case.description, description)
|
||||||
self.assertIn('ValueError: bad cleanup2', formatted_exc)
|
self.assertIn('ValueError: bad cleanup2', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
self.assertNotIn('ZeroDivisionError', formatted_exc)
|
self.assertNotIn('ZeroDivisionError', formatted_exc)
|
||||||
self.assertNotIn('TypeError', formatted_exc)
|
self.assertNotIn('TypeError', formatted_exc)
|
||||||
self.assertIn(expected_out, formatted_exc)
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
|
||||||
|
test_case, formatted_exc = result.errors[2]
|
||||||
|
self.assertEqual(test_case.description, description)
|
||||||
|
self.assertIn('TypeError: bad cleanup1', formatted_exc)
|
||||||
|
self.assertNotIn('ExceptionGroup', formatted_exc)
|
||||||
|
self.assertNotIn('ZeroDivisionError', formatted_exc)
|
||||||
|
self.assertNotIn('ValueError', formatted_exc)
|
||||||
|
self.assertIn(expected_out, formatted_exc)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -13,6 +13,7 @@ from test.test_unittest.support import (
|
||||||
LoggingResult,
|
LoggingResult,
|
||||||
ResultWithNoStartTestRunStopTestRun,
|
ResultWithNoStartTestRunStopTestRun,
|
||||||
)
|
)
|
||||||
|
from test.support.testcase import ExceptionIsLikeMixin
|
||||||
|
|
||||||
|
|
||||||
def resultFactory(*_):
|
def resultFactory(*_):
|
||||||
|
@ -604,7 +605,7 @@ class TestClassCleanup(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
@support.force_not_colorized_test_class
|
@support.force_not_colorized_test_class
|
||||||
class TestModuleCleanUp(unittest.TestCase):
|
class TestModuleCleanUp(ExceptionIsLikeMixin, unittest.TestCase):
|
||||||
def test_add_and_do_ModuleCleanup(self):
|
def test_add_and_do_ModuleCleanup(self):
|
||||||
module_cleanups = []
|
module_cleanups = []
|
||||||
|
|
||||||
|
@ -646,11 +647,50 @@ class TestModuleCleanUp(unittest.TestCase):
|
||||||
[(module_cleanup_good, (1, 2, 3),
|
[(module_cleanup_good, (1, 2, 3),
|
||||||
dict(four='hello', five='goodbye')),
|
dict(four='hello', five='goodbye')),
|
||||||
(module_cleanup_bad, (), {})])
|
(module_cleanup_bad, (), {})])
|
||||||
with self.assertRaises(CustomError) as e:
|
with self.assertRaises(Exception) as e:
|
||||||
unittest.case.doModuleCleanups()
|
unittest.case.doModuleCleanups()
|
||||||
self.assertEqual(str(e.exception), 'CleanUpExc')
|
self.assertExceptionIsLike(e.exception,
|
||||||
|
ExceptionGroup('module cleanup failed',
|
||||||
|
[CustomError('CleanUpExc')]))
|
||||||
self.assertEqual(unittest.case._module_cleanups, [])
|
self.assertEqual(unittest.case._module_cleanups, [])
|
||||||
|
|
||||||
|
def test_doModuleCleanup_with_multiple_errors_in_addModuleCleanup(self):
|
||||||
|
def module_cleanup_bad1():
|
||||||
|
raise TypeError('CleanUpExc1')
|
||||||
|
|
||||||
|
def module_cleanup_bad2():
|
||||||
|
raise ValueError('CleanUpExc2')
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
unittest.addModuleCleanup(module_cleanup_bad1)
|
||||||
|
unittest.addModuleCleanup(module_cleanup_bad2)
|
||||||
|
with self.assertRaises(ExceptionGroup) as e:
|
||||||
|
unittest.case.doModuleCleanups()
|
||||||
|
self.assertExceptionIsLike(e.exception,
|
||||||
|
ExceptionGroup('module cleanup failed', [
|
||||||
|
ValueError('CleanUpExc2'),
|
||||||
|
TypeError('CleanUpExc1'),
|
||||||
|
]))
|
||||||
|
|
||||||
|
def test_doModuleCleanup_with_exception_group_in_addModuleCleanup(self):
|
||||||
|
def module_cleanup_bad():
|
||||||
|
raise ExceptionGroup('CleanUpExc', [
|
||||||
|
ValueError('CleanUpExc2'),
|
||||||
|
TypeError('CleanUpExc1'),
|
||||||
|
])
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
unittest.addModuleCleanup(module_cleanup_bad)
|
||||||
|
with self.assertRaises(ExceptionGroup) as e:
|
||||||
|
unittest.case.doModuleCleanups()
|
||||||
|
self.assertExceptionIsLike(e.exception,
|
||||||
|
ExceptionGroup('module cleanup failed', [
|
||||||
|
ExceptionGroup('CleanUpExc', [
|
||||||
|
ValueError('CleanUpExc2'),
|
||||||
|
TypeError('CleanUpExc1'),
|
||||||
|
]),
|
||||||
|
]))
|
||||||
|
|
||||||
def test_addModuleCleanup_arg_errors(self):
|
def test_addModuleCleanup_arg_errors(self):
|
||||||
cleanups = []
|
cleanups = []
|
||||||
def cleanup(*args, **kwargs):
|
def cleanup(*args, **kwargs):
|
||||||
|
@ -871,9 +911,11 @@ class TestModuleCleanUp(unittest.TestCase):
|
||||||
ordering = []
|
ordering = []
|
||||||
blowUp = True
|
blowUp = True
|
||||||
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest)
|
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestableTest)
|
||||||
with self.assertRaises(CustomError) as cm:
|
with self.assertRaises(Exception) as cm:
|
||||||
suite.debug()
|
suite.debug()
|
||||||
self.assertEqual(str(cm.exception), 'CleanUpExc')
|
self.assertExceptionIsLike(cm.exception,
|
||||||
|
ExceptionGroup('module cleanup failed',
|
||||||
|
[CustomError('CleanUpExc')]))
|
||||||
self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'test',
|
self.assertEqual(ordering, ['setUpModule', 'setUpClass', 'test',
|
||||||
'tearDownClass', 'tearDownModule', 'cleanup_exc'])
|
'tearDownClass', 'tearDownModule', 'cleanup_exc'])
|
||||||
self.assertEqual(unittest.case._module_cleanups, [])
|
self.assertEqual(unittest.case._module_cleanups, [])
|
||||||
|
|
|
@ -149,9 +149,7 @@ def doModuleCleanups():
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
exceptions.append(exc)
|
exceptions.append(exc)
|
||||||
if exceptions:
|
if exceptions:
|
||||||
# Swallows all but first exception. If a multi-exception handler
|
raise ExceptionGroup('module cleanup failed', exceptions)
|
||||||
# gets written we should use that here instead.
|
|
||||||
raise exceptions[0]
|
|
||||||
|
|
||||||
|
|
||||||
def skip(reason):
|
def skip(reason):
|
||||||
|
|
|
@ -223,6 +223,11 @@ class TestSuite(BaseTestSuite):
|
||||||
if result._moduleSetUpFailed:
|
if result._moduleSetUpFailed:
|
||||||
try:
|
try:
|
||||||
case.doModuleCleanups()
|
case.doModuleCleanups()
|
||||||
|
except ExceptionGroup as eg:
|
||||||
|
for e in eg.exceptions:
|
||||||
|
self._createClassOrModuleLevelException(result, e,
|
||||||
|
'setUpModule',
|
||||||
|
currentModule)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._createClassOrModuleLevelException(result, e,
|
self._createClassOrModuleLevelException(result, e,
|
||||||
'setUpModule',
|
'setUpModule',
|
||||||
|
@ -235,15 +240,15 @@ class TestSuite(BaseTestSuite):
|
||||||
errorName = f'{method_name} ({parent})'
|
errorName = f'{method_name} ({parent})'
|
||||||
self._addClassOrModuleLevelException(result, exc, errorName, info)
|
self._addClassOrModuleLevelException(result, exc, errorName, info)
|
||||||
|
|
||||||
def _addClassOrModuleLevelException(self, result, exception, errorName,
|
def _addClassOrModuleLevelException(self, result, exc, errorName,
|
||||||
info=None):
|
info=None):
|
||||||
error = _ErrorHolder(errorName)
|
error = _ErrorHolder(errorName)
|
||||||
addSkip = getattr(result, 'addSkip', None)
|
addSkip = getattr(result, 'addSkip', None)
|
||||||
if addSkip is not None and isinstance(exception, case.SkipTest):
|
if addSkip is not None and isinstance(exc, case.SkipTest):
|
||||||
addSkip(error, str(exception))
|
addSkip(error, str(exc))
|
||||||
else:
|
else:
|
||||||
if not info:
|
if not info:
|
||||||
result.addError(error, sys.exc_info())
|
result.addError(error, (type(exc), exc, exc.__traceback__))
|
||||||
else:
|
else:
|
||||||
result.addError(error, info)
|
result.addError(error, info)
|
||||||
|
|
||||||
|
@ -273,6 +278,13 @@ class TestSuite(BaseTestSuite):
|
||||||
previousModule)
|
previousModule)
|
||||||
try:
|
try:
|
||||||
case.doModuleCleanups()
|
case.doModuleCleanups()
|
||||||
|
except ExceptionGroup as eg:
|
||||||
|
if isinstance(result, _DebugResult):
|
||||||
|
raise
|
||||||
|
for e in eg.exceptions:
|
||||||
|
self._createClassOrModuleLevelException(result, e,
|
||||||
|
'tearDownModule',
|
||||||
|
previousModule)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if isinstance(result, _DebugResult):
|
if isinstance(result, _DebugResult):
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
:func:`unittest.doModuleCleanups` no longer swallows all but first exception
|
||||||
|
raised in the cleanup code, but raises a :exc:`ExceptionGroup` if multiple
|
||||||
|
errors occurred.
|
Loading…
Add table
Add a link
Reference in a new issue