88import inspect
99import builtins
1010import unittest
11+ import unittest .mock
1112import re
1213import tempfile
1314import random
4142class TracebackCases (unittest .TestCase ):
4243 # For now, a very minimal set of tests. I want to be sure that
4344 # formatting of SyntaxErrors works based on changes for 2.1.
45+ def setUp (self ):
46+ super ().setUp ()
47+ self .colorize = traceback ._COLORIZE
48+ traceback ._COLORIZE = False
49+
50+ def tearDown (self ):
51+ super ().tearDown ()
52+ traceback ._COLORIZE = self .colorize
4453
4554 def get_exception_format (self , func , exc ):
4655 try :
@@ -521,7 +530,7 @@ def test_signatures(self):
521530 self .assertEqual (
522531 str (inspect .signature (traceback .print_exception )),
523532 ('(exc, /, value=<implicit>, tb=<implicit>, '
524- 'limit=None, file=None, chain=True)' ))
533+ 'limit=None, file=None, chain=True, **kwargs )' ))
525534
526535 self .assertEqual (
527536 str (inspect .signature (traceback .format_exception )),
@@ -3031,7 +3040,7 @@ def some_inner(k, v):
30313040
30323041 def test_custom_format_frame (self ):
30333042 class CustomStackSummary (traceback .StackSummary ):
3034- def format_frame_summary (self , frame_summary ):
3043+ def format_frame_summary (self , frame_summary , colorize = False ):
30353044 return f'{ frame_summary .filename } :{ frame_summary .lineno } '
30363045
30373046 def some_inner ():
@@ -3056,7 +3065,7 @@ def g():
30563065 tb = g ()
30573066
30583067 class Skip_G (traceback .StackSummary ):
3059- def format_frame_summary (self , frame_summary ):
3068+ def format_frame_summary (self , frame_summary , colorize = False ):
30603069 if frame_summary .name == 'g' :
30613070 return None
30623071 return super ().format_frame_summary (frame_summary )
@@ -3076,7 +3085,6 @@ def __repr__(self) -> str:
30763085 raise Exception ("Unrepresentable" )
30773086
30783087class TestTracebackException (unittest .TestCase ):
3079-
30803088 def do_test_smoke (self , exc , expected_type_str ):
30813089 try :
30823090 raise exc
@@ -4245,6 +4253,85 @@ def test_levenshtein_distance_short_circuit(self):
42454253 res3 = traceback ._levenshtein_distance (a , b , threshold )
42464254 self .assertGreater (res3 , threshold , msg = (a , b , threshold ))
42474255
4256+ class TestColorizedTraceback (unittest .TestCase ):
4257+ def test_colorized_traceback (self ):
4258+ def foo (* args ):
4259+ x = {'a' :{'b' : None }}
4260+ y = x ['a' ]['b' ]['c' ]
4261+
4262+ def baz (* args ):
4263+ return foo (1 ,2 ,3 ,4 )
4264+
4265+ def bar ():
4266+ return baz (1 ,
4267+ 2 ,3
4268+ ,4 )
4269+ try :
4270+ bar ()
4271+ except Exception as e :
4272+ exc = traceback .TracebackException .from_exception (
4273+ e , capture_locals = True
4274+ )
4275+ lines = "" .join (exc .format (colorize = True ))
4276+ red = traceback ._ANSIColors .RED
4277+ boldr = traceback ._ANSIColors .BOLD_RED
4278+ reset = traceback ._ANSIColors .RESET
4279+ self .assertIn ("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset , lines )
4280+ self .assertIn ("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset , lines )
4281+ self .assertIn ("return " + red + "baz" + reset + boldr + "(1," + reset , lines )
4282+ self .assertIn (boldr + "2,3" + reset , lines )
4283+ self .assertIn (boldr + ",4)" + reset , lines )
4284+ self .assertIn (red + "bar" + reset + boldr + "()" + reset , lines )
4285+
4286+ def test_colorized_traceback_is_the_default (self ):
4287+ def foo ():
4288+ 1 / 0
4289+
4290+ from _testcapi import exception_print
4291+ try :
4292+ foo ()
4293+ self .fail ("No exception thrown." )
4294+ except Exception as e :
4295+ with captured_output ("stderr" ) as tbstderr :
4296+ with unittest .mock .patch ('traceback._can_colorize' , return_value = True ):
4297+ exception_print (e )
4298+ actual = tbstderr .getvalue ().splitlines ()
4299+
4300+ red = traceback ._ANSIColors .RED
4301+ boldr = traceback ._ANSIColors .BOLD_RED
4302+ reset = traceback ._ANSIColors .RESET
4303+ lno_foo = foo .__code__ .co_firstlineno
4304+ expected = ['Traceback (most recent call last):' ,
4305+ f' File "{ __file__ } ", '
4306+ f'line { lno_foo + 5 } , in test_colorized_traceback_is_the_default' ,
4307+ f' { red } foo{ reset + boldr } (){ reset } ' ,
4308+ f' { red } ~~~{ reset + boldr } ^^{ reset } ' ,
4309+ f' File "{ __file__ } ", '
4310+ f'line { lno_foo + 1 } , in foo' ,
4311+ f' { red } 1{ reset + boldr } /{ reset + red } 0{ reset } ' ,
4312+ f' { red } ~{ reset + boldr } ^{ reset + red } ~{ reset } ' ,
4313+ 'ZeroDivisionError: division by zero' ]
4314+ self .assertEqual (actual , expected )
4315+
4316+ def test_colorized_detection_checks_for_environment_variables (self ):
4317+ with unittest .mock .patch ("sys.stderr" ) as stderr_mock :
4318+ stderr_mock .isatty .return_value = True
4319+ with unittest .mock .patch ("os.environ" , {'TERM' : 'dumb' }):
4320+ self .assertEqual (traceback ._can_colorize (), False )
4321+ with unittest .mock .patch ("os.environ" , {'PY_COLORS' : '1' }):
4322+ self .assertEqual (traceback ._can_colorize (), True )
4323+ with unittest .mock .patch ("os.environ" , {'PY_COLORS' : '0' }):
4324+ self .assertEqual (traceback ._can_colorize (), False )
4325+ with unittest .mock .patch ("os.environ" , {'NO_COLOR' : '1' }):
4326+ self .assertEqual (traceback ._can_colorize (), False )
4327+ with unittest .mock .patch ("os.environ" , {'NO_COLOR' : '1' , "PY_COLORS" : '1' }):
4328+ self .assertEqual (traceback ._can_colorize (), True )
4329+ with unittest .mock .patch ("os.environ" , {'FORCE_COLOR' : '1' }):
4330+ self .assertEqual (traceback ._can_colorize (), True )
4331+ with unittest .mock .patch ("os.environ" , {'FORCE_COLOR' : '1' , 'NO_COLOR' : '1' }):
4332+ self .assertEqual (traceback ._can_colorize (), False )
4333+ stderr_mock .isatty .return_value = False
4334+ self .assertEqual (traceback ._can_colorize (), False )
42484335
42494336if __name__ == "__main__" :
42504337 unittest .main ()
0 commit comments