8
8
import inspect
9
9
import builtins
10
10
import unittest
11
+ import unittest .mock
11
12
import re
12
13
import tempfile
13
14
import random
41
42
class TracebackCases (unittest .TestCase ):
42
43
# For now, a very minimal set of tests. I want to be sure that
43
44
# 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
44
53
45
54
def get_exception_format (self , func , exc ):
46
55
try :
@@ -521,7 +530,7 @@ def test_signatures(self):
521
530
self .assertEqual (
522
531
str (inspect .signature (traceback .print_exception )),
523
532
('(exc, /, value=<implicit>, tb=<implicit>, '
524
- 'limit=None, file=None, chain=True)' ))
533
+ 'limit=None, file=None, chain=True, **kwargs )' ))
525
534
526
535
self .assertEqual (
527
536
str (inspect .signature (traceback .format_exception )),
@@ -3031,7 +3040,7 @@ def some_inner(k, v):
3031
3040
3032
3041
def test_custom_format_frame (self ):
3033
3042
class CustomStackSummary (traceback .StackSummary ):
3034
- def format_frame_summary (self , frame_summary ):
3043
+ def format_frame_summary (self , frame_summary , colorize = False ):
3035
3044
return f'{ frame_summary .filename } :{ frame_summary .lineno } '
3036
3045
3037
3046
def some_inner ():
@@ -3056,7 +3065,7 @@ def g():
3056
3065
tb = g ()
3057
3066
3058
3067
class Skip_G (traceback .StackSummary ):
3059
- def format_frame_summary (self , frame_summary ):
3068
+ def format_frame_summary (self , frame_summary , colorize = False ):
3060
3069
if frame_summary .name == 'g' :
3061
3070
return None
3062
3071
return super ().format_frame_summary (frame_summary )
@@ -3076,7 +3085,6 @@ def __repr__(self) -> str:
3076
3085
raise Exception ("Unrepresentable" )
3077
3086
3078
3087
class TestTracebackException (unittest .TestCase ):
3079
-
3080
3088
def do_test_smoke (self , exc , expected_type_str ):
3081
3089
try :
3082
3090
raise exc
@@ -4245,6 +4253,85 @@ def test_levenshtein_distance_short_circuit(self):
4245
4253
res3 = traceback ._levenshtein_distance (a , b , threshold )
4246
4254
self .assertGreater (res3 , threshold , msg = (a , b , threshold ))
4247
4255
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 )
4248
4335
4249
4336
if __name__ == "__main__" :
4250
4337
unittest .main ()
0 commit comments