@@ -30,9 +30,9 @@ def callback(self, frame, event, arg):
3030 if (event == "call"
3131 or event == "return"
3232 or event == "exception" ):
33- self .add_event (event , frame )
33+ self .add_event (event , frame , arg )
3434
35- def add_event (self , event , frame = None ):
35+ def add_event (self , event , frame = None , arg = None ):
3636 """Add an event to the log."""
3737 if frame is None :
3838 frame = sys ._getframe (1 )
@@ -43,7 +43,7 @@ def add_event(self, event, frame=None):
4343 frameno = len (self .frames )
4444 self .frames .append (frame )
4545
46- self .events .append ((frameno , event , ident (frame )))
46+ self .events .append ((frameno , event , ident (frame ), arg ))
4747
4848 def get_events (self ):
4949 """Remove calls to add_event()."""
@@ -89,11 +89,16 @@ def trace_pass(self, frame):
8989
9090
9191class TestCaseBase (unittest .TestCase ):
92- def check_events (self , callable , expected ):
92+ def check_events (self , callable , expected , check_args = False ):
9393 events = capture_events (callable , self .new_watcher ())
94- if events != expected :
95- self .fail ("Expected events:\n %s\n Received events:\n %s"
96- % (pprint .pformat (expected ), pprint .pformat (events )))
94+ if check_args :
95+ if events != expected :
96+ self .fail ("Expected events:\n %s\n Received events:\n %s"
97+ % (pprint .pformat (expected ), pprint .pformat (events )))
98+ else :
99+ if [(frameno , event , ident ) for frameno , event , ident , arg in events ] != expected :
100+ self .fail ("Expected events:\n %s\n Received events:\n %s"
101+ % (pprint .pformat (expected ), pprint .pformat (events )))
97102
98103
99104class ProfileHookTestCase (TestCaseBase ):
@@ -119,7 +124,7 @@ def f(p):
119124 def test_caught_exception (self ):
120125 def f (p ):
121126 try : 1 / 0
122- except : pass
127+ except ZeroDivisionError : pass
123128 f_ident = ident (f )
124129 self .check_events (f , [(1 , 'call' , f_ident ),
125130 (1 , 'return' , f_ident ),
@@ -128,7 +133,7 @@ def f(p):
128133 def test_caught_nested_exception (self ):
129134 def f (p ):
130135 try : 1 / 0
131- except : pass
136+ except ZeroDivisionError : pass
132137 f_ident = ident (f )
133138 self .check_events (f , [(1 , 'call' , f_ident ),
134139 (1 , 'return' , f_ident ),
@@ -151,9 +156,9 @@ def f(p):
151156 def g (p ):
152157 try :
153158 f (p )
154- except :
159+ except ZeroDivisionError :
155160 try : f (p )
156- except : pass
161+ except ZeroDivisionError : pass
157162 f_ident = ident (f )
158163 g_ident = ident (g )
159164 self .check_events (g , [(1 , 'call' , g_ident ),
@@ -164,6 +169,7 @@ def g(p):
164169 (1 , 'return' , g_ident ),
165170 ])
166171
172+ @unittest .expectedFailure # TODO: RUSTPYTHON
167173 def test_exception_propagation (self ):
168174 def f (p ):
169175 1 / 0
@@ -182,7 +188,7 @@ def g(p):
182188 def test_raise_twice (self ):
183189 def f (p ):
184190 try : 1 / 0
185- except : 1 / 0
191+ except ZeroDivisionError : 1 / 0
186192 f_ident = ident (f )
187193 self .check_events (f , [(1 , 'call' , f_ident ),
188194 (1 , 'return' , f_ident ),
@@ -191,7 +197,7 @@ def f(p):
191197 def test_raise_reraise (self ):
192198 def f (p ):
193199 try : 1 / 0
194- except : raise
200+ except ZeroDivisionError : raise
195201 f_ident = ident (f )
196202 self .check_events (f , [(1 , 'call' , f_ident ),
197203 (1 , 'return' , f_ident ),
@@ -255,6 +261,24 @@ def g(p):
255261 (1 , 'return' , g_ident ),
256262 ])
257263
264+ @unittest .expectedFailure # TODO: RUSTPYTHON
265+ def test_unfinished_generator (self ):
266+ def f ():
267+ for i in range (2 ):
268+ yield i
269+ def g (p ):
270+ next (f ())
271+
272+ f_ident = ident (f )
273+ g_ident = ident (g )
274+ self .check_events (g , [(1 , 'call' , g_ident , None ),
275+ (2 , 'call' , f_ident , None ),
276+ (2 , 'return' , f_ident , 0 ),
277+ (2 , 'call' , f_ident , None ),
278+ (2 , 'return' , f_ident , None ),
279+ (1 , 'return' , g_ident , None ),
280+ ], check_args = True )
281+
258282 def test_stop_iteration (self ):
259283 def f ():
260284 for i in range (2 ):
@@ -300,7 +324,7 @@ def f(p):
300324 def test_caught_exception (self ):
301325 def f (p ):
302326 try : 1 / 0
303- except : pass
327+ except ZeroDivisionError : pass
304328 f_ident = ident (f )
305329 self .check_events (f , [(1 , 'call' , f_ident ),
306330 (1 , 'return' , f_ident ),
@@ -415,5 +439,104 @@ def show_events(callable):
415439 pprint .pprint (capture_events (callable ))
416440
417441
442+ class TestEdgeCases (unittest .TestCase ):
443+
444+ def setUp (self ):
445+ self .addCleanup (sys .setprofile , sys .getprofile ())
446+ sys .setprofile (None )
447+
448+ def test_reentrancy (self ):
449+ def foo (* args ):
450+ ...
451+
452+ def bar (* args ):
453+ ...
454+
455+ class A :
456+ def __call__ (self , * args ):
457+ pass
458+
459+ def __del__ (self ):
460+ sys .setprofile (bar )
461+
462+ sys .setprofile (A ())
463+ sys .setprofile (foo )
464+ self .assertEqual (sys .getprofile (), bar )
465+
466+ def test_same_object (self ):
467+ def foo (* args ):
468+ ...
469+
470+ sys .setprofile (foo )
471+ del foo
472+ sys .setprofile (sys .getprofile ())
473+
474+ def test_profile_after_trace_opcodes (self ):
475+ def f ():
476+ ...
477+
478+ sys ._getframe ().f_trace_opcodes = True
479+ prev_trace = sys .gettrace ()
480+ sys .settrace (lambda * args : None )
481+ f ()
482+ sys .settrace (prev_trace )
483+ sys .setprofile (lambda * args : None )
484+ f ()
485+
486+ @unittest .expectedFailure # TODO: RUSTPYTHON
487+ def test_method_with_c_function (self ):
488+ # gh-122029
489+ # When we have a PyMethodObject whose im_func is a C function, we
490+ # should record both the call and the return. f = classmethod(repr)
491+ # is just a way to create a PyMethodObject with a C function.
492+ class A :
493+ f = classmethod (repr )
494+ events = []
495+ sys .setprofile (lambda frame , event , args : events .append (event ))
496+ A ().f ()
497+ sys .setprofile (None )
498+ # The last c_call is the call to sys.setprofile
499+ self .assertEqual (events , ['c_call' , 'c_return' , 'c_call' ])
500+
501+ class B :
502+ f = classmethod (max )
503+ events = []
504+ sys .setprofile (lambda frame , event , args : events .append (event ))
505+ # Not important, we only want to trigger INSTRUMENTED_CALL_KW
506+ B ().f (1 , key = lambda x : 0 )
507+ sys .setprofile (None )
508+ # The last c_call is the call to sys.setprofile
509+ self .assertEqual (
510+ events ,
511+ ['c_call' ,
512+ 'call' , 'return' ,
513+ 'call' , 'return' ,
514+ 'c_return' ,
515+ 'c_call'
516+ ]
517+ )
518+
519+ # Test CALL_FUNCTION_EX
520+ events = []
521+ sys .setprofile (lambda frame , event , args : events .append (event ))
522+ # Not important, we only want to trigger INSTRUMENTED_CALL_KW
523+ args = (1 ,)
524+ m = B ().f
525+ m (* args , key = lambda x : 0 )
526+ sys .setprofile (None )
527+ # The last c_call is the call to sys.setprofile
528+ # INSTRUMENTED_CALL_FUNCTION_EX has different behavior than the other
529+ # instrumented call bytecodes, it does not unpack the callable before
530+ # calling it. This is probably not ideal because it's not consistent,
531+ # but at least we get a consistent call stack (no unmatched c_call).
532+ self .assertEqual (
533+ events ,
534+ ['call' , 'return' ,
535+ 'call' , 'return' ,
536+ 'c_call'
537+ ]
538+ )
539+
540+
418541if __name__ == "__main__" :
419542 unittest .main ()
0 commit comments