@@ -556,20 +556,35 @@ def g(frame, event, arg):
556556class JumpTracer :
557557"""Defines a trace function that jumps from one place to another."""
558558
559- def __init__ (self ,function ,jumpFrom ,jumpTo ):
560- self .function = function
559+ def __init__ (self ,function ,jumpFrom ,jumpTo ,event = 'line' ,
560+ decorated = False ):
561+ self .code = function .__code__
561562self .jumpFrom = jumpFrom
562563self .jumpTo = jumpTo
564+ self .event = event
565+ self .firstLine = None if decorated else self .code .co_firstlineno
563566self .done = False
564567
565568def trace (self ,frame ,event ,arg ):
566- if not self .done and frame .f_code == self .function .__code__ :
567- firstLine = frame .f_code .co_firstlineno
568- if event == 'line' and frame .f_lineno == firstLine + self .jumpFrom :
569+ if self .done :
570+ return
571+ # frame.f_code.co_firstlineno is the first line of the decorator when
572+ # 'function' is decorated and the decorator may be written using
573+ # multiple physical lines when it is too long. Use the first line
574+ # trace event in 'function' to find the first line of 'function'.
575+ if (self .firstLine is None and frame .f_code == self .code and
576+ event == 'line' ):
577+ self .firstLine = frame .f_lineno - 1
578+ if (event == self .event and self .firstLine and
579+ frame .f_lineno == self .firstLine + self .jumpFrom ):
580+ f = frame
581+ while f is not None and f .f_code != self .code :
582+ f = f .f_back
583+ if f is not None :
569584# Cope with non-integer self.jumpTo (because of
570585# no_jump_to_non_integers below).
571586try :
572- frame .f_lineno = firstLine + self .jumpTo
587+ frame .f_lineno = self . firstLine + self .jumpTo
573588except TypeError :
574589frame .f_lineno = self .jumpTo
575590self .done = True
@@ -609,8 +624,9 @@ def compare_jump_output(self, expected, received):
609624"Expected: " + repr (expected )+ "\n " +
610625"Received: " + repr (received ))
611626
612- def run_test (self ,func ,jumpFrom ,jumpTo ,expected ,error = None ):
613- tracer = JumpTracer (func ,jumpFrom ,jumpTo )
627+ def run_test (self ,func ,jumpFrom ,jumpTo ,expected ,error = None ,
628+ event = 'line' ,decorated = False ):
629+ tracer = JumpTracer (func ,jumpFrom ,jumpTo ,event ,decorated )
614630sys .settrace (tracer .trace )
615631output = []
616632if error is None :
@@ -621,15 +637,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
621637sys .settrace (None )
622638self .compare_jump_output (expected ,output )
623639
624- def jump_test (jumpFrom ,jumpTo ,expected ,error = None ):
640+ def jump_test (jumpFrom ,jumpTo ,expected ,error = None , event = 'line' ):
625641"""Decorator that creates a test that makes a jump
626642 from one place to another in the following code.
627643 """
628644def decorator (func ):
629645@wraps (func )
630646def test (self ):
631- # +1 to compensate a decorator line
632- self . run_test ( func , jumpFrom + 1 , jumpTo + 1 , expected , error )
647+ self . run_test ( func , jumpFrom , jumpTo , expected ,
648+ error = error , event = event , decorated = True )
633649return test
634650return decorator
635651
@@ -1104,6 +1120,36 @@ class fake_function:
11041120sys .settrace (None )
11051121self .compare_jump_output ([2 ,3 ,2 ,3 ,4 ],namespace ["output" ])
11061122
1123+ @jump_test (2 ,3 , [1 ],event = 'call' ,error = (ValueError ,"can't jump from"
1124+ " the 'call' trace event of a new frame" ))
1125+ def test_no_jump_from_call (output ):
1126+ output .append (1 )
1127+ def nested ():
1128+ output .append (3 )
1129+ nested ()
1130+ output .append (5 )
1131+
1132+ @jump_test (2 ,1 , [1 ],event = 'return' ,error = (ValueError ,
1133+ "can only jump from a 'line' trace event" ))
1134+ def test_no_jump_from_return_event (output ):
1135+ output .append (1 )
1136+ return
1137+
1138+ @jump_test (2 ,1 , [1 ],event = 'exception' ,error = (ValueError ,
1139+ "can only jump from a 'line' trace event" ))
1140+ def test_no_jump_from_exception_event (output ):
1141+ output .append (1 )
1142+ 1 / 0
1143+
1144+ @jump_test (3 ,2 , [2 ],event = 'return' ,error = (ValueError ,
1145+ "can't jump from a yield statement" ))
1146+ def test_no_jump_from_yield (output ):
1147+ def gen ():
1148+ output .append (2 )
1149+ yield 3
1150+ next (gen ())
1151+ output .append (5 )
1152+
11071153
11081154if __name__ == "__main__" :
11091155unittest .main ()