@@ -124,7 +124,7 @@ def test_subprocess(script):
124124 server_socket .close ()
125125 response = client_socket .recv (1024 )
126126 if response != b"ready" :
127- raise RuntimeError (f"Unexpected response from subprocess: { response !r } " )
127+ raise RuntimeError (f"Unexpected response from subprocess: { response } " )
128128
129129 yield SubprocessInfo (proc , client_socket )
130130 finally :
@@ -1689,15 +1689,14 @@ class TestSampleProfilerIntegration(unittest.TestCase):
16891689 @classmethod
16901690 def setUpClass (cls ):
16911691 cls .test_script = '''
1692- import operator
1693- import gc
1692+ import time
1693+ import os
16941694
16951695def slow_fibonacci(n):
16961696 """Recursive fibonacci - should show up prominently in profiler."""
16971697 if n <= 1:
16981698 return n
1699- # Use operator.call(...) to force native frames between interpreter frames:
1700- return operator.call(slow_fibonacci, n-1) + slow_fibonacci(n-2)
1699+ return slow_fibonacci(n-1) + slow_fibonacci(n-2)
17011700
17021701def cpu_intensive_work():
17031702 """CPU intensive work that should show in profiler."""
@@ -1708,32 +1707,46 @@ def cpu_intensive_work():
17081707 result = result % 1000000
17091708 return result
17101709
1710+ def medium_computation():
1711+ """Medium complexity function."""
1712+ result = 0
1713+ for i in range(100):
1714+ result += i * i
1715+ return result
1716+
1717+ def fast_loop():
1718+ """Fast simple loop."""
1719+ total = 0
1720+ for i in range(50):
1721+ total += i
1722+ return total
1723+
17111724def nested_calls():
17121725 """Test nested function calls."""
17131726 def level1():
17141727 def level2():
1715- return cpu_intensive_work ()
1728+ return medium_computation ()
17161729 return level2()
17171730 return level1()
17181731
1719- class ExpensiveGarbage:
1720- def __init__(self):
1721- self.cycle = self
1722- def __del__(self):
1723- cpu_intensive_work()
1724-
1725- def garbage_collection():
1726- """GC-intensive work."""
1727- ExpensiveGarbage()
1728- gc.collect()
1729-
17301732def main_loop():
17311733 """Main test loop with different execution paths."""
1734+ iteration = 0
1735+
17321736 while True:
1733- cpu_intensive_work()
1734- slow_fibonacci(24)
1735- garbage_collection()
1736- nested_calls()
1737+ iteration += 1
1738+
1739+ # Different execution paths - focus on CPU intensive work
1740+ if iteration % 3 == 0:
1741+ # Very CPU intensive
1742+ result = cpu_intensive_work()
1743+ elif iteration % 5 == 0:
1744+ # Expensive recursive operation
1745+ result = slow_fibonacci(12)
1746+ else:
1747+ # Medium operation
1748+ result = nested_calls()
1749+
17371750 # No sleep - keep CPU busy
17381751
17391752if __name__ == "__main__":
@@ -1765,8 +1778,6 @@ def test_sampling_basic_functionality(self):
17651778
17661779 # Should see some of our test functions
17671780 self .assertIn ("slow_fibonacci" , output )
1768- self .assertIn ("<native>" , output )
1769- self .assertIn ("<GC>" , output )
17701781
17711782 def test_sampling_with_pstats_export (self ):
17721783 pstats_out = tempfile .NamedTemporaryFile (
@@ -1864,8 +1875,6 @@ def test_sampling_with_collapsed_export(self):
18641875 stack_parts = stack_trace .split (";" )
18651876 for part in stack_parts :
18661877 # Each part should be file:function:line
1867- if part in {"<native>" , "<GC>" }:
1868- continue
18691878 self .assertIn (":" , part )
18701879
18711880 def test_sampling_all_threads (self ):
@@ -1916,8 +1925,6 @@ def test_sample_target_script(self):
19161925
19171926 # Should see some of our test functions
19181927 self .assertIn ("slow_fibonacci" , output )
1919- self .assertIn ("<native>" , output )
1920- self .assertIn ("<GC>" , output )
19211928
19221929 def test_sample_target_module (self ):
19231930 tempdir = tempfile .TemporaryDirectory (delete = False )
@@ -1951,39 +1958,6 @@ def test_sample_target_module(self):
19511958
19521959 # Should see some of our test functions
19531960 self .assertIn ("slow_fibonacci" , output )
1954- self .assertIn ("<native>" , output )
1955- self .assertIn ("<GC>" , output )
1956-
1957- def test_sample_no_native_no_gc (self ):
1958- script_file = tempfile .NamedTemporaryFile (delete = False )
1959- script_file .write (self .test_script .encode ("utf-8" ))
1960- script_file .flush ()
1961- self .addCleanup (close_and_unlink , script_file )
1962-
1963- test_args = ["profiling.sampling.sample" , "-d" , "1" , "--no-native" , "--no-gc" , script_file .name ]
1964-
1965- with (
1966- mock .patch ("sys.argv" , test_args ),
1967- io .StringIO () as captured_output ,
1968- mock .patch ("sys.stdout" , captured_output ),
1969- ):
1970- try :
1971- profiling .sampling .sample .main ()
1972- except PermissionError :
1973- self .skipTest ("Insufficient permissions for remote profiling" )
1974-
1975- output = captured_output .getvalue ()
1976-
1977- # Basic checks on output
1978- self .assertIn ("Captured" , output )
1979- self .assertIn ("samples" , output )
1980- self .assertIn ("Profile Stats" , output )
1981-
1982- # Should see some of our test functions
1983- self .assertIn ("slow_fibonacci" , output )
1984- # But not ones we intentionally excluded:
1985- self .assertNotIn ("<native>" , output )
1986- self .assertNotIn ("<GC>" , output )
19871961
19881962
19891963@skip_if_not_supported
@@ -3046,5 +3020,96 @@ def test_parse_mode_function(self):
30463020 profiling .sampling .sample ._parse_mode ("invalid" )
30473021
30483022
3023+ @requires_subprocess ()
3024+ @skip_if_not_supported
3025+ class TestGCFrameTracking (unittest .TestCase ):
3026+ """Tests for GC frame tracking in the sampling profiler."""
3027+
3028+ @classmethod
3029+ def setUpClass (cls ):
3030+ """Create a static test script with GC frames and CPU-intensive work."""
3031+ cls .gc_test_script = '''
3032+ import gc
3033+
3034+ class ExpensiveGarbage:
3035+ """Class that triggers GC with expensive finalizer (callback)."""
3036+ def __init__(self):
3037+ self.cycle = self
3038+
3039+ def __del__(self):
3040+ # CPU-intensive work in the finalizer callback
3041+ result = 0
3042+ for i in range(100000):
3043+ result += i * i
3044+ if i % 1000 == 0:
3045+ result = result % 1000000
3046+
3047+ def main_loop():
3048+ """Main loop that triggers GC with expensive callback."""
3049+ while True:
3050+ ExpensiveGarbage()
3051+ gc.collect()
3052+
3053+ if __name__ == "__main__":
3054+ main_loop()
3055+ '''
3056+
3057+ def test_gc_frames_enabled (self ):
3058+ """Test that GC frames appear when gc tracking is enabled."""
3059+ with (
3060+ test_subprocess (self .gc_test_script ) as subproc ,
3061+ io .StringIO () as captured_output ,
3062+ mock .patch ("sys.stdout" , captured_output ),
3063+ ):
3064+ try :
3065+ profiling .sampling .sample .sample (
3066+ subproc .process .pid ,
3067+ duration_sec = 1 ,
3068+ sample_interval_usec = 5000 ,
3069+ show_summary = False ,
3070+ native = True ,
3071+ gc = True ,
3072+ )
3073+ except PermissionError :
3074+ self .skipTest ("Insufficient permissions for remote profiling" )
3075+
3076+ output = captured_output .getvalue ()
3077+
3078+ # Should capture samples
3079+ self .assertIn ("Captured" , output )
3080+ self .assertIn ("samples" , output )
3081+
3082+ # GC frames should be present
3083+ self .assertIn ("<GC>" , output )
3084+
3085+ def test_gc_frames_disabled (self ):
3086+ """Test that GC frames do not appear when gc tracking is disabled."""
3087+ with (
3088+ test_subprocess (self .gc_test_script ) as subproc ,
3089+ io .StringIO () as captured_output ,
3090+ mock .patch ("sys.stdout" , captured_output ),
3091+ ):
3092+ try :
3093+ profiling .sampling .sample .sample (
3094+ subproc .process .pid ,
3095+ duration_sec = 1 ,
3096+ sample_interval_usec = 5000 ,
3097+ show_summary = False ,
3098+ native = True ,
3099+ gc = False ,
3100+ )
3101+ except PermissionError :
3102+ self .skipTest ("Insufficient permissions for remote profiling" )
3103+
3104+ output = captured_output .getvalue ()
3105+
3106+ # Should capture samples
3107+ self .assertIn ("Captured" , output )
3108+ self .assertIn ("samples" , output )
3109+
3110+ # GC frames should NOT be present
3111+ self .assertNotIn ("<GC>" , output )
3112+
3113+
30493114if __name__ == "__main__" :
30503115 unittest .main ()
0 commit comments