@@ -510,20 +510,35 @@ def g(frame, event, arg):
510
510
class JumpTracer :
511
511
"""Defines a trace function that jumps from one place to another."""
512
512
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__
515
516
self .jumpFrom = jumpFrom
516
517
self .jumpTo = jumpTo
518
+ self .event = event
519
+ self .firstLine = None if decorated else self .code .co_firstlineno
517
520
self .done = False
518
521
519
522
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 :
523
538
# Cope with non-integer self.jumpTo (because of
524
539
# no_jump_to_non_integers below).
525
540
try :
526
- frame .f_lineno = firstLine + self .jumpTo
541
+ frame .f_lineno = self . firstLine + self .jumpTo
527
542
except TypeError :
528
543
frame .f_lineno = self .jumpTo
529
544
self .done = True
@@ -563,8 +578,9 @@ def compare_jump_output(self, expected, received):
563
578
"Expected: " + repr (expected ) + "\n " +
564
579
"Received: " + repr (received ))
565
580
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 )
568
584
sys .settrace (tracer .trace )
569
585
output = []
570
586
if error is None :
@@ -575,15 +591,15 @@ def run_test(self, func, jumpFrom, jumpTo, expected, error=None):
575
591
sys .settrace (None )
576
592
self .compare_jump_output (expected , output )
577
593
578
- def jump_test (jumpFrom , jumpTo , expected , error = None ):
594
+ def jump_test (jumpFrom , jumpTo , expected , error = None , event = 'line' ):
579
595
"""Decorator that creates a test that makes a jump
580
596
from one place to another in the following code.
581
597
"""
582
598
def decorator (func ):
583
599
@wraps (func )
584
600
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 )
587
603
return test
588
604
return decorator
589
605
@@ -1058,6 +1074,36 @@ class fake_function:
1058
1074
sys .settrace (None )
1059
1075
self .compare_jump_output ([2 , 3 , 2 , 3 , 4 ], namespace ["output" ])
1060
1076
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
+
1061
1107
1062
1108
if __name__ == "__main__" :
1063
1109
unittest .main ()
0 commit comments