mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +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
|
Usually you can use TestResult.skip() or one of the skipping decorators
|
||||||
instead of raising this directly.
|
instead of raising this directly.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
class _ExpectedFailure(Exception):
|
class _ExpectedFailure(Exception):
|
||||||
"""
|
"""
|
||||||
|
@ -42,7 +41,17 @@ class _UnexpectedSuccess(Exception):
|
||||||
"""
|
"""
|
||||||
The test was supposed to fail, but it didn't!
|
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):
|
def _id(obj):
|
||||||
return obj
|
return obj
|
||||||
|
@ -263,7 +272,7 @@ class TestCase(object):
|
||||||
not have a method with the specified name.
|
not have a method with the specified name.
|
||||||
"""
|
"""
|
||||||
self._testMethodName = methodName
|
self._testMethodName = methodName
|
||||||
self._resultForDoCleanups = None
|
self._outcomeForDoCleanups = None
|
||||||
try:
|
try:
|
||||||
testMethod = getattr(self, methodName)
|
testMethod = getattr(self, methodName)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -367,6 +376,36 @@ class TestCase(object):
|
||||||
RuntimeWarning, 2)
|
RuntimeWarning, 2)
|
||||||
result.addSuccess(self)
|
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):
|
def run(self, result=None):
|
||||||
orig_result = result
|
orig_result = result
|
||||||
if result is None:
|
if result is None:
|
||||||
|
@ -375,7 +414,6 @@ class TestCase(object):
|
||||||
if startTestRun is not None:
|
if startTestRun is not None:
|
||||||
startTestRun()
|
startTestRun()
|
||||||
|
|
||||||
self._resultForDoCleanups = result
|
|
||||||
result.startTest(self)
|
result.startTest(self)
|
||||||
|
|
||||||
testMethod = getattr(self, self._testMethodName)
|
testMethod = getattr(self, self._testMethodName)
|
||||||
|
@ -390,51 +428,42 @@ class TestCase(object):
|
||||||
result.stopTest(self)
|
result.stopTest(self)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
success = False
|
outcome = _Outcome()
|
||||||
try:
|
self._outcomeForDoCleanups = outcome
|
||||||
self.setUp()
|
|
||||||
except SkipTest as e:
|
self._executeTestPart(self.setUp, outcome)
|
||||||
self._addSkip(result, str(e))
|
if outcome.success:
|
||||||
except Exception:
|
self._executeTestPart(testMethod, outcome, isTest=True)
|
||||||
result.addError(self, sys.exc_info())
|
self._executeTestPart(self.tearDown, outcome)
|
||||||
|
|
||||||
|
self.doCleanups()
|
||||||
|
if outcome.success:
|
||||||
|
result.addSuccess(self)
|
||||||
else:
|
else:
|
||||||
try:
|
if outcome.skipped is not None:
|
||||||
testMethod()
|
self._addSkip(result, outcome.skipped)
|
||||||
except self.failureException:
|
for exc_info in outcome.errors:
|
||||||
result.addFailure(self, sys.exc_info())
|
result.addError(self, exc_info)
|
||||||
except _ExpectedFailure as e:
|
for exc_info in outcome.failures:
|
||||||
addExpectedFailure = getattr(result, 'addExpectedFailure', None)
|
result.addFailure(self, exc_info)
|
||||||
if addExpectedFailure is not None:
|
if outcome.unexpectedSuccess 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:
|
|
||||||
addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
|
addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
|
||||||
if addUnexpectedSuccess is not None:
|
if addUnexpectedSuccess is not None:
|
||||||
addUnexpectedSuccess(self)
|
addUnexpectedSuccess(self)
|
||||||
else:
|
else:
|
||||||
warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
|
warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
|
||||||
RuntimeWarning)
|
RuntimeWarning)
|
||||||
result.addFailure(self, sys.exc_info())
|
result.addFailure(self, outcome.unexpectedSuccess)
|
||||||
except SkipTest as e:
|
|
||||||
self._addSkip(result, str(e))
|
|
||||||
except Exception:
|
|
||||||
result.addError(self, sys.exc_info())
|
|
||||||
else:
|
|
||||||
success = True
|
|
||||||
|
|
||||||
try:
|
if outcome.expectedFailure is not None:
|
||||||
self.tearDown()
|
addExpectedFailure = getattr(result, 'addExpectedFailure', None)
|
||||||
except Exception:
|
if addExpectedFailure is not None:
|
||||||
result.addError(self, sys.exc_info())
|
addExpectedFailure(self, outcome.expectedFailure)
|
||||||
success = False
|
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:
|
finally:
|
||||||
result.stopTest(self)
|
result.stopTest(self)
|
||||||
if orig_result is None:
|
if orig_result is None:
|
||||||
|
@ -445,16 +474,15 @@ class TestCase(object):
|
||||||
def doCleanups(self):
|
def doCleanups(self):
|
||||||
"""Execute all cleanup functions. Normally called for you after
|
"""Execute all cleanup functions. Normally called for you after
|
||||||
tearDown."""
|
tearDown."""
|
||||||
result = self._resultForDoCleanups
|
outcome = self._outcomeForDoCleanups or _Outcome()
|
||||||
ok = True
|
|
||||||
while self._cleanups:
|
while self._cleanups:
|
||||||
function, args, kwargs = self._cleanups.pop(-1)
|
function, args, kwargs = self._cleanups.pop()
|
||||||
try:
|
part = lambda: function(*args, **kwargs)
|
||||||
function(*args, **kwargs)
|
self._executeTestPart(part, outcome)
|
||||||
except Exception:
|
|
||||||
ok = False
|
# return this for backwards compatibility
|
||||||
result.addError(self, sys.exc_info())
|
# even though we no longer us it internally
|
||||||
return ok
|
return outcome.success
|
||||||
|
|
||||||
def __call__(self, *args, **kwds):
|
def __call__(self, *args, **kwds):
|
||||||
return self.run(*args, **kwds)
|
return self.run(*args, **kwds)
|
||||||
|
|
|
@ -177,8 +177,8 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
|
||||||
super(Foo, self).test()
|
super(Foo, self).test()
|
||||||
raise RuntimeError('raised by Foo.test')
|
raise RuntimeError('raised by Foo.test')
|
||||||
|
|
||||||
expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown',
|
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||||
'stopTest']
|
'addError', 'stopTest']
|
||||||
Foo(events).run(result)
|
Foo(events).run(result)
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
|
|
||||||
|
@ -195,8 +195,8 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
|
||||||
super(Foo, self).test()
|
super(Foo, self).test()
|
||||||
raise RuntimeError('raised by Foo.test')
|
raise RuntimeError('raised by Foo.test')
|
||||||
|
|
||||||
expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addError',
|
expected = ['startTestRun', 'startTest', 'setUp', 'test',
|
||||||
'tearDown', 'stopTest', 'stopTestRun']
|
'tearDown', 'addError', 'stopTest', 'stopTestRun']
|
||||||
Foo(events).run()
|
Foo(events).run()
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
|
|
||||||
|
@ -216,8 +216,8 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
|
||||||
super(Foo, self).test()
|
super(Foo, self).test()
|
||||||
self.fail('raised by Foo.test')
|
self.fail('raised by Foo.test')
|
||||||
|
|
||||||
expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown',
|
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||||
'stopTest']
|
'addFailure', 'stopTest']
|
||||||
Foo(events).run(result)
|
Foo(events).run(result)
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
|
|
||||||
|
@ -231,8 +231,8 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
|
||||||
super(Foo, self).test()
|
super(Foo, self).test()
|
||||||
self.fail('raised by Foo.test')
|
self.fail('raised by Foo.test')
|
||||||
|
|
||||||
expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addFailure',
|
expected = ['startTestRun', 'startTest', 'setUp', 'test',
|
||||||
'tearDown', 'stopTest', 'stopTestRun']
|
'tearDown', 'addFailure', 'stopTest', 'stopTestRun']
|
||||||
events = []
|
events = []
|
||||||
Foo(events).run()
|
Foo(events).run()
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
|
@ -1126,3 +1126,82 @@ test case
|
||||||
# exercise the TestCase instance in a way that will invoke
|
# exercise the TestCase instance in a way that will invoke
|
||||||
# the type equality lookup mechanism
|
# the type equality lookup mechanism
|
||||||
unpickled_test.assertEqual(set(), set())
|
unpickled_test.assertEqual(set(), set())
|
||||||
|
|
||||||
|
def testKeyboardInterrupt(self):
|
||||||
|
def _raise(self=None):
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
def nothing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Test1(unittest.TestCase):
|
||||||
|
test_something = _raise
|
||||||
|
|
||||||
|
class Test2(unittest.TestCase):
|
||||||
|
setUp = _raise
|
||||||
|
test_something = nothing
|
||||||
|
|
||||||
|
class Test3(unittest.TestCase):
|
||||||
|
test_something = nothing
|
||||||
|
tearDown = _raise
|
||||||
|
|
||||||
|
class Test4(unittest.TestCase):
|
||||||
|
def test_something(self):
|
||||||
|
self.addCleanup(_raise)
|
||||||
|
|
||||||
|
for klass in (Test1, Test2, Test3, Test4):
|
||||||
|
with self.assertRaises(KeyboardInterrupt):
|
||||||
|
klass('test_something').run()
|
||||||
|
|
||||||
|
def testSkippingEverywhere(self):
|
||||||
|
def _skip(self=None):
|
||||||
|
raise unittest.SkipTest('some reason')
|
||||||
|
def nothing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Test1(unittest.TestCase):
|
||||||
|
test_something = _skip
|
||||||
|
|
||||||
|
class Test2(unittest.TestCase):
|
||||||
|
setUp = _skip
|
||||||
|
test_something = nothing
|
||||||
|
|
||||||
|
class Test3(unittest.TestCase):
|
||||||
|
test_something = nothing
|
||||||
|
tearDown = _skip
|
||||||
|
|
||||||
|
class Test4(unittest.TestCase):
|
||||||
|
def test_something(self):
|
||||||
|
self.addCleanup(_skip)
|
||||||
|
|
||||||
|
for klass in (Test1, Test2, Test3, Test4):
|
||||||
|
result = unittest.TestResult()
|
||||||
|
klass('test_something').run(result)
|
||||||
|
self.assertEqual(len(result.skipped), 1)
|
||||||
|
self.assertEqual(result.testsRun, 1)
|
||||||
|
|
||||||
|
def testSystemExit(self):
|
||||||
|
def _raise(self=None):
|
||||||
|
raise SystemExit
|
||||||
|
def nothing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Test1(unittest.TestCase):
|
||||||
|
test_something = _raise
|
||||||
|
|
||||||
|
class Test2(unittest.TestCase):
|
||||||
|
setUp = _raise
|
||||||
|
test_something = nothing
|
||||||
|
|
||||||
|
class Test3(unittest.TestCase):
|
||||||
|
test_something = nothing
|
||||||
|
tearDown = _raise
|
||||||
|
|
||||||
|
class Test4(unittest.TestCase):
|
||||||
|
def test_something(self):
|
||||||
|
self.addCleanup(_raise)
|
||||||
|
|
||||||
|
for klass in (Test1, Test2, Test3, Test4):
|
||||||
|
result = unittest.TestResult()
|
||||||
|
klass('test_something').run(result)
|
||||||
|
self.assertEqual(len(result.errors), 1)
|
||||||
|
self.assertEqual(result.testsRun, 1)
|
||||||
|
|
|
@ -58,8 +58,8 @@ class Test_FunctionTestCase(unittest.TestCase):
|
||||||
def tearDown():
|
def tearDown():
|
||||||
events.append('tearDown')
|
events.append('tearDown')
|
||||||
|
|
||||||
expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown',
|
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||||
'stopTest']
|
'addError', 'stopTest']
|
||||||
unittest.FunctionTestCase(test, setUp, tearDown).run(result)
|
unittest.FunctionTestCase(test, setUp, tearDown).run(result)
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
|
|
||||||
|
@ -84,8 +84,8 @@ class Test_FunctionTestCase(unittest.TestCase):
|
||||||
def tearDown():
|
def tearDown():
|
||||||
events.append('tearDown')
|
events.append('tearDown')
|
||||||
|
|
||||||
expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown',
|
expected = ['startTest', 'setUp', 'test', 'tearDown',
|
||||||
'stopTest']
|
'addFailure', 'stopTest']
|
||||||
unittest.FunctionTestCase(test, setUp, tearDown).run(result)
|
unittest.FunctionTestCase(test, setUp, tearDown).run(result)
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,7 @@ class TestCleanUp(unittest.TestCase):
|
||||||
[(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')),
|
[(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')),
|
||||||
(cleanup2, (), {})])
|
(cleanup2, (), {})])
|
||||||
|
|
||||||
result = test.doCleanups()
|
self.assertTrue(test.doCleanups())
|
||||||
self.assertTrue(result)
|
|
||||||
|
|
||||||
self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))])
|
self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))])
|
||||||
|
|
||||||
def testCleanUpWithErrors(self):
|
def testCleanUpWithErrors(self):
|
||||||
|
@ -44,14 +42,12 @@ class TestCleanUp(unittest.TestCase):
|
||||||
def testNothing(self):
|
def testNothing(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class MockResult(object):
|
class MockOutcome(object):
|
||||||
|
success = True
|
||||||
errors = []
|
errors = []
|
||||||
def addError(self, test, exc_info):
|
|
||||||
self.errors.append((test, exc_info))
|
|
||||||
|
|
||||||
result = MockResult()
|
|
||||||
test = TestableTest('testNothing')
|
test = TestableTest('testNothing')
|
||||||
test._resultForDoCleanups = result
|
test._outcomeForDoCleanups = MockOutcome
|
||||||
|
|
||||||
exc1 = Exception('foo')
|
exc1 = Exception('foo')
|
||||||
exc2 = Exception('bar')
|
exc2 = Exception('bar')
|
||||||
|
@ -65,10 +61,11 @@ class TestCleanUp(unittest.TestCase):
|
||||||
test.addCleanup(cleanup2)
|
test.addCleanup(cleanup2)
|
||||||
|
|
||||||
self.assertFalse(test.doCleanups())
|
self.assertFalse(test.doCleanups())
|
||||||
|
self.assertFalse(MockOutcome.success)
|
||||||
|
|
||||||
(test1, (Type1, instance1, _)), (test2, (Type2, instance2, _)) = reversed(MockResult.errors)
|
(Type1, instance1, _), (Type2, instance2, _) = reversed(MockOutcome.errors)
|
||||||
self.assertEqual((test1, Type1, instance1), (test, Exception, exc1))
|
self.assertEqual((Type1, instance1), (Exception, exc1))
|
||||||
self.assertEqual((test2, Type2, instance2), (test, Exception, exc2))
|
self.assertEqual((Type2, instance2), (Exception, exc2))
|
||||||
|
|
||||||
def testCleanupInRun(self):
|
def testCleanupInRun(self):
|
||||||
blowUp = False
|
blowUp = False
|
||||||
|
|
|
@ -23,6 +23,11 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #10611: SystemExit exception will no longer kill a unittest run.
|
||||||
|
|
||||||
|
- Issue #9857: It is now possible to skip a test in a setUp, tearDown or clean
|
||||||
|
up function.
|
||||||
|
|
||||||
- Issue #10573: use actual/expected consistently in unittest methods.
|
- Issue #10573: use actual/expected consistently in unittest methods.
|
||||||
The order of the args of assertCountEqual is also changed.
|
The order of the args of assertCountEqual is also changed.
|
||||||
|
|
||||||
|
@ -322,7 +327,7 @@ Library
|
||||||
- configparser: the SafeConfigParser class has been renamed to ConfigParser.
|
- configparser: the SafeConfigParser class has been renamed to ConfigParser.
|
||||||
The legacy ConfigParser class has been removed but its interpolation mechanism
|
The legacy ConfigParser class has been removed but its interpolation mechanism
|
||||||
is still available as LegacyInterpolation.
|
is still available as LegacyInterpolation.
|
||||||
|
|
||||||
- configparser: Usage of RawConfigParser is now discouraged for new projects
|
- configparser: Usage of RawConfigParser is now discouraged for new projects
|
||||||
in favor of ConfigParser(interpolation=None).
|
in favor of ConfigParser(interpolation=None).
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
##################################################################
|
##################################################################
|
||||||
[project attributes]
|
[project attributes]
|
||||||
proj.directory-list = [{'dirloc': loc('..'),
|
proj.directory-list = [{'dirloc': loc('..'),
|
||||||
'excludes': [u'Lib/__pycache__',
|
'excludes': [u'Lib/unittest/test/__pycache__',
|
||||||
|
u'Lib/__pycache__',
|
||||||
u'Doc/build',
|
u'Doc/build',
|
||||||
|
u'Lib/unittest/__pycache__',
|
||||||
u'build'],
|
u'build'],
|
||||||
'filter': '*',
|
'filter': '*',
|
||||||
'include_hidden': False,
|
'include_hidden': False,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue