Skip to content

Commit 269fe68

Browse files
committed
Separate tests
1 parent e831b33 commit 269fe68

File tree

1 file changed

+125
-60
lines changed

1 file changed

+125
-60
lines changed

Lib/test/test_profiling/test_sampling_profiler.py

Lines changed: 125 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -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
16951695
def 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
17021701
def 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+
17111724
def 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-
17301732
def 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
17391752
if __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+
30493114
if __name__ == "__main__":
30503115
unittest.main()

0 commit comments

Comments
 (0)