aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/unittest
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2021-09-10 18:55:05 +0300
committerGitHub <noreply@github.com>2021-09-10 17:55:05 +0200
commitf0f29f328d8b4568e8c0d4c55c7d120d96f80911 (patch)
treea136806ec7f2fe5b0ca4d444f3d73d732b49e392 /Lib/unittest
parentab327f2929589407595a3de95727c8ab34ddd4af (diff)
downloadcpython-f0f29f328d8b4568e8c0d4c55c7d120d96f80911.tar.gz
cpython-f0f29f328d8b4568e8c0d4c55c7d120d96f80911.zip
bpo-25894: Always report skipped and failed subtests separately (GH-28082)
* In default mode output separate characters for skipped and failed subtests. * In verbose mode output separate lines (including description) for skipped and failed subtests. * In verbose mode output test description for errors in test cleanup.
Diffstat (limited to 'Lib/unittest')
-rw-r--r--Lib/unittest/runner.py38
-rw-r--r--Lib/unittest/test/test_result.py196
2 files changed, 185 insertions, 49 deletions
diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py
index 45e7e4c0458..f316d316601 100644
--- a/Lib/unittest/runner.py
+++ b/Lib/unittest/runner.py
@@ -5,6 +5,7 @@ import time
import warnings
from . import result
+from .case import _SubTest
from .signals import registerResult
__unittest = True
@@ -40,6 +41,7 @@ class TextTestResult(result.TestResult):
self.showAll = verbosity > 1
self.dots = verbosity == 1
self.descriptions = descriptions
+ self._newline = True
def getDescription(self, test):
doc_first_line = test.shortDescription()
@@ -54,11 +56,39 @@ class TextTestResult(result.TestResult):
self.stream.write(self.getDescription(test))
self.stream.write(" ... ")
self.stream.flush()
+ self._newline = False
+
+ def _write_status(self, test, status):
+ is_subtest = isinstance(test, _SubTest)
+ if is_subtest or self._newline:
+ if not self._newline:
+ self.stream.writeln()
+ if is_subtest:
+ self.stream.write(" ")
+ self.stream.write(self.getDescription(test))
+ self.stream.write(" ... ")
+ self.stream.writeln(status)
+ self._newline = True
+
+ def addSubTest(self, test, subtest, err):
+ if err is not None:
+ if self.showAll:
+ if issubclass(err[0], subtest.failureException):
+ self._write_status(subtest, "FAIL")
+ else:
+ self._write_status(subtest, "ERROR")
+ elif self.dots:
+ if issubclass(err[0], subtest.failureException):
+ self.stream.write('F')
+ else:
+ self.stream.write('E')
+ self.stream.flush()
+ super(TextTestResult, self).addSubTest(test, subtest, err)
def addSuccess(self, test):
super(TextTestResult, self).addSuccess(test)
if self.showAll:
- self.stream.writeln("ok")
+ self._write_status(test, "ok")
elif self.dots:
self.stream.write('.')
self.stream.flush()
@@ -66,7 +96,7 @@ class TextTestResult(result.TestResult):
def addError(self, test, err):
super(TextTestResult, self).addError(test, err)
if self.showAll:
- self.stream.writeln("ERROR")
+ self._write_status(test, "ERROR")
elif self.dots:
self.stream.write('E')
self.stream.flush()
@@ -74,7 +104,7 @@ class TextTestResult(result.TestResult):
def addFailure(self, test, err):
super(TextTestResult, self).addFailure(test, err)
if self.showAll:
- self.stream.writeln("FAIL")
+ self._write_status(test, "FAIL")
elif self.dots:
self.stream.write('F')
self.stream.flush()
@@ -82,7 +112,7 @@ class TextTestResult(result.TestResult):
def addSkip(self, test, reason):
super(TextTestResult, self).addSkip(test, reason)
if self.showAll:
- self.stream.writeln("skipped {0!r}".format(reason))
+ self._write_status(test, "skipped {0!r}".format(reason))
elif self.dots:
self.stream.write("s")
self.stream.flush()
diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py
index d6efc7ef066..3b9da127764 100644
--- a/Lib/unittest/test/test_result.py
+++ b/Lib/unittest/test/test_result.py
@@ -305,12 +305,51 @@ class Test_TestResult(unittest.TestCase):
self.assertIs(test_case, subtest)
self.assertIn("some recognizable failure", formatted_exc)
+ def testStackFrameTrimming(self):
+ class Frame(object):
+ class tb_frame(object):
+ f_globals = {}
+ result = unittest.TestResult()
+ self.assertFalse(result._is_relevant_tb_level(Frame))
+
+ Frame.tb_frame.f_globals['__unittest'] = True
+ self.assertTrue(result._is_relevant_tb_level(Frame))
+
+ def testFailFast(self):
+ result = unittest.TestResult()
+ result._exc_info_to_string = lambda *_: ''
+ result.failfast = True
+ result.addError(None, None)
+ self.assertTrue(result.shouldStop)
+
+ result = unittest.TestResult()
+ result._exc_info_to_string = lambda *_: ''
+ result.failfast = True
+ result.addFailure(None, None)
+ self.assertTrue(result.shouldStop)
+
+ result = unittest.TestResult()
+ result._exc_info_to_string = lambda *_: ''
+ result.failfast = True
+ result.addUnexpectedSuccess(None)
+ self.assertTrue(result.shouldStop)
+
+ def testFailFastSetByRunner(self):
+ runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True)
+ def test(result):
+ self.assertTrue(result.failfast)
+ result = runner.run(test)
+
+
+class Test_TextTestResult(unittest.TestCase):
+ maxDiff = None
+
def testGetDescriptionWithoutDocstring(self):
result = unittest.TextTestResult(None, True, 1)
self.assertEqual(
result.getDescription(self),
'testGetDescriptionWithoutDocstring (' + __name__ +
- '.Test_TestResult)')
+ '.Test_TextTestResult)')
def testGetSubTestDescriptionWithoutDocstring(self):
with self.subTest(foo=1, bar=2):
@@ -318,13 +357,13 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self._subtest),
'testGetSubTestDescriptionWithoutDocstring (' + __name__ +
- '.Test_TestResult) (foo=1, bar=2)')
+ '.Test_TextTestResult) (foo=1, bar=2)')
with self.subTest('some message'):
result = unittest.TextTestResult(None, True, 1)
self.assertEqual(
result.getDescription(self._subtest),
'testGetSubTestDescriptionWithoutDocstring (' + __name__ +
- '.Test_TestResult) [some message]')
+ '.Test_TextTestResult) [some message]')
def testGetSubTestDescriptionWithoutDocstringAndParams(self):
with self.subTest():
@@ -332,10 +371,10 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self._subtest),
'testGetSubTestDescriptionWithoutDocstringAndParams '
- '(' + __name__ + '.Test_TestResult) (<subtest>)')
+ '(' + __name__ + '.Test_TextTestResult) (<subtest>)')
def testGetSubTestDescriptionForFalsyValues(self):
- expected = 'testGetSubTestDescriptionForFalsyValues (%s.Test_TestResult) [%s]'
+ expected = 'testGetSubTestDescriptionForFalsyValues (%s.Test_TextTestResult) [%s]'
result = unittest.TextTestResult(None, True, 1)
for arg in [0, None, []]:
with self.subTest(arg):
@@ -351,7 +390,7 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self._subtest),
'testGetNestedSubTestDescriptionWithoutDocstring '
- '(' + __name__ + '.Test_TestResult) (baz=2, bar=3, foo=1)')
+ '(' + __name__ + '.Test_TextTestResult) (baz=2, bar=3, foo=1)')
def testGetDuplicatedNestedSubTestDescriptionWithoutDocstring(self):
with self.subTest(foo=1, bar=2):
@@ -360,7 +399,7 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self._subtest),
'testGetDuplicatedNestedSubTestDescriptionWithoutDocstring '
- '(' + __name__ + '.Test_TestResult) (baz=3, bar=4, foo=1)')
+ '(' + __name__ + '.Test_TextTestResult) (baz=3, bar=4, foo=1)')
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
@@ -370,7 +409,7 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self),
('testGetDescriptionWithOneLineDocstring '
- '(' + __name__ + '.Test_TestResult)\n'
+ '(' + __name__ + '.Test_TextTestResult)\n'
'Tests getDescription() for a method with a docstring.'))
@unittest.skipIf(sys.flags.optimize >= 2,
@@ -382,7 +421,7 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self._subtest),
('testGetSubTestDescriptionWithOneLineDocstring '
- '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n'
+ '(' + __name__ + '.Test_TextTestResult) (foo=1, bar=2)\n'
'Tests getDescription() for a method with a docstring.'))
@unittest.skipIf(sys.flags.optimize >= 2,
@@ -395,7 +434,7 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self),
('testGetDescriptionWithMultiLineDocstring '
- '(' + __name__ + '.Test_TestResult)\n'
+ '(' + __name__ + '.Test_TextTestResult)\n'
'Tests getDescription() for a method with a longer '
'docstring.'))
@@ -410,44 +449,111 @@ class Test_TestResult(unittest.TestCase):
self.assertEqual(
result.getDescription(self._subtest),
('testGetSubTestDescriptionWithMultiLineDocstring '
- '(' + __name__ + '.Test_TestResult) (foo=1, bar=2)\n'
+ '(' + __name__ + '.Test_TextTestResult) (foo=1, bar=2)\n'
'Tests getDescription() for a method with a longer '
'docstring.'))
- def testStackFrameTrimming(self):
- class Frame(object):
- class tb_frame(object):
- f_globals = {}
- result = unittest.TestResult()
- self.assertFalse(result._is_relevant_tb_level(Frame))
-
- Frame.tb_frame.f_globals['__unittest'] = True
- self.assertTrue(result._is_relevant_tb_level(Frame))
-
- def testFailFast(self):
- result = unittest.TestResult()
- result._exc_info_to_string = lambda *_: ''
- result.failfast = True
- result.addError(None, None)
- self.assertTrue(result.shouldStop)
-
- result = unittest.TestResult()
- result._exc_info_to_string = lambda *_: ''
- result.failfast = True
- result.addFailure(None, None)
- self.assertTrue(result.shouldStop)
-
- result = unittest.TestResult()
- result._exc_info_to_string = lambda *_: ''
- result.failfast = True
- result.addUnexpectedSuccess(None)
- self.assertTrue(result.shouldStop)
-
- def testFailFastSetByRunner(self):
- runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True)
- def test(result):
- self.assertTrue(result.failfast)
- result = runner.run(test)
+ class Test(unittest.TestCase):
+ def testSuccess(self):
+ pass
+ def testSkip(self):
+ self.skipTest('skip')
+ def testFail(self):
+ self.fail('fail')
+ def testError(self):
+ raise Exception('error')
+ def testSubTestSuccess(self):
+ with self.subTest('one', a=1):
+ pass
+ with self.subTest('two', b=2):
+ pass
+ def testSubTestMixed(self):
+ with self.subTest('success', a=1):
+ pass
+ with self.subTest('skip', b=2):
+ self.skipTest('skip')
+ with self.subTest('fail', c=3):
+ self.fail('fail')
+ with self.subTest('error', d=4):
+ raise Exception('error')
+
+ tearDownError = None
+ def tearDown(self):
+ if self.tearDownError is not None:
+ raise self.tearDownError
+
+ def _run_test(self, test_name, verbosity, tearDownError=None):
+ stream = io.StringIO()
+ stream = unittest.runner._WritelnDecorator(stream)
+ result = unittest.TextTestResult(stream, True, verbosity)
+ test = self.Test(test_name)
+ test.tearDownError = tearDownError
+ test.run(result)
+ return stream.getvalue()
+
+ def testDotsOutput(self):
+ self.assertEqual(self._run_test('testSuccess', 1), '.')
+ self.assertEqual(self._run_test('testSkip', 1), 's')
+ self.assertEqual(self._run_test('testFail', 1), 'F')
+ self.assertEqual(self._run_test('testError', 1), 'E')
+
+ def testLongOutput(self):
+ classname = f'{__name__}.{self.Test.__qualname__}'
+ self.assertEqual(self._run_test('testSuccess', 2),
+ f'testSuccess ({classname}) ... ok\n')
+ self.assertEqual(self._run_test('testSkip', 2),
+ f"testSkip ({classname}) ... skipped 'skip'\n")
+ self.assertEqual(self._run_test('testFail', 2),
+ f'testFail ({classname}) ... FAIL\n')
+ self.assertEqual(self._run_test('testError', 2),
+ f'testError ({classname}) ... ERROR\n')
+
+ def testDotsOutputSubTestSuccess(self):
+ self.assertEqual(self._run_test('testSubTestSuccess', 1), '.')
+
+ def testLongOutputSubTestSuccess(self):
+ classname = f'{__name__}.{self.Test.__qualname__}'
+ self.assertEqual(self._run_test('testSubTestSuccess', 2),
+ f'testSubTestSuccess ({classname}) ... ok\n')
+
+ def testDotsOutputSubTestMixed(self):
+ self.assertEqual(self._run_test('testSubTestMixed', 1), 'sFE')
+
+ def testLongOutputSubTestMixed(self):
+ classname = f'{__name__}.{self.Test.__qualname__}'
+ self.assertEqual(self._run_test('testSubTestMixed', 2),
+ f'testSubTestMixed ({classname}) ... \n'
+ f" testSubTestMixed ({classname}) [skip] (b=2) ... skipped 'skip'\n"
+ f' testSubTestMixed ({classname}) [fail] (c=3) ... FAIL\n'
+ f' testSubTestMixed ({classname}) [error] (d=4) ... ERROR\n')
+
+ def testDotsOutputTearDownFail(self):
+ out = self._run_test('testSuccess', 1, AssertionError('fail'))
+ self.assertEqual(out, 'F')
+ out = self._run_test('testError', 1, AssertionError('fail'))
+ self.assertEqual(out, 'EF')
+ out = self._run_test('testFail', 1, Exception('error'))
+ self.assertEqual(out, 'FE')
+ out = self._run_test('testSkip', 1, AssertionError('fail'))
+ self.assertEqual(out, 'sF')
+
+ def testLongOutputTearDownFail(self):
+ classname = f'{__name__}.{self.Test.__qualname__}'
+ out = self._run_test('testSuccess', 2, AssertionError('fail'))
+ self.assertEqual(out,
+ f'testSuccess ({classname}) ... FAIL\n')
+ out = self._run_test('testError', 2, AssertionError('fail'))
+ self.assertEqual(out,
+ f'testError ({classname}) ... ERROR\n'
+ f'testError ({classname}) ... FAIL\n')
+ out = self._run_test('testFail', 2, Exception('error'))
+ self.assertEqual(out,
+ f'testFail ({classname}) ... FAIL\n'
+ f'testFail ({classname}) ... ERROR\n')
+ out = self._run_test('testSkip', 2, AssertionError('fail'))
+ self.assertEqual(out,
+ f"testSkip ({classname}) ... skipped 'skip'\n"
+ f'testSkip ({classname}) ... FAIL\n')
classDict = dict(unittest.TestResult.__dict__)