@@ -555,20 +555,35 @@ def g(frame, event, arg):
555555class JumpTracer :
556556"""Defines a trace function that jumps from one place to another."""
557557
558- def __init__ (self ,function ,jumpFrom ,jumpTo ):
559- self .function = function
558+ def __init__ (self ,function ,jumpFrom ,jumpTo ,event = 'line' ,
559+ decorated = False ):
560+ self .code = function .__code__
560561self .jumpFrom = jumpFrom
561562self .jumpTo = jumpTo
563+ self .event = event
564+ self .firstLine = None if decorated else self .code .co_firstlineno
562565self .done = False
563566
564567def trace (self ,frame ,event ,arg ):
565- if not self .done and frame .f_code == self .function .__code__ :
566- firstLine = frame .f_code .co_firstlineno
567- if event == 'line' and frame .f_lineno == firstLine + self .jumpFrom :
568+ if self .done :
569+ return
570+ # frame.f_code.co_firstlineno is the first line of the decorator when
571+ # 'function' is decorated and the decorator may be written using
572+ # multiple physical lines when it is too long. Use the first line
573+ # trace event in 'function' to find the first line of 'function'.
574+ if (self .firstLine is None and frame .f_code == self .code and
575+ event == 'line' ):
576+ self .firstLine = frame .f_lineno - 1
577+ if (event == self .event and self .firstLine and
578+ frame .f_lineno == self .firstLine + self .jumpFrom ):
579+ f = frame
580+ while f is not None and f .f_code != self .code :
581+ f = f .f_back
582+ if f is not None :
568583# Cope with non-integer self.jumpTo (because of
569584# no_jump_to_non_integers below).
570585try :
571- frame .f_lineno = firstLine + self .jumpTo
586+ frame .f_lineno = self . firstLine + self .jumpTo
572587except TypeError :
573588frame .f_lineno = self .jumpTo
574589self .done = True
@@ -608,8 +623,9 @@ def compare_jump_output(self, expected, received):
608623"Expected: " + repr (expected )+ "\n " +
609624"Received: " + repr (received ))
610625
611- def run_test (self ,func ,jumpFrom ,jumpTo ,expected ,error = None ):
612- tracer = JumpTracer (func ,jumpFrom ,jumpTo )
626+ def run_test (self ,func ,jumpFrom ,jumpTo ,expected ,error = None ,
627+ event = 'line' ,decorated = False ):
628+ tracer = JumpTracer (func ,jumpFrom ,jumpTo ,event ,decorated )
613629sys .settrace (tracer .trace )
614630output = []
615631if error is None :
@@ -620,15 +636,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
620636sys .settrace (None )
621637self .compare_jump_output (expected ,output )
622638
623- def jump_test (jumpFrom ,jumpTo ,expected ,error = None ):
639+ def jump_test (jumpFrom ,jumpTo ,expected ,error = None , event = 'line' ):
624640"""Decorator that creates a test that makes a jump
625641 from one place to another in the following code.
626642 """
627643def decorator (func ):
628644@wraps (func )
629645def test (self ):
630- # +1 to compensate a decorator line
631- self . run_test ( func , jumpFrom + 1 , jumpTo + 1 , expected , error )
646+ self . run_test ( func , jumpFrom , jumpTo , expected ,
647+ error = error , event = event , decorated = True )
632648return test
633649return decorator
634650
@@ -1128,6 +1144,36 @@ class fake_function:
11281144sys .settrace (None )
11291145self .compare_jump_output ([2 ,3 ,2 ,3 ,4 ],namespace ["output" ])
11301146
1147+ @jump_test (2 ,3 , [1 ],event = 'call' ,error = (ValueError ,"can't jump from"
1148+ " the 'call' trace event of a new frame" ))
1149+ def test_no_jump_from_call (output ):
1150+ output .append (1 )
1151+ def nested ():
1152+ output .append (3 )
1153+ nested ()
1154+ output .append (5 )
1155+
1156+ @jump_test (2 ,1 , [1 ],event = 'return' ,error = (ValueError ,
1157+ "can only jump from a 'line' trace event" ))
1158+ def test_no_jump_from_return_event (output ):
1159+ output .append (1 )
1160+ return
1161+
1162+ @jump_test (2 ,1 , [1 ],event = 'exception' ,error = (ValueError ,
1163+ "can only jump from a 'line' trace event" ))
1164+ def test_no_jump_from_exception_event (output ):
1165+ output .append (1 )
1166+ 1 / 0
1167+
1168+ @jump_test (3 ,2 , [2 ],event = 'return' ,error = (ValueError ,
1169+ "can't jump from a yield statement" ))
1170+ def test_no_jump_from_yield (output ):
1171+ def gen ():
1172+ output .append (2 )
1173+ yield 3
1174+ next (gen ())
1175+ output .append (5 )
1176+
11311177
11321178if __name__ == "__main__" :
11331179unittest .main ()