@@ -3109,7 +3109,99 @@ def test_gc_frames_disabled(self):
31093109
31103110 # GC frames should NOT be present
31113111 self .assertNotIn ("<GC>" , output )
3112+ @requires_subprocess ()
3113+ @skip_if_not_supported
3114+ class TestNativeFrameTracking (unittest .TestCase ):
3115+ """Tests for native frame tracking in the sampling profiler."""
3116+
3117+ @classmethod
3118+ def setUpClass (cls ):
3119+ """Create a static test script with native frames and CPU-intensive work."""
3120+ cls .native_test_script = '''
3121+ import operator
3122+
3123+ def python_to_c():
3124+ # Native code at the top of the stack:
3125+ sum(range(1_000_000))
3126+ # Python code at the top of the stack:
3127+ for _ in range(1_000_000):
3128+ pass
3129+
3130+ def main_loop():
3131+ while True:
3132+ # Native code in the middle of the stack:
3133+ operator.call(python_to_c)
3134+
3135+ if __name__ == "__main__":
3136+ main_loop()
3137+ '''
3138+
3139+ def test_native_frames_enabled (self ):
3140+ """Test that native frames appear when native tracking is enabled."""
3141+ collapsed_file = tempfile .NamedTemporaryFile (
3142+ suffix = ".txt" , delete = False
3143+ )
3144+ self .addCleanup (close_and_unlink , collapsed_file )
3145+
3146+ with (
3147+ test_subprocess (self .native_test_script ) as subproc ,
3148+ ):
3149+ # Suppress profiler output when testing file export
3150+ with (
3151+ io .StringIO () as captured_output ,
3152+ mock .patch ("sys.stdout" , captured_output ),
3153+ ):
3154+ try :
3155+ profiling .sampling .sample .sample (
3156+ subproc .process .pid ,
3157+ duration_sec = 1 ,
3158+ filename = collapsed_file .name ,
3159+ output_format = "collapsed" ,
3160+ sample_interval_usec = 5000 ,
3161+ native = True ,
3162+ )
3163+ except PermissionError :
3164+ self .skipTest ("Insufficient permissions for remote profiling" )
3165+
3166+ # Verify file was created and contains valid data
3167+ self .assertTrue (os .path .exists (collapsed_file .name ))
3168+ self .assertGreater (os .path .getsize (collapsed_file .name ), 0 )
3169+
3170+ # Check file format
3171+ with open (collapsed_file .name , "r" ) as f :
3172+ content = f .read ()
3173+
3174+ lines = [line .rsplit (" " , 1 )[0 ] for line in content .strip ().split ("\n " )]
3175+ self .assertGreater (len (lines ), 0 )
3176+
3177+ # All samples should have native code in the middle of the stack:
3178+ self .assertTrue (all (";<native>;" in line for line in lines ))
3179+ # Some samples should have native code at the top of the stack:
3180+ self .assertTrue (any (line .endswith (";<native>" ) for line in lines ))
3181+ # Some samples should have Python code at the top of the stack:
3182+ self .assertTrue (any (not line .endswith (";<native>" ) for line in lines ))
3183+
3184+ def test_native_frames_disabled (self ):
3185+ """Test that native frames do not appear when native tracking is disabled."""
3186+ with (
3187+ test_subprocess (self .native_test_script ) as subproc ,
3188+ io .StringIO () as captured_output ,
3189+ mock .patch ("sys.stdout" , captured_output ),
3190+ ):
3191+ try :
3192+ profiling .sampling .sample .sample (
3193+ subproc .process .pid ,
3194+ duration_sec = 1 ,
3195+ sample_interval_usec = 5000 ,
3196+ show_summary = False ,
3197+ )
3198+ except PermissionError :
3199+ self .skipTest ("Insufficient permissions for remote profiling" )
3200+
3201+ output = captured_output .getvalue ()
31123202
3203+ # native frames should NOT be present
3204+ self .assertNotIn ("<native>" , output )
31133205
31143206if __name__ == "__main__" :
31153207 unittest .main ()
0 commit comments