@@ -555,20 +555,35 @@ def g(frame, event, arg):
555
555
class JumpTracer :
556
556
"""Defines a trace function that jumps from one place to another."""
557
557
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__
560
561
self .jumpFrom = jumpFrom
561
562
self .jumpTo = jumpTo
563
+ self .event = event
564
+ self .firstLine = None if decorated else self .code .co_firstlineno
562
565
self .done = False
563
566
564
567
def 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 :
568
583
# Cope with non-integer self.jumpTo (because of
569
584
# no_jump_to_non_integers below).
570
585
try :
571
- frame .f_lineno = firstLine + self .jumpTo
586
+ frame .f_lineno = self . firstLine + self .jumpTo
572
587
except TypeError :
573
588
frame .f_lineno = self .jumpTo
574
589
self .done = True
@@ -608,8 +623,9 @@ def compare_jump_output(self, expected, received):
608
623
"Expected: " + repr (expected ) + "\n " +
609
624
"Received: " + repr (received ))
610
625
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 )
613
629
sys .settrace (tracer .trace )
614
630
output = []
615
631
if error is None :
@@ -620,15 +636,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
620
636
sys .settrace (None )
621
637
self .compare_jump_output (expected , output )
622
638
623
- def jump_test (jumpFrom , jumpTo , expected , error = None ):
639
+ def jump_test (jumpFrom , jumpTo , expected , error = None , event = 'line' ):
624
640
"""Decorator that creates a test that makes a jump
625
641
from one place to another in the following code.
626
642
"""
627
643
def decorator (func ):
628
644
@wraps (func )
629
645
def 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 )
632
648
return test
633
649
return decorator
634
650
@@ -1128,6 +1144,36 @@ class fake_function:
1128
1144
sys .settrace (None )
1129
1145
self .compare_jump_output ([2 , 3 , 2 , 3 , 4 ], namespace ["output" ])
1130
1146
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
+
1131
1177
1132
1178
if __name__ == "__main__" :
1133
1179
unittest .main ()
0 commit comments