Skip to content

Commit

Permalink
bpo-43913: Fix bugs in cleaning up classes and modules in unittest. (p…
Browse files Browse the repository at this point in the history
…ythonGH-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.
  • Loading branch information
serhiy-storchaka authored Aug 30, 2021
1 parent 7e246a3 commit 08d9e59
Show file tree
Hide file tree
Showing 4 changed files with 719 additions and 70 deletions.
140 changes: 79 additions & 61 deletions Lib/unittest/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def _handleClassSetUp(self, test, result):
if getattr(currentClass, "__unittest_skip__", False):
return

failed = False
try:
currentClass._classSetupFailed = False
except TypeError:
Expand All @@ -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
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down
Loading

0 comments on commit 08d9e59

Please sign in to comment.