@@ -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+
25722787if __name__ == "__main__" :
25732788 unittest .main ()
0 commit comments