Skip to content

Commit 1767b39

Browse files
committed
More tests
1 parent ad43334 commit 1767b39

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

Lib/test/test_profiling/test_sampling_profiler.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,5 +2569,220 @@ def main():
25692569
self.assertIn("idle_worker", wall_mode_output)
25702570

25712571

2572+
class TestGilModeFiltering(unittest.TestCase):
2573+
"""Test GIL mode filtering functionality (--mode=gil)."""
2574+
2575+
def test_gil_mode_validation(self):
2576+
"""Test that CLI accepts gil mode choice correctly."""
2577+
test_args = ["profiling.sampling.sample", "--mode", "gil", "-p", "12345"]
2578+
2579+
with (
2580+
mock.patch("sys.argv", test_args),
2581+
mock.patch("profiling.sampling.sample.sample") as mock_sample,
2582+
):
2583+
try:
2584+
profiling.sampling.sample.main()
2585+
except SystemExit:
2586+
pass # Expected due to invalid PID
2587+
2588+
# Should have attempted to call sample with mode=2 (GIL mode)
2589+
mock_sample.assert_called_once()
2590+
call_args = mock_sample.call_args[1]
2591+
self.assertEqual(call_args["mode"], 2) # PROFILING_MODE_GIL
2592+
2593+
def test_gil_mode_sample_function_call(self):
2594+
"""Test that sample() function correctly uses GIL mode."""
2595+
with (
2596+
mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler,
2597+
mock.patch("profiling.sampling.sample.PstatsCollector") as mock_collector,
2598+
):
2599+
# Mock the profiler instance
2600+
mock_instance = mock.Mock()
2601+
mock_profiler.return_value = mock_instance
2602+
2603+
# Mock the collector instance
2604+
mock_collector_instance = mock.Mock()
2605+
mock_collector.return_value = mock_collector_instance
2606+
2607+
# Call sample with GIL mode and a filename to avoid pstats creation
2608+
profiling.sampling.sample.sample(
2609+
12345,
2610+
mode=2, # PROFILING_MODE_GIL
2611+
duration_sec=1,
2612+
sample_interval_usec=1000,
2613+
filename="test_output.txt",
2614+
)
2615+
2616+
# Verify SampleProfiler was created with correct mode
2617+
mock_profiler.assert_called_once()
2618+
call_args = mock_profiler.call_args
2619+
self.assertEqual(call_args[1]['mode'], 2) # mode parameter
2620+
2621+
# Verify profiler.sample was called
2622+
mock_instance.sample.assert_called_once()
2623+
2624+
# Verify collector.export was called since we provided a filename
2625+
mock_collector_instance.export.assert_called_once_with("test_output.txt")
2626+
2627+
def test_gil_mode_collector_configuration(self):
2628+
"""Test that collectors are configured correctly for GIL mode."""
2629+
with (
2630+
mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler,
2631+
mock.patch("profiling.sampling.sample.PstatsCollector") as mock_collector,
2632+
):
2633+
# Mock the profiler instance
2634+
mock_instance = mock.Mock()
2635+
mock_profiler.return_value = mock_instance
2636+
2637+
# Call sample with GIL mode
2638+
profiling.sampling.sample.sample(
2639+
12345,
2640+
mode=2, # PROFILING_MODE_GIL
2641+
output_format="pstats",
2642+
)
2643+
2644+
# Verify collector was created with skip_idle=True (since mode != WALL)
2645+
mock_collector.assert_called_once()
2646+
call_args = mock_collector.call_args[1]
2647+
self.assertTrue(call_args['skip_idle'])
2648+
2649+
def test_gil_mode_with_collapsed_format(self):
2650+
"""Test GIL mode with collapsed stack format."""
2651+
with (
2652+
mock.patch("profiling.sampling.sample.SampleProfiler") as mock_profiler,
2653+
mock.patch("profiling.sampling.sample.CollapsedStackCollector") as mock_collector,
2654+
):
2655+
# Mock the profiler instance
2656+
mock_instance = mock.Mock()
2657+
mock_profiler.return_value = mock_instance
2658+
2659+
# Call sample with GIL mode and collapsed format
2660+
profiling.sampling.sample.sample(
2661+
12345,
2662+
mode=2, # PROFILING_MODE_GIL
2663+
output_format="collapsed",
2664+
filename="test_output.txt",
2665+
)
2666+
2667+
# Verify collector was created with skip_idle=True
2668+
mock_collector.assert_called_once()
2669+
call_args = mock_collector.call_args[1]
2670+
self.assertTrue(call_args['skip_idle'])
2671+
2672+
def test_gil_mode_cli_argument_parsing(self):
2673+
"""Test CLI argument parsing for GIL mode with various options."""
2674+
test_args = [
2675+
"profiling.sampling.sample",
2676+
"--mode", "gil",
2677+
"--interval", "500",
2678+
"--duration", "5",
2679+
"-p", "12345"
2680+
]
2681+
2682+
with (
2683+
mock.patch("sys.argv", test_args),
2684+
mock.patch("profiling.sampling.sample.sample") as mock_sample,
2685+
):
2686+
try:
2687+
profiling.sampling.sample.main()
2688+
except SystemExit:
2689+
pass # Expected due to invalid PID
2690+
2691+
# Verify all arguments were parsed correctly
2692+
mock_sample.assert_called_once()
2693+
call_args = mock_sample.call_args[1]
2694+
self.assertEqual(call_args["mode"], 2) # GIL mode
2695+
self.assertEqual(call_args["sample_interval_usec"], 500)
2696+
self.assertEqual(call_args["duration_sec"], 5)
2697+
2698+
@requires_subprocess()
2699+
def test_gil_mode_integration_behavior(self):
2700+
"""Integration test: GIL mode should capture GIL-holding threads."""
2701+
# Create a test script with GIL-releasing operations
2702+
gil_test_script = '''
2703+
import time
2704+
import threading
2705+
2706+
def gil_releasing_work():
2707+
time.sleep(999999)
2708+
2709+
def gil_holding_work():
2710+
x = 1
2711+
while True:
2712+
x += 1
2713+
2714+
def main():
2715+
# Start both threads
2716+
idle_thread = threading.Thread(target=idle_worker)
2717+
cpu_thread = threading.Thread(target=cpu_active_worker)
2718+
idle_thread.start()
2719+
cpu_thread.start()
2720+
idle_thread.join()
2721+
cpu_thread.join()
2722+
2723+
main()
2724+
'''
2725+
with test_subprocess(gil_test_script) as proc:
2726+
with (
2727+
io.StringIO() as captured_output,
2728+
mock.patch("sys.stdout", captured_output),
2729+
):
2730+
try:
2731+
profiling.sampling.sample.sample(
2732+
proc.pid,
2733+
duration_sec=0.5,
2734+
sample_interval_usec=5000,
2735+
mode=2, # GIL mode
2736+
show_summary=False,
2737+
all_threads=True,
2738+
)
2739+
except (PermissionError, RuntimeError) as e:
2740+
self.skipTest("Insufficient permissions for remote profiling")
2741+
2742+
gil_mode_output = captured_output.getvalue()
2743+
2744+
# Test wall-clock mode for comparison
2745+
with (
2746+
io.StringIO() as captured_output,
2747+
mock.patch("sys.stdout", captured_output),
2748+
):
2749+
try:
2750+
profiling.sampling.sample.sample(
2751+
proc.pid,
2752+
duration_sec=0.5,
2753+
sample_interval_usec=5000,
2754+
mode=0, # Wall-clock mode
2755+
show_summary=False,
2756+
all_threads=True,
2757+
)
2758+
except (PermissionError, RuntimeError) as e:
2759+
self.skipTest("Insufficient permissions for remote profiling")
2760+
2761+
wall_mode_output = captured_output.getvalue()
2762+
2763+
# GIL mode should primarily capture GIL-holding work
2764+
# (Note: actual behavior depends on threading implementation)
2765+
self.assertIn("gil_holding_work", gil_mode_output)
2766+
2767+
# Wall-clock mode should capture both types of work
2768+
self.assertIn("gil_holding_work", wall_mode_output)
2769+
2770+
def test_mode_constants_are_defined(self):
2771+
"""Test that all profiling mode constants are properly defined."""
2772+
self.assertEqual(profiling.sampling.sample.PROFILING_MODE_WALL, 0)
2773+
self.assertEqual(profiling.sampling.sample.PROFILING_MODE_CPU, 1)
2774+
self.assertEqual(profiling.sampling.sample.PROFILING_MODE_GIL, 2)
2775+
2776+
def test_parse_mode_function(self):
2777+
"""Test the _parse_mode function with all valid modes."""
2778+
self.assertEqual(profiling.sampling.sample._parse_mode("wall"), 0)
2779+
self.assertEqual(profiling.sampling.sample._parse_mode("cpu"), 1)
2780+
self.assertEqual(profiling.sampling.sample._parse_mode("gil"), 2)
2781+
2782+
# Test invalid mode raises KeyError
2783+
with self.assertRaises(KeyError):
2784+
profiling.sampling.sample._parse_mode("invalid")
2785+
2786+
25722787
if __name__ == "__main__":
25732788
unittest.main()

0 commit comments

Comments
 (0)