bpo-43913: Fix bugs in cleaning up classes and modules in unittest. (GH-28006)
* Functions registered with addModuleCleanup() were not called unless
the user defines tearDownModule() in their test module.
* Functions registered with addClassCleanup() were not called if
tearDownClass is set to None.
* Buffering in TestResult did not work with functions registered
with addClassCleanup() and addModuleCleanup().
* Errors in functions registered with addClassCleanup() and
addModuleCleanup() were not handled correctly in buffered and
debug modes.
* Errors in setUpModule() and functions registered with
addModuleCleanup() were reported in wrong order.
* And several lesser bugs.
(cherry picked from commit 08d9e597c8ef5a2b26375ac954fdf224f5d82c3c)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
diff --git a/Lib/unittest/suite.py b/Lib/unittest/suite.py
index 41993f9..6f45b6f 100644
--- a/Lib/unittest/suite.py
+++ b/Lib/unittest/suite.py
@@ -149,6 +149,7 @@ def _handleClassSetUp(self, test, result):
if getattr(currentClass, "__unittest_skip__", False):
return
+ failed = False
try:
currentClass._classSetupFailed = False
except TypeError:
@@ -157,27 +158,32 @@ def _handleClassSetUp(self, test, result):
pass
setUpClass = getattr(currentClass, 'setUpClass', None)
+ doClassCleanups = getattr(currentClass, 'doClassCleanups', None)
if setUpClass is not None:
_call_if_exists(result, '_setupStdout')
try:
- setUpClass()
- except Exception as e:
- if isinstance(result, _DebugResult):
- raise
- currentClass._classSetupFailed = True
- className = util.strclass(currentClass)
- self._createClassOrModuleLevelException(result, e,
- 'setUpClass',
- className)
+ try:
+ setUpClass()
+ except Exception as e:
+ if isinstance(result, _DebugResult):
+ raise
+ failed = True
+ try:
+ currentClass._classSetupFailed = True
+ except TypeError:
+ pass
+ className = util.strclass(currentClass)
+ self._createClassOrModuleLevelException(result, e,
+ 'setUpClass',
+ className)
+ if failed and doClassCleanups is not None:
+ doClassCleanups()
+ for exc_info in currentClass.tearDown_exceptions:
+ self._createClassOrModuleLevelException(
+ result, exc_info[1], 'setUpClass', className,
+ info=exc_info)
finally:
_call_if_exists(result, '_restoreStdout')
- if currentClass._classSetupFailed is True:
- currentClass.doClassCleanups()
- if len(currentClass.tearDown_exceptions) > 0:
- for exc in currentClass.tearDown_exceptions:
- self._createClassOrModuleLevelException(
- result, exc[1], 'setUpClass', className,
- info=exc)
def _get_previous_module(self, result):
previousModule = None
@@ -205,20 +211,22 @@ def _handleModuleFixture(self, test, result):
if setUpModule is not None:
_call_if_exists(result, '_setupStdout')
try:
- setUpModule()
- except Exception as e:
try:
- case.doModuleCleanups()
- except Exception as exc:
- self._createClassOrModuleLevelException(result, exc,
+ setUpModule()
+ except Exception as e:
+ if isinstance(result, _DebugResult):
+ raise
+ result._moduleSetUpFailed = True
+ self._createClassOrModuleLevelException(result, e,
'setUpModule',
currentModule)
- if isinstance(result, _DebugResult):
- raise
- result._moduleSetUpFailed = True
- self._createClassOrModuleLevelException(result, e,
- 'setUpModule',
- currentModule)
+ if result._moduleSetUpFailed:
+ try:
+ case.doModuleCleanups()
+ except Exception as e:
+ self._createClassOrModuleLevelException(result, e,
+ 'setUpModule',
+ currentModule)
finally:
_call_if_exists(result, '_restoreStdout')
@@ -251,30 +259,33 @@ def _handleModuleTearDown(self, result):
except KeyError:
return
- tearDownModule = getattr(module, 'tearDownModule', None)
- if tearDownModule is not None:
- _call_if_exists(result, '_setupStdout')
+ _call_if_exists(result, '_setupStdout')
+ try:
+ tearDownModule = getattr(module, 'tearDownModule', None)
+ if tearDownModule is not None:
+ try:
+ tearDownModule()
+ except Exception as e:
+ if isinstance(result, _DebugResult):
+ raise
+ self._createClassOrModuleLevelException(result, e,
+ 'tearDownModule',
+ previousModule)
try:
- tearDownModule()
+ case.doModuleCleanups()
except Exception as e:
if isinstance(result, _DebugResult):
raise
self._createClassOrModuleLevelException(result, e,
'tearDownModule',
previousModule)
- finally:
- _call_if_exists(result, '_restoreStdout')
- try:
- case.doModuleCleanups()
- except Exception as e:
- self._createClassOrModuleLevelException(result, e,
- 'tearDownModule',
- previousModule)
+ finally:
+ _call_if_exists(result, '_restoreStdout')
def _tearDownPreviousClass(self, test, result):
previousClass = getattr(result, '_previousTestClass', None)
currentClass = test.__class__
- if currentClass == previousClass:
+ if currentClass == previousClass or previousClass is None:
return
if getattr(previousClass, '_classSetupFailed', False):
return
@@ -284,27 +295,34 @@ def _tearDownPreviousClass(self, test, result):
return
tearDownClass = getattr(previousClass, 'tearDownClass', None)
- if tearDownClass is not None:
- _call_if_exists(result, '_setupStdout')
- try:
- tearDownClass()
- except Exception as e:
- if isinstance(result, _DebugResult):
- raise
- className = util.strclass(previousClass)
- self._createClassOrModuleLevelException(result, e,
- 'tearDownClass',
- className)
- finally:
- _call_if_exists(result, '_restoreStdout')
- previousClass.doClassCleanups()
- if len(previousClass.tearDown_exceptions) > 0:
- for exc in previousClass.tearDown_exceptions:
- className = util.strclass(previousClass)
- self._createClassOrModuleLevelException(result, exc[1],
- 'tearDownClass',
- className,
- info=exc)
+ doClassCleanups = getattr(previousClass, 'doClassCleanups', None)
+ if tearDownClass is None and doClassCleanups is None:
+ return
+
+ _call_if_exists(result, '_setupStdout')
+ try:
+ if tearDownClass is not None:
+ try:
+ tearDownClass()
+ except Exception as e:
+ if isinstance(result, _DebugResult):
+ raise
+ className = util.strclass(previousClass)
+ self._createClassOrModuleLevelException(result, e,
+ 'tearDownClass',
+ className)
+ if doClassCleanups is not None:
+ doClassCleanups()
+ for exc_info in previousClass.tearDown_exceptions:
+ if isinstance(result, _DebugResult):
+ raise exc_info[1]
+ className = util.strclass(previousClass)
+ self._createClassOrModuleLevelException(result, exc_info[1],
+ 'tearDownClass',
+ className,
+ info=exc_info)
+ finally:
+ _call_if_exists(result, '_restoreStdout')
class _ErrorHolder(object):