Skip to content

Commit 08d9e59

Browse files
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.
1 parent 7e246a3 commit 08d9e59

File tree

4 files changed

+719
-70
lines changed

4 files changed

+719
-70
lines changed

Lib/unittest/suite.py

Lines changed: 79 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def _handleClassSetUp(self, test, result):
149149
if getattr(currentClass, "__unittest_skip__", False):
150150
return
151151

152+
failed = False
152153
try:
153154
currentClass._classSetupFailed = False
154155
except TypeError:
@@ -157,27 +158,32 @@ def _handleClassSetUp(self, test, result):
157158
pass
158159

159160
setUpClass = getattr(currentClass, 'setUpClass', None)
161+
doClassCleanups = getattr(currentClass, 'doClassCleanups', None)
160162
if setUpClass is not None:
161163
_call_if_exists(result, '_setupStdout')
162164
try:
163-
setUpClass()
164-
except Exception as e:
165-
if isinstance(result, _DebugResult):
166-
raise
167-
currentClass._classSetupFailed = True
168-
className = util.strclass(currentClass)
169-
self._createClassOrModuleLevelException(result, e,
170-
'setUpClass',
171-
className)
165+
try:
166+
setUpClass()
167+
except Exception as e:
168+
if isinstance(result, _DebugResult):
169+
raise
170+
failed = True
171+
try:
172+
currentClass._classSetupFailed = True
173+
except TypeError:
174+
pass
175+
className = util.strclass(currentClass)
176+
self._createClassOrModuleLevelException(result, e,
177+
'setUpClass',
178+
className)
179+
if failed and doClassCleanups is not None:
180+
doClassCleanups()
181+
for exc_info in currentClass.tearDown_exceptions:
182+
self._createClassOrModuleLevelException(
183+
result, exc_info[1], 'setUpClass', className,
184+
info=exc_info)
172185
finally:
173186
_call_if_exists(result, '_restoreStdout')
174-
if currentClass._classSetupFailed is True:
175-
currentClass.doClassCleanups()
176-
if len(currentClass.tearDown_exceptions) > 0:
177-
for exc in currentClass.tearDown_exceptions:
178-
self._createClassOrModuleLevelException(
179-
result, exc[1], 'setUpClass', className,
180-
info=exc)
181187

182188
def _get_previous_module(self, result):
183189
previousModule = None
@@ -205,20 +211,22 @@ def _handleModuleFixture(self, test, result):
205211
if setUpModule is not None:
206212
_call_if_exists(result, '_setupStdout')
207213
try:
208-
setUpModule()
209-
except Exception as e:
210214
try:
211-
case.doModuleCleanups()
212-
except Exception as exc:
213-
self._createClassOrModuleLevelException(result, exc,
215+
setUpModule()
216+
except Exception as e:
217+
if isinstance(result, _DebugResult):
218+
raise
219+
result._moduleSetUpFailed = True
220+
self._createClassOrModuleLevelException(result, e,
214221
'setUpModule',
215222
currentModule)
216-
if isinstance(result, _DebugResult):
217-
raise
218-
result._moduleSetUpFailed = True
219-
self._createClassOrModuleLevelException(result, e,
220-
'setUpModule',
221-
currentModule)
223+
if result._moduleSetUpFailed:
224+
try:
225+
case.doModuleCleanups()
226+
except Exception as e:
227+
self._createClassOrModuleLevelException(result, e,
228+
'setUpModule',
229+
currentModule)
222230
finally:
223231
_call_if_exists(result, '_restoreStdout')
224232

@@ -251,30 +259,33 @@ def _handleModuleTearDown(self, result):
251259
except KeyError:
252260
return
253261

254-
tearDownModule = getattr(module, 'tearDownModule', None)
255-
if tearDownModule is not None:
256-
_call_if_exists(result, '_setupStdout')
262+
_call_if_exists(result, '_setupStdout')
263+
try:
264+
tearDownModule = getattr(module, 'tearDownModule', None)
265+
if tearDownModule is not None:
266+
try:
267+
tearDownModule()
268+
except Exception as e:
269+
if isinstance(result, _DebugResult):
270+
raise
271+
self._createClassOrModuleLevelException(result, e,
272+
'tearDownModule',
273+
previousModule)
257274
try:
258-
tearDownModule()
275+
case.doModuleCleanups()
259276
except Exception as e:
260277
if isinstance(result, _DebugResult):
261278
raise
262279
self._createClassOrModuleLevelException(result, e,
263280
'tearDownModule',
264281
previousModule)
265-
finally:
266-
_call_if_exists(result, '_restoreStdout')
267-
try:
268-
case.doModuleCleanups()
269-
except Exception as e:
270-
self._createClassOrModuleLevelException(result, e,
271-
'tearDownModule',
272-
previousModule)
282+
finally:
283+
_call_if_exists(result, '_restoreStdout')
273284

274285
def _tearDownPreviousClass(self, test, result):
275286
previousClass = getattr(result, '_previousTestClass', None)
276287
currentClass = test.__class__
277-
if currentClass == previousClass:
288+
if currentClass == previousClass or previousClass is None:
278289
return
279290
if getattr(previousClass, '_classSetupFailed', False):
280291
return
@@ -284,27 +295,34 @@ def _tearDownPreviousClass(self, test, result):
284295
return
285296

286297
tearDownClass = getattr(previousClass, 'tearDownClass', None)
287-
if tearDownClass is not None:
288-
_call_if_exists(result, '_setupStdout')
289-
try:
290-
tearDownClass()
291-
except Exception as e:
292-
if isinstance(result, _DebugResult):
293-
raise
294-
className = util.strclass(previousClass)
295-
self._createClassOrModuleLevelException(result, e,
296-
'tearDownClass',
297-
className)
298-
finally:
299-
_call_if_exists(result, '_restoreStdout')
300-
previousClass.doClassCleanups()
301-
if len(previousClass.tearDown_exceptions) > 0:
302-
for exc in previousClass.tearDown_exceptions:
303-
className = util.strclass(previousClass)
304-
self._createClassOrModuleLevelException(result, exc[1],
305-
'tearDownClass',
306-
className,
307-
info=exc)
298+
doClassCleanups = getattr(previousClass, 'doClassCleanups', None)
299+
if tearDownClass is None and doClassCleanups is None:
300+
return
301+
302+
_call_if_exists(result, '_setupStdout')
303+
try:
304+
if tearDownClass is not None:
305+
try:
306+
tearDownClass()
307+
except Exception as e:
308+
if isinstance(result, _DebugResult):
309+
raise
310+
className = util.strclass(previousClass)
311+
self._createClassOrModuleLevelException(result, e,
312+
'tearDownClass',
313+
className)
314+
if doClassCleanups is not None:
315+
doClassCleanups()
316+
for exc_info in previousClass.tearDown_exceptions:
317+
if isinstance(result, _DebugResult):
318+
raise exc_info[1]
319+
className = util.strclass(previousClass)
320+
self._createClassOrModuleLevelException(result, exc_info[1],
321+
'tearDownClass',
322+
className,
323+
info=exc_info)
324+
finally:
325+
_call_if_exists(result, '_restoreStdout')
308326

309327

310328
class _ErrorHolder(object):

0 commit comments

Comments
 (0)