summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorRobert Collins <rbtcollins@hp.com>2015-03-06 00:46:35 (GMT)
committerRobert Collins <rbtcollins@hp.com>2015-03-06 00:46:35 (GMT)
commitf0c819acd0f85eafe12a7ff706650cb39d3fbf34 (patch)
treebfd78fdedfd364995c6c707ac1f137cb763597e9 /Lib
parente37a1946c7196169b8d3117f65391a12bb8d97f7 (diff)
downloadcpython-f0c819acd0f85eafe12a7ff706650cb39d3fbf34.zip
cpython-f0c819acd0f85eafe12a7ff706650cb39d3fbf34.tar.gz
cpython-f0c819acd0f85eafe12a7ff706650cb39d3fbf34.tar.bz2
Issue #22936: Allow showing local variables in unittest errors.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/unittest/main.py23
-rw-r--r--Lib/unittest/result.py7
-rw-r--r--Lib/unittest/runner.py10
-rw-r--r--Lib/unittest/test/test_break.py3
-rw-r--r--Lib/unittest/test/test_program.py26
-rw-r--r--Lib/unittest/test/test_result.py43
-rw-r--r--Lib/unittest/test/test_runner.py10
7 files changed, 97 insertions, 25 deletions
diff --git a/Lib/unittest/main.py b/Lib/unittest/main.py
index 486d39f..b209a3a 100644
--- a/Lib/unittest/main.py
+++ b/Lib/unittest/main.py
@@ -58,7 +58,7 @@ class TestProgram(object):
def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader,
exit=True, verbosity=1, failfast=None, catchbreak=None,
- buffer=None, warnings=None):
+ buffer=None, warnings=None, *, tb_locals=False):
if isinstance(module, str):
self.module = __import__(module)
for part in module.split('.')[1:]:
@@ -73,6 +73,7 @@ class TestProgram(object):
self.catchbreak = catchbreak
self.verbosity = verbosity
self.buffer = buffer
+ self.tb_locals = tb_locals
if warnings is None and not sys.warnoptions:
# even if DeprecationWarnings are ignored by default
# print them anyway unless other warnings settings are
@@ -159,7 +160,9 @@ class TestProgram(object):
parser.add_argument('-q', '--quiet', dest='verbosity',
action='store_const', const=0,
help='Quiet output')
-
+ parser.add_argument('--locals', dest='tb_locals',
+ action='store_true',
+ help='Show local variables in tracebacks')
if self.failfast is None:
parser.add_argument('-f', '--failfast', dest='failfast',
action='store_true',
@@ -231,10 +234,18 @@ class TestProgram(object):
self.testRunner = runner.TextTestRunner
if isinstance(self.testRunner, type):
try:
- testRunner = self.testRunner(verbosity=self.verbosity,
- failfast=self.failfast,
- buffer=self.buffer,
- warnings=self.warnings)
+ try:
+ testRunner = self.testRunner(verbosity=self.verbosity,
+ failfast=self.failfast,
+ buffer=self.buffer,
+ warnings=self.warnings,
+ tb_locals=self.tb_locals)
+ except TypeError:
+ # didn't accept the tb_locals argument
+ testRunner = self.testRunner(verbosity=self.verbosity,
+ failfast=self.failfast,
+ buffer=self.buffer,
+ warnings=self.warnings)
except TypeError:
# didn't accept the verbosity, buffer or failfast arguments
testRunner = self.testRunner()
diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py
index 8e0a643..a18f11b 100644
--- a/Lib/unittest/result.py
+++ b/Lib/unittest/result.py
@@ -45,6 +45,7 @@ class TestResult(object):
self.unexpectedSuccesses = []
self.shouldStop = False
self.buffer = False
+ self.tb_locals = False
self._stdout_buffer = None
self._stderr_buffer = None
self._original_stdout = sys.stdout
@@ -179,9 +180,11 @@ class TestResult(object):
if exctype is test.failureException:
# Skip assert*() traceback levels
length = self._count_relevant_tb_levels(tb)
- msgLines = traceback.format_exception(exctype, value, tb, length)
else:
- msgLines = traceback.format_exception(exctype, value, tb)
+ length = None
+ tb_e = traceback.TracebackException(
+ exctype, value, tb, limit=length, capture_locals=self.tb_locals)
+ msgLines = list(tb_e.format())
if self.buffer:
output = sys.stdout.getvalue()
diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py
index 28b8865..2112262 100644
--- a/Lib/unittest/runner.py
+++ b/Lib/unittest/runner.py
@@ -126,7 +126,13 @@ class TextTestRunner(object):
resultclass = TextTestResult
def __init__(self, stream=None, descriptions=True, verbosity=1,
- failfast=False, buffer=False, resultclass=None, warnings=None):
+ failfast=False, buffer=False, resultclass=None, warnings=None,
+ *, tb_locals=False):
+ """Construct a TextTestRunner.
+
+ Subclasses should accept **kwargs to ensure compatibility as the
+ interface changes.
+ """
if stream is None:
stream = sys.stderr
self.stream = _WritelnDecorator(stream)
@@ -134,6 +140,7 @@ class TextTestRunner(object):
self.verbosity = verbosity
self.failfast = failfast
self.buffer = buffer
+ self.tb_locals = tb_locals
self.warnings = warnings
if resultclass is not None:
self.resultclass = resultclass
@@ -147,6 +154,7 @@ class TextTestRunner(object):
registerResult(result)
result.failfast = self.failfast
result.buffer = self.buffer
+ result.tb_locals = self.tb_locals
with warnings.catch_warnings():
if self.warnings:
# if self.warnings is set, use it to filter all the warnings
diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py
index 0bf1a22..2c75019 100644
--- a/Lib/unittest/test/test_break.py
+++ b/Lib/unittest/test/test_break.py
@@ -211,6 +211,7 @@ class TestBreak(unittest.TestCase):
self.verbosity = verbosity
self.failfast = failfast
self.catchbreak = catchbreak
+ self.tb_locals = False
self.testRunner = FakeRunner
self.test = test
self.result = None
@@ -221,6 +222,7 @@ class TestBreak(unittest.TestCase):
self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,
'verbosity': verbosity,
'failfast': failfast,
+ 'tb_locals': False,
'warnings': None})])
self.assertEqual(FakeRunner.runArgs, [test])
self.assertEqual(p.result, result)
@@ -235,6 +237,7 @@ class TestBreak(unittest.TestCase):
self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,
'verbosity': verbosity,
'failfast': failfast,
+ 'tb_locals': False,
'warnings': None})])
self.assertEqual(FakeRunner.runArgs, [test])
self.assertEqual(p.result, result)
diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py
index 725d67f..1cfc179 100644
--- a/Lib/unittest/test/test_program.py
+++ b/Lib/unittest/test/test_program.py
@@ -134,6 +134,7 @@ class InitialisableProgram(unittest.TestProgram):
result = None
verbosity = 1
defaultTest = None
+ tb_locals = False
testRunner = None
testLoader = unittest.defaultTestLoader
module = '__main__'
@@ -147,18 +148,19 @@ RESULT = object()
class FakeRunner(object):
initArgs = None
test = None
- raiseError = False
+ raiseError = 0
def __init__(self, **kwargs):
FakeRunner.initArgs = kwargs
if FakeRunner.raiseError:
- FakeRunner.raiseError = False
+ FakeRunner.raiseError -= 1
raise TypeError
def run(self, test):
FakeRunner.test = test
return RESULT
+
class TestCommandLineArgs(unittest.TestCase):
def setUp(self):
@@ -166,7 +168,7 @@ class TestCommandLineArgs(unittest.TestCase):
self.program.createTests = lambda: None
FakeRunner.initArgs = None
FakeRunner.test = None
- FakeRunner.raiseError = False
+ FakeRunner.raiseError = 0
def testVerbosity(self):
program = self.program
@@ -256,6 +258,7 @@ class TestCommandLineArgs(unittest.TestCase):
self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity',
'failfast': 'failfast',
'buffer': 'buffer',
+ 'tb_locals': False,
'warnings': 'warnings'})
self.assertEqual(FakeRunner.test, 'test')
self.assertIs(program.result, RESULT)
@@ -274,10 +277,25 @@ class TestCommandLineArgs(unittest.TestCase):
self.assertEqual(FakeRunner.test, 'test')
self.assertIs(program.result, RESULT)
+ def test_locals(self):
+ program = self.program
+
+ program.testRunner = FakeRunner
+ program.parseArgs([None, '--locals'])
+ self.assertEqual(True, program.tb_locals)
+ program.runTests()
+ self.assertEqual(FakeRunner.initArgs, {'buffer': False,
+ 'failfast': False,
+ 'tb_locals': True,
+ 'verbosity': 1,
+ 'warnings': None})
+
def testRunTestsOldRunnerClass(self):
program = self.program
- FakeRunner.raiseError = True
+ # Two TypeErrors are needed to fall all the way back to old-style
+ # runners - one to fail tb_locals, one to fail buffer etc.
+ FakeRunner.raiseError = 2
program.testRunner = FakeRunner
program.verbosity = 'verbosity'
program.failfast = 'failfast'
diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py
index 489fe17..e39e2ea 100644
--- a/Lib/unittest/test/test_result.py
+++ b/Lib/unittest/test/test_result.py
@@ -8,6 +8,20 @@ import traceback
import unittest
+class MockTraceback(object):
+ class TracebackException:
+ def __init__(self, *args, **kwargs):
+ self.capture_locals = kwargs.get('capture_locals', False)
+ def format(self):
+ result = ['A traceback']
+ if self.capture_locals:
+ result.append('locals')
+ return result
+
+def restore_traceback():
+ unittest.result.traceback = traceback
+
+
class Test_TestResult(unittest.TestCase):
# Note: there are not separate tests for TestResult.wasSuccessful(),
# TestResult.errors, TestResult.failures, TestResult.testsRun or
@@ -227,6 +241,25 @@ class Test_TestResult(unittest.TestCase):
self.assertIs(test_case, test)
self.assertIsInstance(formatted_exc, str)
+ def test_addError_locals(self):
+ class Foo(unittest.TestCase):
+ def test_1(self):
+ 1/0
+
+ test = Foo('test_1')
+ result = unittest.TestResult()
+ result.tb_locals = True
+
+ unittest.result.traceback = MockTraceback
+ self.addCleanup(restore_traceback)
+ result.startTestRun()
+ test.run(result)
+ result.stopTestRun()
+
+ self.assertEqual(len(result.errors), 1)
+ test_case, formatted_exc = result.errors[0]
+ self.assertEqual('A tracebacklocals', formatted_exc)
+
def test_addSubTest(self):
class Foo(unittest.TestCase):
def test_1(self):
@@ -398,6 +431,7 @@ def __init__(self, stream=None, descriptions=None, verbosity=None):
self.testsRun = 0
self.shouldStop = False
self.buffer = False
+ self.tb_locals = False
classDict['__init__'] = __init__
OldResult = type('OldResult', (object,), classDict)
@@ -454,15 +488,6 @@ class Test_OldTestResult(unittest.TestCase):
runner.run(Test('testFoo'))
-class MockTraceback(object):
- @staticmethod
- def format_exception(*_):
- return ['A traceback']
-
-def restore_traceback():
- unittest.result.traceback = traceback
-
-
class TestOutputBuffering(unittest.TestCase):
def setUp(self):
diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py
index 7c0bd51..9cbc260 100644
--- a/Lib/unittest/test/test_runner.py
+++ b/Lib/unittest/test/test_runner.py
@@ -158,7 +158,7 @@ class Test_TextTestRunner(unittest.TestCase):
self.assertEqual(runner.warnings, None)
self.assertTrue(runner.descriptions)
self.assertEqual(runner.resultclass, unittest.TextTestResult)
-
+ self.assertFalse(runner.tb_locals)
def test_multiple_inheritance(self):
class AResult(unittest.TestResult):
@@ -172,14 +172,13 @@ class Test_TextTestRunner(unittest.TestCase):
# on arguments in its __init__ super call
ATextResult(None, None, 1)
-
def testBufferAndFailfast(self):
class Test(unittest.TestCase):
def testFoo(self):
pass
result = unittest.TestResult()
runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True,
- buffer=True)
+ buffer=True)
# Use our result object
runner._makeResult = lambda: result
runner.run(Test('testFoo'))
@@ -187,6 +186,11 @@ class Test_TextTestRunner(unittest.TestCase):
self.assertTrue(result.failfast)
self.assertTrue(result.buffer)
+ def test_locals(self):
+ runner = unittest.TextTestRunner(stream=io.StringIO(), tb_locals=True)
+ result = runner.run(unittest.TestSuite())
+ self.assertEqual(True, result.tb_locals)
+
def testRunnerRegistersResult(self):
class Test(unittest.TestCase):
def testFoo(self):