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