@@ -482,20 +482,35 @@ def g(frame, why, extra):
482
482
class JumpTracer :
483
483
"""Defines a trace function that jumps from one place to another."""
484
484
485
- def __init__ (self , function , jumpFrom , jumpTo ):
486
- self .function = function
485
+ def __init__ (self , function , jumpFrom , jumpTo , event = 'line' ,
486
+ decorated = False ):
487
+ self .code = function .func_code
487
488
self .jumpFrom = jumpFrom
488
489
self .jumpTo = jumpTo
490
+ self .event = event
491
+ self .firstLine = None if decorated else self .code .co_firstlineno
489
492
self .done = False
490
493
491
494
def trace (self , frame , event , arg ):
492
- if not self .done and frame .f_code == self .function .func_code :
493
- firstLine = frame .f_code .co_firstlineno
494
- if event == 'line' and frame .f_lineno == firstLine + self .jumpFrom :
495
+ if self .done :
496
+ return
497
+ # frame.f_code.co_firstlineno is the first line of the decorator when
498
+ # 'function' is decorated and the decorator may be written using
499
+ # multiple physical lines when it is too long. Use the first line
500
+ # trace event in 'function' to find the first line of 'function'.
501
+ if (self .firstLine is None and frame .f_code == self .code and
502
+ event == 'line' ):
503
+ self .firstLine = frame .f_lineno - 1
504
+ if (event == self .event and self .firstLine and
505
+ frame .f_lineno == self .firstLine + self .jumpFrom ):
506
+ f = frame
507
+ while f is not None and f .f_code != self .code :
508
+ f = f .f_back
509
+ if f is not None :
495
510
# Cope with non-integer self.jumpTo (because of
496
511
# no_jump_to_non_integers below).
497
512
try :
498
- frame .f_lineno = firstLine + self .jumpTo
513
+ frame .f_lineno = self . firstLine + self .jumpTo
499
514
except TypeError :
500
515
frame .f_lineno = self .jumpTo
501
516
self .done = True
@@ -535,8 +550,9 @@ def compare_jump_output(self, expected, received):
535
550
"Expected: " + repr (expected ) + "\n " +
536
551
"Received: " + repr (received ))
537
552
538
- def run_test (self , func , jumpFrom , jumpTo , expected , error = None ):
539
- tracer = JumpTracer (func , jumpFrom , jumpTo )
553
+ def run_test (self , func , jumpFrom , jumpTo , expected , error = None ,
554
+ event = 'line' , decorated = False ):
555
+ tracer = JumpTracer (func , jumpFrom , jumpTo , event , decorated )
540
556
sys .settrace (tracer .trace )
541
557
output = []
542
558
if error is None :
@@ -547,15 +563,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
547
563
sys .settrace (None )
548
564
self .compare_jump_output (expected , output )
549
565
550
- def jump_test (jumpFrom , jumpTo , expected , error = None ):
566
+ def jump_test (jumpFrom , jumpTo , expected , error = None , event = 'line' ):
551
567
"""Decorator that creates a test that makes a jump
552
568
from one place to another in the following code.
553
569
"""
554
570
def decorator (func ):
555
571
@wraps (func )
556
572
def test (self ):
557
- # +1 to compensate a decorator line
558
- self . run_test ( func , jumpFrom + 1 , jumpTo + 1 , expected , error )
573
+ self . run_test ( func , jumpFrom , jumpTo , expected ,
574
+ error = error , event = event , decorated = True )
559
575
return test
560
576
return decorator
561
577
@@ -1018,6 +1034,36 @@ class fake_function:
1018
1034
sys .settrace (None )
1019
1035
self .compare_jump_output ([2 , 3 , 2 , 3 , 4 ], namespace ["output" ])
1020
1036
1037
+ @jump_test (2 , 3 , [1 ], event = 'call' , error = (ValueError , "can't jump from"
1038
+ " the 'call' trace event of a new frame" ))
1039
+ def test_no_jump_from_call (output ):
1040
+ output .append (1 )
1041
+ def nested ():
1042
+ output .append (3 )
1043
+ nested ()
1044
+ output .append (5 )
1045
+
1046
+ @jump_test (2 , 1 , [1 ], event = 'return' , error = (ValueError ,
1047
+ "can only jump from a 'line' trace event" ))
1048
+ def test_no_jump_from_return_event (output ):
1049
+ output .append (1 )
1050
+ return
1051
+
1052
+ @jump_test (2 , 1 , [1 ], event = 'exception' , error = (ValueError ,
1053
+ "can only jump from a 'line' trace event" ))
1054
+ def test_no_jump_from_exception_event (output ):
1055
+ output .append (1 )
1056
+ 1 / 0
1057
+
1058
+ @jump_test (3 , 2 , [2 ], event = 'return' , error = (ValueError ,
1059
+ "can't jump from a yield statement" ))
1060
+ def test_no_jump_from_yield (output ):
1061
+ def gen ():
1062
+ output .append (2 )
1063
+ yield 3
1064
+ next (gen ())
1065
+ output .append (5 )
1066
+
1021
1067
1022
1068
def test_main ():
1023
1069
test_support .run_unittest (
0 commit comments