@@ -482,20 +482,35 @@ def g(frame, why, extra):
482482class JumpTracer :
483483 """Defines a trace function that jumps from one place to another."""
484484
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
487488 self .jumpFrom = jumpFrom
488489 self .jumpTo = jumpTo
490+ self .event = event
491+ self .firstLine = None if decorated else self .code .co_firstlineno
489492 self .done = False
490493
491494 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 :
495510 # Cope with non-integer self.jumpTo (because of
496511 # no_jump_to_non_integers below).
497512 try :
498- frame .f_lineno = firstLine + self .jumpTo
513+ frame .f_lineno = self . firstLine + self .jumpTo
499514 except TypeError :
500515 frame .f_lineno = self .jumpTo
501516 self .done = True
@@ -535,8 +550,9 @@ def compare_jump_output(self, expected, received):
535550 "Expected: " + repr (expected ) + "\n " +
536551 "Received: " + repr (received ))
537552
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 )
540556 sys .settrace (tracer .trace )
541557 output = []
542558 if error is None :
@@ -547,15 +563,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
547563 sys .settrace (None )
548564 self .compare_jump_output (expected , output )
549565
550- def jump_test (jumpFrom , jumpTo , expected , error = None ):
566+ def jump_test (jumpFrom , jumpTo , expected , error = None , event = 'line' ):
551567 """Decorator that creates a test that makes a jump
552568 from one place to another in the following code.
553569 """
554570 def decorator (func ):
555571 @wraps (func )
556572 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 )
559575 return test
560576 return decorator
561577
@@ -1018,6 +1034,36 @@ class fake_function:
10181034 sys .settrace (None )
10191035 self .compare_jump_output ([2 , 3 , 2 , 3 , 4 ], namespace ["output" ])
10201036
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+
10211067
10221068def test_main ():
10231069 test_support .run_unittest (
0 commit comments