Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
0f221d09
Commit
0f221d09
authored
Nov 08, 2018
by
Lisa Roach
Committed by
GitHub
Nov 08, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-24412: Adds cleanUps for setUpClass and setUpModule. (GH-9190)
parent
49fa4a9f
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
783 additions
and
18 deletions
+783
-18
Doc/library/unittest.rst
Doc/library/unittest.rst
+65
-0
Doc/whatsnew/3.8.rst
Doc/whatsnew/3.8.rst
+9
-0
Lib/unittest/__init__.py
Lib/unittest/__init__.py
+4
-3
Lib/unittest/case.py
Lib/unittest/case.py
+45
-1
Lib/unittest/suite.py
Lib/unittest/suite.py
+50
-10
Lib/unittest/test/test_runner.py
Lib/unittest/test/test_runner.py
+606
-4
Misc/NEWS.d/next/Library/2018-09-11-10-51-16.bpo-24412.i-F_E5.rst
...S.d/next/Library/2018-09-11-10-51-16.bpo-24412.i-F_E5.rst
+4
-0
No files found.
Doc/library/unittest.rst
View file @
0f221d09
...
@@ -1448,6 +1448,39 @@ Test cases
...
@@ -1448,6 +1448,39 @@ Test cases
.. versionadded:: 3.1
.. versionadded:: 3.1
.. classmethod:: addClassCleanup(function, *args, **kwargs)
Add a function to be called after :meth:`tearDownClass` to cleanup
resources used during the test class. Functions will be called in reverse
order to the order they are added (:abbr:`LIFO (last-in, first-out)`).
They are called with any arguments and keyword arguments passed into
:meth:`addClassCleanup` when they are added.
If :meth:`setUpClass` fails, meaning that :meth:`tearDownClass` is not
called, then any cleanup functions added will still be called.
.. versionadded:: 3.8
.. classmethod:: doClassCleanups()
This method is called unconditionally after :meth:`tearDownClass`, or
after :meth:`setUpClass` if :meth:`setUpClass` raises an exception.
It is responsible for calling all the cleanup functions added by
:meth:`addCleanupClass`. If you need cleanup functions to be called
*prior* to :meth:`tearDownClass` then you can call
:meth:`doCleanupsClass` yourself.
:meth:`doCleanupsClass` pops methods off the stack of cleanup
functions one at a time, so it can be called at any time.
.. versionadded:: 3.8
.. class:: FunctionTestCase(testFunc, setUp=None, tearDown=None, description=None)
.. class:: FunctionTestCase(testFunc, setUp=None, tearDown=None, description=None)
...
@@ -2268,6 +2301,38 @@ module will be run and the ``tearDownModule`` will not be run. If the exception
...
@@ -2268,6 +2301,38 @@ module will be run and the ``tearDownModule`` will not be run. If the exception
:exc:`SkipTest` exception then the module will be reported as having been skipped
:exc:`SkipTest` exception then the module will be reported as having been skipped
instead of as an error.
instead of as an error.
To add cleanup code that must be run even in the case of an exception, use
``addModuleCleanup``:
.. function:: addModuleCleanup(function, *args, **kwargs)
Add a function to be called after :func:`tearDownModule` to cleanup
resources used during the test class. Functions will be called in reverse
order to the order they are added (:abbr:`LIFO (last-in, first-out)`).
They are called with any arguments and keyword arguments passed into
:meth:`addModuleCleanup` when they are added.
If :meth:`setUpModule` fails, meaning that :func:`tearDownModule` is not
called, then any cleanup functions added will still be called.
.. versionadded:: 3.8
.. function:: doModuleCleanups()
This function is called unconditionally after :func:`tearDownModule`, or
after :func:`setUpModule` if :func:`setUpModule` raises an exception.
It is responsible for calling all the cleanup functions added by
:func:`addCleanupModule`. If you need cleanup functions to be called
*prior* to :func:`tearDownModule` then you can call
:func:`doModuleCleanups` yourself.
:func:`doModuleCleanups` pops methods off the stack of cleanup
functions one at a time, so it can be called at any time.
.. versionadded:: 3.8
Signal Handling
Signal Handling
---------------
---------------
...
...
Doc/whatsnew/3.8.rst
View file @
0f221d09
...
@@ -233,6 +233,15 @@ unicodedata
...
@@ -233,6 +233,15 @@ unicodedata
is in a specific normal form. (Contributed by Max Belanger and David Euresti in
is in a specific normal form. (Contributed by Max Belanger and David Euresti in
:issue:`32285`).
:issue:`32285`).
unittest
--------
* Added :func:`~unittest.addModuleCleanup()` and
:meth:`~unittest.TestCase.addClassCleanup()` to unittest to support
cleanups for :func:`~unittest.setUpModule()` and
:meth:`~unittest.TestCase.setUpClass()`.
(Contributed by Lisa Roach in :issue:`24412`.)
venv
venv
----
----
...
...
Lib/unittest/__init__.py
View file @
0f221d09
...
@@ -48,7 +48,8 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite',
...
@@ -48,7 +48,8 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite',
'TextTestRunner'
,
'TestLoader'
,
'FunctionTestCase'
,
'main'
,
'TextTestRunner'
,
'TestLoader'
,
'FunctionTestCase'
,
'main'
,
'defaultTestLoader'
,
'SkipTest'
,
'skip'
,
'skipIf'
,
'skipUnless'
,
'defaultTestLoader'
,
'SkipTest'
,
'skip'
,
'skipIf'
,
'skipUnless'
,
'expectedFailure'
,
'TextTestResult'
,
'installHandler'
,
'expectedFailure'
,
'TextTestResult'
,
'installHandler'
,
'registerResult'
,
'removeResult'
,
'removeHandler'
]
'registerResult'
,
'removeResult'
,
'removeHandler'
,
'addModuleCleanup'
]
# Expose obsolete functions for backwards compatibility
# Expose obsolete functions for backwards compatibility
__all__
.
extend
([
'getTestCaseNames'
,
'makeSuite'
,
'findTestCases'
])
__all__
.
extend
([
'getTestCaseNames'
,
'makeSuite'
,
'findTestCases'
])
...
@@ -56,8 +57,8 @@ __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
...
@@ -56,8 +57,8 @@ __all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
__unittest
=
True
__unittest
=
True
from
.result
import
TestResult
from
.result
import
TestResult
from
.case
import
(
TestCase
,
FunctionTestCase
,
SkipTest
,
skip
,
skipIf
,
from
.case
import
(
addModuleCleanup
,
TestCase
,
FunctionTestCase
,
SkipTest
,
skip
,
skipUnless
,
expectedFailure
)
skip
If
,
skip
Unless
,
expectedFailure
)
from
.suite
import
BaseTestSuite
,
TestSuite
from
.suite
import
BaseTestSuite
,
TestSuite
from
.loader
import
(
TestLoader
,
defaultTestLoader
,
makeSuite
,
getTestCaseNames
,
from
.loader
import
(
TestLoader
,
defaultTestLoader
,
makeSuite
,
getTestCaseNames
,
findTestCases
)
findTestCases
)
...
...
Lib/unittest/case.py
View file @
0f221d09
...
@@ -84,6 +84,30 @@ class _Outcome(object):
...
@@ -84,6 +84,30 @@ class _Outcome(object):
def
_id
(
obj
):
def
_id
(
obj
):
return
obj
return
obj
_module_cleanups
=
[]
def
addModuleCleanup
(
function
,
*
args
,
**
kwargs
):
"""Same as addCleanup, except the cleanup items are called even if
setUpModule fails (unlike tearDownModule)."""
_module_cleanups
.
append
((
function
,
args
,
kwargs
))
def
doModuleCleanups
():
"""Execute all module cleanup functions. Normally called for you after
tearDownModule."""
exceptions
=
[]
while
_module_cleanups
:
function
,
args
,
kwargs
=
_module_cleanups
.
pop
()
try
:
function
(
*
args
,
**
kwargs
)
except
Exception
as
exc
:
exceptions
.
append
(
exc
)
if
exceptions
:
# Swallows all but first exception. If a multi-exception handler
# gets written we should use that here instead.
raise
exceptions
[
0
]
def
skip
(
reason
):
def
skip
(
reason
):
"""
"""
Unconditionally skip a test.
Unconditionally skip a test.
...
@@ -390,6 +414,8 @@ class TestCase(object):
...
@@ -390,6 +414,8 @@ class TestCase(object):
_classSetupFailed
=
False
_classSetupFailed
=
False
_class_cleanups
=
[]
def
__init__
(
self
,
methodName
=
'runTest'
):
def
__init__
(
self
,
methodName
=
'runTest'
):
"""Create an instance of the class that will use the named test
"""Create an instance of the class that will use the named test
method when executed. Raises a ValueError if the instance does
method when executed. Raises a ValueError if the instance does
...
@@ -445,6 +471,12 @@ class TestCase(object):
...
@@ -445,6 +471,12 @@ class TestCase(object):
Cleanup items are called even if setUp fails (unlike tearDown)."""
Cleanup items are called even if setUp fails (unlike tearDown)."""
self
.
_cleanups
.
append
((
function
,
args
,
kwargs
))
self
.
_cleanups
.
append
((
function
,
args
,
kwargs
))
@
classmethod
def
addClassCleanup
(
cls
,
function
,
*
args
,
**
kwargs
):
"""Same as addCleanup, except the cleanup items are called even if
setUpClass fails (unlike tearDownClass)."""
cls
.
_class_cleanups
.
append
((
function
,
args
,
kwargs
))
def
setUp
(
self
):
def
setUp
(
self
):
"Hook method for setting up the test fixture before exercising it."
"Hook method for setting up the test fixture before exercising it."
pass
pass
...
@@ -651,9 +683,21 @@ class TestCase(object):
...
@@ -651,9 +683,21 @@ class TestCase(object):
function
(
*
args
,
**
kwargs
)
function
(
*
args
,
**
kwargs
)
# return this for backwards compatibility
# return this for backwards compatibility
# even though we no longer us it internally
# even though we no longer us
e
it internally
return
outcome
.
success
return
outcome
.
success
@
classmethod
def
doClassCleanups
(
cls
):
"""Execute all class cleanup functions. Normally called for you after
tearDownClass."""
cls
.
tearDown_exceptions
=
[]
while
cls
.
_class_cleanups
:
function
,
args
,
kwargs
=
cls
.
_class_cleanups
.
pop
()
try
:
function
(
*
args
,
**
kwargs
)
except
Exception
as
exc
:
cls
.
tearDown_exceptions
.
append
(
sys
.
exc_info
())
def
__call__
(
self
,
*
args
,
**
kwds
):
def
__call__
(
self
,
*
args
,
**
kwds
):
return
self
.
run
(
*
args
,
**
kwds
)
return
self
.
run
(
*
args
,
**
kwds
)
...
...
Lib/unittest/suite.py
View file @
0f221d09
...
@@ -166,10 +166,18 @@ class TestSuite(BaseTestSuite):
...
@@ -166,10 +166,18 @@ class TestSuite(BaseTestSuite):
raise
raise
currentClass
.
_classSetupFailed
=
True
currentClass
.
_classSetupFailed
=
True
className
=
util
.
strclass
(
currentClass
)
className
=
util
.
strclass
(
currentClass
)
errorName
=
'setUpClass (%s)'
%
className
self
.
_createClassOrModuleLevelException
(
result
,
e
,
self
.
_addClassOrModuleLevelException
(
result
,
e
,
errorName
)
'setUpClass'
,
className
)
finally
:
finally
:
_call_if_exists
(
result
,
'_restoreStdout'
)
_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
):
def
_get_previous_module
(
self
,
result
):
previousModule
=
None
previousModule
=
None
...
@@ -199,21 +207,37 @@ class TestSuite(BaseTestSuite):
...
@@ -199,21 +207,37 @@ class TestSuite(BaseTestSuite):
try
:
try
:
setUpModule
()
setUpModule
()
except
Exception
as
e
:
except
Exception
as
e
:
try
:
case
.
doModuleCleanups
()
except
Exception
as
exc
:
self
.
_createClassOrModuleLevelException
(
result
,
exc
,
'setUpModule'
,
currentModule
)
if
isinstance
(
result
,
_DebugResult
):
if
isinstance
(
result
,
_DebugResult
):
raise
raise
result
.
_moduleSetUpFailed
=
True
result
.
_moduleSetUpFailed
=
True
errorName
=
'setUpModule (%s)'
%
currentModule
self
.
_createClassOrModuleLevelException
(
result
,
e
,
self
.
_addClassOrModuleLevelException
(
result
,
e
,
errorName
)
'setUpModule'
,
currentModule
)
finally
:
finally
:
_call_if_exists
(
result
,
'_restoreStdout'
)
_call_if_exists
(
result
,
'_restoreStdout'
)
def
_addClassOrModuleLevelException
(
self
,
result
,
exception
,
errorName
):
def
_createClassOrModuleLevelException
(
self
,
result
,
exc
,
method_name
,
parent
,
info
=
None
):
errorName
=
f'
{
method_name
}
(
{
parent
}
)'
self
.
_addClassOrModuleLevelException
(
result
,
exc
,
errorName
,
info
)
def
_addClassOrModuleLevelException
(
self
,
result
,
exception
,
errorName
,
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
(
exception
,
case
.
SkipTest
):
addSkip
(
error
,
str
(
exception
))
addSkip
(
error
,
str
(
exception
))
else
:
else
:
result
.
addError
(
error
,
sys
.
exc_info
())
if
not
info
:
result
.
addError
(
error
,
sys
.
exc_info
())
else
:
result
.
addError
(
error
,
info
)
def
_handleModuleTearDown
(
self
,
result
):
def
_handleModuleTearDown
(
self
,
result
):
previousModule
=
self
.
_get_previous_module
(
result
)
previousModule
=
self
.
_get_previous_module
(
result
)
...
@@ -235,10 +259,17 @@ class TestSuite(BaseTestSuite):
...
@@ -235,10 +259,17 @@ class TestSuite(BaseTestSuite):
except
Exception
as
e
:
except
Exception
as
e
:
if
isinstance
(
result
,
_DebugResult
):
if
isinstance
(
result
,
_DebugResult
):
raise
raise
errorName
=
'tearDownModule (%s)'
%
previousModule
self
.
_createClassOrModuleLevelException
(
result
,
e
,
self
.
_addClassOrModuleLevelException
(
result
,
e
,
errorName
)
'tearDownModule'
,
previousModule
)
finally
:
finally
:
_call_if_exists
(
result
,
'_restoreStdout'
)
_call_if_exists
(
result
,
'_restoreStdout'
)
try
:
case
.
doModuleCleanups
()
except
Exception
as
e
:
self
.
_createClassOrModuleLevelException
(
result
,
e
,
'tearDownModule'
,
previousModule
)
def
_tearDownPreviousClass
(
self
,
test
,
result
):
def
_tearDownPreviousClass
(
self
,
test
,
result
):
previousClass
=
getattr
(
result
,
'_previousTestClass'
,
None
)
previousClass
=
getattr
(
result
,
'_previousTestClass'
,
None
)
...
@@ -261,10 +292,19 @@ class TestSuite(BaseTestSuite):
...
@@ -261,10 +292,19 @@ class TestSuite(BaseTestSuite):
if
isinstance
(
result
,
_DebugResult
):
if
isinstance
(
result
,
_DebugResult
):
raise
raise
className
=
util
.
strclass
(
previousClass
)
className
=
util
.
strclass
(
previousClass
)
errorName
=
'tearDownClass (%s)'
%
className
self
.
_createClassOrModuleLevelException
(
result
,
e
,
self
.
_addClassOrModuleLevelException
(
result
,
e
,
errorName
)
'tearDownClass'
,
className
)
finally
:
finally
:
_call_if_exists
(
result
,
'_restoreStdout'
)
_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
)
class
_ErrorHolder
(
object
):
class
_ErrorHolder
(
object
):
...
...
Lib/unittest/test/test_runner.py
View file @
0f221d09
This diff is collapsed.
Click to expand it.
Misc/NEWS.d/next/Library/2018-09-11-10-51-16.bpo-24412.i-F_E5.rst
0 → 100644
View file @
0f221d09
Add :func:`~unittest.addModuleCleanup()` and
:meth:`~unittest.TestCase.addClassCleanup()` to unittest to support
cleanups for :func:`~unittest.setUpModule()` and
:meth:`~unittest.TestCase.setUpClass()`. Patch by Lisa Roach.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment