@@ -510,20 +510,35 @@ def g(frame, event, arg):
510510class JumpTracer :
511511 """Defines a trace function that jumps from one place to another."""
512512
513- def __init__ (self , function , jumpFrom , jumpTo ):
514- self .function = function
513+ def __init__ (self , function , jumpFrom , jumpTo , event = 'line' ,
514+ decorated = False ):
515+ self .code = function .__code__
515516 self .jumpFrom = jumpFrom
516517 self .jumpTo = jumpTo
518+ self .event = event
519+ self .firstLine = None if decorated else self .code .co_firstlineno
517520 self .done = False
518521
519522 def trace (self , frame , event , arg ):
520- if not self .done and frame .f_code == self .function .__code__ :
521- firstLine = frame .f_code .co_firstlineno
522- if event == 'line' and frame .f_lineno == firstLine + self .jumpFrom :
523+ if self .done :
524+ return
525+ # frame.f_code.co_firstlineno is the first line of the decorator when
526+ # 'function' is decorated and the decorator may be written using
527+ # multiple physical lines when it is too long. Use the first line
528+ # trace event in 'function' to find the first line of 'function'.
529+ if (self .firstLine is None and frame .f_code == self .code and
530+ event == 'line' ):
531+ self .firstLine = frame .f_lineno - 1
532+ if (event == self .event and self .firstLine and
533+ frame .f_lineno == self .firstLine + self .jumpFrom ):
534+ f = frame
535+ while f is not None and f .f_code != self .code :
536+ f = f .f_back
537+ if f is not None :
523538 # Cope with non-integer self.jumpTo (because of
524539 # no_jump_to_non_integers below).
525540 try :
526- frame .f_lineno = firstLine + self .jumpTo
541+ frame .f_lineno = self . firstLine + self .jumpTo
527542 except TypeError :
528543 frame .f_lineno = self .jumpTo
529544 self .done = True
@@ -563,8 +578,9 @@ def compare_jump_output(self, expected, received):
563578 "Expected: " + repr (expected ) + "\n " +
564579 "Received: " + repr (received ))
565580
566- def run_test (self , func , jumpFrom , jumpTo , expected , error = None ):
567- tracer = JumpTracer (func , jumpFrom , jumpTo )
581+ def run_test (self , func , jumpFrom , jumpTo , expected , error = None ,
582+ event = 'line' , decorated = False ):
583+ tracer = JumpTracer (func , jumpFrom , jumpTo , event , decorated )
568584 sys .settrace (tracer .trace )
569585 output = []
570586 if error is None :
@@ -575,15 +591,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
575591 sys .settrace (None )
576592 self .compare_jump_output (expected , output )
577593
578- def jump_test (jumpFrom , jumpTo , expected , error = None ):
594+ def jump_test (jumpFrom , jumpTo , expected , error = None , event = 'line' ):
579595 """Decorator that creates a test that makes a jump
580596 from one place to another in the following code.
581597 """
582598 def decorator (func ):
583599 @wraps (func )
584600 def test (self ):
585- # +1 to compensate a decorator line
586- self . run_test ( func , jumpFrom + 1 , jumpTo + 1 , expected , error )
601+ self . run_test ( func , jumpFrom , jumpTo , expected ,
602+ error = error , event = event , decorated = True )
587603 return test
588604 return decorator
589605
@@ -1058,6 +1074,36 @@ class fake_function:
10581074 sys .settrace (None )
10591075 self .compare_jump_output ([2 , 3 , 2 , 3 , 4 ], namespace ["output" ])
10601076
1077+ @jump_test (2 , 3 , [1 ], event = 'call' , error = (ValueError , "can't jump from"
1078+ " the 'call' trace event of a new frame" ))
1079+ def test_no_jump_from_call (output ):
1080+ output .append (1 )
1081+ def nested ():
1082+ output .append (3 )
1083+ nested ()
1084+ output .append (5 )
1085+
1086+ @jump_test (2 , 1 , [1 ], event = 'return' , error = (ValueError ,
1087+ "can only jump from a 'line' trace event" ))
1088+ def test_no_jump_from_return_event (output ):
1089+ output .append (1 )
1090+ return
1091+
1092+ @jump_test (2 , 1 , [1 ], event = 'exception' , error = (ValueError ,
1093+ "can only jump from a 'line' trace event" ))
1094+ def test_no_jump_from_exception_event (output ):
1095+ output .append (1 )
1096+ 1 / 0
1097+
1098+ @jump_test (3 , 2 , [2 ], event = 'return' , error = (ValueError ,
1099+ "can't jump from a yield statement" ))
1100+ def test_no_jump_from_yield (output ):
1101+ def gen ():
1102+ output .append (2 )
1103+ yield 3
1104+ next (gen ())
1105+ output .append (5 )
1106+
10611107
10621108if __name__ == "__main__" :
10631109 unittest .main ()
0 commit comments