mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
Issue 10611. Issue 9857. Improve the way exception handling, including test skipping, is done inside TestCase.run
This commit is contained in:
parent
addc6f5a21
commit
b3468f79ef
6 changed files with 185 additions and 74 deletions
|
@ -25,7 +25,6 @@ class SkipTest(Exception):
|
|||
Usually you can use TestResult.skip() or one of the skipping decorators
|
||||
instead of raising this directly.
|
||||
"""
|
||||
pass
|
||||
|
||||
class _ExpectedFailure(Exception):
|
||||
"""
|
||||
|
@ -42,7 +41,17 @@ class _UnexpectedSuccess(Exception):
|
|||
"""
|
||||
The test was supposed to fail, but it didn't!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class _Outcome(object):
|
||||
def __init__(self):
|
||||
self.success = True
|
||||
self.skipped = None
|
||||
self.unexpectedSuccess = None
|
||||
self.expectedFailure = None
|
||||
self.errors = []
|
||||
self.failures = []
|
||||
|
||||
|
||||
def _id(obj):
|
||||
return obj
|
||||
|
@ -263,7 +272,7 @@ class TestCase(object):
|
|||
not have a method with the specified name.
|
||||
"""
|
||||
self._testMethodName = methodName
|
||||
self._resultForDoCleanups = None
|
||||
self._outcomeForDoCleanups = None
|
||||
try:
|
||||
testMethod = getattr(self, methodName)
|
||||
except AttributeError:
|
||||
|
@ -367,6 +376,36 @@ class TestCase(object):
|
|||
RuntimeWarning, 2)
|
||||
result.addSuccess(self)
|
||||
|
||||
def _executeTestPart(self, function, outcome, isTest=False):
|
||||
try:
|
||||
function()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except SkipTest as e:
|
||||
outcome.success = False
|
||||
outcome.skipped = str(e)
|
||||
except _UnexpectedSuccess:
|
||||
exc_info = sys.exc_info()
|
||||
outcome.success = False
|
||||
if isTest:
|
||||
outcome.unexpectedSuccess = exc_info
|
||||
else:
|
||||
outcome.errors.append(exc_info)
|
||||
except _ExpectedFailure:
|
||||
outcome.success = False
|
||||
exc_info = sys.exc_info()
|
||||
if isTest:
|
||||
outcome.expectedFailure = exc_info
|
||||
else:
|
||||
outcome.errors.append(exc_info)
|
||||
except self.failureException:
|
||||
outcome.success = False
|
||||
outcome.failures.append(sys.exc_info())
|
||||
exc_info = sys.exc_info()
|
||||
except:
|
||||
outcome.success = False
|
||||
outcome.errors.append(sys.exc_info())
|
||||
|
||||
def run(self, result=None):
|
||||
orig_result = result
|
||||
if result is None:
|
||||
|
@ -375,7 +414,6 @@ class TestCase(object):
|
|||
if startTestRun is not None:
|
||||
startTestRun()
|
||||
|
||||
self._resultForDoCleanups = result
|
||||
result.startTest(self)
|
||||
|
||||
testMethod = getattr(self, self._testMethodName)
|
||||
|
@ -390,51 +428,42 @@ class TestCase(object):
|
|||
result.stopTest(self)
|
||||
return
|
||||
try:
|
||||
success = False
|
||||
try:
|
||||
self.setUp()
|
||||
except SkipTest as e:
|
||||
self._addSkip(result, str(e))
|
||||
except Exception:
|
||||
result.addError(self, sys.exc_info())
|
||||
outcome = _Outcome()
|
||||
self._outcomeForDoCleanups = outcome
|
||||
|
||||
self._executeTestPart(self.setUp, outcome)
|
||||
if outcome.success:
|
||||
self._executeTestPart(testMethod, outcome, isTest=True)
|
||||
self._executeTestPart(self.tearDown, outcome)
|
||||
|
||||
self.doCleanups()
|
||||
if outcome.success:
|
||||
result.addSuccess(self)
|
||||
else:
|
||||
try:
|
||||
testMethod()
|
||||
except self.failureException:
|
||||
result.addFailure(self, sys.exc_info())
|
||||
except _ExpectedFailure as e:
|
||||
addExpectedFailure = getattr(result, 'addExpectedFailure', None)
|
||||
if addExpectedFailure is not None:
|
||||
addExpectedFailure(self, e.exc_info)
|
||||
else:
|
||||
warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
|
||||
RuntimeWarning)
|
||||
result.addSuccess(self)
|
||||
except _UnexpectedSuccess:
|
||||
if outcome.skipped is not None:
|
||||
self._addSkip(result, outcome.skipped)
|
||||
for exc_info in outcome.errors:
|
||||
result.addError(self, exc_info)
|
||||
for exc_info in outcome.failures:
|
||||
result.addFailure(self, exc_info)
|
||||
if outcome.unexpectedSuccess is not None:
|
||||
addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
|
||||
if addUnexpectedSuccess is not None:
|
||||
addUnexpectedSuccess(self)
|
||||
else:
|
||||
warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
|
||||
RuntimeWarning)
|
||||
result.addFailure(self, sys.exc_info())
|
||||
except SkipTest as e:
|
||||
self._addSkip(result, str(e))
|
||||
except Exception:
|
||||
result.addError(self, sys.exc_info())
|
||||
else:
|
||||
success = True
|
||||
result.addFailure(self, outcome.unexpectedSuccess)
|
||||
|
||||
try:
|
||||
self.tearDown()
|
||||
except Exception:
|
||||
result.addError(self, sys.exc_info())
|
||||
success = False
|
||||
if outcome.expectedFailure is not None:
|
||||
addExpectedFailure = getattr(result, 'addExpectedFailure', None)
|
||||
if addExpectedFailure is not None:
|
||||
addExpectedFailure(self, outcome.expectedFailure)
|
||||
else:
|
||||
warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
|
||||
RuntimeWarning)
|
||||
result.addSuccess(self)
|
||||
|
||||
cleanUpSuccess = self.doCleanups()
|
||||
success = success and cleanUpSuccess
|
||||
if success:
|
||||
result.addSuccess(self)
|
||||
finally:
|
||||
result.stopTest(self)
|
||||
if orig_result is None:
|
||||
|
@ -445,16 +474,15 @@ class TestCase(object):
|
|||
def doCleanups(self):
|
||||
"""Execute all cleanup functions. Normally called for you after
|
||||
tearDown."""
|
||||
result = self._resultForDoCleanups
|
||||
ok = True
|
||||
outcome = self._outcomeForDoCleanups or _Outcome()
|
||||
while self._cleanups:
|
||||
function, args, kwargs = self._cleanups.pop(-1)
|
||||
try:
|
||||
function(*args, **kwargs)
|
||||
except Exception:
|
||||
ok = False
|
||||
result.addError(self, sys.exc_info())
|
||||
return ok
|
||||
function, args, kwargs = self._cleanups.pop()
|
||||
part = lambda: function(*args, **kwargs)
|
||||
self._executeTestPart(part, outcome)
|
||||
|
||||
# return this for backwards compatibility
|
||||
# even though we no longer us it internally
|
||||
return outcome.success
|
||||
|
||||
def __call__(self, *args, **kwds):
|
||||
return self.run(*args, **kwds)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue